| Overall Statistics |
|
Total Orders 277 Average Win 4.45% Average Loss -1.48% Compounding Annual Return 40.640% Drawdown 48.100% Expectancy 1.417 Start Equity 100000 End Equity 560090.17 Net Profit 460.090% Sharpe Ratio 0.979 Sortino Ratio 1.152 Probabilistic Sharpe Ratio 41.950% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 3.01 Alpha 0.181 Beta 1.314 Annual Standard Deviation 0.305 Annual Variance 0.093 Information Ratio 1.001 Tracking Error 0.209 Treynor Ratio 0.227 Total Fees $1150.57 Estimated Strategy Capacity $520000.00 Lowest Capacity Asset QQQ YOQQ4NDP2UAU|QQQ RIWIV7K5Z9LX Portfolio Turnover 5.58% |
from AlgorithmImports import *
class ParameterizedOptionsAlgorithm(QCAlgorithm):
def Initialize(self,
start_date=(2020, 1, 1),
end_date=(2025, 1, 20),
initial_cash=100000,
tickers=["QQQ"], # Tickers to trade during bullish months
macd_fast_period=24,
macd_slow_period=120,
macd_signal_period=12,
option_expiry_min_days=2,
option_expiry_max_days=7,
option_strike_range=10,
contracts_to_trade=10,
hold_through_exp = True, # During bullish months for tech, whether to hold all the way to expiration or not
bullish_months=[6, 7, 11, 12],
neutral_months=[2, 8, 9],
momentum_months=[1, 3, 4, 5, 10]):
"""
QQQ Options Trading Algorithm
Parameters:
- start_date: Backtest start date (year, month, day)
- end_date: Backtest end date (year, month, day)
- initial_cash: Starting cash balance
- tickers: List of tickers to trade
- macd_fast_period: Fast period for MACD
- macd_slow_period: Slow period for MACD
- macd_signal_period: Signal period for MACD
- option_expiry_min_days: Minimum days to expiry for options
- option_expiry_max_days: Maximum days to expiry for options
- option_strike_range: Number of strikes around current price
- contracts_to_trade: Number of contracts to trade
- bullish_months: Months with bullish strategy
- neutral_months: Months with no trading
- momentum_months: Months with momentum strategy
"""
# Set the start and end date of the backtest
self.SetStartDate(start_date[0], start_date[1], start_date[2])
self.SetEndDate(end_date[0], end_date[1], end_date[2])
# Set the initial cash balance
self.SetCash(initial_cash)
# Store configuration parameters
self.tickers = tickers
self.contracts_to_trade = contracts_to_trade
self.bullish_months = bullish_months
self.neutral_months = neutral_months
self.momentum_months = momentum_months
self.hold_through_exp = hold_through_exp
# Option and equity tracking
self.equities = {}
self.options = {}
# Initialize securities
for ticker in tickers:
self.equities[ticker] = self.AddEquity(ticker, Resolution.HOUR)
self.options[ticker] = self.AddOption(ticker, Resolution.Minute)
# Set option universe filter
self.options[ticker].SetFilter(lambda universe:
universe.IncludeWeeklys()
.Expiration(timedelta(option_expiry_min_days), timedelta(option_expiry_max_days))
.Strikes(-option_strike_range, option_strike_range)
)
# Create and initialize MACD with configurable parameters
self.macd = self.MACD(
self.equities["QQQ"].Symbol,
macd_fast_period,
macd_slow_period,
macd_signal_period,
MovingAverageType.Exponential
)
# MACD tracking variables
self.previous_macd = None
self.previous_signal = None
# Track the option holding
self.current_option = None
self.current_tech_options = {}
def OnData(self, data):
for ticker in self.options:
if not data.OptionChains.ContainsKey(self.options[ticker].Symbol):
return
# Skip trading during neutral months
if self.Time.month in self.neutral_months:
return
# Momentum months strategy
if self.Time.month in self.momentum_months:
self._handle_momentum_strategy(data)
# Bullish tech months strategy
if self.Time.month in self.bullish_months:
#self._handle_bullish_strategy(data)
self._handle_momentum_strategy(data)
def _handle_momentum_strategy(self, data):
# Check if MACD is initialized
if self.previous_macd is None or self.previous_signal is None:
self._update_macd_values()
return
# MACD crossover logic
if self._is_macd_bullish_crossover():
self.Debug(f"MACD crossed above on {self.Time}.")
self._buy_call_option(data)
elif self._is_macd_bearish_crossover():
self.Debug(f"MACD crossed below on {self.Time}.")
self._sell_call_option()
# Update MACD values
self._update_macd_values()
def _handle_bullish_strategy(self, data):
# Check if MACD is initialized
if self.previous_macd is None or self.previous_signal is None:
self._update_macd_values()
return
# MACD crossover logic for bullish months
if self._is_macd_bullish_crossover():
self.Debug(f"MACD crossed above on {self.Time}.")
self._buy_tech_options(data)
elif self._is_macd_bearish_crossover():
self.Debug(f"MACD crossed above on {self.Time}.")
self._sell_tech_options()
# Update MACD values
self._update_macd_values()
def _is_macd_bullish_crossover(self):
return (self.previous_macd < self.previous_signal and
self.macd.Current.Value > self.macd.Signal.Current.Value)
def _is_macd_bearish_crossover(self):
return (self.previous_macd > self.previous_signal and
self.macd.Current.Value < self.macd.Signal.Current.Value)
def _update_macd_values(self):
self.previous_macd = self.macd.Current.Value
self.previous_signal = self.macd.Signal.Current.Value
def _buy_call_option(self, data):
option_chain = data.OptionChains[self.options['QQQ'].Symbol]
contract = self._select_call_option(option_chain)
if contract:
if self.current_option is None:
self.Debug(f"Buying {self.contracts_to_trade} Option Contracts: {contract.Symbol}, Strike: {contract.Strike}, Expiry: {contract.Expiry}")
self.MarketOrder(contract.Symbol, self.contracts_to_trade)
self.current_option = contract.Symbol
def _sell_call_option(self):
if self.current_option is not None:
contract = self.current_option
self.Liquidate(contract)
self.Debug(f"Closed QQQ Option Contract: {contract.Value}")
self.current_option = None
def _buy_tech_options(self, data):
if not self.current_tech_options:
for ticker in self.options:
option_chain = data.OptionChains[self.options[ticker].Symbol]
contract = self._select_call_option(option_chain, ticker)
contracts = self.contracts_to_trade // len(self.tickers)
if contract:
self.Debug(f"Buying {contracts} Call Option Contracts: {contract.Symbol}, Strike: {contract.Strike}, Expiry: {contract.Expiry}")
self.MarketOrder(contract.Symbol, contracts)
self.current_tech_options[self.options[ticker].Symbol] = contract.Symbol
def _sell_tech_options(self):
if not self.hold_through_exp:
for ticker, option_symbol in self.current_tech_options.items():
self.Debug(f"Closing {ticker} Option Contract: {option_symbol}")
self.liquidate()
self.Debug("Closed All Option Contracts.")
self.current_tech_options = {} # Reset after liquidation
else:
self.Debug("Holding through expiration.")
def _select_call_option(self, option_chain, ticker='QQQ'):
# Filter options within expiry range
one_week_from_now = self.Time.date() + timedelta(days=5)
one_week_and_two_days_from_now = self.Time.date() + timedelta(days=7)
contracts = [
contract for contract in option_chain
if one_week_from_now <= contract.Expiry.date() <= one_week_and_two_days_from_now
]
# Sort by proximity to current price
price = self.Securities[ticker].Price
contracts = sorted(contracts, key=lambda x: abs(x.Strike - price))
# Return first call option
call_contracts = [c for c in contracts if c.Right == OptionRight.Call]
return call_contracts[0] if call_contracts else None