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 '''