def main():
    logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')

    control_wallet = Wallet('Control')
    test_wallet = Wallet('Test')

    starting_value_per_coin = 100

    for coin in coins:
        control_wallet.buy(coin, starting_value_per_coin, dollars=True)
        test_wallet.buy(coin, starting_value_per_coin, dollars=True)

    starting_value = test_wallet.calculate_value()
    starting_value_control = control_wallet.calculate_value()
    control_wallet.show()
    test_wallet.show()

    net_into_coins = 0

    for current_day in range(len(bitcoin.df) - 1):

        update_all_coins(current_day)
        test_wallet.calculate_value()
        net_into_coins += algorithm_3(test_wallet, coins, starting_value_per_coin)

    control_wallet.show()
    test_wallet.show()

    final_value = test_wallet.calculate_value()
    final_value_control = control_wallet.calculate_value()

    market_gain_test = final_value - (starting_value + net_into_coins)
    market_gain_control = final_value_control - starting_value_control
    print("\nNet spent on coins: " + '{:.2f}'.format(net_into_coins) + "\n")
    print("Algorithm Gain:  $" + '{:.2f}'.format(market_gain_test) + "  (Gain / Start = " + '{:.2f}'.format(market_gain_test / starting_value ) + ")")
    print("Control Gain:    $" + '{:.2f}'.format(market_gain_control) + "  (Gain / Start = " + '{:.2f}'.format(market_gain_control / starting_value_control ) + ")")

    print(f"$ gained per $ spent: ${'{:.2f}'.format(market_gain_test / net_into_coins)}")
Exemple #2
0
class Backtest:
    def __init__(self, start_new_trades, data, universe, look_back_period=80):
        '''
        args:
            start_new_trades: a function for getting trades, must return a list of trades
            data: Data object
            universe: a list of tick names
            look_back_period: backtest period
        '''
        self.setup_backtest()
        self.setup_manual(start_new_trades, data, universe, look_back_period)

    def setup_backtest(self):
        # spot info
        self.wallet = Wallet()
        self.current_date = None
        self.strategy_pool = []
        # hist info
        self.num_trades = 0
        self.trading_hist = []
        self.holding_hist = []
        # statistical info
        self.NAV_all = {}
        self.NAV_indi = {}

    def setup_manual(self, start_new_trades, data, universe, look_back_period):
        self.lookback_period = look_back_period  # rolling period
        self.start_new_trades = start_new_trades  # get position, input=data, output = target position
        self.data = data  # class, support get asset daily data
        self.universe = universe

    def timely_trade(self, freq, st=None, ed=None):
        '''timely initiate new trades
        '''
        all_dates = self.data.get_all_trading_days()
        if st is not None:
            all_dates = [x for x in all_dates if x > st]
        if ed is not None:
            all_dates = [x for x in all_dates if x < ed]
        # adjust frequency for all_dates
        trading_dates = reshuffle_dates(freq, all_dates)
        # execution
        for i, date in enumerate(all_dates):
            if i % (len(all_dates) // 10) == 0:
                print('{}/{} running...'.format(i, len(all_dates)))
            if date in trading_dates:
                self.actions(date)
            self.daily_summary()

    def daily_summary(self):
        '''generate summary statistics for current day
        '''
        today = self.current_date
        current_holding = self.wallet.current_holding()
        NAV = {
            x: current_holding[x] * self.data.get_asset_price_daily(x, today)
            for x in current_holding if x != 'cash'
        }
        NAV['cash'] = current_holding['cash']
        self.NAV_indi[today] = NAV
        self.NAV_all[today] = sum(NAV.values())

    def actions(self, date):
        '''
        main function, responsible for daily issues
        flow:
            1. get relevant data
            2. get position
                old status check
                new add in
                sum position
            3. identify trades and validate trades
            4. trade execution
        '''
        self.current_date = date
        # 1. get data
        used_data = self.data.get_rolling(date, self.universe,
                                          self.lookback_period)
        '''
        mark: room to improve in c: count only the additional and sum them up
            
        # 2. get position
        a. for existing trades, update status
        b. add new trades to current pool
        c. calculate total units of position
        final output is units of position
        '''
        # a. update status
        active_strategies = [
            x for x in self.strategy_pool if x.current_status == 'In Use'
        ]
        for trade in active_strategies:
            trade.status_update(date, used_data)
        # b. open new trades
        # only open if currently not using same strategy
        new_trades = self.start_new_trades(
            date,
            used_data)  # f(data), return dictionary, key=asset, value = units
        active_names = [x.name for x in active_strategies]
        for trade_i in new_trades:
            if trade_i.name not in active_names:
                self.strategy_pool.append(trade_i)
        # c. sum target positions
        active_strategies = [
            x for x in self.strategy_pool if x.current_status == 'In Use'
        ]
        total_positions = [trade.position for trade in active_strategies]
        target_position = {}
        for trade_pos in total_positions:
            for asset in trade_pos:
                if asset not in target_position:
                    target_position[asset] = trade_pos[asset]
                else:
                    target_position[asset] += trade_pos[asset]


#        print(date, target_position)

# 3. identify trades, validate trades
        daily_trades = self.identify_trades(target_position)
        #        print(daily_trades)
        if len(daily_trades) > 0:
            self.trading_hist.append(daily_trades)

        # 4. trade execution
        self.trades_execution(daily_trades, date)

        #5. saving
        if len(daily_trades) > 0:
            holding_log = self.wallet.current_holding()
            holding_log['date'] = date
            self.holding_hist.append(holding_log)

    def identify_trades(self, target_position):
        '''identify trades from targetposition and current position
        input:
            target position
        reff:
            current position
        return:
            trades to make in day
        '''
        daily_trades = []
        for i in self.wallet.current_holding():
            if i not in target_position and i != 'cash':
                target_position[i] = 0
        for i in target_position:
            current_pos = self.wallet.get_position(i)
            target_pos = target_position[i]
            # check tradable
            if not self.tradable(i, self.current_date):
                continue
            # trading info generation
            price = self.data.get_asset_price_daily(i, self.current_date)
            trade = {'asset': i, 'date': self.current_date, 'price': price}
            if current_pos > target_pos:
                trade['direction'] = 'close'
                trade['quantity'] = current_pos - target_pos
                daily_trades.append(trade)
            elif current_pos < target_pos:
                trade['direction'] = 'open'
                trade['quantity'] = target_pos - current_pos
                daily_trades.append(trade)
        return daily_trades

    def tradable(self, asset, date):
        '''check if price exist for the day
        '''
        if self.data.get_asset_price_daily(
                asset, date) is not None:  # asset has price info on the day
            return True
        else:
            return False

    def trades_execution(self, trades, date):
        '''execution
        trade = {'direction':'open',
                 'asset': 'cash',
                 'quantity': 100}
        trades = [trade1, trade2, ...]
        '''
        for trade in trades:
            self.open_trade(trade)

    def open_trade(self, trade):
        '''trading entry execution
        '''
        if trade['direction'] == 'open':
            self.wallet.buy(trade['asset'], trade['quantity'], trade['price'])
            self.num_trades += 1
            return
        elif trade['direction'] == 'close':
            self.wallet.sell(trade['asset'], trade['quantity'], trade['price'])
            self.num_trades += 1
            return
Exemple #3
0
class Backtest:
    def __init__(self):
        self.setup_backtest()

    def setup_backtest(self):
        self.lookback_period = 80
        self.z_in_upper = 2
        self.z_in_lower = -2
        self.z_out_upper = 0
        self.z_out_lower = 0
        self.p_threshold = 0.05
        self.non_coint_threshold = 50

    def setup_pass(self):
        self.wallet = Wallet()
        self.current_trade = {}
        self.profits = []
        self.num_trades = 0
        self.rolling_holdings = []

    def open_trade(self, p_val, zscore, price_a, price_b, hedge):
        if p_val <= self.p_threshold:
            if zscore > self.z_in_upper:
                # Go Short: the spread is more than the mean so we short B and long A
                self.current_trade = helper.build_trade(
                    price_a, price_b, hedge, 'short'
                )
                self.wallet.sell('b', self.current_trade['quantity_b'], price_b)
                self.wallet.buy('a', self.current_trade['quantity_a'], price_a)
                self.num_trades += 1
                return

            if zscore < self.z_in_lower:
                # Go Long: the spread is less than the mean so we short A and long B
                self.current_trade = helper.build_trade(
                    price_a, price_b, hedge, 'long'
                )
                self.wallet.sell('a', self.current_trade['quantity_a'], price_a)
                self.wallet.buy('b', self.current_trade['quantity_b'], price_b)
                self.num_trades += 1
                return

    def close_trade(self, p_val, zscore, price_a, price_b, hedge):
        # if p_val > self.p_threshold:
        #     if self.current_trade['non_coint_count'] > self.non_coint_threshold:
        #         self.close_for_non_cointegration(
        #             p_val, zscore, price_a, price_b, hedge
        #         )
        #         self.current_trade = {}
        #         return
        #     else:
        #         self.current_trade['non_coint_count'] += 1
        if self.current_trade['type'] == 'short' and zscore < self.z_out_lower:
            self.wallet.sell('a', self.current_trade['quantity_a'], price_a)
            self.wallet.buy('b', self.current_trade['quantity_b'], price_b)
            self.current_trade = {}
            return

        if self.current_trade['type'] == 'long' and zscore > self.z_out_upper:
            self.wallet.sell('b', self.current_trade['quantity_b'], price_b)
            self.wallet.buy('a', self.current_trade['quantity_a'], price_a)
            self.current_trade = {}
            return

    # def close_for_non_cointegration(self, p_val, zscore, price_a, price_b, hedge):
    #     if self.current_trade['type'] == 'short':
    #         self.wallet.sell('a', self.current_trade['quantity_a'], price_a)
    #         self.wallet.buy('b', self.current_trade['quantity_b'], price_b)
    #     elif self.current_trade['type'] == 'long':
    #         self.wallet.sell('b', self.current_trade['quantity_b'], price_b)
    #         self.wallet.buy('a', self.current_trade['quantity_a'], price_a)

    def run(self, pairs):
        for pair, vals in pairs.items():
            # prices_a, prices_b = helper.generate_coint_series()
            prices_a = vals['prices_a']
            prices_b = vals['prices_b']

            self.setup_pass()

            for i in range(self.lookback_period, len(prices_a)):
                subset_prices_a, subset_prices_b = helper.get_subset(
                    prices_a, prices_b, i, self.lookback_period
                )
                subset_prices_a.name = 'subset_prices_a'
                subset_prices_b.name = 'subset_prices_b'

                hedge = helper.simple_hedge(subset_prices_a, subset_prices_b)
                spreads = helper.simple_spreads(subset_prices_a, subset_prices_b, 0)
                zscore = helper.simple_zscore(spreads)

                p_val = CointegrationService().p_value(
                    subset_prices_a, subset_prices_b
                )

                if helper.currently_trading(self.current_trade):
                    self.close_trade(
                        p_val,
                        zscore,
                        subset_prices_a.iloc[-1],
                        subset_prices_b.iloc[-1],
                        hedge
                    )
                else:
                    self.open_trade(
                        p_val,
                        zscore,
                        subset_prices_a.iloc[-1],
                        subset_prices_b.iloc[-1],
                        hedge
                    )

                self.rolling_holdings.append(self.wallet.holdings['btc'])

                print('holdings (BTC): ', self.wallet.holdings['btc'])
                print('holdings (Asset A): ', self.wallet.holdings['a'])
                print('holdings (Asset B): ', self.wallet.holdings['b'])
                print('zscore:', zscore)
                print('hedge:', hedge)
                print('-'*20)
                print()

            result = {
                'pair': pair,
                'holdings': self.wallet.holdings['btc'],
                'rolling_holdings': self.rolling_holdings,
                'avg_ratio': vals['avg_ratio'],
                'num_trades': self.num_trades
            }
            with open('backtest_results.json', 'r') as f:
                results_list = json.load(f)
                results_list.append(result)
            with open('backtest_results.json', 'w') as f:
                json.dump(results_list, f)
Exemple #4
0
class myTrader(gym.Env):
    """Custom Environment that follows gym interface"""
    metadata = {'render.modes': ['human']}

    def __init__(self, data, balance, commission=0.01, **kwargs):
        #TODO saskirot jau sakuma datus liste
        super(myTrader, self).__init__()
        self.data = data
        self.plt = None
        self.ax = None
        self.risk = RiskAdjustedReturns(window_size=5)
        self.base_precision: int = kwargs.get('base_precision', 2)
        self.asset_precision: int = kwargs.get('asset_precision', 8)
        self.window_size = kwargs.get('window_size', 1)
        self.wallet = Wallet(data, usd=balance, commission=commission)
        self.minimum_balance: int = kwargs.get('minimum_balance', 100)
        self.current_step = 0
        self.history = {
            'episodes': [],
            'stocks': [],
            'worth': [],
            'action': [],
            'buy': {
                'x': [],
                'y': []
            },
            'sell': {
                'x': [],
                'y': []
            }
        }
        self.n_discrete_actions: int = kwargs.get('n_discrete_actions', 24)
        amount = 1.0 / (self.n_discrete_actions -
                        (self.n_discrete_actions * 0.5))
        self.action = list(product(range(2), np.arange(amount, 1, amount)))
        self.action.append((0, 1.0))
        self.action.append((1, 1.0))
        self.action.append((2, 0))
        self.action_space = spaces.Discrete(len(self.action))
        self.observation_space = spaces.Box(
            low=-np.inf,
            high=np.inf,
            shape=self.data.values[self.current_step].shape)

    def step(self, action):
        # Execute one time step within the environment
        done = False
        if TradingEnvAction.BUY == TradingEnvAction(
                self.action[action][0]) and self.wallet.buy(
                    self.action[action][1]):
            self.history['buy']['x'].append([self.current_step])
            self.history['buy']['y'].append(
                self.data['close'][self.current_step])

        elif TradingEnvAction.SELL == TradingEnvAction(
                self.action[action][0]) and self.wallet.sell(
                    self.action[action][1]):
            self.history['sell']['x'].append([self.current_step])
            self.history['sell']['y'].append(
                self.data['close'][self.current_step])

        self.history['action'].append(
            [TradingEnvAction(self.action[action][0]), self.action[action][1]])
        self.history['episodes'].append(self.current_step)
        self.history['stocks'].append(self.data['close'][self.current_step])
        self.history['worth'].append(self.wallet.total())

        reward = self.risk.get_reward(self.history['worth'])
        if self.wallet.total() <= self.minimum_balance or len(
                self.data) - 1 == self.current_step:
            done = True
        obs = self.data.values[self.current_step]
        self.current_step += 1
        self.wallet.step()
        return obs, reward, done, {}

    def _observation(self):
        if (self.current_step - self.window_size) < 0:
            obs = np.zeros((self.observation_space.shape))
            obs[-(self.current_step + 1):] = self.data.values[0:(
                self.current_step + 1)]
        else:
            obs = self.data.values[((self.current_step + 1) -
                                    self.window_size):self.current_step + 1]
        return obs

    def reset(self):
        self.current_step = 0
        self.wallet.reset()
        self.history = {
            'episodes': [],
            'stocks': [],
            'worth': [],
            'action': [],
            'buy': {
                'x': [],
                'y': []
            },
            'sell': {
                'x': [],
                'y': []
            }
        }
        obs = self.data.values[self.current_step]
        self.history['episodes'].append(self.current_step)
        self.history['worth'].append(self.wallet.total())
        self.history['action'].append([TradingEnvAction(2), 0])
        self.history['stocks'].append(self.data['close'][self.current_step])
        self.current_step += 1
        self.wallet.step()

        return obs

    def render(self, mode='human'):
        # Render the environment to the screen
        if mode == 'system':
            tqdm.write('Episode:{0} worth:{1} Action:{2} amount:{3}'.format(
                self.history['episodes'][-1], self.history['worth'][-1],
                self.history['action'][-1][0], self.history['action'][-1][1]))
        elif mode == 'human':
            if not self.plt:
                plt.ion()
                fig, ax = plt.subplots(2)
                self.ax = ax
                self.plt = plt
            self.ax[0].plot(self.history['stocks'], color='red')
            if self.history['buy']:
                self.ax[0].scatter(self.history['buy']['x'],
                                   self.history['buy']['y'],
                                   label='skitscat',
                                   color='green',
                                   s=100,
                                   marker="^")
            if self.history['sell']:
                self.ax[0].scatter(self.history['sell']['x'],
                                   self.history['sell']['y'],
                                   label='skitscat',
                                   color='red',
                                   s=100,
                                   marker="v")
            self.ax[1].plot(self.history['worth'], color='grey')
            self.plt.draw()
            self.plt.pause(0.01)
Exemple #5
0
class Realtime:
    def __init__(self):
        self.setup_backtest()

    def setup_backtest(self):
        self.lookback_period = 80
        self.z_upper = 2
        self.z_lower = -2
        self.p_threshold = 0.05
        self.non_coint_threshold = 10
        self.ticker_service = TickerService()
        self.price_service = PriceService()

    def setup_pass(self, asset_a, asset_b):
        self.wallet = Wallet()
        self.current_trade = {}
        self.pass_number = 0
        self.asset_a = asset_a
        self.asset_b = asset_b
        self.rolling_holdings = []
        self.num_trades = 0

    def open_trade(self, p_val, zscore, ticker_data_a, ticker_data_b, hedge):
        if helper.is_cointegrated(self.asset_a, self.asset_b):
            if zscore > self.z_upper:
                # Go Short: the spread is more than the mean so we short B and long A
                price_a = ticker_data_a['ask']
                price_b = ticker_data_b['bid']

                self.current_trade = helper.build_trade(
                    price_a, price_b, hedge, 'short')
                self.wallet.sell('b', self.current_trade['quantity_b'],
                                 price_b)
                self.wallet.buy('a', self.current_trade['quantity_a'], price_a)
                self.num_trades += 1
                return

            if zscore < self.z_lower:
                # Go Long: the spread is less than the mean so we short A and long B
                price_a = ticker_data_a['bid']
                price_b = ticker_data_b['ask']

                self.current_trade = helper.build_trade(
                    price_a, price_b, hedge, 'long')
                self.wallet.sell('a', self.current_trade['quantity_a'],
                                 price_a)
                self.wallet.buy('b', self.current_trade['quantity_b'], price_b)
                self.num_trades += 1
                return

    def close_trade(self, p_val, zscore, ticker_data_a, ticker_data_b, hedge):
        if not helper.is_cointegrated(self.asset_a, self.asset_b):
            if self.current_trade['non_coint_count'] > self.non_coint_threshold:
                self.close_for_non_cointegration(p_val, zscore, ticker_data_a,
                                                 ticker_data_b, hedge)
                self.current_trade = {}
                return
            else:
                self.current_trade['non_coint_count'] += 1

        if self.current_trade['type'] == 'short' and zscore < self.z_lower:
            price_a = ticker_data_a['bid']
            price_b = ticker_data_b['ask']

            self.wallet.sell('a', self.current_trade['quantity_a'], price_a)
            self.wallet.buy('b', self.current_trade['quantity_b'], price_b)

            self.current_trade = {}
            return

        if self.current_trade['type'] == 'long' and zscore > self.z_upper:
            price_a = ticker_data_a['ask']
            price_b = ticker_data_b['bid']

            self.wallet.sell('b', self.current_trade['quantity_b'], price_b)
            self.wallet.buy('a', self.current_trade['quantity_a'], price_a)

            self.current_trade = {}
            return

    def close_for_non_cointegration(self, p_val, zscore, ticker_data_a,
                                    ticker_data_b, hedge):
        if self.current_trade['type'] == 'short':
            price_a = ticker_data_a['bid']
            price_b = ticker_data_b['ask']

            self.wallet.sell('a', self.current_trade['quantity_a'], price_a)
            self.wallet.buy('b', self.current_trade['quantity_b'], price_b)
        else:
            price_a = ticker_data_a['ask']
            price_b = ticker_data_b['bid']

            self.wallet.sell('b', self.current_trade['quantity_b'], price_b)
            self.wallet.buy('a', self.current_trade['quantity_a'], price_a)

    def run(self, asset_a, asset_b):
        self.setup_pass(asset_a, asset_b)

        while True:
            # try:
            historic_prices = self.price_service.historic_prices(
                1, '5m', [asset_a, asset_b])
            historic_prices_a = historic_prices[asset_a][-80:]
            historic_prices_b = historic_prices[asset_b][-80:]

            ticker_data_a = self.ticker_service.ticker_for(asset_a)
            ticker_data_b = self.ticker_service.ticker_for(asset_b)

            historic_prices_a = pd.Series(np.append(
                historic_prices_a.values, ticker_data_a['avg_price']),
                                          name='subset_prices_a')
            historic_prices_b = pd.Series(np.append(
                historic_prices_b.values, ticker_data_b['avg_price']),
                                          name='subset_prices_b')

            hedge = helper.simple_hedge(historic_prices_a, historic_prices_b)
            spreads = helper.simple_spreads(historic_prices_a,
                                            historic_prices_b, 0)
            zscore = helper.simple_zscore(spreads)

            p_val = CointegrationService().p_value(historic_prices_a,
                                                   historic_prices_b)

            if helper.currently_trading(self.current_trade):
                self.close_trade(p_val, zscore, ticker_data_a, ticker_data_b,
                                 hedge)
            else:
                self.open_trade(p_val, zscore, ticker_data_a, ticker_data_b,
                                hedge)

            self.pass_number += 1
            self.rolling_holdings.append(self.wallet.holdings['btc'])

            print('pass ' + str(self.pass_number))
            print('holdings (BTC): ', self.wallet.holdings['btc'])
            print('holidings (Asset A): ', self.wallet.holdings['a'])
            print('holidings (Asset B): ', self.wallet.holdings['b'])
            print('zscore:', zscore)
            print('hedge:', hedge)
            print('-' * 20)
            print()

            result = {
                'pair': asset_a + '|' + asset_b,
                'holdings': self.wallet.holdings['btc'],
                'num_trades': self.num_trades,
                'timestamp': datetime.datetime.now().timestamp()
            }
            with open('realtime_results.json', 'r') as f:
                results_list = json.load(f)
                results_list.append(result)
            with open('realtime_results.json', 'w') as f:
                json.dump(results_list, f)

            time.sleep(60 * 5)