Exemple #1
0
    def RecalibrateAllocations(self):
        if self.last_date_index >= len(self.all_dates):
            return

        # set index of how much data you're allowed to use to make predictions
        if dt.CompareDates(self.all_dates[self.last_date_index], self.last_date) >= 0:
            return

        while dt.CompareDates(self.all_dates[self.last_date_index], self.last_date) < 0:
            self.last_date_index += 1
            if self.last_date_index >= len(self.all_dates):
                return

        from sklearn import linear_model

        print('fitting ' + str(self.last_date) + ' index: ' + str(self.last_date_index))

        y_preds = [] # projected returns for each strategy
        # train giant model
        for reg in [linear_model.Lasso()]:
            # fit on all data before today
            for col in range(0, len(self.y[0])):
                x = self.x[0:self.last_date_index - 1]
                y = list(row[col] for row in self.y[0:self.last_date_index - 1])

                reg.fit(x, y)

                y_preds = reg.predict([self.x[self.last_date_index]])
                y_preds.append(y_preds[0])

        from sklearn.metrics import explained_variance_score, mean_squared_error, r2_score
        exp_var = explained_variance_score(self.y[self.last_date_index], y_preds)
        mse = mean_squared_error(self.y[self.last_date_index], y_preds)
        r2 = r2_score(self.y[self.last_date_index], y_preds)
        print('prediction_stats: exp_var: ' + str(exp_var) + ' mse: ' + str(mse) + ' r2: ' + str(r2))

        trader_to_allocate = {}
        total_allocation = TOTAL_ALLOCATION
        sum_proj_pnl = 0
        for index in self.y_rev_legend:
            trader = self.y_rev_legend[index]

            if y_preds[index] < 0:
                self.alloc[trader] = MIN_ALLOCATION
                total_allocation -= MIN_ALLOCATION
            else:
                sum_proj_pnl += y_preds[index]
                trader_to_allocate[trader] = y_preds[index]

        for key in trader_to_allocate:
            self.alloc[key] = min(MAX_ALLOCATION, trader_to_allocate[key] / sum_proj_pnl * total_allocation)

        print(str(self.last_date) + ' allocs: ' + str(self.alloc))
Exemple #2
0
def FindOldestDate(shc_market_data_lines, shc_market_line_index):
    last_date = None
    for shc in shc_market_data_lines.keys():
        contract = ci.ContractInfoDatabase[shc]
        line = shc_market_data_lines[shc][shc_market_line_index[shc]]
        date = fp.TokenizeToDate(contract, line)
        if (not last_date) or (dt.CompareDates(date, last_date) < 0):
            last_date = date

    return last_date
Exemple #3
0
def ReplayMarketData(shc_market_data_lines, shc_market_line_index, pm_list):
    print('Running sims for ' + str(pm_list))

    # oldest date
    last_date = FindOldestDate(shc_market_data_lines, shc_market_line_index)
    line_num = 0

    while True:
        if not last_date:
            break

        next_line = None
        next_shc = None
        next_date = last_date

        for shc in shc_market_data_lines.keys():
            if shc_market_line_index[shc] >= len(shc_market_data_lines[
                    shc]) - 1:  # -1 because of header in input
                # print('reached end of stream for ' + shc + ' skipping.')
                continue

            contract = ci.ContractInfoDatabase[shc]
            line = shc_market_data_lines[shc][shc_market_line_index[shc]]
            date = fp.TokenizeToDate(contract, line)
            # print('comparing ' + shc + ' date:' + date + ' & next_date:' + next_date)

            if dt.CompareDates(next_date, date) >= 0:
                next_line, next_shc, last_date, next_date = line, shc, date, date
                shc_market_line_index[shc] = shc_market_line_index[shc] + 1
                break

        if not next_shc:
            last_date = FindOldestDate(shc_market_data_lines,
                                       shc_market_line_index)
            continue

        for pm in pm_list:
            pm.OnMarketDataUpdate(next_shc, next_date, next_line)

        # print('next_line: ' + next_line.strip() + ' next_shc: ' + next_shc + ' last_date: ' + last_date)
        # print(shc_market_line_index)

    for shc in shc_market_data_lines.keys():
        shc_market_line_index[shc] = 0
Exemple #4
0
    def OnMarketDataUpdate(self, shc, date, line, risk_dollars):
        Trader.OnMarketDataUpdate(self, shc, date, line, risk_dollars)

        contract_index = (1 if shc == self.contracts[1] else 0)
        self.market_data[contract_index] = line
        if not all(self.market_data):
            return

        contracts = [
            ci.ContractInfoDatabase[self.contracts[0]],
            ci.ContractInfoDatabase[self.contracts[1]]
        ]
        contracts.append(
            ci.ContractInfo(contracts[0].Name + ' VS. ' + contracts[1].Name,
                            0.01, 10))

        date, open_price, high_price, low_price, close_price = \
            [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]
        try:
            # unpack list
            for index in [0, 1]:
                date[index], open_price[index], high_price[index], low_price[index], close_price[index] = \
                    fp.TokenizeToPriceInfo(contracts[index], self.market_data[index])
        except ValueError or TypeError:
            return

        if dt.CompareDates(date[0], date[1]) != 0:
            return
        # print(str(self.market_data))

        for index in [0, 1]:
            self.lookback_prices[index].append(
                [high_price[index], low_price[index], close_price[index]])

        if len(self.lookback_prices[0]) < self.ma_lookback_days + 1:
            # not initialized yet, push and continue
            return

        # save ma and update list
        # print(list(row[2] for row in lookback_prices))
        ma, vol, dollar_vol, weight = [0, 0, 0], [0, 0, 0], [0, 0,
                                                             0], [1, 1, 1]
        for index in [0, 1]:
            ma[index] = statistics.mean(row[2]
                                        for row in self.lookback_prices[index])
            vol[index] = statistics.mean(
                abs(row[0] - row[1]) for row in self.lookback_prices[index])
            dollar_vol[index] = vol[index] * contracts[index].TickValue

        is_inverted = False  # maintain a flag if we invert ratio
        ratio = dollar_vol[0] / dollar_vol[1]
        if ratio < 1:
            ratio = 1 / ratio
            is_inverted = True

        high_price[2] = self.ComputeSpreadPrice(ratio, is_inverted,
                                                high_price[0], high_price[1])
        low_price[2] = self.ComputeSpreadPrice(ratio, is_inverted,
                                               low_price[0], low_price[1])
        close_price[2] = self.ComputeSpreadPrice(ratio, is_inverted,
                                                 close_price[0],
                                                 close_price[1])
        self.lookback_prices[2].append(
            [high_price[2], low_price[2], close_price[2]])
        ma[2] = statistics.mean(row[2] for row in self.lookback_prices[2])
        vol[2] = statistics.mean(
            abs(row[0] - row[1]) for row in self.lookback_prices[2])
        dev_from_ma = close_price[2] - ma[2]

        if self.log_level > 0:
            print('INFO vol ', vol, ' dollar vol ', dollar_vol, ' ratio ',
                  ratio)

        for index in [0, 1, 2]:
            if len(self.lookback_prices[index]) >= self.ma_lookback_days:
                self.lookback_prices[index].pop(0)

        loss_ticks = self.o_loss_ticks * vol[2]
        net_change = self.o_net_change * vol[2]

        # this is a tough one, and this solution is imperfect
        # but preferred for its simplicity
        spread_tick_value = min(contracts[0].TickValue,
                                contracts[1].TickValue * ratio)
        if is_inverted:
            spread_tick_value = min(contracts[0].TickValue * ratio,
                                    contracts[1].TickValue)

        if self.log_level > 0:
            print('INFO vol:',
                  vol,
                  'adjusted params:',
                  'net_change:',
                  net_change,
                  'loss_ticks:',
                  loss_ticks,
                  'spread_tick_value:',
                  spread_tick_value,
                  sep=' ')

        traded_today = False

        if self.my_position[
                2] == 0:  # flat, see if we want to get into a position
            # how much did today's close price deviate from moving average ?
            # +ve value means breaking out to the upside
            # -ve value means breaking out to the downside
            if abs(dev_from_ma) > net_change:  # blown out
                trade_size = int((risk_dollars / spread_tick_value) /
                                 loss_ticks + 1)
                self.my_position[0] = trade_size * (ratio
                                                    if is_inverted else 1)
                self.my_position[1] = trade_size * (1
                                                    if is_inverted else ratio)
                self.my_vwap = list(close_price)
                self.my_position[2] = trade_size * (1
                                                    if dev_from_ma < 0 else -1)
                self.my_vwap[2] = close_price[2]
                self.trades.append([
                    date[0], ('B' if dev_from_ma < 0 else 'S'), trade_size,
                    close_price[2], self.my_position[2], self.my_pnl[2],
                    vol[2], ma[2], dev_from_ma, high_price[2], low_price[2]
                ])
                traded_today = True

                if self.log_level > 0:
                    print('INFO initiating position ', self.trades[-1])
        else:  # have a position already, check for stop outs
            if ((self.my_position[2] > 0
                 and self.my_vwap[2] - low_price[2] > loss_ticks)
                    or (self.my_position[2] < 0
                        and high_price[2] - self.my_vwap[2] > loss_ticks)):
                stopout_price = self.my_vwap[2] + \
                                (loss_ticks * (1 if self.my_position[2] < 0 else -1))
                trade_pnl = abs(
                    self.my_position[2]) * loss_ticks * spread_tick_value
                self.my_pnl[2] -= trade_pnl
                buysell = ('S' if self.my_position[2] > 0 else 'B')

                self.trades.append([
                    date[0], buysell,
                    abs(self.my_position[2]), stopout_price, 0, self.my_pnl[2],
                    vol[2], ma[2], dev_from_ma, high_price[2], low_price[2]
                ])
                traded_today = True
                self.my_position = [0, 0, 0]

                if self.log_level > 0:
                    print('INFO stopped out ', self.trades[-1])
            elif abs(dev_from_ma) < 0.5 * net_change:  # trend dying out
                self.my_pnl[0] += self.my_position[0] * (
                    close_price[0] - self.my_vwap[0]) * contracts[0].TickValue
                self.my_pnl[1] += self.my_position[1] * (
                    close_price[1] - self.my_vwap[1]) * contracts[1].TickValue
                self.my_pnl[2] = self.my_pnl[0] + self.my_pnl[1]
                buysell = ('S' if self.my_position[2] > 0 else 'B')

                self.trades.append([
                    date[0], buysell,
                    abs(self.my_position[2]), close_price[2], 0,
                    self.my_pnl[2], vol[2], ma[2], dev_from_ma, high_price[2],
                    low_price[2]
                ])
                traded_today = True
                self.my_position = [0, 0, 0]

                if self.log_level > 0:
                    print('INFO took a win ', self.trades[-1])

        if not traded_today:
            unreal_pnl = self.my_position[0] * (close_price[0] - self.my_vwap[0]) * contracts[0].TickValue + \
                         self.my_position[1] * (close_price[1] - self.my_vwap[1]) * contracts[1].TickValue
            self.trades.append([
                date[0], '-', self.my_position[2], close_price[2], 0,
                self.my_pnl[2] + unreal_pnl, vol[2], ma[2], dev_from_ma,
                high_price[2], low_price[2]
            ])

        self.alloc.append(risk_dollars)
        if len(self.trades) >= 2:
            self.daily_pnl.append(self.trades[-1][5] - self.trades[-2][5])
            if self.trades[-2][5] != 0:
                self.pct_pnl_change.append(
                    100 * self.daily_pnl[-1] /
                    abs(self.trades[-2][5]))  # what % pnl increase?
        else:
            self.daily_pnl.append(self.trades[-1][5])
Exemple #5
0
    def OnMarketDataUpdate(self, shc, date, line, risk_dollars):
        Trader.OnMarketDataUpdate(self, shc, date, line, risk_dollars)

        contract_index = (1 if shc == self.contracts[1] else 0)
        self.market_data[contract_index] = line
        if not all(self.market_data):
            return

        contracts = [
            ci.ContractInfoDatabase[self.contracts[0]],
            ci.ContractInfoDatabase[self.contracts[1]]
        ]

        date, open_price, high_price, low_price, close_price = \
            [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]
        try:
            # unpack list
            for index in [0, 1]:
                date[index], open_price[index], high_price[index], low_price[index], close_price[index] = \
                    fp.TokenizeToPriceInfo(contracts[index], self.market_data[index])
        except ValueError or TypeError:
            return

        if dt.CompareDates(date[0], date[1]) != 0:
            return
        # print(str(self.market_data))

        for index in [0, 1]:
            self.lookback_prices[index].append(
                [high_price[index], low_price[index], close_price[index]])

        if len(self.lookback_prices[0]) < self.ma_lookback_days + 1:
            # not initialized yet, push and continue
            return

        # save ma and update list
        # print(list(row[2] for row in lookback_prices))
        ma, vol, dev_from_ma = [0, 0], [0, 0], [0, 0]
        for index in [0, 1]:
            ma[index] = statistics.mean(row[2]
                                        for row in self.lookback_prices[index])
            vol[index] = statistics.mean(
                abs(row[0] - row[1]) for row in self.lookback_prices[index])
            dev_from_ma[index] = close_price[index] - ma[index]
            self.lookback_dev_from_ma[index].append(dev_from_ma[index])

        # Need at least 2 points, not enough degrees of freedom
        if len(self.lookback_dev_from_ma[0]) < 2:
            return

        corr = numpy.corrcoef(self.lookback_dev_from_ma[0],
                              self.lookback_dev_from_ma[1])
        cov = numpy.cov(self.lookback_dev_from_ma[0],
                        self.lookback_dev_from_ma[1])
        corr_0_1 = corr[0, 1]  # get the correlation between the 2 series

        # get the strength of the moves
        # this holds the answer to 'for every 1 unit move in B, how much should A move'
        # we will use this to predict expected moves and then use the difference
        # with actual move to accumulate positions
        cov_0_1 = cov[0, 0] / cov[0, 1]

        # project what the price-change should be
        # this is designed so that for weaker correlations, projections are dampened
        projected_dev_from_ma = dev_from_ma[1] * cov_0_1
        projected_price = ma[0] + projected_dev_from_ma
        dev_from_projection = projected_dev_from_ma * abs(
            corr_0_1) - dev_from_ma[0]
        if math.isnan(dev_from_projection):
            if self.log_level > 0:
                print('Skipping because dev_from_projection:',
                      dev_from_projection, 'or correlation:', corr_0_1,
                      'less than', self.min_correlation)
            return

        # track it so we know how big an average deviation is
        self.lookback_dev_from_projection.append(
            dev_from_projection)  # this measure only cares about the magnitude
        dev_from_projection_vol = statistics.mean(
            abs(item) for item in self.lookback_dev_from_projection)

        if self.log_level > 0:
            print('dev_from_projection',
                  dev_from_projection,
                  'dev_from_projection_vol',
                  dev_from_projection_vol,
                  'entries',
                  self.lookback_dev_from_projection,
                  sep=' ')

        if len(self.lookback_dev_from_ma[0]) < self.ma_lookback_days + 1:
            # need to have a long enough history
            # of relative deviations to project in the future
            if self.log_level > 0:
                print('not enough lookback_dev_from_ma history',
                      len(self.lookback_dev_from_ma[0]),
                      self.ma_lookback_days,
                      sep=' ')
            return

        for index in [0, 1]:
            while len(self.lookback_prices[index]) > self.ma_lookback_days:
                self.lookback_prices[index].pop(0)
            while len(
                    self.lookback_dev_from_ma[index]) > self.ma_lookback_days:
                self.lookback_dev_from_ma[index].pop(0)
        while len(self.lookback_dev_from_projection) > self.ma_lookback_days:
            self.lookback_dev_from_projection.pop(0)

        if self.log_level > 0:
            print(contracts[0].Name, 'projected by', contracts[1].Name,
                  'correlation:', corr_0_1, 'coefficient:', cov_0_1)
            print('projected_dev_from_ma',
                  projected_dev_from_ma,
                  'actual',
                  dev_from_ma[0],
                  'dev_from_projection',
                  dev_from_projection,
                  sep=' ')

        loss_ticks = self.o_loss_ticks * vol[0]
        net_change = self.o_net_change * dev_from_projection_vol

        if self.log_level > 0:
            print('INFO vol:',
                  vol,
                  'adjusted params:',
                  'net_change:',
                  net_change,
                  'loss_ticks:',
                  loss_ticks,
                  sep=' ')

        traded_today = False

        if self.my_position == 0:  # flat, see if we want to get into a position
            # how much did today's close price deviate from moving average ?
            # +ve value means breaking out to the upside
            # -ve value means breaking out to the downside
            if abs(dev_from_projection) > net_change:  # blown out
                trade_size = int((risk_dollars / contracts[0].TickValue) /
                                 loss_ticks + 1)
                self.my_position = trade_size * (1 if dev_from_projection > 0
                                                 else -1)
                self.my_vwap = close_price[0]
                self.trades.append([
                    date[0], ('B' if dev_from_projection > 0 else 'S'),
                    trade_size, close_price[0], self.my_position, self.my_pnl,
                    dev_from_projection_vol, ma[0], dev_from_projection,
                    projected_price, corr_0_1
                ])
                traded_today = True

                if self.log_level > 0:
                    print('INFO initiating position ', self.trades[-1])
        else:  # have a position already, check for stop outs
            if ((self.my_position > 0
                 and self.my_vwap - low_price[0] > loss_ticks)
                    or (self.my_position < 0
                        and high_price[0] - self.my_vwap > loss_ticks)):
                stopout_price = self.my_vwap + \
                                (loss_ticks * (1 if self.my_position < 0 else -1))
                trade_pnl = abs(
                    self.my_position) * loss_ticks * contracts[0].TickValue
                self.my_pnl -= trade_pnl
                buysell = ('S' if self.my_position > 0 else 'B')

                self.trades.append([
                    date[0], buysell,
                    abs(self.my_position), stopout_price, 0, self.my_pnl,
                    dev_from_projection_vol, ma[0], dev_from_projection,
                    projected_price, corr_0_1
                ])
                traded_today = True
                self.my_position = 0

                if self.log_level > 0:
                    print('INFO stopped out ', self.trades[-1])
            elif abs(dev_from_projection
                     ) < 0.5 * net_change:  # deviation dying out
                stopout_price = close_price[0]
                trade_pnl = self.my_position * (
                    stopout_price - self.my_vwap) * contracts[0].TickValue
                self.my_pnl += trade_pnl
                buysell = ('S' if self.my_position > 0 else 'B')

                self.trades.append([
                    date[0], buysell,
                    abs(self.my_position), stopout_price, 0, self.my_pnl,
                    vol[0], ma[0], dev_from_projection_vol, projected_price,
                    corr_0_1
                ])
                traded_today = True
                self.my_position = 0

                if self.log_level > 0:
                    print('INFO took a win ', self.trades[-1])

        if not traded_today:
            unreal_pnl = self.my_position * (
                close_price[0] - self.my_vwap) * contracts[0].TickValue
            self.trades.append([
                date[0], '-', self.my_position, close_price[0], 0,
                self.my_pnl + unreal_pnl, dev_from_projection_vol, ma[0],
                dev_from_projection, projected_price, corr_0_1
            ])

        self.alloc.append(risk_dollars)
        if len(self.trades) >= 2:
            self.daily_pnl.append(self.trades[-1][5] - self.trades[-2][5])
            if self.trades[-2][5] != 0:
                self.pct_pnl_change.append(
                    100 * self.daily_pnl[-1] /
                    abs(self.trades[-2][5]))  # what % pnl increase?
        else:
            self.daily_pnl.append(self.trades[-1][5])

        if self.log_level > 0:
            print('TRADE ' + str(self.ShortName()) + ' ' +
                  str(self.trades[-1]))
            print('ALLOC ' + str(self.ShortName()) + ' ' + str(self.alloc[-1]))
def PairsReversionStrategy(contracts, data_csv=[], data_list=[], **strategy_params):
    """
    Run a mean reversion strategy on either the data_csv file
    or the data_list list, if both are passed, it will return error

    Trading parameters:
    net_change:         how much does today's price have to deviate from
                        ma to consider trend to be starting
    ma_lookback_days:   how many days to build moving average over
    loss_ticks:         where to stop out on a losing position

    :param contracts:
    :param data_csv: csv filename to load data from
    :param data_list: list to load data from
    :param strategy_params: dictionary of trading parameters
    :return: (error/successs code, list of trade information)
    """
    trades = []
    log_level = strategy_params.pop('log_level', 0)

    if not data_csv and not data_list:
        if log_level > 0:
            print('Error neither have datafile nor datalist')
        return -1, None, None

    if data_csv and data_list:
        if log_level > 0:
            print("Error cannot have both datafile and datalist")
        return -1, None, None

    # get an iterable based on arguments passed
    market_data_1 = data_list or open(data_csv[0], 'r')
    market_data_2 = data_list or open(data_csv[1], 'r')
    if log_level > 0:
        print('INFO opened data file/list ', market_data_1, ' and ', market_data_2)

    # dump out trading parameters
    if log_level > 0:
        print('INFO trading params ', strategy_params)

    # pull out parameters, use defaults if missing
    ma_lookback_days = int(strategy_params.pop('ma_lookback_days', 10))
    o_loss_ticks = float(strategy_params.pop('loss_ticks', 5.0))
    o_net_change = float(strategy_params.pop('net_change', 5.0))
    risk_dollars = float(strategy_params.pop('risk_dollars', 1000.0))

    # define some model specific variables
    lookback_prices = [[], [], []] # maintain, update ma
    my_position, my_vwap, my_pnl = [0,0,0], [0,0,0], [0,0,0] # position, position vwap, pnl
    market_data = [list(reversed(list(market_data_1))), list(reversed(list(market_data_2)))]
    market_data_index = [0, 0]

    syn_contract = ci.ContractInfo(contracts[0].Name + 'VS. ' + contracts[1].Name, 0.01, 10)

    while market_data_index[0] < len(market_data[0]) and market_data_index[1] < len(market_data[1]):
        date, open_price, high_price, low_price, close_price = [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]
        try:
            if log_level > 0:
                print('INFO looking at index: ', market_data_index[0], '/',
                      len(market_data[0]), ' ', market_data_index[1], '/', len(market_data[1]))

            # unpack list
            for index in [0, 1]:
                date[index], open_price[index], high_price[index], low_price[index], close_price[index] =\
                    fp.TokenizeToPriceInfo(contracts[index], market_data[index][market_data_index[index]])
        except ValueError or TypeError:
            # update indices
            for index in [0,1]:
                market_data_index[index] += 1
            continue

        # sanity check to make sure we are looking at same day on both contracts
        if date[0] != date[1]:
            # need to figure out which contract is lagging and bring that upto speed
            if du.CompareDates(date[0], date[1]) < 0:
                market_data_index[0] += 1
            else:
                market_data_index[1] += 1
            continue

        for index in [0, 1]:
            market_data_index[index] += 1
            lookback_prices[index].append(high_price[index], low_price[index], close_price[index])

        if len(lookback_prices[0]) < ma_lookback_days + 1:
            # not initialized yet, push and continue
            continue

        # save ma and update list
        ma, vol , dollar_vol, weight = [0, 0, 0], [0, 0, 0], [0, 0, 0], [1, 1, 1]
        for index in [0, 1]:
            ma[index] = statistics.mean(row[2] for row in lookback_prices[index])
            vol[index] = statistics.mean(abs(row[0] - row[1]) for row in lookback_prices[index])
            dollar_vol[index] = vol[index] * contracts[index].TickValue

        is_inverted = False # maintain a flag if we invert ratio
        ratio = dollar_vol[0] / dollar_vol[1]

        if ratio < 1:
            ratio = 1 / ratio
            is_inverted = True

        high_price[2] = ComputeSpreadPrice(ratio, is_inverted, high_price[0], high_price[1])
        low_price[2] = ComputeSpreadPrice(ratio, is_inverted, low_price[0], low_price[1])
        close_price[2] = ComputeSpreadPrice(ratio, is_inverted, close_price[0], close_price[1])
        lookback_prices[2].append(high_price[2], low_price[2], close_price[2])
        ma[2] = statistics.mean(row[2] for row in lookback_prices[2])
        vol[2] = statistics.mean(abs(row[0] - row[1]) for row in lookback_prices[2])
        dev_from_ma = close_price[2] - ma[2]

        if log_level > 0:
            print('INFO vol ', vol, ' dollar vol ', dollar_vol, ' ratio ', ratio)

        for index in [0, 1, 2]:
            if len(lookback_prices[index]) >= ma_lookback_days:
                lookback_prices[index].pop(0)

        loss_ticks = o_loss_ticks * vol[2]
        net_change = o_net_change * vol[2]

        # this is a tough one and this solution is imperfect
        # but preferred for its simplicity
        spread_tick_value = min(contracts[0].TickValue, contracts[1].TickValue * ratio)
        if is_inverted:
            spread_tick_value = min(contracts[0].TickValue * ratio, contracts[1].TickValue)

        if log_level > 0:
            print('INFO vol:', vol, 'adjusted params:', 'net_change:', net_change, 'loss_ticks:', loss_ticks,
                  'spread_tick_value:', spread_tick_value, sep=' ')

        traded_today = False

        if my_position[2] == 0: # flat, see if we want to get into a position
            # how much did today's close price deviate from moving average
            # +ve value means breaking out to the upside
            # -ve value means breaking out to the downside
            if abs(dev_from_ma) > net_change: # blown out
                trade_size = int((risk_dollars / spread_tick_value) / loss_ticks + 1)
                my_position[0] = trade_size * (ratio if is_inverted else 1)
                my_position[1] = trade_size * (1 if is_inverted else ratio)
                my_vwap = list(close_price)
                my_position[2] = trade_size * (1 if dev_from_ma < 0 else -1)
                my_vwap[2] = close_price[2]
                trades.append([date[0], ('B' if dev_from_ma < 0 else 'S'), trade_size, close_price[2],
                               my_position[2], my_pnl[2], vol[2], ma[2], dev_from_ma, high_price[2], low_price[2]])
                traded_today = True

            if log_level > 0:
                print('INFO initiating position ', trades[-1])
        else: # have a position already, check for stop outs
            if ((my_position[2] > 0 and my_vwap[2] - low_price[2] > loss_ticks) or
                    (my_position[2] < 0 and high_price[2] - my_vwap[2] > loss_ticks)):
                stopout_price = my_vwap[2] + (loss_ticks * (1 if my_position[2] < 0 else -1))
                trade_pnl = abs(my_position[2] * loss_ticks * spread_tick_value)
                my_pnl[2] -= trade_pnl
                buysell = ('S' if my_position[2] > 0 else 'B')

                traded_today = True
                trades.append([date[0], buysell, abs(my_position[2]), stopout_price, 0, my_pnl[2], vol[2], ma[2],
                               dev_from_ma, high_price[2], low_price[2]])
                my_position = [0, 0, 0]

                if log_level > 0:
                    print('INFO stopped out ', trades[-1])
            elif abs(dev_from_ma) < 0.5 * net_change: # trend dying out
                my_pnl[0] += my_position[0] * (close_price[0] - my_vwap[0]) * contracts[0].TickValue
                my_pnl[1] += my_position[1] * (close_price[1] - my_vwap[1]) * contracts[1].TickValue
                my_pnl[2] = my_pnl[0] + my_pnl[1]
                buysell = ('S' if my_position[2] > 0 else 'B')

                trades.append(
                    [date[0], buysell, abs(my_position[2]), close_price[2], 0, my_pnl[2], vol[2], ma[2], dev_from_ma,
                     high_price[2], low_price[2]])
                traded_today = True
                my_position = [0, 0, 0]

                if log_level > 0:
                    print('INFO took a win ', trades[-1])

        if not traded_today:
            unreal_pnl = my_position[0] * (close_price[0] - my_vwap[0]) * contracts[0].TickValue + \
                         my_position[1] * (close_price[1] - my_vwap[1]) * contracts[1].TickValue

            trades.append(
                [date[0], '-', my_position[2], close_price[2], 0, my_pnl[2] + unreal_pnl, vol[2], ma[2], dev_from_ma,
                 high_price[2], low_price[2]])

    return 0, syn_contract, trades
def StatArbStrategy(contracts, data_csv=[], data_list=[], **strategy_params):
    """
    Trade contract[0] using contract[1] as leading indicator

    Trading parameters:
    net_change:         how much does today's price have to deviate from
                        ma to consider trend to be starting
    ma_lookback_days:   how many days to build moving average over
    loss_ticks:         where to stop out on a losing position
    :param contracts:
    :param data_csv: csv filename to load data from
    :param data_list: list to load data from
    :param strategy_params: dictionary of trading parameters
    :return: (error/successs code, list of trade information)
    """
    trades = []
    log_level = strategy_params.pop('log_level', 0)

    if not data_csv and not data_list:
        if log_level > 0:
            print('Error neither have datafile nor datalist')
        return -1, None, None

    if data_csv and data_list:
        if log_level > 0:
            print("Error cannot have both datafile and datalist")
        return -1, None, None

    # get an iterable based on arguments passed
    market_data_1 = data_list or open(data_csv[0], 'r')
    market_data_2 = data_list or open(data_csv[1], 'r')
    if log_level > 0:
        print('INFO opened data file/list ', market_data_1, ' and ', market_data_2)

    # dump out trading parameters
    if log_level > 0:
        print('INFO trading params ', strategy_params)

    # pull out parameters, use defaults if missing
    ma_lookback_days = int(strategy_params.pop('ma_lookback_days', 10))
    o_loss_ticks = float(strategy_params.pop('loss_ticks', 5.0))
    o_net_change = float(strategy_params.pop('net_change', 5.0))
    risk_dollars = float(strategy_params.pop('risk_dollars', 1000.0))
    min_correlation = float(strategy_params.pop('min_correlation', 0.75))

    # define some model specific variables
    lookback_prices = [[], []] # maintain, update ma
    lookback_dev_from_ma = [[], []]
    lookback_dev_from_projection = []
    my_position, my_vwap, my_pnl = 0, 0, 0 # position, position vwap, pnl

    market_data = [list(reversed(list(market_data_1))), list(reversed(list(market_data_2)))]
    market_data_index = [0, 0]

    while market_data_index[0] < len(market_data[0]) and market_data_index[1] < len(market_data[1]):
        date, open_price, high_price, low_price, close_price = [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]
        try:
            if log_level > 0:
                print('INFO looking at index: ', market_data_index[0], '/',
                      len(market_data[0]), ' ', market_data_index[1], '/', len(market_data[1]))

            # unpack list
            for index in [0, 1]:
                date[index], open_price[index], high_price[index], low_price[index], close_price[index] = \
                    fp.TokenizeToPriceInfo(contracts[index], market_data[index][market_data_index[index]])
        except ValueError or TypeError:
            # update indices
            for index in [0, 1]:
                market_data_index[index] += 1
            continue

        # sanity check to make sure we are looking at same day on both contracts
        if date[0] != date[1]:
            # need to figure out which contract is lagging and bring that upto speed
            if du.CompareDates(date[0], date[1]) < 0:
                market_data_index[0] += 1
            else:
                market_data_index[1] += 1
            continue

        for index in [0, 1]:
            market_data_index[index] += 1
            lookback_prices[index].append(high_price[index], low_price[index], close_price[index])

        if len(lookback_prices[0]) < ma_lookback_days + 1:
            # not initialized yet, push and continue
            continue

        # save ma and update list
        ma, vol, dev_from_ma = [0, 0], [0, 0], [0, 0]
        for index in [0, 1]:
            ma[index] = statistics.mean(row[2] for row in lookback_prices[index])
            vol[index] = statistics.mean(abs(row[0] - row[1]) for row in lookback_prices[index])
            dev_from_ma[index] = close_price[index] - ma[index]
            lookback_dev_from_ma[index].append(dev_from_ma[index])

        # Need at least 2 points, not enough degrees of freedom
        if len(lookback_dev_from_ma[0]) < 2:
            continue

        corr = numpy.corrcoef(lookback_dev_from_ma[0], lookback_dev_from_ma[1])
        cov = numpy.cov(lookback_dev_from_ma[0], lookback_dev_from_ma[1])
        corr_0_1 = corr[0, 1] # get the correlation between the 2 series

        # get the strength of the moves
        # this holds the answer to 'for every 1 unit move in B, how much should A move'
        # we will use this to predict expected moves and then use the difference
        # with actual move to accumulate positions
        cov_0_1 = cov[0, 0] / cov[0, 1]

        # project what the price-change should be
        # this is designed so that for weaker correlations, projections are dampened
        projected_dev_from_ma = dev_from_ma[1] * cov_0_1
        projected_price = ma[0] + projected_dev_from_ma
        dev_from_projection = projected_dev_from_ma * abs(corr_0_1) - dev_from_ma[0]
        if math.isnan(dev_from_projection) or corr_0_1 < min_correlation:
            if log_level > 0:
                print('Skipping because dev_from_projection', dev_from_projection,
                      'or correlation:', corr_0_1, 'less than', min_correlation)
            continue

        # track it so we know how big an average deviation is
        lookback_dev_from_projection.append(dev_from_projection) # this measure only cares about the magnitude
        dev_from_projection_vol = statistics.mean(abs(item) for item in lookback_dev_from_projection)

        if log_level > 0:
            print('dev_from_projection', dev_from_projection, 'dev_from_projection_vol',
                  dev_from_projection_vol, 'entries', lookback_dev_from_projection, sep=' ')

        if len(lookback_dev_from_ma[0]) < ma_lookback_days + 1:
            # need to have a long enough history
            # or relative deviations to project in the future
            if log_level > 0:
                print('not enough lookback_dev_from_ma history', len(lookback_dev_from_ma[0]), ma_lookback_days,
                      sep=' ')
            continue

        for index in [0, 1]:
            while len(lookback_prices[index]) > ma_lookback_days:
                lookback_prices[index].pop(0)
            while len(lookback_dev_from_ma[index]) > ma_lookback_days:
                lookback_dev_from_ma[index].pop(0)
        while len(lookback_dev_from_projection) > ma_lookback_days:
            lookback_dev_from_projection.pop(0)

        if log_level > 0:
            print(contracts[0].Name, 'projected by', contracts[1].Name,
                  'correlation', corr_0_1, 'coefficient:', cov_0_1)
            print('projected_dev_from_ma', projected_dev_from_ma, 'actual',dev_from_ma[0],
                  'dev_from_projection', dev_from_projection, sep=' ')

        loss_ticks = o_loss_ticks * vol[0]
        net_change = o_net_change * dev_from_projection_vol

        if log_level > 0:
            print('INFO vol:', vol, 'adjusted params:', 'net_change:', net_change, 'loss_ticks:', loss_ticks,
                  sep=' ')

        traded_today = False

        if my_position == 0: # flat, see if we want to get into a position
            # how much did today's close price deviate from moving average ?
            # +ve value means breaking out to the upside
            # -ve value means breaking out to the downside
            if abs(dev_from_projection) > net_change: # blown out
                trade_size = int((risk_dollars / contracts[0].TickValue) / loss_ticks + 1)
                my_position = trade_size * (1 if dev_from_projection > 0 else -1)
                my_vwap = close_price[0]
                trades.append([date[0], ('B' if dev_from_projection > 0 else 'S'), trade_size, close_price[0],
                               my_position, my_pnl, dev_from_projection_vol, ma[0], dev_from_projection, projected_price, corr_0_1])
                traded_today = True

                if log_level > 0:
                    print('INFO initiating position ', trades[-1])

        else:  # have a position already, check for stop outs
            if ((my_position > 0 and my_vwap - low_price[0] > loss_ticks) or
                    (my_position < 0 and high_price[0] - my_vwap > loss_ticks)):
                stopout_price = my_vwap + loss_ticks * (1 if my_position < 0 else -1)
                trade_pnl = abs(my_position) * loss_ticks * contracts[0].TickValue
                my_pnl -= trade_pnl
                buysell = ('S' if my_position > 0 else 'B')

                trades.append(
                    [date[0], buysell, abs(my_position), stopout_price, 0, my_pnl, dev_from_projection_vol, ma[0],
                     dev_from_projection, projected_price, corr_0_1])
                traded_today = True
                my_position = 0

                traded_today = True
                my_position = 0

                if log_level > 0:
                    print('INFO stopped out ', trades[-1])

            elif abs(dev_from_projection) < 0.5 * net_change: # deviation dying out
                stopout_price = close_price[0]
                trade_pnl = my_position * (stopout_price - my_vwap) * contracts[0].TickValue
                my_pnl += trade_pnl
                buysell = ('S' if my_position > 0 else 'B')

                trades.append(
                    [date[0], buysell, abs(my_position), stopout_price, 0, my_pnl, vol[0], ma[0],
                     dev_from_projection_vol, projected_price, corr_0_1])
                traded_today = True
                my_position = 0

                if log_level > 0:
                    print('INFO took a win ', trades[-1])

        if not traded_today:
            unreal_pnl = my_position * (close_price[0] - my_vwap) * contracts[0].TickValue
            trades.append([date[0], '-', my_position, close_price[0], 0, my_pnl + unreal_pnl, dev_from_projection_vol, ma[0], dev_from_projection, projected_price, corr_0_1])

    return 0, trades