Created with Highcharts 12.1.2EquityJan 11Jan 25Feb 8Feb 22Mar 8Mar 22Apr 5Apr 19May 3May 17May 31Jun 14Jun 28Jul 12Jul 26Aug 9Aug 23Sep 6900k1,000k1,100k-10-5000.050.1-0.075-0.05-0.02500500k1,000k
Overall Statistics
Total Orders
62
Average Win
0.91%
Average Loss
-2.03%
Compounding Annual Return
11.325%
Drawdown
6.900%
Expectancy
0.121
Start Equity
1000000
End Equity
1074355
Net Profit
7.436%
Sharpe Ratio
0.841
Sortino Ratio
0.287
Probabilistic Sharpe Ratio
44.338%
Loss Rate
23%
Win Rate
77%
Profit-Loss Ratio
0.45
Alpha
0.059
Beta
0.088
Annual Standard Deviation
0.094
Annual Variance
0.009
Information Ratio
-1.114
Tracking Error
0.134
Treynor Ratio
0.903
Total Fees
$0.00
Estimated Strategy Capacity
$770000.00
Lowest Capacity Asset
SPXW 31Q9OUVOMDM32|SPX 31
Portfolio Turnover
0.38%
# region imports
from AlgorithmImports import *
# endregion


class OneDTEIndexOptionUniverseAlgorithm(QCAlgorithm):

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