Overall Statistics
Total Orders
1
Average Win
0%
Average Loss
0%
Compounding Annual Return
38.353%
Drawdown
31.000%
Expectancy
0
Start Equity
100000
End Equity
621972.64
Net Profit
521.973%
Sharpe Ratio
1.05
Sortino Ratio
1.235
Probabilistic Sharpe Ratio
51.013%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0.142
Beta
1.191
Annual Standard Deviation
0.254
Annual Variance
0.065
Information Ratio
1.009
Tracking Error
0.16
Treynor Ratio
0.224
Total Fees
$3.36
Estimated Strategy Capacity
$0
Lowest Capacity Asset
AAPL R735QTJ8XC9X
Portfolio Turnover
0.05%
from AlgorithmImports import *
from datetime import timedelta

class CoveredCallWithSMAStrategy(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2019, 1, 1)  # Set Start Date
        #self.SetEndDate(2029, 12, 31)  # Set End Date
        self.SetCash(100000)  # Set Strategy Cash
        self.symbol = self.AddEquity("AAPL", Resolution.Daily).Symbol
        self.option_symbol = self.AddOption(self.symbol, Resolution.Daily).Symbol
        self.contract = None
        self.sma = self.SMA(self.symbol, 200, Resolution.Daily, Field.Close)  # 200-day Simple Moving Average
        self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday), self.TimeRules.AfterMarketOpen(self.symbol, 10), self.CheckSMAAndTrade)
        self.SetWarmUp(200)
        
    def CheckSMAAndTrade(self):
        if self.IsWarmingUp:
            return

        current_price = self.Securities[self.symbol].Price
        if not self.Portfolio[self.symbol].Invested:
            self.SetHoldings(self.symbol, 1.0)  # Buy shares
        
        # Fetch the option chain
        option_chain = self.OptionChainProvider.GetOptionContractList(self.option_symbol, self.Time)
        if not option_chain:
            self.Log("No option chain available.")
            return
        
        # Filter for weekly options expiring next Friday
        expiry = self.Time + timedelta((4 - self.Time.weekday()) % 7)
        options = [x for x in option_chain if x.ID.Date == expiry]

        if not options:
            self.Log("No options available for the required expiry.")
            return
        
        # Determine if we are above or below the SMA
        if current_price > self.sma.Current.Value:
            self.Log("Price is above the 200-day SMA. Selling OTM covered call.")
            # Sell an OTM covered call with a delta of 0.30
            options = sorted(options, key=lambda x: x.ID.StrikePrice)
            calls = [x for x in options if x.ID.OptionRight == OptionRight.Call]
            otm_calls = sorted(calls, key=lambda x: abs(self.CalculateDelta(x) - 0.30))
            if otm_calls:
                self.contract = otm_calls[0]
        else:
            self.Log("Price is below the 200-day SMA. Selling ITM covered call.")
            # Sell an ITM covered call with a delta of 0.70
            options = sorted(options, key=lambda x: x.ID.StrikePrice, reverse=True)
            calls = [x for x in options if x.ID.OptionRight == OptionRight.Call]
            itm_calls = sorted(calls, key=lambda x: abs(self.CalculateDelta(x) - 0.70))
            if itm_calls:
                self.contract = itm_calls[0]
        
        if self.contract and not self.Portfolio[self.contract.Symbol].Invested:
            self.Sell(self.contract.Symbol, 1)  # Sell the covered call

    def CalculateDelta(self, option_contract):
        # Option Greeks are calculated and provided by QuantConnect's underlying system
        chain = self.Securities[self.option_symbol].OptionChainProvider.GetOptionContractList(self.option_symbol, self.Time)
        for option in chain:
            if option.Symbol == option_contract.Symbol:
                return option.Greeks.Delta
        return None
    
    def OnOrderEvent(self, orderEvent):
        self.Log(f"OrderEvent: {orderEvent}")
    
    def OnEndOfAlgorithm(self):
        self.Log(f"Final Portfolio Value: {self.Portfolio.TotalPortfolioValue}")