Created with Highcharts 12.1.2EquityJan 2024Jan…Feb 2024Mar 2024Apr 2024May 2024Jun 2024Jul 2024Aug 2024Sep 2024Oct 2024Nov 2024Dec 2024Jan 2025Feb 2025Mar 2025Apr 2025975k1,000k1,025k1,050k-0.5-0.25000.020.04-0.04-0.0204k6k8k0100k200k607080
Overall Statistics
Total Orders
352
Average Win
0.02%
Average Loss
-1.15%
Compounding Annual Return
2.443%
Drawdown
1.300%
Expectancy
0.014
Start Equity
1000000
End Equity
1029320
Net Profit
2.932%
Sharpe Ratio
-7.008
Sortino Ratio
-3.34
Probabilistic Sharpe Ratio
98.429%
Loss Rate
1%
Win Rate
99%
Profit-Loss Ratio
0.02
Alpha
-0.038
Beta
0
Annual Standard Deviation
0.005
Annual Variance
0
Information Ratio
-0.882
Tracking Error
0.109
Treynor Ratio
-261.257
Total Fees
$0.00
Estimated Strategy Capacity
$4000.00
Lowest Capacity Asset
SPXW YPZ4T6Y6SOKU|SPX 31
Portfolio Turnover
0.01%
# region imports
from AlgorithmImports import *
# endregion

class OneDTEIndexOptionUniverseAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2024, 1, 1)
        self.set_cash(1_000_000)
        self.settings.automatic_indicator_warm_up = True
        # Add 1DTE SPY contracts.
        self._index = self.add_index('SPX')
        self._index.std = IndicatorExtensions.Of(StandardDeviation(21), self.roc(self._index.symbol, 1, Resolution.DAILY))
        self._option = self.add_index_option(self._index.symbol, 'SPXW')
        self._option.set_filter(lambda u: u.include_weeklys().expiration(1, 1).strikes(-40, 40))
        # Create a member to track when the algorithm should trade.
        self._can_trade = False
        date_rule = self.date_rules.every_day(self._option.symbol)
        self.schedule.on(date_rule, self.time_rules.at(15, 45), lambda: setattr(self, '_can_trade', True))
        self.schedule.on(date_rule, self.time_rules.at(16, 15), lambda: setattr(self, '_can_trade', False))
        self.set_warm_up(timedelta(31))

    def on_data(self, data):
        if self.is_warming_up or self.portfolio.invested:
            return
        # Only process data between 3:45 PM and 4:15 PM on Thursdays.
        if not self._can_trade:
            return
        # Get the Option chain.
        chain = data.option_chains.get(self._option.symbol)
        if not chain:
            return

        upper_threshold = self._index.price * (1 + 4*self._index.std.current.value)
        lower_threshold = self._index.price * (1 - 4*self._index.std.current.value)
        self.plot('STD', "Price", self._index.price)
        self.plot('STD', "upper_threshold", upper_threshold)
        self.plot('STD', "lower_threshold", lower_threshold)

        # Order the OTM calls by strike to find the nearest to ATM
        call_contracts = sorted(
            [contract for contract in chain if contract.right == OptionRight.CALL and contract.strike > upper_threshold],
            key=lambda x: x.strike
        )
        if not call_contracts:
            return
        # Order the OTM puts by strike to find the nearest to ATM
        put_contracts = sorted(
            [contract for contract in chain if contract.right == OptionRight.PUT and contract.strike < lower_threshold],
            key=lambda x: x.strike, reverse=True
        )
        if not put_contracts:
            return
        # Select the call and put contracts.
        call = call_contracts[0]
        put = put_contracts[0]
        # Place the trade.
        self.buy(OptionStrategies.short_strangle(self._option.symbol, call.strike, put.strike, call.expiry), 10)