| Overall Statistics |
|
Total Orders 2 Average Win 0% Average Loss -15.62% Compounding Annual Return 93.316% Drawdown 42.800% Expectancy 0 Start Equity 150000 End Equity 789973.05 Net Profit 426.649% Sharpe Ratio 1.577 Sortino Ratio 2.03 Probabilistic Sharpe Ratio 71.545% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0.453 Beta 1.967 Annual Standard Deviation 0.423 Annual Variance 0.179 Information Ratio 1.567 Tracking Error 0.356 Treynor Ratio 0.339 Total Fees $1.95 Estimated Strategy Capacity $0 Lowest Capacity Asset AVGO UEW4IOBWVPT1 Portfolio Turnover 0.10% |
from AlgorithmImports import *
class PoorMansCoveredCallWithOTM(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2023, 1, 1)
self.SetCash(150000)
self.ticker = "AVGO"
self.symbol = self.AddEquity(self.ticker, Resolution.DAILY).Symbol
option = self.AddOption(self.ticker, Resolution.DAILY)
option.SetFilter(self.OptionFilter)
self.option_symbol = option.Symbol
self.long_call_ticket = None
self.contracts_held = 0
self.long_call_quantity = 3
self.Schedule.On(self.DateRules.EveryDay(self.ticker), self.TimeRules.At(10, 0), self.DailyTrade)
def OptionFilter(self, universe):
return universe.Strikes(-37, +37).Expiration(0, 270)
def on_data(self, data):
holds_long_call = False
for symbol, holding in self.portfolio.items():
if symbol.security_type == SecurityType.OPTION:
try:
underlying = symbol.underlying if hasattr(symbol, 'Underlying') else symbol.id.underlying if hasattr(symbol, 'ID') else None
except Exception:
underlying = None
if underlying and underlying.value == self.ticker:
if symbol.id.option_right == OptionRight.CALL and holding.quantity > 0:
holds_long_call = True
break
if not holds_long_call:
self.BuyLongCall(data)
def BuyLongCall(self, data):
chain = data.OptionChains.get(self.option_symbol)
if not chain:
return
expiries = sorted(set([x.Expiry for x in chain]))
if not expiries:
return
target_expiry = expiries[-1]
underlying_price = self.Securities[self.ticker].Price
calls = [x for x in chain if x.Right == OptionRight.Call and x.Expiry == target_expiry]
atm_call = sorted(calls, key=lambda x: abs(x.Strike - underlying_price))[0]
if atm_call and self.Portfolio.Cash > atm_call.AskPrice * 100 * self.long_call_quantity:
self.MarketOrder(atm_call.Symbol, self.long_call_quantity)
self.contracts_held = self.long_call_quantity
self.Debug(f"Bought long call: {atm_call.Symbol}")
def DailyTrade(self):
chain = self.CurrentSlice.OptionChains.get(self.option_symbol)
if not chain or self.contracts_held == 0:
return
today = self.Time.date()
underlying_price = self.Securities[self.ticker].Price
seven_day_calls = [x for x in chain if x.right == OptionRight.CALL and (x.expiry.date() - today).days == 7]
odte_calls = seven_day_calls #[x for x in chain if x.Right == OptionRight.Call and x.Expiry.date() == today]
if not odte_calls:
return
# 1. Vendre 1 call ATM
atm_call = sorted(odte_calls, key=lambda x: abs(x.Strike - underlying_price))[0]
self.MarketOrder(atm_call.Symbol, -1)
self.Debug(f"Sold 1 ATM ODTE call: {atm_call.Symbol}")
# 2. Vendre 3 calls OTM avec delta ≈ 0.10
otm_candidates = [x for x in odte_calls if x.Strike > underlying_price]
if otm_candidates:
otm_sorted = sorted(otm_candidates, key=lambda x: abs(x.greeks.delta - 0.15))
otm_targets = otm_sorted[:1] # Just get one with delta ~0.10, sell 3 of it
for contract in otm_targets:
self.MarketOrder(otm_targets, -2)
self.Debug(f"Sold 3 OTM ODTE calls (delta~0.15): {contract.Symbol}")
def GetOptionGreeks(self, contract):
# Get greeks if available from the option security
option_security = self.Securities[contract.Symbol]
if option_security is not None and option_security.HasGreekData:
return option_security.Greeks
# Fallback to estimation if not available
return OptionGreeks()