Overall Statistics
Total Trades
16
Average Win
0.09%
Average Loss
-0.17%
Compounding Annual Return
-0.574%
Drawdown
0.500%
Expectancy
-0.119
Net Profit
-0.121%
Sharpe Ratio
-0.721
Probabilistic Sharpe Ratio
20.825%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
0.54
Alpha
0.005
Beta
-0.011
Annual Standard Deviation
0.007
Annual Variance
0
Information Ratio
-5.123
Tracking Error
0.179
Treynor Ratio
0.467
Total Fees
$12.00
Estimated Strategy Capacity
$1300000.00
################################################################################
# The Weekly QQQ Call Write
# ----------------------------------
#
# Entry:
# -------
# On every Friday 30 minutes before market close, short as many calls with 50% 
# of portfolio cash DTE=5-9 day(Weekly), Strike Price >= Number of sigma of current price 
# Test different sigma for sweet spot
#
# Exit:
# -------
# Close the call with a stop loss order when loss is 100%(schedule price check 
# every hour or use a stop loss order in OnData ?), 
# or 
# Close the call 30 minutes before market close next Friday
#
################################################################################

from QuantConnect.Indicators import *
from datetime import timedelta, datetime
import numpy as np

class WeeklyCall(QCAlgorithm):

    # ==================================================================================
    # Main entry point for the algo     
    # ==================================================================================
    def Initialize(self):
        # Set the initial cash
        self.SetCash(100000)
        # Start and end dates
        self.SetStartDate(2020,11,1)
        self.SetEndDate(2021,1,16)
        

        
        # parameters ------------------------------------------------------------
        self.minDTE = 5  # minimum target days till expiration
        self.maxDTE = 9  # maximum target days till expiration
        self.numSigma = 1.5 # To calculate strike Price
        self.numEntryPosition = 0.5 # Number of short selling call option
        self.ToleranceOfLoss = 2 # 2 represents for stop loss in 100% loss
        self.DaysSTD = 10 # Number of days to calculate Standard Deviation
        # ------------------------------------------------------------------------
        
        self.SetWarmup(self.DaysSTD, Resolution.Daily)
        
        self.CurrentContract = str()
        self.PreparedContract = str()
        self.CostOfContract = 0
        
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
        
        self.symbol = self.AddEquity("QQQ", Resolution.Minute).Symbol
        self.Securities["QQQ"].SetDataNormalizationMode(DataNormalizationMode.Raw);
        self.qqq_std_price = self.STD("QQQ", self.DaysSTD, Resolution.Daily)
        # self.window = []
        
        # Set Benchmark
        self.SetBenchmark("QQQ")
        
        #self.Schedule.On(self.DateRules.WeekEnd("QQQ"), \
        #                self.TimeRules.BeforeMarketClose("QQQ", 31), \
        #                self.PrepareContract)
        
        self.Schedule.On(self.DateRules.WeekEnd("QQQ"), \
                        self.TimeRules.BeforeMarketClose("QQQ", 30), \
                        self.SellCall)
        self.contract = None
                         
        
        
    def OnData(self, data):

        if self.contract != None and self.Portfolio[self.contract].Price >= 2 * self.CostOfContract:
            self.Liquidate()
            self.contract = None
        
        if self.CurrentContract == str() and self.Time.weekday() == 4 and self.Time.hour == 15 and self.Time.minute == 29:
            self.OptionFilter(data)


    def SellCall(self):
        if self.contract is None:
            return
        
        self.AddOptionContract(self.contract, Resolution.Minute)
        if not self.Securities[self.contract].IsTradable:
            self.contract = None
            return
        self.CostOfContract = self.MarketOrder(self.contract, -1).AverageFillPrice
        
    
    def OptionFilter(self, data):
        if not(data.ContainsKey(self.symbol) and data[self.symbol] is not None):
            return str()
        
        UnderlyingPrice = data[self.symbol].Close
        std = self.qqq_std_price.Current.Value
        est_strike = self.MyRound(self.numSigma * std + UnderlyingPrice)

        contracts = self.OptionChainProvider.GetOptionContractList("QQQ", data.Time)
        
        Contracts_Expiry = [contract for contract in contracts if contract.ID.OptionRight == OptionRight.Call and
                    self.minDTE <= (contract.ID.Date - data.Time).days <= self.maxDTE and
                    contract.ID.StrikePrice >= est_strike]

        if len(Contracts_Expiry) == 0:
            return str()
                    
        Final_Contract = sorted(Contracts_Expiry, key=lambda x: abs(x.ID.StrikePrice - est_strike))
        self.contract = Final_Contract[0]
        return self.contract
        
    # Round the target number to its closest integer divisible by the baseinteger divisible by the base         
    def MyRound(self, target, base=5):
        return base * round(target/base)