Overall Statistics |
Total Trades 6 Average Win 8.37% Average Loss 0% Compounding Annual Return 17.211% Drawdown 28.300% Expectancy 0 Net Profit 17.211% Sharpe Ratio 0.798 Probabilistic Sharpe Ratio 39.019% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha -0.06 Beta 0.894 Annual Standard Deviation 0.255 Annual Variance 0.065 Information Ratio -0.88 Tracking Error 0.104 Treynor Ratio 0.227 Total Fees $132.75 Estimated Strategy Capacity $260000000.00 |
from clr import AddReference AddReference("System") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Data import * from QuantConnect.Algorithm import * import numpy as np from datetime import timedelta from QuantConnect.Securities.Option import OptionPriceModels import pandas as pd import numpy as np class BasicTemplateIndexOptionsAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2009, 1, 1) self.SetEndDate(2009, 12, 31) self.initial_cash = 1000000 self.SetCash(self.initial_cash) self.benchmark_shares = None # Set securities and universe self.spy = self.AddEquity('SPY', Resolution.Minute).Symbol self.Securities['SPY'].SetDataNormalizationMode(DataNormalizationMode.Raw); self.spx = self.AddIndex('SPX', Resolution.Minute).Symbol self.underlying = self.spy # Set parameters self.spy_percentage = 1.0 # % invested in SPY self.max_leverage = 1.05 self.min_leverage = 0.95 self.target_expiry = 365 self.multiplier = 100 # 100 for SPY and 1000 for SPX since SPX is 10 times largest # Set helper amounts self.opts_to_trade = pd.Series() self.opts_to_sell = pd.Series() self.opt_invested = False # Use SPY as the benchmark self.SetBenchmark('SPY') self.SetWarmUp(TimeSpan.FromDays(30)) # Schedule OTM put hedge every week 30 mins prior to market close self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose(self.underlying,30), self.opt_replication) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose(self.underlying,15), self.check_expiries) # self.Schedule.On(self.DateRules.EveryDay(), # self.TimeRules.BeforeMarketClose(self.underlying,5), # self.check_leverage) def OnAssignmentOrderEvent(self, assignmentEvent): # If we get assigned, close out all positions self.Liquidate() self.Log(str(assignmentEvent)) self.opt_invested = False def OnData(self, data): if self.IsWarmingUp: return if self.underlying not in data.Bars: return if not self.opts_to_trade.empty: remaining_opts = self.opts_to_trade.copy(True) for opt, amount in self.opts_to_trade.iteritems(): if data.ContainsKey(opt): self.MarketOrder(opt, amount) del remaining_opts[opt] self.opt_invested = True self.opts_to_trade = remaining_opts def get_benchmark_performance(self): price = self.Securities[self.underlying].Close if not self.benchmark_shares: self.benchmark_shares = self.initial_cash / price return self.benchmark_shares * price def log_holdings(self): # Log holdings, amount of portfolio hedged, etc. return def check_expiries(self): opt_pos = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] if opt_pos: # Get current time curr_dt = self.Time close_out_opts = [x for x in opt_pos if (x.ID.Date - curr_dt) < timedelta(0)] for opt in close_out_opts: # Get SPY level to see if option is ITM underlying_px = self.Securities[self.underlying].Price if opt.ID.OptionRight == 1: if opt.ID.StrikePrice >= underlying_px: self.Liquidate(opt) self.opt_invested = False else: if opt.ID.StrikePrice <= underlying_px: self.Liquidate(opt) self.opt_invested = False def opt_replication(self): if self.opt_invested: return # Get contract list and convert to DF contracts = self.OptionChainProvider.GetOptionContractList(self.underlying, self.Time) if len(contracts) == 0: return opts = contract_list_to_df(contracts) puts = opts[opts['Right'] == 1] calls = opts[opts['Right'] == 0] # Get index level curr_idx_lvl = self.Securities[self.underlying].Price # Get puts 1 year out filtered_puts_expiry = self.select_closest_expiry(puts, self.target_expiry) filtered_puts_expiry = puts.loc[filtered_puts_expiry.index] # Get calls 1 year out filtered_calls_expiry = self.select_closest_expiry(calls, self.target_expiry) filtered_calls_expiry = calls.loc[filtered_calls_expiry.index] # Get ATM put puts_to_sell = self.select_closest_strike(filtered_puts_expiry, curr_idx_lvl) puts_to_sell_info = puts.loc[puts_to_sell] puts_to_sell_symbol = puts_to_sell_info['Symbol'] # Get ATM call calls_to_buy = self.select_closest_strike(filtered_calls_expiry, curr_idx_lvl) calls_to_buy_info = calls.loc[calls_to_buy] calls_to_buy_symbol = calls_to_buy_info['Symbol'] # Calculate size to buy puts_to_sell_amount = -self.Portfolio.TotalPortfolioValue / (self.multiplier * curr_idx_lvl) calls_to_buy_amount = self.Portfolio.TotalPortfolioValue / (self.multiplier * curr_idx_lvl) # Create series of weights opts_to_trade = pd.Series(index = puts_to_sell_symbol, data = puts_to_sell_amount) opts_to_trade = opts_to_trade.append(pd.Series(index = calls_to_buy_symbol, data = calls_to_buy_amount)) # Calculate final amounts # opts_to_buy *= opts_to_buy_amount opts_to_trade = opts_to_trade.round(0) # Get rid of zero quantity opts_to_trade = opts_to_trade[opts_to_trade.abs() > 0] # Add security [self.AddOptionContract(sym, Resolution.Minute) for sym in opts_to_trade.index] self.opts_to_trade = opts_to_trade def select_closest_strike(self, options, strike): strike_distance = (options['Strike'] - strike).abs() opts_to_trade = strike_distance.groupby(options['Expiry']).idxmin().values return opts_to_trade def select_closest_expiry(self, options, expiry_time): """Select option closest to desired expiration Args: options (pd.DataFrame): df with options information expiry_time (int): number of calendar days until expiry desired Returns: pd.Series: series with days to expiration as values and option symbols as index """ # Get current time curr_dt = self.Time # Get desired expiration date desired_expiry = curr_dt + pd.Timedelta(days = expiry_time) desired_expiry = desired_expiry.replace(hour = 0, minute = 0) # Find distance to expiration expiry_distances = options['Expiry'] - desired_expiry # Get only expiries 1 year out #expiry_distances = expiry_distances[expiry_distances > pd.Timedelta(0)] # Get lowest expiration distance and options expiring on that date min_expiry_days = expiry_distances.abs().min() min_expiry = expiry_distances.abs().idxmin() min_expiry_date = expiry_distances.loc[min_expiry] # If min expiry is after desired time, then get expiry before desired time if min_expiry_days.days > 0: second_min_expiry_days = expiry_distances[expiry_distances < min_expiry_days].max() second_min_expiry = expiry_distances[expiry_distances < min_expiry_days].idxmax() second_min_expiry_date = expiry_distances.loc[second_min_expiry] else: second_min_expiry_days = expiry_distances[expiry_distances > min_expiry_days].min() second_min_expiry = expiry_distances[expiry_distances > min_expiry_days].idxmin() second_min_expiry_date = expiry_distances.loc[second_min_expiry] # If the expiry is more than 5 days out, then create custom expiry # by trading two options of different expiries if min_expiry_days.days > 5: opts_to_trade = expiry_distances[expiry_distances.isin([min_expiry_date, second_min_expiry_date])] else: opts_to_trade = expiry_distances[expiry_distances == min_expiry_date] return opts_to_trade def OnEndOfAlgorithm(self) -> None: spy_pos = self.Portfolio['SPY'].Quantity self.Log('SPY quantity: {0}'.format(spy_pos)) def contract_list_to_df(contract_list): return pd.DataFrame([[x.ID.OptionRight, float(x.ID.StrikePrice), x.ID.Date, x.ID.ToString() ] for x in contract_list], index=[x.ID for x in contract_list], columns=['Right', 'Strike', 'Expiry', 'Symbol'])