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