Overall Statistics
Total Orders
10
Average Win
0.61%
Average Loss
-1.08%
Compounding Annual Return
162.131%
Drawdown
1.300%
Expectancy
0.249
Start Equity
50000
End Equity
50670
Net Profit
1.340%
Sharpe Ratio
3.869
Sortino Ratio
0
Probabilistic Sharpe Ratio
60.740%
Loss Rate
20%
Win Rate
80%
Profit-Loss Ratio
0.56
Alpha
0.261
Beta
1.127
Annual Standard Deviation
0.122
Annual Variance
0.015
Information Ratio
2.546
Tracking Error
0.112
Treynor Ratio
0.418
Total Fees
$10.00
Estimated Strategy Capacity
$2500000.00
Lowest Capacity Asset
SPXW 32E3B8UZGP5DA|SPX 31
Portfolio Turnover
0.82%
# Backtest demo for Valle and Serkan

from AlgorithmImports import *
import numpy as np

class SPXWeeklyOptionsDemoAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2024, 1, 8)
        self.set_end_date(2024, 1, 12)
        self.cash = 50000
        self.set_cash(self.cash)

        # Add SPX and weekly SPX options
        self.spx = self.add_index("SPX")
        self.set_benchmark(self.spx.symbol)  # Set the benchmark to SPX
        spxw = self.add_index_option(self.spx.symbol, "SPXW")
        self.spxw_option = spxw.symbol

        # Set filter for the option chain
        #spxw.set_filter(lambda u: (u.expiration(0, 3).include_weeklys()))  # use almost no filter, when data is missing and errors occur
        spxw.set_filter(lambda u: (u.strikes(-50, 50) # strikes are filtered below/above the opening price of the day
                                    .expiration(0, 3) # expiration must be set to 3, because of warup period. E.g. SA+SU+holiday
                                    .puts_only()
                                    .include_weeklys()))

        # Add sma indicator. If activated, also uncomment first line in on_data
        #self.sma100 = self.sma(self.spx.symbol, 100)
        #self.SetWarmup(100, Resolution.Minute)

        self.fill_price = None
        self.option_orders = {}



    def on_data(self,slice):
        #if not self.sma100.IsReady:
        #    #self.log("SMA100 not ready")
        #    return
        
        # Create market order
        entry_hour = 12
        entry_minute = 55
        strike_offset = 5
        stop_loss = 1.00
        if self.time.hour == entry_hour and self.time.minute == entry_minute:
            # Select an option by strike offset
            chain = slice.option_chains.get_value(self.spxw_option)
            short_contracts = [contract for contract in chain if contract.expiry.date() == self.Time.date() and contract.right == 1 and contract.bid_price != 0 and contract.strike <= chain.underlying.close - strike_offset]
            
            # Sort the contracts by strike, so that we can select the highest strike
            short_contracts = sorted(short_contracts, key = lambda x: x.strike, reverse=True)

            # select the highest strike
            contract_short = short_contracts[0]

            # Order short
            symbol = contract_short.symbol
            self.Securities[symbol].set_fee_model(InteractiveBrokersFeeModel())
            self.Securities[symbol].set_buying_power_model(BuyingPowerModel.NULL)
            self.market_order(symbol, -1)
            key = np.random.randint(999999)
            self.option_orders[key] = {
                'symbol': symbol,
                'stop': self.fill_price * (1 + stop_loss),
                'position': 'short',
            }

        # Check if the stop is reached or the option is expired at 16:00
        if self.portfolio.invested == True:
            if self.option_orders:
                option_orders_copy = list(self.option_orders.items())
                # Sort the dictionary by position, so that we can close the short positions first
                option_orders_copy.sort(key=lambda x: x[1]['position'], reverse=True)
                for key, order in option_orders_copy:
                    security = self.Securities[order['symbol']]
                    if (self.time.hour == 16 and self.time.minute == 0):
                        self.liquidate(order['symbol'])
                        if key in self.option_orders:
                            self.option_orders.pop(key)
                        else:
                            self.log(f"A Key {key} not found in option_orders")

                    if order['position'] == 'short' and security.high >= order['stop']:
                        self.market_order(order['symbol'], 1)
                        # Check if key is still in the dictionary
                        if key in self.option_orders:
                            self.option_orders.pop(key)
                        else:
                            self.log(f"B Key {key} not found in option_orders")



    def on_order_event(self, order_event):
        if order_event.Status == OrderStatus.Filled:
            self.fill_price = order_event.FillPrice  # Store fill price for stop loss calculation