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()