Overall Statistics
Total Orders
72
Average Win
0.59%
Average Loss
-0.12%
Compounding Annual Return
-0.885%
Drawdown
11.200%
Expectancy
1.649
Start Equity
2000000
End Equity
1955116
Net Profit
-2.244%
Sharpe Ratio
-1.358
Sortino Ratio
-0.212
Probabilistic Sharpe Ratio
1.009%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
4.96
Alpha
-0.052
Beta
-0.003
Annual Standard Deviation
0.038
Annual Variance
0.001
Information Ratio
-1.004
Tracking Error
0.143
Treynor Ratio
16.104
Total Fees
$0.00
Estimated Strategy Capacity
$3000.00
Lowest Capacity Asset
SPXW YA41I8A1DZPQ|SPX 31
Portfolio Turnover
0.01%
from AlgorithmImports import *
from datetime import datetime, timedelta

class FedAnnouncementIronCondorStrategy(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 5, 13)
        self.SetEndDate(2024, 12, 1)
        self.SetCash(2000000)
        self.SetTimeZone(TimeZones.NewYork)

        # Add SPX index and options
        self.index = self.AddIndex("SPX")
        
        # Add SPXW options with iron condor filter
        option = self.AddIndexOption(self.index.Symbol, "SPXW")
        option.SetFilter(lambda x: x.IncludeWeeklys().IronCondor(0, 20, 40))
        self._symbol = option.Symbol

        # Risk management parameters
        self.max_portfolio_risk = 0.20  # 20%! max risk per trade
        self.profit_target = 1.5 # 150% of initial credit
        self.stop_loss = 1  # 150% of max potential loss
        self.trade_open = False
        self.initial_credit = 0
        self.max_potential_loss = 0
        self.target_delta = 0.15  # Target delta for option selection

         # Kurtosis Open IV distribution HIGH dates
        # self.announcement_dates = [
        #     datetime(2022, 5, 16), datetime(2022, 5, 18), datetime(2022, 5, 19), datetime(2022, 5, 20),
        #     datetime(2022, 5, 23), datetime(2022, 5, 25), datetime(2022, 5, 26), datetime(2022, 5, 31),
        #     datetime(2022, 6, 3), datetime(2022, 6, 7), datetime(2022, 6, 8), datetime(2022, 6, 9),
        #     datetime(2022, 6, 14), datetime(2022, 6, 15), datetime(2022, 6, 16), datetime(2022, 6, 17),
        #     datetime(2022, 6, 27), datetime(2022, 6, 28), datetime(2022, 6, 29), datetime(2022, 6, 30),
        #     datetime(2022, 7, 6), datetime(2022, 7, 20), datetime(2022, 7, 21), datetime(2022, 7, 28),
        #     datetime(2022, 8, 1), datetime(2022, 8, 11), datetime(2022, 8, 15), datetime(2022, 8, 16),
        #     datetime(2022, 9, 13), datetime(2022, 9, 14), datetime(2022, 9, 16), datetime(2022, 9, 21),
        #     datetime(2022, 9, 28), datetime(2022, 9, 29), datetime(2022, 10, 5), datetime(2022, 10, 7),
        #     datetime(2022, 10, 12), datetime(2022, 10, 14), datetime(2022, 10, 19), datetime(2022, 10, 24),
        #     datetime(2022, 10, 25), datetime(2022, 11, 2), datetime(2022, 11, 9), datetime(2022, 11, 23),
        #     datetime(2022, 11, 28), datetime(2022, 12, 1), datetime(2022, 12, 5), datetime(2022, 12, 7),
        #     datetime(2022, 12, 14), datetime(2023, 1, 12), datetime(2023, 1, 25), datetime(2023, 1, 30),
        #     datetime(2023, 2, 1), datetime(2023, 2, 14), datetime(2023, 3, 13), datetime(2023, 3, 15),
        #     datetime(2023, 3, 16), datetime(2023, 3, 22), datetime(2023, 3, 30), datetime(2023, 4, 3),
        #     datetime(2023, 4, 4), datetime(2023, 4, 19), datetime(2023, 5, 1), datetime(2023, 6, 5),
        #     datetime(2023, 6, 12), datetime(2023, 6, 14), datetime(2023, 7, 19), datetime(2023, 10, 23),
        # ]

        self.announcement_dates = [
            datetime(2022, 5, 17), datetime(2022, 5, 19), datetime(2022, 5, 22), datetime(2022, 5, 24), datetime(2022, 5, 27), datetime(2022, 6, 2), datetime(2022, 6, 5), datetime(2022, 6, 10), datetime(2022, 6, 13), datetime(2022, 6, 18), datetime(2022, 6, 22), datetime(2022, 6, 26), datetime(2022, 7, 3), datetime(2022, 7, 15), datetime(2022, 7, 22), datetime(2022, 7, 25), datetime(2022, 8, 4), datetime(2022, 8, 9), datetime(2022, 8, 12), datetime(2022, 8, 17), datetime(2022, 9, 8), datetime(2022, 9, 12), datetime(2022, 9, 17), datetime(2022, 9, 23), datetime(2022, 9, 27), datetime(2022, 10, 2), datetime(2022, 10, 9), datetime(2022, 10, 13), datetime(2022, 10, 16), datetime(2022, 10, 20), datetime(2022, 10, 26), datetime(2022, 11, 5), datetime(2022, 11, 12), datetime(2022, 11, 19), datetime(2022, 11, 25), datetime(2022, 12, 3), datetime(2022, 12, 6), datetime(2022, 12, 10), datetime(2022, 12, 15), datetime(2023, 1, 14), datetime(2023, 1, 22), datetime(2023, 1, 27), datetime(2023, 2, 5), datetime(2023, 2, 11), datetime(2023, 3, 8), datetime(2023, 3, 14), datetime(2023, 3, 18), datetime(2023, 3, 25), datetime(2023, 3, 29), datetime(2023, 4, 2), datetime(2023, 4, 7), datetime(2023, 4, 16), datetime(2023, 4, 20), datetime(2023, 5, 3), datetime(2023, 6, 8), datetime(2023, 6, 11), datetime(2023, 6, 15), datetime(2023, 7, 14), datetime(2023, 10, 20)]

        # Schedule to monitor and potentially close positions
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(15, 50), self.CheckPositionManagement)

    def OnData(self, slice):
        if self.portfolio.invested or not self.Time.date() in [date.date() for date in self.announcement_dates]:
            return

        chain = slice.OptionChains.get(self._symbol)
        if not chain:
            return

        # Find contracts with the farthest expiry       
        expiry = max([x.Expiry for x in chain])
        chain = sorted([x for x in chain if x.Expiry == expiry], key=lambda x: x.Strike)

        # Delta-filtered option selection
        put_contracts = [x for x in chain if x.Right == OptionRight.PUT and abs(x.Greeks.Delta) <= self.target_delta]
        call_contracts = [x for x in chain if x.Right == OptionRight.CALL and abs(x.Greeks.Delta) <= self.target_delta]

        if len(call_contracts) < 2 or len(put_contracts) < 2:
            return

        # Select strategy legs with delta consideration
        near_call = min(call_contracts, key=lambda x: abs(x.Greeks.Delta - self.target_delta))
        far_call = min([x for x in call_contracts if x.Strike > near_call.Strike], 
                       key=lambda x: abs(x.Greeks.Delta - self.target_delta))
        
        near_put = min(put_contracts, key=lambda x: abs(x.Greeks.Delta + self.target_delta))
        far_put = min([x for x in put_contracts if x.Strike < near_put.Strike], 
                      key=lambda x: abs(x.Greeks.Delta + self.target_delta))

        # Calculate credit and potential loss
        credit = (near_call.BidPrice - far_call.AskPrice) + (near_put.BidPrice - far_put.AskPrice)
        spread_width = max(far_call.Strike - near_call.Strike, near_put.Strike - far_put.Strike)
        max_potential_loss = spread_width * 100 - credit * 100

        # Position sizing
        total_portfolio_value = self.Portfolio.TotalPortfolioValue
        max_trade_risk = total_portfolio_value * self.max_portfolio_risk
        contracts = int(max_trade_risk / max_potential_loss)

        if contracts == 0:
            return

        # Create iron condor strategy
        iron_condor = OptionStrategies.IronCondor(
            self._symbol, 
            far_put.Strike,
            near_put.Strike,
            near_call.Strike,
            far_call.Strike,
            expiry)

        # Buy the iron condor
        self.Buy(iron_condor, contracts)
        
        # Store trade details
        self.initial_credit = credit * 100 * contracts
        self.max_potential_loss = max_potential_loss * contracts
        self.trade_open = True
        
        self.Debug(f"Opened iron condor at {self.Time}, Contracts: {contracts}, Credit: ${self.initial_credit:.2f}")

    def CheckPositionManagement(self):
        if not self.Portfolio.Invested:
            return

        # Calculate total unrealized PnL
        total_pnl = sum([holding.UnrealizedProfit for holding in self.Portfolio.Values if holding.Invested])

        # Profit target exit
        if total_pnl >= self.initial_credit * self.profit_target:
            self.Liquidate()
            self.Debug(f"Closed position at profit target on {self.Time}")
            self.trade_open = False

        # Stop loss exit
        elif total_pnl <= -self.max_potential_loss * self.stop_loss:
            self.Liquidate()
            self.Debug(f"Closed position at stop loss on {self.Time}")
            self.trade_open = False