| Overall Statistics |
|
Total Orders 6 Average Win 2.63% Average Loss -2.20% Compounding Annual Return 111.611% Drawdown 0.100% Expectancy 0.097 Start Equity 100000 End Equity 100758 Net Profit 0.758% Sharpe Ratio 9.268 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.19 Alpha -0.027 Beta 0.802 Annual Standard Deviation 0.035 Annual Variance 0.001 Information Ratio -13.182 Tracking Error 0.009 Treynor Ratio 0.41 Total Fees $0.00 Estimated Strategy Capacity $1800000.00 Lowest Capacity Asset SPXW 32YYLBOK3V13I|SPX 31 Portfolio Turnover 2.11% Drawdown Recovery 0 |
# region imports
from AlgorithmImports import *
# endregion
class OptionChainFullExample(QCAlgorithm):
_last_ticket: OrderTicket = None
_current_date = None
_traded_today = False
def initialize(self) -> None:
self.set_start_date(2026, 2, 6)
self.set_end_date(2026, 2, 9)
self.set_cash(100000)
self.settings.automatic_indicator_warm_up = True
self.universe_settings.minimum_time_in_universe = timedelta(0)
# Warm-up the option contracts as soon as it is added to the algorithm
self.settings.seed_initial_prices = True
# The EMA/price cross will determine we trade ATM contracts
self.index = self.add_index("SPX")
self._option_chain_symbol = Symbol.create_canonical_option(self.index, "SPXW", Market.USA, "?SPXW")
def on_data(self, data: Slice) -> None:
if self.is_warming_up:
return
# Ensure we have SPX bar data
bar = data.bars.get(self.index)
if bar is None:
return
# Daily reset (first bar of a new calendar day)
today = self.time.date()
if self._current_date is None or today != self._current_date:
self._traded_today = False
self._current_date = today
# Only one trade per day
if self._traded_today:
return
spot = self.securities[self.index.symbol].price
self._get_at_the_money_put_credit_spread(OptionRight.PUT, spot)
self._traded_today = True
return
def _get_at_the_money_put_credit_spread(self, right: OptionRight, spot: float) -> Option | None:
chain = self.option_chain(self._option_chain_symbol)
expiry = min([x.expiry for x in chain])
contracts = sorted([x for x in chain if x.expiry == expiry and x.right == right],
key=lambda x: abs(spot - x.strike))
if not contracts:
return None
short_contract = contracts[0]
long_candidates = sorted([x for x in chain if x.expiry == expiry and x.right == right],
key=lambda x: abs(spot - x.strike - 10))
long_contract = long_candidates[0]
self.add_option_contract(short_contract)
self.add_option_contract(long_contract)
short_mid = (short_contract.bid_price + short_contract.ask_price) / 2
long_mid = (long_contract.bid_price + long_contract.ask_price) / 2
entry_credit = (short_mid - long_mid) * 100 # dollars per spread
legs = []
legs.append(Leg.create(short_contract.symbol, -1))
legs.append(Leg.create(long_contract.symbol, 1))
tickets = self.combo_market_order(legs, 1)
combo_price = 0
for ticket in tickets:
self.debug(f"Symbol: {ticket.symbol}; Quantity filled: {ticket.quantity_filled}; Fill price: {ticket.average_fill_price}")
combo_price += ticket.quantity_filled * ticket.average_fill_price
self.debug(f"Combo price: {abs(combo_price)}")
return