Overall Statistics
Total Orders
2933
Average Win
0.12%
Average Loss
-0.08%
Compounding Annual Return
42.300%
Drawdown
4.700%
Expectancy
0.456
Start Equity
100000
End Equity
142163
Net Profit
42.163%
Sharpe Ratio
2.314
Sortino Ratio
3.228
Probabilistic Sharpe Ratio
95.680%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
1.52
Alpha
0
Beta
0
Annual Standard Deviation
0.115
Annual Variance
0.013
Information Ratio
2.476
Tracking Error
0.115
Treynor Ratio
0
Total Fees
$1635.00
Estimated Strategy Capacity
$2000.00
Lowest Capacity Asset
QQQ 323F7C7O1OBC6|QQQ RIWIV7K5Z9LX
Portfolio Turnover
57.36%
from AlgorithmImports import *
import datetime

class ShortStrangleWithStockCoverAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 1, 1)  # Set Start Date
        self.SetEndDate(2022, 12, 31)  # Set End Date
        self.SetCash(100000)  # Set Strategy Cash
        self.symbol = self.AddEquity("QQQ", Resolution.Daily).Symbol
        self.option_symbol = self.AddOption(self.symbol).Symbol
        self.contracts = []
        self.cover_orders = {}
        self.closing_orders = {}
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
        self.SetWarmUp(40)
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
        #self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday),  self.on_data)
        #self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(15, 45), self.CheckStopOrders)
        option = self.add_option(self.symbol) #, self.Time)
        #option_chain.set_filter(min_expiry=timedelta(days=30), max_expiry=timedelta(days=40))
        option.set_filter(self.universe_func)
        self.symbolo = option.symbol

    def universe_func(self, universe: OptionFilterUniverse) -> OptionFilterUniverse:
        return universe.include_weeklys().strikes(-15, 15).expiration(timedelta(30), timedelta(40))    

    def on_data(self, slice: Slice) -> None:
      if self.Time.hour == 10 and self.Time.minute == 0:
                
        option_chain = self.OptionChainProvider.GetOptionContractList(self.symbolo, self.Time)
        if not option_chain:
            self.Log("No option chain available.")
            return
        
    

        #target_expiry = self.Time + timedelta(days=30)
        #expiry_dates = sorted(list(set([x.ID.Date for x in option_chain])))
        #expiry = min([date for date in expiry_dates if date >= target_expiry], key=lambda x: abs((x - target_expiry).days), default=None)

        call_otm_level = 1.02 * self.Securities[self.symbol].Price
        put_otm_level = 0.98 * self.Securities[self.symbol].Price
        #expiry = sorted([x.ID.Date for x in option_chain], key=lambda x: abs((x - self.Time).days - 30))
        otm_calls = sorted([x for x in option_chain if x.ID.OptionRight == OptionRight.Call and x.ID.StrikePrice > call_otm_level])
        otm_puts = sorted([x for x in option_chain if x.ID.OptionRight == OptionRight.Put and x.ID.StrikePrice < put_otm_level])

        if not otm_calls or not otm_puts:
            return

        self.contracts = [self.AddOptionContract(otm_calls[0], Resolution.Daily), self.AddOptionContract(otm_puts[0], Resolution.Daily)]
        self.Sell(self.contracts[0].Symbol, 1)
        self.Sell(self.contracts[1].Symbol, 1)

        self.cover_orders[self.contracts[0].Symbol] = self.StopMarketOrder(self.symbol, 100, otm_calls[0].ID.StrikePrice*1.01)
        self.cover_orders[self.contracts[1].Symbol] = self.StopMarketOrder(self.symbol, -100, otm_puts[0].ID.StrikePrice*0.99)
        self.closing_orders[self.contracts[0].Symbol] = self.StopMarketOrder(self.symbol, -100, otm_calls[0].ID.StrikePrice*0.995)
        self.closing_orders[self.contracts[1].Symbol] = self.StopMarketOrder(self.symbol, 100, otm_puts[0].ID.StrikePrice*1.005)
        
        self.Log(f"Opened strangle with contracts: {self.contracts[0].Symbol} (Call) and {self.contracts[1].Symbol} (Put)")


    def LiquidateStockCover(self, contract):
        stock_quantity = self.Portfolio[self.symbol].Quantity
        if (contract.Symbol in self.cover_orders) and stock_quantity!=0:
            self.Liquidate(self.symbol)
            #del self.cover_orders[contract.Symbol]
            #self.contracts.remove(contract)
            #self.Buy(contract.Symbol, 1)  # Close the short option position
            self.Log(f"Liquidated stock cover for contract: {contract.Symbol}")
    
    def OnOrderEvent(self, orderEvent):
     self.Log(f"OrderEvent: {orderEvent}")
     if orderEvent.Status == OrderStatus.Filled:
            order = self.Transactions.GetOrderById(orderEvent.OrderId)
            if order.Type == OrderType.StopMarket:
                if order.Symbol in self.cover_orders.values():
                    if order.Quantity > 0:
                        self.MarketOrder(order.Symbol, 100)
                    else:
                        self.MarketOrder(order.Symbol, -100)
                    self.cover_orders = {k: v for k, v in self.cover_orders.items() if v.OrderId != orderEvent.OrderId}
                elif order.Symbol in self.closing_orders.values():
                    if order.Quantity > 0:
                        self.MarketOrder(order.Symbol, -100)
                    else:
                        self.MarketOrder(order.Symbol, 100)
                    self.closing_orders = {k: v for k, v in self.closing_orders.items() if v.OrderId != orderEvent.OrderId}

    def OnEndOfAlgorithm(self):
        self.Log(f"Final Portfolio Value: {self.Portfolio.TotalPortfolioValue}")