Esempio n. 1
0
    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