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)}")
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
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)
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)
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)