Overall Statistics |
Total Trades 11 Average Win 0.00% Average Loss 0% Compounding Annual Return 0% Drawdown 0.000% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 24.151% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha -0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -2.755 Tracking Error 0.135 Treynor Ratio 0 Total Fees $7.00 Estimated Strategy Capacity $1100.00 Lowest Capacity Asset SPY 31KZK16ZBHROM|SPY R735QTJ8XC9X |
from SymbolData import * class RetrospectiveFluorescentPinkWhale(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 11, 26) # Set Start Date self.SetCash(100000) # Set Strategy Cash # ~~~ static vs dyanmic equity universe ~~~ # Static Universe # # OptionChainProvider vs AddOption # If you're using greeks, you'll need to use AddOption # # # AddOption leads to slow backtests, basically impossible to use with dynamic # # OptionChainProvider - faster backtests but lack of greeks and IV # If no greeks/IV needed -> OptionChainProvider # Dynamic Equity Universe # OptionChainProvider leads to much faster backtests # # If using AddOption, generally will use bottom technique to retrieve contracts # data.OptionsChains[option_chain.Symbol(identifier for chain)].Contracts # list of OptionContract objects # OptionContract.Greeks.Delta, etc # Medium sized static universe self.benchmark = "SPY" tickers = ["SPY", "QQQ", "GLD", "TLT"] self.symbol_data = {} for ticker in tickers: equity = self.AddEquity(ticker, Resolution.Minute) # this will be done automatically equity.SetDataNormalizationMode(DataNormalizationMode.Raw) # rebalance scheduled event self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.BeforeMarketClose(self.benchmark, 5), self.Rebalance) self.SetSecurityInitializer(lambda security: security.SetMarketPrice(self.GetLastKnownPrice(security))) ### STRATEGY #### # Entry: 5 minutes before close daily (35 day Current MA) > (35 day MA - 5 periods) -> Long # Write Weekly ATM put, and buy ~ $10 below market price # Just let it expire with either max profit or close before expiration, buy back for loss # open_option_positions = [symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested and \ # symbol.SecurityType == SecurityType.Option] # # 30 mins before market close on friday/OEC # for symbol in open_option_positions: # # close out # self.Liquidate(symbol) def Rebalance(self): for symbol, symbol_data in self.symbol_data.items(): if symbol.Value != "SPY": continue; if not symbol_data.sma_window.IsReady: continue signal = symbol_data.long_signal if signal and not self.Portfolio[symbol].Invested: # day of week - self.Time.weekday(), from there calculate dte needed current_market_price = self.Securities[symbol].Price short_put = self.select_option(symbol, OptionRight.Put, current_market_price, 7) long_put = self.select_option(symbol, OptionRight.Put, current_market_price - 25, 7) # could put this inside of select_option # nuanced detail - when we call AddOptionContract to subscribe to # option data, data is not immediately available, it takes 1 time step # so if we try to call market order/ limit order, it won't be able to fill self.AddOptionContract(short_put) self.AddOptionContract(long_put) short_put_bid = self.Securities[short_put].BidPrice long_put_ask = self.Securities[short_put].AskPrice self.LimitOrder(short_put, -1, short_put_bid) self.LimitOrder(long_put, 1, long_put_ask) def select_option(self, underlying_symbol, option_right, strike, expiry_dte): # underlying symbol and date of option data needed # looks into option open interest data for that underlying # returns that # list of Symbol objects contract_symbols = self.OptionChainProvider.GetOptionContractList(underlying_symbol, self.Time) filtered_for_right = [symbol for symbol in contract_symbols if symbol.ID.OptionRight == option_right] expiry_date = self.Time + timedelta(expiry_dte) # ascending order - reverse=False - default sorted_by_expiry = sorted(filtered_for_right, key=lambda s:abs(s.ID.Date - expiry_date), reverse=False) if len(sorted_by_expiry) > 0: desired_expiry_date = sorted_by_expiry[0].ID.Date else: self.Debug(f"No contracts found for {underlying_symbol}") return None contracts_with_desired_expiry = [symbol for symbol in filtered_for_right if symbol.ID.Date == desired_expiry_date] # sorted ascending again sorted_by_strike = sorted(contracts_with_desired_expiry, key=lambda s: abs(s.ID.StrikePrice - strike), reverse=True) if len(sorted_by_strike) > 0: # returns a symbol object for selected option return sorted_by_strike[0] else: return None def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: symbol = security.Symbol if not symbol in self.symbol_data: self.symbol_data[symbol] = SymbolData(self, symbol) for security in changes.RemovedSecurities: symbol = security.Symbol if symbol in self.symbol_data: symbol_data = self.symbol_data.pop(symbol, None) symbol_data.kill_data_subscriptions() def OnData(self, data): '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. Arguments: data: Slice object keyed by symbol containing the stock data '''
class SymbolData: def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol # self.algorithm.SMA self.sma = SimpleMovingAverage(35) # User defined method OnSMA will fire everytime SMA is updated self.sma.Updated += self.OnSMA # consolidator is subscribed to receive data self.daily_consolidator = QuoteBarConsolidator(timedelta(days=1)) self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.daily_consolidator) # consolidator will update to indicator self.algorithm.RegisterIndicator(self.symbol, self.sma, self.daily_consolidator) self.sma_window = RollingWindow[IndicatorDataPoint](5) # pandas dataframe stuff is buggy # self.warm_up_sma() def warm_up_sma(self): # option 1: update consolidator with minute historical data -> spit out daily bar -> update sma # option 2: daily historical data -> update sma # daily data in # 0th index is symbol, 1st index is Time history = self.algorithm.History(self.symbol, 35, Resolution.Daily).unstack(0).close # time series daily close data, with each column being symbol if not history.empty: if str(self.symbol) in history.columns: close_pandas_series_data = history[str(self.symbol)] for time, close in close_pandas_series_data: self.sma.Update(time, close) def OnSMA(self, sender, updated): if self.sma.IsReady: # self.sma - SimpleMovingAverage/Indicator Object # self.sma.Current = IndicatorDataPoint object # self.sma.Current.Value - float object self.sma_window.Add(self.sma.Current) def kill_data_subscriptions(self): self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.daily_consolidator) @property def long_signal(self): return self.sma.Current.Value > self.sma_window[4].Value