Overall Statistics
Total Trades
86
Average Win
0.01%
Average Loss
-0.01%
Compounding Annual Return
-0.151%
Drawdown
0.200%
Expectancy
-0.169
Net Profit
-0.075%
Sharpe Ratio
-0.663
Probabilistic Sharpe Ratio
7.102%
Loss Rate
60%
Win Rate
40%
Profit-Loss Ratio
1.10
Alpha
-0.001
Beta
-0.003
Annual Standard Deviation
0.002
Annual Variance
0
Information Ratio
-0.765
Tracking Error
0.136
Treynor Ratio
0.323
Total Fees
$86.00
Estimated Strategy Capacity
$3000.00
Lowest Capacity Asset
QQQ Y8HUL38O8FS6|QQQ RIWIV7K5Z9LX
Portfolio Turnover
0.01%
# region imports
from AlgorithmImports import *
# endregion

class MuscularTanMosquito(QCAlgorithm):

    # In raw price diff
    # sl = .25
    sl_dlrs = 200 #DEFAULT -- overridden by the left hand pane.
    pct_otm = 1.02 # DEFAULT

    dir = -1
    cts = 1

    ticker = 'QQQ'

    def Initialize(self):
        self.SetStartDate(2022, 11, 19)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        self.symbol = self.AddEquity(self.ticker, Resolution.Minute).Symbol

        # Required for options...
        self.Securities[self.symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)
        
        self.Schedule.On(self.DateRules.EveryDay(self.symbol),
                 self.TimeRules.BeforeMarketClose(self.symbol, 10),
                 self.EOD)
    
        self.open_d = None 
        self.high_d = None
        self.high_d1 = None 

        # PARAMETERS (actual)

        tst = self.GetParameter("SL")
        if tst: self.sl_dlrs = int(tst)

        
        tst = self.GetParameter("pct_otm")
        if tst: self.pct_otm = float(tst)


    def EOD(self):
        invested = [kvp.Key for kvp in self.Portfolio if kvp.Value.Invested]
        for symbol in invested:
            opnl = self.Portfolio[symbol].UnrealizedProfit
            self.Liquidate(symbol, f"EOD Exit -- PNL {opnl}")

    def OnData(self, data: Slice):

        if not data.ContainsKey(self.symbol): 
            return

        try:
            bar = data.Bars
            o,h,l,c = bar[self.symbol].Open, bar[self.symbol].High, bar[self.symbol].Low, bar[self.symbol].Close
        except:
            return 

        if (self.Time).hour == 9 and (self.Time).minute == 31:
            self.open_d = o
            self.high_d1 = self.high_d
            self.high_d = h
            hist = self.History(self.symbol, 5, Resolution.Daily).loc[self.symbol]
            self.high_d1 = hist.iloc[-1].high
            self.Debug(f'{self.Time} -- open > high[1] ? {self.open_d} > {self.high_d1}')

        
        self.high_d = max(h, self.high_d)

        if all([self.high_d, self.high_d1]):
            if self.EntryLogic:
                if not self.Portfolio.Invested:
                    # quantity = self.CalculateOrderQuantity(self.symbol, 0.95)
                    # self.MarketOrder(self.symbol, quantity, False, "Entry")
                    ct = self.GetOption(self.symbol, OptionRight.Call, c * self.pct_otm, self.Time + timedelta(days=5))
                    self.EnterOptions(ct)

    def EnterOptions(self, ct, dir=1):
        # added = [self.AddOptionContract(c) for c in buys + sells]
        self.AddOptionContract(ct)

        # price = self.Securities[ct].Price
        
        # total = self.Portfolio.TotalPortfolioValue
        # n_cts = total * .95 / price

        self.MarketOrder(ct, self.dir * self.cts)



    def GetOption(self, symbol, right, stk, expiry):
        # TO pass expiry: self.Time + timedelta(days = 30)
        contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)   
        
        #Get Expiry (nearest to date, small to large diff)
        expiry_sorted = sorted(contracts, key=lambda k: abs(k.ID.Date - expiry), reverse=False) #Asc
        closest = expiry_sorted[0].ID.Date
        correct_exp = [symbol for symbol in contracts if symbol.ID.Date == closest]
        
        #Select Side (Call / Put)
        rights = [symbol for symbol in correct_exp if symbol.ID.OptionRight == right]
        
        if len(rights) == 0: return None
        
        #self.Debug(f'Inner: {inner} ----- Outer: {outer}') 

        #Select Strike
        ct = sorted(rights, key=lambda k: abs(k.ID.StrikePrice - stk), reverse=False)[0]
    
        
        #self.Debug(f' (EOVertical) -- Inner: {inner_ct.ID.StrikePrice} ---- Outer: {outer_ct.ID.StrikePrice}')
        return ct

    @property
    def EntryLogic(self):
        return self.open_d > self.high_d1



    def OnOrderEvent(self, orderEvent):
        #ONLY concerned with FILLED orders. Wait on partials, etc.
        if orderEvent.Status != OrderStatus.Filled:
            return
                
        order_symbol = orderEvent.Symbol
        
        oid = orderEvent.OrderId
        order = self.Transactions.GetOrderById(oid)
        shares = orderEvent.AbsoluteFillQuantity
        entry_price = orderEvent.FillPrice 
        
        dir = orderEvent.Direction

        buy = dir == OrderDirection.Buy
        sell = dir == OrderDirection.Sell
        
        fill_price = orderEvent.FillPrice   

        entry = order.Tag.startswith("Entry")
        exit = order.Tag.startswith("SSL") or order.Tag.startswith("LSL")
        if entry:
            sl_pts = abs(self.sl_dlrs / shares / 100)  
            if buy:
                self.StopMarketOrder(order_symbol, -1 * shares, entry_price - sl_pts, "LSL")
            
            if sell:
                self.StopMarketOrder(order_symbol, -1 * shares, entry_price + sl_pts, "SSL")

            return 
        
        if exit:
            self.Transactions.CancelOpenOrders()

#region imports
from AlgorithmImports import *
#endregion
    # def ExecuteSpread(self, buys, sells, use_limits = False):
    #     added = [self.AddOptionContract(c) for c in buys + sells]
    #     if buys[0] not in self.Securities: return 

    #     #if not set(buys+sells).issubset(self.Securities): return  #More accurate, confusing.
    #     #usd_per_ct = self.GetICMargin(buys, sells)
    #     #self.Debug(f'USD per ct -- {usd_per_ct}')
    #     #num_cts = int(self.Portfolio.MarginRemaining / usd_per_ct * self.max_util)
    #     pf = (self.Portfolio.MarginRemaining * self.max_util)
    #     num_cts = 2 #max(num_cts,5)

    #     ## ---------------- ADDIT: APPROX of Margin
    #     call_dist = abs(buys[0].ID.StrikePrice - sells[0].ID.StrikePrice)
    #     put_dist = abs(buys[1].ID.StrikePrice - sells[1].ID.StrikePrice)
    #     distance = max(call_dist, put_dist)

    #     credit = abs(self.Securities[sells[0]].BidPrice - self.Securities[buys[0]].AskPrice) + \
    #             abs(self.Securities[sells[1]].BidPrice - self.Securities[buys[1]].AskPrice)

    #     self.Debug(f'Distance - credit * 100 --- {distance} - {credit} * 100 -- > {abs(distance - credit) * 100} ')

    #     if max(call_dist, put_dist) == 0:
    #         #Approximate
    #         ul = self.Securities[sells[0]].Underlying.Price * .2 
    #         k = sells[0].ID.StrikePrice
    #         m1 = .2 * ul - abs(k - ul)
    #         usd_per_cts = (m1 - credit) * 100
    #         self.Debug(f'Strangle Calc ----  per_ct: {m1}')
    #     else:
    #         usd_per_ct = (distance - credit) * 100
    #     num_cts = int(pf / usd_per_ct)

    #     ##  ---------------------- End Addit 

    #     self.Debug(f'Num Contracts -- {num_cts}')
    #     for buy in buys:
    #         if use_limits:
    #             ask = self.Securities[buy].AskPrice #Does this need to be buy.Symbol ? 
    #             self.LimitOrder(buy, num_cts, ask)
    #         else:
    #             self.MarketOrder(buy, num_cts)

    #     for sell in sells:
    #         if use_limits:
    #             bid = self.Securities[sell].BidPrice
    #             self.LimitOrder(sell, -num_cts, bid)
    #         else:
    #             self.MarketOrder(sell, -num_cts)

'''
                
    def GetVertical(self, symbol, right, inner, outer, expiry):
        #Get Contracts (OCP, bc no greeks, iv)
        contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
        
        #Get Expiry
        expiry_sorted = sorted(contracts, key=lambda k: abs(k.ID.Date - expiry), reverse=False) #Asc
        closest = expiry_sorted[0].ID.Date
        correct_exp = [symbol for symbol in contracts if symbol.ID.Date == closest]
        
        #Select Side (Call / Put)
        rights = [symbol for symbol in correct_exp if symbol.ID.OptionRight == right]
        
        if len(rights) == 0: return None, None
        
        #self.Debug(f'Inner: {inner} ----- Outer: {outer}') #THIS is correct... 
        #Select Strike
        inner_ct = sorted(rights, key=lambda k: abs(k.ID.StrikePrice - inner), reverse=False)[0]
        outer_ct = sorted(rights,key=lambda k: abs(k.ID.StrikePrice - outer), reverse=False)[0]
        
        #Not sure if I need to split this? (Maybe Just SORT by strike, and take FIRST, then NEXT?)
        
        #self.Debug(f' (EOVertical) -- Inner: {inner_ct.ID.StrikePrice} ---- Outer: {outer_ct.ID.StrikePrice}')
        return inner_ct, outer_ct

'''