Created with Highcharts 12.1.2EquityJan 10Jan 24Feb 7Feb 21Mar 7Mar 21Apr 4Apr 18May 2May 16May 30Jun 13Jun 27Jul 11Jul 25Aug 8Aug 22Sep 5500k750k1,000k1,250k-50-25000.250.5-0.4-0.200200k400k0100k200k
Overall Statistics
Total Orders
422
Average Win
1.00%
Average Loss
-1.54%
Compounding Annual Return
-39.271%
Drawdown
33.300%
Expectancy
-0.203
Start Equity
1000000
End Equity
716478
Net Profit
-28.352%
Sharpe Ratio
-0.792
Sortino Ratio
-0.549
Probabilistic Sharpe Ratio
2.744%
Loss Rate
52%
Win Rate
48%
Profit-Loss Ratio
0.65
Alpha
-0.249
Beta
0.079
Annual Standard Deviation
0.33
Annual Variance
0.109
Information Ratio
-0.274
Tracking Error
0.376
Treynor Ratio
-3.297
Total Fees
$0.00
Estimated Strategy Capacity
$21000.00
Lowest Capacity Asset
SPXW 320C1DA8L4WI6|SPX 31
Portfolio Turnover
3.53%
# region imports
from AlgorithmImports import *
# endregion


class OneDTEIndexOptionUniverseAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2022, 1, 1)
        self.set_end_date(2022, 9, 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)