def __init__(self, transaction_fee=LIMIT_ORDER_FEE, **kwargs): """ Environment designed for automated market making. :param kwargs: refer to BaseEnvironment.py """ super(MarketMaker, self).__init__(**kwargs) # environment attributes to override in sub-class self.actions = np.eye(17, dtype=np.float32) # get Broker class to keep track of PnL and orders self.broker = Broker(max_position=self.max_position, transaction_fee=transaction_fee) self.action_space = spaces.Discrete(len(self.actions)) self.reset() # reset to load observation.shape self.observation_space = spaces.Box(low=-10, high=10, shape=self.observation.shape, dtype=np.float32) print( '{} MarketMaker #{} instantiated\nobservation_space: {}'.format( self.sym, self._seed, self.observation_space.shape), 'reward_type = {}'.format(self.reward_type))
def test_case_two(self): print('\nTest_Case_Two') test_position = Broker() midpoint = 100. fee = .003 order_open = MarketOrder(ccy='BTC-USD', side='short', price=midpoint, step=1) test_position.add(order=order_open) self.assertEqual(1, test_position.short_inventory.position_count) self.assertEqual(0, test_position.long_inventory.position_count) self.assertEqual( 0., test_position.long_inventory.get_unrealized_pnl(price=midpoint)) print('SHORT Unrealized_pnl: %f' % test_position.short_inventory.get_unrealized_pnl(price=midpoint)) order_close = MarketOrder(ccy='BTC-USD', side='short', price=midpoint - (midpoint * fee * 15), step=100) test_position.remove(order=order_close) self.assertEqual(0, test_position.short_inventory.position_count) self.assertEqual(0, test_position.long_inventory.position_count) self.assertEqual( 0., test_position.long_inventory.get_unrealized_pnl(price=midpoint)) print('SHORT Unrealized_pnl: %f' % test_position.short_inventory.get_unrealized_pnl(price=midpoint)) print('SHORT Realized_pnl: %f' % test_position.realized_pnl)
def test_case_three(self): print('\nTest_Case_Three') test_position = Broker(5) midpoint = 100. for i in range(10): order_open = MarketOrder(ccy='BTC-USD', side='long', price=midpoint - i, step=i) test_position.add(order=order_open) self.assertEqual(5, test_position.long_inventory.position_count) self.assertEqual(0, test_position.short_inventory.position_count) print('Confirm we have 5 positions: %i' % test_position.long_inventory.position_count) for i in range(10): order_open = MarketOrder(ccy='BTC-USD', side='long', price=midpoint + i, step=i) test_position.remove(order=order_open) self.assertEqual(0, test_position.long_inventory.position_count) self.assertEqual(0, test_position.short_inventory.position_count)
def test_case_one(self): print('\nTest_Case_One') test_position = Broker() midpoint = 100. fee = .003 order_open = Order(ccy='BTC-USD', side='long', price=midpoint, step=1) test_position.add(order=order_open) self.assertEqual(1, test_position.long_inventory.position_count) print('LONG Unrealized_pnl: %f' % test_position.long_inventory.get_unrealized_pnl()) self.assertEqual(0, test_position.short_inventory.position_count) self.assertEqual(0., test_position.short_inventory.get_unrealized_pnl()) order_close = Order(ccy='BTC-USD', side='long', price=midpoint + (midpoint * fee * 5), step=100) test_position.remove(order=order_close) self.assertEqual(0, test_position.long_inventory.position_count) print('LONG Unrealized_pnl: %f' % test_position.long_inventory.get_unrealized_pnl()) self.assertEqual(test_position.short_inventory.position_count, 0) self.assertEqual(test_position.short_inventory.get_unrealized_pnl(), 0.) print('LONG Realized_pnl: %f' % test_position.get_realized_pnl())
def test_long_pnl(self): test_position = Broker() step = 0 bid_price = 101. ask_price = 102. buy_volume = 100 sell_volume = 100 pnl = 0. def walk_forward(pnl, step, bid_price, ask_price, buy_volume, sell_volume, down=True): for i in range(50): step += 1 if down: bid_price *= 0.99 ask_price *= 0.99 else: bid_price *= 1.01 ask_price *= 1.01 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl if i % 10 == 0: print('bid_price={:.2f} | ask_price={:.2f}'.format(bid_price, ask_price)) return step, bid_price, ask_price, buy_volume, sell_volume, pnl test_position.add( order=LimitOrder(ccy='BTC-USD', side='long', price=100., step=step, queue_ahead=1000)) step, _, _, buy_volume, sell_volume, pnl = walk_forward(pnl, step, bid_price, ask_price, buy_volume, sell_volume, down=True) self.assertEqual(1, test_position.long_inventory_count) test_position.add( order=LimitOrder(ccy='BTC-USD', side='short', price=105., step=step, queue_ahead=0)) _, _, _, _, _, pnl = walk_forward(pnl, step, bid_price, ask_price, buy_volume, sell_volume, down=False) realized_pnl = test_position.realized_pnl self.assertEqual(0.05, realized_pnl, "Expected Realized PnL of 0.5 and got {}".format(realized_pnl)) self.assertEqual(0, test_position.short_inventory_count + test_position.long_inventory_count) print("PnL: {}".format(pnl))
def test_avg_exe(self): test_position = Broker() # perform a partial fill on the first order step = 0 bid_price = 101. ask_price = 102. buy_volume = 500 sell_volume = 500 pnl = 0. test_position.add(order=LimitOrder(ccy='BTC-USD', side='long', price=bid_price, step=step, queue_ahead=0)) print("taking first step...") step += 1 pnl += test_position.step_limit_order_pnl(bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) self.assertEqual(500, test_position.long_inventory.order.executed) self.assertEqual(0, test_position.long_inventory_count) # if order gets filled with a bid below the order's price, the order should NOT # receive any price improvement during the execution. bid_price = 99. ask_price = 100. test_position.add(order=LimitOrder(ccy='BTC-USD', side='long', price=bid_price, step=step, queue_ahead=0)) print("taking second step...") step += 1 pnl += test_position.step_limit_order_pnl(bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) self.assertEqual(1, test_position.long_inventory_count) self.assertEqual(100., test_position.long_inventory.average_price) print("PnL: {}".format(pnl))
def __init__(self, *, fitting_file='LTC-USD_2019-04-07.csv.xz', testing_file='LTC-USD_2019-04-08.csv.xz', step_size=1, max_position=5, window_size=10, seed=1, action_repeats=10, training=True, format_3d=False, z_score=True, reward_type='trade_completion', scale_rewards=True): super(PriceJump, self).__init__(fitting_file=fitting_file, testing_file=testing_file, step_size=step_size, max_position=max_position, window_size=window_size, seed=seed, action_repeats=action_repeats, training=training, format_3d=format_3d, z_score=z_score, reward_type=reward_type, scale_rewards=scale_rewards) self.actions = np.eye(3, dtype=np.float32) # get Broker class to keep track of PnL and orders self.broker = Broker(max_position=max_position, transaction_fee=MARKET_ORDER_FEE) self.action_space = spaces.Discrete(len(self.actions)) self.reset() # reset to load observation.shape self.observation_space = spaces.Box(low=-10, high=10, shape=self.observation.shape, dtype=np.float32) print( '{} PriceJump #{} instantiated.\nself.observation_space.shape : {}' .format(self.sym, self._seed, self.observation_space.shape))
def __init__(self, symbol: str, fitting_file: str, testing_file: str, max_position: int = 10, window_size: int = 100, seed: int = 1, action_repeats: int = 5, training: bool = True, format_3d: bool = False, reward_type: str = 'default', transaction_fee: bool = True, ema_alpha: list or float or None = EMA_ALPHA): """ Base class for creating environments extending OpenAI's GYM framework. :param symbol: currency pair to trade / experiment :param fitting_file: prior trading day (e.g., T-1) :param testing_file: current trading day (e.g., T) :param max_position: maximum number of positions able to hold in inventory :param window_size: number of lags to include in observation space :param seed: random seed number :param action_repeats: number of steps to take in environment after a given action :param training: if TRUE, then randomize starting point in environment :param format_3d: if TRUE, reshape observation space from matrix to tensor :param reward_type: method for calculating the environment's reward: 1) 'default' --> inventory count * change in midpoint price returns 2) 'default_with_fills' --> inventory count * change in midpoint price returns + closed trade PnL 3) 'realized_pnl' --> change in realized pnl between time steps 4) 'differential_sharpe_ratio' --> http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.1.7210&rep=rep1&type=pdf 5) 'asymmetrical' --> extended version of *default* and enhanced with a reward for being filled above or below midpoint, and returns only negative rewards for Unrealized PnL to discourage long-term speculation. 6) 'trade_completion' --> reward is generated per trade's round trip :param ema_alpha: decay factor for EMA, usually between 0.9 and 0.9999; if NONE, raw values are returned in place of smoothed values """ assert reward_type in VALID_REWARD_TYPES, \ 'Error: {} is not a valid reward type. Value must be in:\n{}'.format( reward_type, VALID_REWARD_TYPES) self.viz = Visualize( columns=['midpoint', 'buys', 'sells', 'inventory', 'realized_pnl'], store_historical_observations=True) # get Broker class to keep track of PnL and orders self.broker = Broker(max_position=max_position, transaction_fee=transaction_fee) # properties required for instantiation self.symbol = symbol self.action_repeats = action_repeats self._seed = seed self._random_state = np.random.RandomState(seed=self._seed) self.training = training self.max_position = max_position self.window_size = window_size self.reward_type = reward_type self.format_3d = format_3d # e.g., [window, features, *NEW_AXIS*] self.testing_file = testing_file # properties that get reset() self.reward = np.array([0.0], dtype=np.float32) self.step_reward = np.array([0.0], dtype=np.float32) self.done = False self.local_step_number = 0 self.midpoint = 0.0 self.observation = None self.action = 0 self.last_pnl = 0. self.last_midpoint = None self.midpoint_change = None self.A_t, self.B_t = 0., 0. # variables for Differential Sharpe Ratio self.episode_stats = ExperimentStatistics() self.best_bid = self.best_ask = None # properties to override in sub-classes self.actions = None self.action_space = None self.observation_space = None # get historical data for simulations self.data_pipeline = DataPipeline(alpha=ema_alpha) # three different data sets, for different purposes: # 1) midpoint_prices - midpoint prices that have not been transformed # 2) raw_data - raw limit order book data, not including imbalances # 3) normalized_data - z-scored limit order book and order flow imbalance # data, also midpoint price feature is replace by midpoint log price change self._midpoint_prices, self._raw_data, self._normalized_data = \ self.data_pipeline.load_environment_data( fitting_file=fitting_file, testing_file=testing_file, include_imbalances=True, as_pandas=True, ) # derive best bid and offer self._best_bids = self._raw_data['midpoint'] - ( self._raw_data['spread'] / 2) self._best_asks = self._raw_data['midpoint'] + ( self._raw_data['spread'] / 2) self.max_steps = self._raw_data.shape[0] - self.action_repeats - 1 # load indicators into the indicator manager self.tns = IndicatorManager() self.rsi = IndicatorManager() for window in INDICATOR_WINDOW: self.tns.add( ('tns_{}'.format(window), TnS(window=window, alpha=ema_alpha))) self.rsi.add( ('rsi_{}'.format(window), RSI(window=window, alpha=ema_alpha))) # buffer for appending lags self.data_buffer = deque(maxlen=self.window_size) # Index of specific data points used to generate the observation space features = self._raw_data.columns.tolist() self.best_bid_index = features.index('bids_distance_0') self.best_ask_index = features.index('asks_distance_0') self.notional_bid_index = features.index('bids_notional_0') self.notional_ask_index = features.index('asks_notional_0') self.buy_trade_index = features.index('buys') self.sell_trade_index = features.index('sells') # typecast all data sets to numpy self._raw_data = self._raw_data.to_numpy(dtype=np.float32) self._normalized_data = self._normalized_data.to_numpy( dtype=np.float32) self._midpoint_prices = self._midpoint_prices.to_numpy( dtype=np.float64) self._best_bids = self._best_bids.to_numpy(dtype=np.float32) self._best_asks = self._best_asks.to_numpy(dtype=np.float32) # rendering class self._render = TradingGraph(sym=self.symbol) # graph midpoint prices self._render.reset_render_data( y_vec=self._midpoint_prices[:np.shape(self._render.x_vec)[0]])
def test_queues_ahead_features(self): test_position = Broker() # perform a partial fill on the first order step = 0 bid_price = 100. ask_price = 200. buy_volume = 0 sell_volume = 0 order_open_long = LimitOrder(ccy='BTC-USD', side='long', price=bid_price, step=step, queue_ahead=0) order_open_short = LimitOrder(ccy='BTC-USD', side='short', price=ask_price, step=step, queue_ahead=2000) print('opening long position = {}'.format(order_open_long)) test_position.add(order=order_open_long) print('opening short position = {}'.format(order_open_short)) test_position.add(order=order_open_short) print('\ntaking first step...') step += 1 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl print("#1 long_inventory.order = \n{}".format( test_position.long_inventory.order)) print("#1 short_inventory.order = \n{}".format( test_position.short_inventory.order)) bid_queue, ask_queue = test_position.get_queues_ahead_features() print("#1 get_queues_ahead_features:\nbid_queue={} || ask_queue={}". format(bid_queue, ask_queue)) self.assertEqual(0., bid_queue) self.assertEqual(-0.67, round(ask_queue, 2)) print('\ntaking second step...') buy_volume = 500 sell_volume = 500 step += 1 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl print("#2 long_inventory.order = \n{}".format( test_position.long_inventory.order)) print("#2 short_inventory.order = \n{}".format( test_position.short_inventory.order)) bid_queue, ask_queue = test_position.get_queues_ahead_features() print("#2 get_queues_ahead_features:\nbid_queue={} || ask_queue={}". format(bid_queue, ask_queue)) self.assertEqual(0.5, bid_queue) self.assertEqual(-0.6, round(ask_queue, 2)) print('\ntaking third step...') buy_volume = 500 sell_volume = 499 step += 1 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl print("#3 long_inventory.order = \n{}".format( test_position.long_inventory.order)) print("#3 short_inventory.order = \n{}".format( test_position.short_inventory.order)) bid_queue, ask_queue = test_position.get_queues_ahead_features() print("#3 get_queues_ahead_features:\nbid_queue={} || ask_queue={}". format(bid_queue, ask_queue)) self.assertEqual(0.999, bid_queue) self.assertEqual(-0.5, round(ask_queue, 2)) print('\ntaking fourth step...') buy_volume = 500 sell_volume = 500 step += 1 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl print("#4 long_inventory.order = \n{}".format( test_position.long_inventory.order)) print("#4 short_inventory.order = \n{}".format( test_position.short_inventory.order)) bid_queue, ask_queue = test_position.get_queues_ahead_features() print("#4 get_queues_ahead_features:\nbid_queue={} || ask_queue={}". format(bid_queue, ask_queue)) self.assertEqual(0.0, bid_queue) self.assertEqual(-0.33, round(ask_queue, 2)) print("PnL: {}".format(pnl))
def test_lob_queuing(self): test_position = Broker() # perform a partial fill on the first order step = 0 bid_price = 102. ask_price = 103. buy_volume = 500 sell_volume = 500 queue_ahead = 800 order_open = LimitOrder(ccy='BTC-USD', side='long', price=bid_price, step=step, queue_ahead=queue_ahead) test_position.add(order=order_open) step += 1 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl print("#1 long_inventory.order = \n{}".format( test_position.long_inventory.order)) self.assertEqual(300, test_position.long_inventory.order.queue_ahead) self.assertEqual(0, test_position.long_inventory.order.executed) self.assertEqual(0, test_position.long_inventory_count) step += 1 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl print("#2 long_inventory.order = \n{}".format( test_position.long_inventory.order)) self.assertEqual(200, test_position.long_inventory.order.executed) self.assertEqual(0, test_position.long_inventory_count) # if order gets filled with a bid below the order's price, the order should NOT # receive any price improvement during the execution. bid_price = 100. ask_price = 102. order_open = LimitOrder(ccy='BTC-USD', side='long', price=bid_price, step=step, queue_ahead=queue_ahead) test_position.add(order=order_open) print("#3 long_inventory.order = \n{}".format( test_position.long_inventory.order)) self.assertEqual(0, test_position.long_inventory_count) bid_price = 100. for i in range(5): step += 1 pnl, is_long_order_filled, is_short_order_filled = \ test_position.step_limit_order_pnl( bid_price=bid_price, ask_price=ask_price, buy_volume=buy_volume, sell_volume=sell_volume, step=step) pnl += pnl self.assertEqual(1, test_position.long_inventory_count) self.assertEqual(100.40, round(test_position.long_inventory.average_price, 2)) print("PnL: {}".format(pnl))
def __init__(self, *, fitting_file='ETH-USD_2018-12-31.xz', testing_file='ETH-USD_2019-01-01.xz', step_size=1, max_position=5, window_size=10, seed=1, action_repeats=10, training=True, format_3d=False, z_score=True): # properties required for instantiation self.action_repeats = action_repeats self._seed = seed self._random_state = np.random.RandomState(seed=self._seed) self.training = training self.step_size = step_size self.max_position = max_position self.window_size = window_size self.format_3d = format_3d # e.g., [window, features, *NEW_AXIS*] self.action = 0 # derive gym.env properties self.actions = np.eye(3) self.sym = testing_file[:7] # slice the CCY from the filename # properties that get reset() self.reward = 0.0 self.done = False self.local_step_number = 0 self.midpoint = 0.0 self.observation = None # get Broker class to keep track of PnL and orders self.broker = Broker(max_position=max_position) # get historical data for simulations self.sim = Sim(use_arctic=False) self.data = self._load_environment_data(fitting_file, testing_file) self.prices_ = self.data[ 'coinbase_midpoint'].values # used to calculate PnL self.normalized_data = self.data.copy() self.data = self.data.values self.max_steps = self.data.shape[0] - self.step_size * \ self.action_repeats - 1 # normalize midpoint data self.normalized_data['coinbase_midpoint'] = \ np.log(self.normalized_data['coinbase_midpoint'].values) self.normalized_data['coinbase_midpoint'] = ( self.normalized_data['coinbase_midpoint'] - self.normalized_data['coinbase_midpoint'].shift(1)).fillna(0.) # load indicators into the indicator manager self.tns = IndicatorManager() self.rsi = IndicatorManager() for window in INDICATOR_WINDOW: self.tns.add(('tns_{}'.format(window), TnS(window=window))) self.rsi.add(('rsi_{}'.format(window), RSI(window=window))) if z_score: logger.info("Pre-scaling {}-{} data...".format( self.sym, self._seed)) self.normalized_data = self.normalized_data.apply(self.sim.z_score, axis=1).values logger.info("...{}-{} pre-scaling complete.".format( self.sym, self._seed)) else: self.normalized_data = self.normalized_data.values # rendering class self._render = TradingGraph(sym=self.sym) # graph midpoint prices self._render.reset_render_data( y_vec=self.prices_[:np.shape(self._render.x_vec)[0]]) # buffer for appending lags self.data_buffer = list() self.action_space = spaces.Discrete(len(self.actions)) self.reset() # reset to load observation.shape self.observation_space = spaces.Box(low=-10, high=10, shape=self.observation.shape, dtype=np.float32) print( '{} PriceJump #{} instantiated.\nself.observation_space.shape : {}' .format(self.sym, self._seed, self.observation_space.shape))
def __init__(self, *, fitting_file='LTC-USD_2019-04-07.csv.xz', testing_file='LTC-USD_2019-04-08.csv.xz', step_size=1, max_position=5, window_size=10, seed=1, action_repeats=10, training=True, format_3d=False, z_score=True): # properties required for instantiation self.action_repeats = action_repeats self._seed = seed self._random_state = np.random.RandomState(seed=self._seed) self.training = training self.step_size = step_size self.max_position = max_position self.window_size = window_size self.format_3d = format_3d # e.g., [window, features, *NEW_AXIS*] self.action = 0 # derive gym.env properties self.actions = np.eye(3, dtype=np.float32) self.sym = testing_file[:7] # slice the CCY from the filename # properties that get reset() self.reward = 0.0 self.done = False self.local_step_number = 0 self.midpoint = 0.0 self.observation = None # get Broker class to keep track of PnL and orders self.broker = Broker(max_position=max_position) # get historical data for simulations self.sim = Sim(use_arctic=False, z_score=z_score) self.prices_, self.data, self.normalized_data = self.sim.load_environment_data( fitting_file, testing_file) self.max_steps = self.data.shape[0] - self.step_size * \ self.action_repeats - 1 # load indicators into the indicator manager self.tns = IndicatorManager() self.rsi = IndicatorManager() for window in INDICATOR_WINDOW: self.tns.add(('tns_{}'.format(window), TnS(window=window))) self.rsi.add(('rsi_{}'.format(window), RSI(window=window))) # rendering class self._render = TradingGraph(sym=self.sym) # graph midpoint prices self._render.reset_render_data( y_vec=self.prices_[:np.shape(self._render.x_vec)[0]]) # buffer for appending lags self.data_buffer = list() self.action_space = spaces.Discrete(len(self.actions)) self.reset() # reset to load observation.shape self.observation_space = spaces.Box(low=-10, high=10, shape=self.observation.shape, dtype=np.float32) print( '{} PriceJump #{} instantiated.\nself.observation_space.shape : {}' .format(self.sym, self._seed, self.observation_space.shape))