| 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