Overall Statistics
Total Orders
5288
Average Win
0.36%
Average Loss
-2.31%
Compounding Annual Return
81.274%
Drawdown
16.400%
Expectancy
0.044
Start Equity
100000
End Equity
595674.1
Net Profit
495.674%
Sharpe Ratio
2.138
Sortino Ratio
1.926
Probabilistic Sharpe Ratio
96.384%
Loss Rate
10%
Win Rate
90%
Profit-Loss Ratio
0.16
Alpha
0
Beta
0
Annual Standard Deviation
0.256
Annual Variance
0.065
Information Ratio
2.174
Tracking Error
0.256
Treynor Ratio
0
Total Fees
$12743.40
Estimated Strategy Capacity
$0
Lowest Capacity Asset
QQQ 323UYHPDSACFA|QQQ RIWIV7K5Z9LX
Portfolio Turnover
19.87%
from AlgorithmImports import *
import datetime

class ShortStrangleWithStockCoverAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 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.Hour).Symbol
        self.option_symbol = self.AddOption(self.symbol, Resolution.Hour).Symbol
        self.contracts = []
        self.cover_orders = {}
        self.closing_orders = {}
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
        self.SetWarmUp(40)
        self.lev = 2
        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, Resolution.Hour) #, 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(-5, 5).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.025 * self.Securities[self.symbol].Price
        put_otm_level = 0.975 * 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.nbr_contract = max(1, int(self.lev * self.Portfolio.Cash / self.Securities[self.symbol].Price / 100))

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

        self.cover_orders[self.contracts[0].Symbol] = self.StopMarketOrder(self.symbol, 100*self.nbr_contract, otm_calls[0].ID.StrikePrice)
        self.cover_orders[self.contracts[1].Symbol] = self.StopMarketOrder(self.symbol, -100*self.nbr_contract, otm_puts[0].ID.StrikePrice)
        if self.Portfolio[self.symbol].Quantity!=0 and (self.Securities[self.symbol].Price<otm_calls[0].ID.StrikePrice*0.985
            or self.Securities[self.symbol].Price>otm_puts[0].ID.StrikePrice*1.015):
            self.closing_orders[self.contracts[0].Symbol] = self.set_holdings(self.symbol, 0) #, 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)
        if self.Portfolio[self.symbol].Quantity!=0 and (self.Securities[self.symbol].Price>otm_calls[0].ID.StrikePrice*1.015
            or self.Securities[self.symbol].Price<otm_puts[0].ID.StrikePrice*0.985):
            self.closing_orders[self.contracts[0].Symbol] = self.Liquidate()

        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}")