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