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 _create_order_at_level(self, level: int, side: str) -> float: """ Create a new order at a specified LOB level. :param level: (int) level in the limit order book :param side: (str) direction of trade e.g., 'long' or 'short' :return: (float) reward with penalties added """ reward = 0.0 if side == 'long': notional_index = self.notional_bid_index price_index = self.best_bid_index elif side == 'short': notional_index = self.notional_ask_index price_index = self.best_ask_index else: notional_index = price_index = None # get price data from numpy array price_level_price = self._get_book_data(index=price_index + level) # transform percentage into a hard number price_level_price = round(self.midpoint * (price_level_price + 1.), 2) price_level_queue = self._get_book_data(index=notional_index + level) # create a new order order = LimitOrder(ccy=self.symbol, side=side, price=price_level_price, step=self.local_step_number, queue_ahead=price_level_queue) # add a penalty or encouragement, depending if order is accepted if self.broker.add(order=order) is False: reward -= ENCOURAGEMENT else: reward += ENCOURAGEMENT return reward
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 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, 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(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, 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., test_position.long_inventory.average_price) print("PnL: {}".format(pnl))
def _create_order_at_level(self, reward: float, discouragement: float, level=0, side='long'): """ Create a new order at a specified LOB level :param reward: (float) current step reward :param discouragement: (float) penalty deducted from reward for erroneous actions :param level: (int) level in the limit order book :param side: (str) direction of trade e.g., 'long' or 'short' :return: (float) reward with penalties added """ adjustment = 1 if level > 0 else 0 if side == 'long': best = self._get_book_data(MarketMaker.best_bid_index - level) denormalized_best = round(self.midpoint * (best + 1), 2) inside_best = self._get_book_data(MarketMaker.best_bid_index - level + adjustment) denormalized_inside_best = round(self.midpoint * (inside_best + 1), 2) plus_one = denormalized_best + 0.01 if denormalized_inside_best == plus_one: # stick to best bid bid_price = denormalized_best # since LOB is rendered as cumulative notional, deduct the prior price # level to derive the notional value of orders ahead in the queue bid_queue_ahead = self._get_book_data( MarketMaker.notional_bid_index - level) - self._get_book_data( MarketMaker.notional_bid_index - level + adjustment) else: # insert a cent ahead to jump a queue bid_price = plus_one bid_queue_ahead = 0. bid_order = LimitOrder(ccy=self.sym, side='long', price=bid_price, step=self.local_step_number, queue_ahead=bid_queue_ahead) if self.broker.add(order=bid_order) is False: reward -= discouragement else: reward += discouragement if side == 'short': best = self._get_book_data(MarketMaker.best_ask_index + level) denormalized_best = round(self.midpoint * (best + 1), 2) inside_best = self._get_book_data(MarketMaker.best_ask_index + level - adjustment) denormalized_inside_best = round(self.midpoint * (inside_best + 1), 2) plus_one = denormalized_best - 0.01 if denormalized_inside_best == plus_one: ask_price = denormalized_best # since LOB is rendered as cumulative notional, deduct the prior price # level to derive the notional value of orders ahead in the queue ask_queue_ahead = self._get_book_data( MarketMaker.notional_ask_index + level) - self._get_book_data( MarketMaker.notional_ask_index + level - adjustment) else: ask_price = plus_one ask_queue_ahead = 0. ask_order = LimitOrder(ccy=self.sym, side='short', price=ask_price, step=self.local_step_number, queue_ahead=ask_queue_ahead) if self.broker.add(order=ask_order) is False: reward -= discouragement else: reward += discouragement return reward
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))