def backtest(self): #now we want to loop through and create options objects that we bought and sold at the roll rolled_options = [] rolled_strikes = [] rolled_prices = [] roll_posted = [] df = self.df for index, row in df.iterrows(): #hardcoding 2 as risk free rate for now options_rolled = [] option_strikes = [] option_prices = [] for i, option in enumerate(self.strategy): option_type = option[0] zscore = option[1] buy_sell = option[2] #30 delta put #problem is we don't know implied vol until we define strike #don't know strike until we get implied vol #strike = strike_from_delta(row['roll_SPY'],row['roll_days_to_expir']/365,.01,row['roll_VIX']/100,.30,option_type) if self.backtest_type == 'constant_margin': if i == 0: strike = calculate_strike(option_type, row['roll_SPY'], row['roll_VIX'], zscore) implied_vol = get_implied_vol(option_type, strike, row['roll_SPY'], row['roll_VIX'], row['roll_SKEW']) o = Option(buy_sell, option_type, row['roll_SPY'], strike, row['roll_days_to_expir'] / 365, None, .01, implied_vol / 100, 0.03) o.get_price_delta() options_rolled.append([o]) option_strikes.append(strike) option_prices.append(o.calc_price) if i == 0: #this is the sold put amt_posted = strike / self.leverage #maitenance = strike1 - strike2 #.7 = maitenance / amt_posted #.7 * amt_posted #willing to lose at maximum 70% strike2 = strike - (self.max_loss * amt_posted) implied_vol = get_implied_vol(option_type, strike2, row['roll_SPY'], row['roll_VIX'], row['roll_SKEW']) o2 = Option('BUY', option_type, row['roll_SPY'], strike2, row['roll_days_to_expir'] / 365, None, .01, implied_vol / 100, 0.03) o2.get_price_delta() options_rolled.append([o2]) option_strikes.append(strike2) option_prices.append(o2.calc_price) #we don't need to get any other options calculations in this strategy break else: strike = calculate_strike(option_type, row['roll_SPY'], row['roll_VIX'], zscore) implied_vol = get_implied_vol(option_type, strike, row['roll_SPY'], row['roll_VIX'], row['roll_SKEW']) o = Option(buy_sell, option_type, row['roll_SPY'], strike, row['roll_days_to_expir'] / 365, None, .01, implied_vol / 100, 0.03) o.get_price_delta() options_rolled.append([o]) option_strikes.append(strike) option_prices.append(o.calc_price) if i == 0: #this is the sold put amt_posted = strike / self.leverage rolled_options.append(options_rolled) rolled_strikes.append(option_strikes) rolled_prices.append(option_prices) roll_posted.append(amt_posted) df['rolled_options'] = rolled_options df['rolled_strikes'] = rolled_strikes df['rolled_prices'] = rolled_prices df['previous_rolled_strikes'] = df['rolled_strikes'].shift(1) df['previous_rolled_strikes'] = df['previous_rolled_strikes'].fillna( method='bfill') df['previous_days_to_expir'] = ( df['next_expiration'].shift(1).fillna(method='bfill') - df['date']).dt.days df['roll_posted'] = roll_posted df['roll_posted'] = df['roll_posted'].shift(1).fillna(method='bfill') #now we create a time series of the same options measured as time decays and the market moves options_list = [] prices_list = [] delta_list = [] vega_list = [] theta_list = [] gamma_list = [] portfolio_prices = [] portfolio_deltas = [] portfolio_vegas = [] portfolio_thetas = [] portfolio_gammas = [] previous_option_price = [] maintenances_list = [] margin_triggers = [] for index, row in df.iterrows(): #hardcoding 2 as risk free rate for now options = [] prices = [] deltas = [] vegas = [] thetas = [] gammas = [] previous_rolled_option_price = [] for i, option in enumerate(self.strategy): option_type = option[0] zscore = option[1] buy_sell = option[2] strike = row['rolled_strikes'][i] implied_vol = get_implied_vol(option_type, strike, row['SPY'], row['VIX'], row['SKEW']) o = Option(buy_sell, option_type, row['SPY'], strike, row['days_to_expir'] / 365, None, .02, implied_vol / 100, 0.03) o.get_price_delta() options.append([o]) prices.append(o.calc_price) deltas.append(o.delta) o.get_vega() vegas.append(o.vega) o.get_theta() thetas.append(o.theta) o.get_gamma() gammas.append(o.gamma) ##caculate price on previously rolled option ##if backtester is too slow we could add in a codition that it must be roll to caculate, otherwise previous option = current option previous_strike = row['previous_rolled_strikes'][i] previous_implied_vol = get_implied_vol(option_type, previous_strike, row['SPY'], row['VIX'], row['SKEW']) o_previous = Option(buy_sell, option_type, row['SPY'], previous_strike, row['previous_days_to_expir'] / 365, None, .02, previous_implied_vol / 100, 0.03) o_previous.get_price_delta() previous_rolled_option_price.append(o_previous.calc_price) #-price for sold calls #for the sold naked put if i == 0: if (len(self.strategy) > 1 and (self.strategy[0][2] == 'SELL' and self.strategy[1][2] == 'BUY')): #then we're doing a spread maintenance = row['previous_rolled_strikes'][0] - row[ 'previous_rolled_strikes'][1] margin_trigger = maintenance > row['roll_posted'] elif self.backtest_type == 'constant_margin': maintenance = row['previous_rolled_strikes'][0] - row[ 'previous_rolled_strikes'][1] margin_trigger = maintenance > row['roll_posted'] else: maintenance = calculate_maintenance_requirements( o_previous, row['SPY']) margin_trigger = maintenance > row['roll_posted'] previous_option_price.append(previous_rolled_option_price) options_list.append(options) prices_list.append(prices) delta_list.append(deltas) vega_list.append(vegas) theta_list.append(thetas) gamma_list.append(gammas) #averages for portfolio attributes portfolio_price = sum(prices) / float(len(prices)) portfolio_delta = sum(deltas) / float(len(deltas)) portfolio_vega = sum(vegas) / float(len(vegas)) portfolio_theta = sum(thetas) / float(len(thetas)) portfolio_gamma = sum(gammas) / float(len(gammas)) portfolio_prices.append(portfolio_price) portfolio_deltas.append(portfolio_delta) portfolio_vegas.append(portfolio_vega) portfolio_thetas.append(portfolio_theta) portfolio_gammas.append(portfolio_gamma) maintenances_list.append(maintenance) margin_triggers.append(margin_trigger) df['previous_option_current_price'] = previous_option_price df['options'] = options_list df['prices'] = prices_list df['deltas'] = delta_list df['vegas'] = vega_list df['thetas'] = theta_list df['gammas'] = gamma_list df['portfolio_price'] = portfolio_prices df['portfolio_delta'] = portfolio_deltas df['portfolio_vega'] = portfolio_vegas df['portfolio_theta'] = portfolio_thetas df['portfolio_gamma'] = portfolio_gammas df['maintenance'] = maintenances_list df['previous_prices'] = df['prices'].shift(1) df['previous_prices'] = df['previous_prices'].fillna(method='bfill') df['margin_trigger'] = margin_triggers returns_list = [] portfolio_returns = [] for index, row in df.iterrows(): returns = [] for i, option in enumerate(self.strategy): if i == 0: #log the put's stike to calculate other returns and leverage from #key assumption - all leverage revolves around the sold put previous_rolled_strike = row['previous_rolled_strikes'][i] current_price = row['previous_option_current_price'][i] previous_price = row['previous_prices'][i] ret = (current_price - previous_price) / previous_rolled_strike returns.append(ret) returns_list.append(returns) portfolio_return = sum(returns) portfolio_returns.append(portfolio_return) df['returns_list'] = returns_list df['portfolio_returns_raw'] = portfolio_returns #Lever df['portfolio_returns_raw'] = df[ 'portfolio_returns_raw'] * self.leverage ##initialize delta hedging ##short the spy to keep delta at a steady .3 df['spy_return'] = (df['SPY'] - df['SPY'].shift(1)) / (df['SPY'].shift(1)) #0 for first return #df['spy_return'] = df['spy_return'].fillna(0) #lever other portfolio metrics as well like greeks df['portfolio_delta'] = df['portfolio_delta'] * self.leverage #df['delta_adjustment'] = .6 - df['portfolio_delta'] #take the delta adjustment one step in the future (that is you look at your portfolio delta at the end of the day and then short the spy by that amount) #df['delta_adjustment'] = df['delta_adjustment'].shift(1).fillna(0) #df['short_spy_return'] = df['delta_adjustment'] * df['spy_return'] #df['portfolio_returns_raw'] = df['portfolio_returns_raw'] + df['short_spy_return'] df['portfolio_vega'] = df['portfolio_vega'] * self.leverage df['portfolio_theta'] = df['portfolio_theta'] * self.leverage df['portfolio_gamma'] = df['portfolio_gamma'] * self.leverage #cum_sum of returns within roll period df['roll_period'] = df['roll_date'].shift(1) df['roll_period'] = df['roll_period'].fillna(method='bfill') df['next_roll_period'] = df['next_roll_date'].shift(1) df['next_roll_period'] = df['next_roll_period'].fillna(method='bfill') df['roll_cumulative_return_raw'] = df.groupby( 'roll_period')['portfolio_returns_raw'].cumsum() #here we start each roll out with the cumulative returns of rolls before it cum_rolls = (df.groupby('next_roll_period') ['roll_cumulative_return_raw'].last() + 1).cumprod() cum_rolls = cum_rolls.drop( cum_rolls.index[len(cum_rolls) - 1]).rename('previous_roll_return') df = df.set_index('roll_period').join(cum_rolls, how='left').set_index('date') df['previous_roll_return'] = df['previous_roll_return'].fillna(1) df['daily_returns_cumulative'] = (df['roll_cumulative_return_raw'] + 1) * df['previous_roll_return'] #df[(df['date'] > '2005-2-25') & (df['date'] < '2011-9-16')]['daily_returns_cumulative'].plot() df['roll_period'] = df['roll_date'].shift(1) #calculate margin pct df['margin_pct'] = df['maintenance'] / df['roll_posted'] self.results = df print('backtest complete') return None