Created with Highcharts 12.1.2EquityJan 2021Jan…May 2021Sep 2021Jan 2022May 2022Sep 2022Jan 2023May 2023Sep 2023Jan 2024May 2024Sep 2024Jan 2025May 202501M2M3M-50-25000.20.4-0.2-0.1001M2M01M2M
Overall Statistics
Total Orders
2066
Average Win
1.05%
Average Loss
-1.15%
Compounding Annual Return
17.171%
Drawdown
43.000%
Expectancy
0.005
Start Equity
1000000
End Equity
1944731
Net Profit
94.473%
Sharpe Ratio
0.439
Sortino Ratio
0.319
Probabilistic Sharpe Ratio
10.866%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
0.91
Alpha
0.141
Beta
0.092
Annual Standard Deviation
0.332
Annual Variance
0.11
Information Ratio
0.257
Tracking Error
0.354
Treynor Ratio
1.577
Total Fees
$0.00
Estimated Strategy Capacity
$770000.00
Lowest Capacity Asset
SPXW 32POSRZ30IUBY|SPX 31
Portfolio Turnover
2.61%
# region imports
from AlgorithmImports import *
# endregion


class OneDTEIndexOptionUniverseAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2021, 1, 1)
        self.set_cash(1_000_000)
        self.settings.automatic_indicator_warm_up = True
        self.portfolio.margin_call_model = MarginCallModel.NULL
        self.portfolio.set_positions(SecurityPositionGroupModel.NULL)
        self.set_security_initializer(MySecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
        # Add 1DTE SPY contracts.
        self._index = self.add_index('SPX')
        self._option = self.add_index_option(self._index.symbol, 'SPXW')
        self._option.set_filter(lambda u: u.include_weeklys().expiration(1, 1).strikes(-20, 20))
        # 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.schedule.on(date_rule, self.time_rules.after_market_open(self._index.symbol, 180), self.liquidate)
        # Load some parameters.
        self._bb_period = self.get_parameter('bb_period', 15)
        self._bb_std = self.get_parameter('bb_std', 3)
        self._position_size = self.get_parameter('position_size', 0.01) # 0.01 => 1%
        self._minimum_dollar_volume = self.get_parameter('minimum_dollar_volume', 1_000_000)

    def on_data(self, data):
        # Only process data during last 30 minutes of the day.
        if not self._can_trade:
            return
        # Get the Option chain.
        chain = data.option_chains.get(self._option.symbol)
        if not chain:
            return
        # Select contracts that have a volume above the upper Bollinger Band and a dollar volume above the threshold.
        contracts = [self.securities[c.symbol] for c in chain]
        contracts = [c for c in contracts if not c.invested and c.volume > c.volume_bb.upper_band.current.value and c.volume * c.price * 100 > self._minimum_dollar_volume]
        if not contracts:
            return
        # Select a contract (for example, the contract with volume furthest above its upper volume Bollinger Band).
        contract = sorted(contracts, key=lambda c: c.volume - c.volume_bb.current.value)[-1]
        # Place the trade.
        self.market_order(
            contract.symbol, 
            -max(1, int(contract.volume*self._position_size)), 
            tag=f'{contract.volume_bb.upper_band.current.value}; {contract.volume}; {self._index.price}'
        )
    
    def on_securities_changed(self, changes):
        # Add a Bollinger Band indicator to each 1DTE contract.
        for security in changes.added_securities:
            security.volume_bb = self.bb(security.symbol, self._bb_period, self._bb_std, resolution=Resolution.MINUTE, selector=Field.VOLUME)


class MySecurityInitializer(BrokerageModelSecurityInitializer):

    def __init__(self, brokerage_model: IBrokerageModel, security_seeder: ISecuritySeeder) -> None:
        super().__init__(brokerage_model, security_seeder)    

    def initialize(self, security: Security) -> None:
        super().initialize(security)
        security.set_buying_power_model(BuyingPowerModel.NULL)