def setUp(self): self.clock_tick_size = 1 self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) self.market: BacktestMarket = BacktestMarket() self.book_data: MockOrderBookLoader = MockOrderBookLoader( self.trading_pair, self.base_asset, self.quote_asset) self.mid_price = 100 self.bid_spread = 0.01 self.ask_spread = 0.01 self.order_refresh_time = 30 self.book_data.set_balanced_order_book(mid_price=self.mid_price, min_price=1, max_price=200, price_step_size=1, volume_step_size=10) self.market.add_data(self.book_data) self.market.set_balance("HBOT", 500) self.market.set_balance("ETH", 5000) self.market.set_quantization_param( QuantizationParams(self.trading_pair, 6, 6, 6, 6)) self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, self.base_asset, self.quote_asset) self.clock.add_iterator(self.market) self.order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.market.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) self.ext_market: BacktestMarket = BacktestMarket() self.ext_data: MockOrderBookLoader = MockOrderBookLoader( self.trading_pair, self.base_asset, self.quote_asset) self.ext_market_info: MarketTradingPairTuple = MarketTradingPairTuple( self.ext_market, self.trading_pair, self.base_asset, self.quote_asset) self.ext_data.set_balanced_order_book(mid_price=100, min_price=1, max_price=400, price_step_size=1, volume_step_size=100) self.ext_market.add_data(self.ext_data) self.order_book_asset_del = OrderBookAssetPriceDelegate( self.ext_market, self.trading_pair) self.one_level_strategy = PureMarketMakingStrategy() self.one_level_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=3.0, filled_order_delay=3.0, order_refresh_tolerance_pct=-1, minimum_spread=-1, asset_price_delegate=self.order_book_asset_del, take_if_crossed=True)
def test_inventory_skew_multiple_orders_status(self): strategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5.0, filled_order_delay=5.0, order_refresh_tolerance_pct=-1, order_levels=5, order_level_spread=Decimal("0.01"), order_level_amount=Decimal("0.5"), inventory_skew_enabled=True, inventory_target_base_pct=Decimal("0.9"), inventory_range_multiplier=Decimal("0.5"), minimum_spread=-1, ) self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + 1) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) status_df: pd.DataFrame = strategy.inventory_skew_stats_data_frame() self.assertEqual("50.0%", status_df.iloc[4, 1]) self.assertEqual("150.0%", status_df.iloc[4, 2])
def setUp(self): self.clock_tick_size = 1 self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) self.market: BacktestMarket = BacktestMarket() self.maker_data: MockOrderBookLoader = MockOrderBookLoader(*self.maker_trading_pairs) self.mid_price = 100 self.maker_data.set_balanced_order_book(mid_price=self.mid_price, min_price=1, max_price=200, price_step_size=1, volume_step_size=10) self.market.add_data(self.maker_data) self.market.set_balance("COINALPHA", 500) self.market.set_balance("WETH", 5000) self.market.set_balance("QETH", 500) self.market.set_quantization_param( QuantizationParams( self.maker_trading_pairs[0], 6, 6, 6, 6 ) ) self.market_info: MarketTradingPairTuple = MarketTradingPairTuple( self.market, self.maker_trading_pairs[0], self.maker_trading_pairs[1], self.maker_trading_pairs[2] ) self.strategy: PureMarketMakingStrategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal(.05), ask_spread=Decimal(.05), order_amount=Decimal(1), order_refresh_time=30, minimum_spread=0, )
def test_basic_one_level_price_type(self): strategies = [] for price_type in ["last_price", "best_bid", "best_ask"]: strategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5.0, filled_order_delay=5.0, order_refresh_tolerance_pct=-1, minimum_spread=-1, price_type=price_type, ) strategies.append(strategy) self.clock.add_iterator(strategy) last_strategy, bid_strategy, ask_strategy = strategies self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(last_strategy.active_buys)) self.assertEqual(1, len(last_strategy.active_sells)) buy_1 = last_strategy.active_buys[0] self.assertEqual(99, buy_1.price) self.assertEqual(1, buy_1.quantity) sell_1 = last_strategy.active_sells[0] self.assertEqual(101, sell_1.price) self.assertEqual(1, sell_1.quantity) # Simulate buy order filled self.simulate_maker_market_trade(False, 100, 98.9) self.assertEqual(0, len(last_strategy.active_buys)) self.assertEqual(1, len(last_strategy.active_sells)) # After filled_ore self.clock.backtest_til(self.start_timestamp + 7) buy_1 = last_strategy.active_buys[0] self.assertEqual(Decimal('97.911'), buy_1.price) self.assertEqual(1, buy_1.quantity) sell_1 = last_strategy.active_sells[0] self.assertEqual(Decimal('99.889'), sell_1.price) self.assertEqual(1, sell_1.quantity) buy_bid = bid_strategy.active_buys[0] buy_target = self.market_info.get_price_by_type(PriceType.BestBid) * Decimal("0.99") self.assertEqual(buy_target, buy_bid.price) sell_bid = bid_strategy.active_sells[0] sell_target = self.market_info.get_price_by_type(PriceType.BestBid) * Decimal("1.01") self.assertEqual(sell_target, sell_bid.price) buy_ask = ask_strategy.active_buys[0] buy_target = self.market_info.get_price_by_type(PriceType.BestAsk) * Decimal("0.99") self.assertEqual(buy_target, buy_ask.price) sell_ask = ask_strategy.active_sells[0] sell_target = self.market_info.get_price_by_type(PriceType.BestAsk) * Decimal("1.01") self.assertEqual(sell_target, sell_ask.price)
def test_strategy_ping_pong_on_bid_fill(self): self.strategy = PureMarketMakingStrategy() self.strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5, filled_order_delay=5, order_refresh_tolerance_pct=-1, ping_pong_enabled=True, ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) self.clock.backtest_til( self.start_timestamp + 2 * self.clock_tick_size ) self.assertEqual(0, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) old_ask = self.strategy.active_sells[0] self.clock.backtest_til( self.start_timestamp + 7 * self.clock_tick_size ) self.assertEqual(0, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) # After new order create cycle (after filled_order_delay), check if a new order is created self.assertTrue(old_ask.client_order_id != self.strategy.active_sells[0].client_order_id) self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) self.clock.backtest_til( self.start_timestamp + 15 * self.clock_tick_size ) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells))
def test_order_quantity_available_balance(self): """ When balance is below the specified order amount, checks if orders created use the remaining available balance for the order size. """ strategy = PureMarketMakingStrategy(self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_refresh_time=5, order_amount=Decimal("100"), order_levels=3) self.clock.add_iterator(strategy) self.market.set_balance("HBOT", Decimal("10")) self.market.set_balance("ETH", Decimal("1000")) self.clock.backtest_til(self.start_timestamp + 1) # Check if order size on both sides is equal to the remaining balance self.assertEqual(Decimal("10.1010"), strategy.active_buys[0].quantity) self.assertEqual(Decimal("10"), strategy.active_sells[0].quantity) # Order levels created self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) strategy.cancel_order(strategy.active_buys[0].client_order_id) strategy.cancel_order(strategy.active_sells[0].client_order_id) # Do not create order on side with 0 balance self.market.set_balance("HBOT", 0) self.market.set_balance("ETH", Decimal("1000")) self.clock.backtest_til(self.start_timestamp + 7) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells))
def test_basic_one_level_price_type_own_last_trade(self): strategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5.0, filled_order_delay=5.0, order_refresh_tolerance_pct=-1, minimum_spread=-1, price_type='last_own_trade_price', ) self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) buy_1 = strategy.active_buys[0] self.assertEqual(99, buy_1.price) self.assertEqual(1, buy_1.quantity) sell_1 = strategy.active_sells[0] self.assertEqual(101, sell_1.price) self.assertEqual(1, sell_1.quantity) # Simulate buy order filled self.simulate_maker_market_trade(False, 100, 98.9) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) # Order has been filled self.clock.backtest_til(self.start_timestamp + 7) buy_1 = strategy.active_buys[0] self.assertEqual(Decimal('98.01'), buy_1.price) self.assertEqual(1, buy_1.quantity) sell_1 = strategy.active_sells[0] self.assertEqual(Decimal('99.99'), sell_1.price) self.assertEqual(1, sell_1.quantity)
class PMMRefreshToleranceUnitTest(unittest.TestCase): start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") start_timestamp: float = start.timestamp() end_timestamp: float = end.timestamp() trading_pair = "HBOT-ETH" base_asset = trading_pair.split("-")[0] quote_asset = trading_pair.split("-")[1] def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): order_book = self.market.get_order_book(self.trading_pair) trade_event = OrderBookTradeEvent( self.trading_pair, self.clock.current_timestamp, TradeType.BUY if is_buy else TradeType.SELL, price, quantity) order_book.apply_trade(trade_event) def setUp(self): self.clock_tick_size = 1 self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) self.market: MockPaperExchange = MockPaperExchange() self.mid_price = 100 self.bid_spread = 0.01 self.ask_spread = 0.01 self.order_refresh_time = 30 self.market.set_balanced_order_book(trading_pair=self.trading_pair, mid_price=self.mid_price, min_price=1, max_price=200, price_step_size=1, volume_step_size=10) self.market.set_balance("HBOT", 500) self.market.set_balance("ETH", 5000) self.market.set_quantization_param( QuantizationParams(self.trading_pair, 6, 6, 6, 6)) self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, self.base_asset, self.quote_asset) self.clock.add_iterator(self.market) self.maker_order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) def test_strategy_ping_pong_on_ask_fill(self): self.strategy = PureMarketMakingStrategy() self.strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5, filled_order_delay=5, order_refresh_tolerance_pct=-1, ping_pong_enabled=True, ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(0, len(self.strategy.active_sells)) old_bid = self.strategy.active_buys[0] self.clock.backtest_til(self.start_timestamp + 7 * self.clock_tick_size) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(0, len(self.strategy.active_sells)) # After new order create cycle (after filled_order_delay), check if a new order is created self.assertTrue(old_bid.client_order_id != self.strategy.active_buys[0].client_order_id) self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) self.clock.backtest_til(self.start_timestamp + 15 * self.clock_tick_size) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) def test_strategy_ping_pong_on_bid_fill(self): self.strategy = PureMarketMakingStrategy() self.strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5, filled_order_delay=5, order_refresh_tolerance_pct=-1, ping_pong_enabled=True, ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) self.assertEqual(0, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) old_ask = self.strategy.active_sells[0] self.clock.backtest_til(self.start_timestamp + 7 * self.clock_tick_size) self.assertEqual(0, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) # After new order create cycle (after filled_order_delay), check if a new order is created self.assertTrue(old_ask.client_order_id != self.strategy.active_sells[0].client_order_id) self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) self.clock.backtest_til(self.start_timestamp + 15 * self.clock_tick_size) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) def test_multiple_orders_ping_pong(self): self.strategy = PureMarketMakingStrategy() self.strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_levels=5, order_level_amount=Decimal("1"), order_level_spread=Decimal("0.01"), order_refresh_time=5, order_refresh_tolerance_pct=-1, filled_order_delay=5, ping_pong_enabled=True, ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(5, len(self.strategy.active_sells)) self.simulate_maker_market_trade(True, Decimal(100), Decimal("102.50")) # After market trade happens, 2 of the asks orders are filled. self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) # Not refreshing time yet, still same active orders self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) old_bids = self.strategy.active_buys old_asks = self.strategy.active_sells self.clock.backtest_til(self.start_timestamp + 7 * self.clock_tick_size) # After order refresh, same numbers of orders but it's a new set. self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.assertNotEqual( [o.client_order_id for o in old_asks], [o.client_order_id for o in self.strategy.active_sells]) self.assertNotEqual( [o.client_order_id for o in old_bids], [o.client_order_id for o in self.strategy.active_buys]) # Simulate sell trade, the first bid gets taken out self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) self.assertEqual(4, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 13 * self.clock_tick_size) # After refresh, same numbers of orders self.assertEqual(4, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) # Another bid order is filled. self.simulate_maker_market_trade(False, Decimal(100), Decimal("97.9")) self.assertEqual(3, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 20 * self.clock_tick_size) # After refresh, numbers of orders back to order_levels of 5 self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(5, len(self.strategy.active_sells))
def test_multiple_orders_ping_pong(self): self.strategy = PureMarketMakingStrategy() self.strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_levels=5, order_level_amount=Decimal("1"), order_level_spread=Decimal("0.01"), order_refresh_time=5, order_refresh_tolerance_pct=-1, filled_order_delay=5, ping_pong_enabled=True, ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(5, len(self.strategy.active_sells)) self.simulate_maker_market_trade(True, Decimal(100), Decimal("102.50")) # After market trade happens, 2 of the asks orders are filled. self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) # Not refreshing time yet, still same active orders self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) old_bids = self.strategy.active_buys old_asks = self.strategy.active_sells self.clock.backtest_til(self.start_timestamp + 7 * self.clock_tick_size) # After order refresh, same numbers of orders but it's a new set. self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.assertNotEqual( [o.client_order_id for o in old_asks], [o.client_order_id for o in self.strategy.active_sells]) self.assertNotEqual( [o.client_order_id for o in old_bids], [o.client_order_id for o in self.strategy.active_buys]) # Simulate sell trade, the first bid gets taken out self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) self.assertEqual(4, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 13 * self.clock_tick_size) # After refresh, same numbers of orders self.assertEqual(4, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) # Another bid order is filled. self.simulate_maker_market_trade(False, Decimal(100), Decimal("97.9")) self.assertEqual(3, len(self.strategy.active_buys)) self.assertEqual(3, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 20 * self.clock_tick_size) # After refresh, numbers of orders back to order_levels of 5 self.assertEqual(5, len(self.strategy.active_buys)) self.assertEqual(5, len(self.strategy.active_sells))
def setUp(self): self.clock_tick_size = 1 self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) self.market: MockPaperExchange = MockPaperExchange() self.mid_price = 100 self.bid_spread = 0.01 self.ask_spread = 0.01 self.order_refresh_time = 30 self.market.set_balanced_order_book(trading_pair=self.trading_pair, mid_price=self.mid_price, min_price=1, max_price=200, price_step_size=1, volume_step_size=10) self.market.set_balance("HBOT", 500) self.market.set_balance("ETH", 5000) self.market.set_quantization_param( QuantizationParams( self.trading_pair, 6, 6, 6, 6 ) ) self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, self.base_asset, self.quote_asset) self.clock.add_iterator(self.market) self.maker_order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) self.one_level_strategy: PureMarketMakingStrategy = PureMarketMakingStrategy() self.one_level_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=4, filled_order_delay=8, hanging_orders_enabled=True, hanging_orders_cancel_pct=0.05, order_refresh_tolerance_pct=0 ) self.multi_levels_strategy: PureMarketMakingStrategy = PureMarketMakingStrategy() self.multi_levels_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_levels=5, order_level_spread=Decimal("0.01"), order_refresh_time=4, filled_order_delay=8, order_refresh_tolerance_pct=0 ) self.hanging_order_multiple_strategy = PureMarketMakingStrategy() self.hanging_order_multiple_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_levels=5, order_level_spread=Decimal("0.01"), order_refresh_time=4, filled_order_delay=8, order_refresh_tolerance_pct=0, hanging_orders_enabled=True )
class PMMRefreshToleranceUnitTest(unittest.TestCase): start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") start_timestamp: float = start.timestamp() end_timestamp: float = end.timestamp() trading_pair = "HBOT-ETH" base_asset = trading_pair.split("-")[0] quote_asset = trading_pair.split("-")[1] def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): order_book = self.market.get_order_book(self.trading_pair) trade_event = OrderBookTradeEvent( self.trading_pair, self.clock.current_timestamp, TradeType.BUY if is_buy else TradeType.SELL, price, quantity ) order_book.apply_trade(trade_event) def setUp(self): self.clock_tick_size = 1 self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) self.market: MockPaperExchange = MockPaperExchange() self.mid_price = 100 self.bid_spread = 0.01 self.ask_spread = 0.01 self.order_refresh_time = 30 self.market.set_balanced_order_book(trading_pair=self.trading_pair, mid_price=self.mid_price, min_price=1, max_price=200, price_step_size=1, volume_step_size=10) self.market.set_balance("HBOT", 500) self.market.set_balance("ETH", 5000) self.market.set_quantization_param( QuantizationParams( self.trading_pair, 6, 6, 6, 6 ) ) self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, self.base_asset, self.quote_asset) self.clock.add_iterator(self.market) self.maker_order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) self.one_level_strategy: PureMarketMakingStrategy = PureMarketMakingStrategy() self.one_level_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=4, filled_order_delay=8, hanging_orders_enabled=True, hanging_orders_cancel_pct=0.05, order_refresh_tolerance_pct=0 ) self.multi_levels_strategy: PureMarketMakingStrategy = PureMarketMakingStrategy() self.multi_levels_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_levels=5, order_level_spread=Decimal("0.01"), order_refresh_time=4, filled_order_delay=8, order_refresh_tolerance_pct=0 ) self.hanging_order_multiple_strategy = PureMarketMakingStrategy() self.hanging_order_multiple_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_levels=5, order_level_spread=Decimal("0.01"), order_refresh_time=4, filled_order_delay=8, order_refresh_tolerance_pct=0, hanging_orders_enabled=True ) def test_active_orders_are_cancelled_when_mid_price_moves(self): strategy = self.one_level_strategy self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) old_bid = strategy.active_buys[0] old_ask = strategy.active_sells[0] # Not the order refresh time yet, orders should remain the same self.clock.backtest_til(self.start_timestamp + 3 * self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) self.market.order_books[self.trading_pair].apply_diffs([OrderBookRow(99.5, 30, 2)], [OrderBookRow(100.1, 30, 2)], 2) self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) new_bid = strategy.active_buys[0] new_ask = strategy.active_sells[0] self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertNotEqual(old_ask, new_ask) self.assertNotEqual(old_bid, new_bid) def test_active_orders_are_kept_when_within_tolerance(self): strategy = self.one_level_strategy self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) old_bid = strategy.active_buys[0] old_ask = strategy.active_sells[0] self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) new_bid = strategy.active_buys[0] new_ask = strategy.active_sells[0] self.assertEqual(old_ask, new_ask) self.assertEqual(old_bid, new_bid) self.clock.backtest_til(self.start_timestamp + 10 * self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) new_bid = strategy.active_buys[0] new_ask = strategy.active_sells[0] self.assertEqual(old_ask, new_ask) self.assertEqual(old_bid, new_bid) def test_multi_levels_active_orders_are_cancelled_when_mid_price_moves(self): strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) old_buys = strategy.active_buys old_sells = strategy.active_sells self.market.order_books[self.trading_pair].apply_diffs([OrderBookRow(99.5, 30, 2)], [OrderBookRow(100.1, 30, 2)], 2) self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) new_buys = strategy.active_buys new_sells = strategy.active_sells self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) self.assertNotEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) self.assertNotEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys]) def test_multiple_active_orders_are_kept_when_within_tolerance(self): strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) old_buys = strategy.active_buys old_sells = strategy.active_sells self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) new_buys = strategy.active_buys new_sells = strategy.active_sells self.assertEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) self.assertEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys]) self.clock.backtest_til(self.start_timestamp + 10 * self.clock_tick_size) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) new_buys = strategy.active_buys new_sells = strategy.active_sells self.assertEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) self.assertEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys]) def test_hanging_orders_multiple_orders_with_refresh_tolerance(self): strategy = self.hanging_order_multiple_strategy self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) self.simulate_maker_market_trade(True, Decimal("100"), Decimal("101.1")) # Before refresh_time hanging orders are not yet created self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time / 2) self.assertEqual(1, len(self.maker_order_fill_logger.event_log)) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(4, len(strategy.active_sells)) self.assertEqual(0, len(strategy.hanging_order_ids)) # At order_refresh_time (4 seconds), hanging order are created # Ask is filled and due to delay is not replenished immediately # Bid orders are now hanging and active self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + 1) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells)) self.assertEqual(1, len(strategy.hanging_order_ids)) # At filled_order_delay (8 seconds), new sets of bid and ask orders are created self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + strategy.filled_order_delay + 1) self.assertEqual(6, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) self.assertEqual(1, len(strategy.hanging_order_ids)) # Check all hanging order ids are indeed in active bids list self.assertTrue(all(h in [order.client_order_id for order in strategy.active_buys] for h in strategy.hanging_order_ids)) old_buys = [o for o in strategy.active_buys if o.client_order_id not in strategy.hanging_order_ids] old_sells = [o for o in strategy.active_sells if o.client_order_id not in strategy.hanging_order_ids] self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + strategy.filled_order_delay + 1) self.assertEqual(6, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) new_buys = [o for o in strategy.active_buys if o.client_order_id not in strategy.hanging_order_ids] new_sells = [o for o in strategy.active_sells if o.client_order_id not in strategy.hanging_order_ids] self.assertEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) self.assertEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys])
def test_inventory_skew_multiple_orders(self): strategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5.0, filled_order_delay=5.0, order_refresh_tolerance_pct=-1, order_levels=5, order_level_spread=Decimal("0.01"), order_level_amount=Decimal("0.5"), inventory_skew_enabled=True, inventory_target_base_pct=Decimal("0.9"), inventory_range_multiplier=Decimal("0.5"), minimum_spread=-1, ) self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + 1) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) first_bid_order = strategy.active_buys[0] first_ask_order = strategy.active_sells[0] self.assertEqual(Decimal("99"), first_bid_order.price) self.assertEqual(Decimal("101"), first_ask_order.price) self.assertEqual(Decimal("0.5"), first_bid_order.quantity) self.assertEqual(Decimal("1.5"), first_ask_order.quantity) last_bid_order = strategy.active_buys[-1] last_ask_order = strategy.active_sells[-1] last_bid_price = Decimal(100 * (1 - 0.01 - (0.01 * 4))).quantize(Decimal("0.001")) last_ask_price = Decimal(100 * (1 + 0.01 + (0.01 * 4))).quantize(Decimal("0.001")) self.assertAlmostEqual(last_bid_price, last_bid_order.price, 3) self.assertAlmostEqual(last_ask_price, last_ask_order.price, 3) self.assertEqual(Decimal("1.5"), last_bid_order.quantity) self.assertEqual(Decimal("4.5"), last_ask_order.quantity) self.simulate_maker_market_trade(True, 5.0, 101.1) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(4, len(strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 3) self.assertEqual(1, len(self.order_fill_logger.event_log)) maker_fill = self.order_fill_logger.event_log[0] self.assertEqual(TradeType.SELL, maker_fill.trade_type) self.assertAlmostEqual(101, maker_fill.price) self.assertAlmostEqual(Decimal("1.5"), Decimal(str(maker_fill.amount)), places=4) # The default filled_order_delay is 60, so gotta wait 60 + 2 here. self.clock.backtest_til(self.start_timestamp + 7 * self.clock_tick_size + 1) self.assertEqual(5, len(strategy.active_buys)) self.assertEqual(5, len(strategy.active_sells)) first_bid_order = strategy.active_buys[0] first_ask_order = strategy.active_sells[0] last_bid_order = strategy.active_buys[-1] last_ask_order = strategy.active_sells[-1] self.assertEqual(Decimal("99"), first_bid_order.price) self.assertEqual(Decimal("101"), first_ask_order.price) self.assertEqual(Decimal("0.651349"), first_bid_order.quantity) self.assertEqual(Decimal("1.34865"), first_ask_order.quantity) last_bid_price = Decimal(100 * (1 - 0.01 - (0.01 * 4))).quantize(Decimal("0.001")) last_ask_price = Decimal(100 * (1 + 0.01 + (0.01 * 4))).quantize(Decimal("0.001")) self.assertAlmostEqual(last_bid_price, last_bid_order.price, 3) self.assertAlmostEqual(last_ask_price, last_ask_order.price, 3) self.assertEqual(Decimal("1.95404"), last_bid_order.quantity) self.assertEqual(Decimal("4.04595"), last_ask_order.quantity)
class PureMMTakeIfCrossUnitTest(unittest.TestCase): start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") start_timestamp: float = start.timestamp() end_timestamp: float = end.timestamp() trading_pair = "HBOT-ETH" base_asset = trading_pair.split("-")[0] quote_asset = trading_pair.split("-")[1] def setUp(self): self.clock_tick_size = 1 self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) self.market: BacktestMarket = BacktestMarket() self.book_data: MockOrderBookLoader = MockOrderBookLoader( self.trading_pair, self.base_asset, self.quote_asset) self.mid_price = 100 self.bid_spread = 0.01 self.ask_spread = 0.01 self.order_refresh_time = 30 self.book_data.set_balanced_order_book(mid_price=self.mid_price, min_price=1, max_price=200, price_step_size=1, volume_step_size=10) self.market.add_data(self.book_data) self.market.set_balance("HBOT", 500) self.market.set_balance("ETH", 5000) self.market.set_quantization_param( QuantizationParams(self.trading_pair, 6, 6, 6, 6)) self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, self.base_asset, self.quote_asset) self.clock.add_iterator(self.market) self.order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.market.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) self.ext_market: BacktestMarket = BacktestMarket() self.ext_data: MockOrderBookLoader = MockOrderBookLoader( self.trading_pair, self.base_asset, self.quote_asset) self.ext_market_info: MarketTradingPairTuple = MarketTradingPairTuple( self.ext_market, self.trading_pair, self.base_asset, self.quote_asset) self.ext_data.set_balanced_order_book(mid_price=100, min_price=1, max_price=400, price_step_size=1, volume_step_size=100) self.ext_market.add_data(self.ext_data) self.order_book_asset_del = OrderBookAssetPriceDelegate( self.ext_market, self.trading_pair) self.one_level_strategy = PureMarketMakingStrategy() self.one_level_strategy.init_params( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=3.0, filled_order_delay=3.0, order_refresh_tolerance_pct=-1, minimum_spread=-1, asset_price_delegate=self.order_book_asset_del, take_if_crossed=True) def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): order_book = self.market.get_order_book(self.trading_pair) trade_event = OrderBookTradeEvent( self.trading_pair, self.clock.current_timestamp, TradeType.BUY if is_buy else TradeType.SELL, price, quantity) order_book.apply_trade(trade_event) def test_strategy_take_if_crossed_bid_order(self): simulate_order_book_widening( self.ext_market.get_order_book(self.trading_pair), 120.0, 130.0) self.strategy = self.one_level_strategy self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(0, len(self.order_fill_logger.event_log)) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) self.assertEqual(1, len(self.order_fill_logger.event_log)) self.assertEqual(0, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 7 * self.clock_tick_size) self.assertEqual(2, len(self.order_fill_logger.event_log)) self.assertEqual(0, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 10 * self.clock_tick_size) self.assertEqual(3, len(self.order_fill_logger.event_log)) self.assertEqual(0, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.order_fill_logger.clear() def test_strategy_take_if_crossed_ask_order(self): simulate_order_book_widening( self.ext_market.get_order_book(self.trading_pair), 80.0, 90.0) self.strategy = self.one_level_strategy self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(0, len(self.order_fill_logger.event_log)) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(1, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) self.assertEqual(1, len(self.order_fill_logger.event_log)) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(0, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) self.assertEqual(2, len(self.order_fill_logger.event_log)) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(0, len(self.strategy.active_sells)) self.clock.backtest_til(self.start_timestamp + 10 * self.clock_tick_size) self.assertEqual(3, len(self.order_fill_logger.event_log)) self.assertEqual(1, len(self.strategy.active_buys)) self.assertEqual(0, len(self.strategy.active_sells)) self.order_fill_logger.clear()
def setUp(self): self.clock_tick_size = 1 self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) self.market: BacktestMarket = BacktestMarket() self.book_data: MockOrderBookLoader = MockOrderBookLoader(self.trading_pair, self.base_asset, self.quote_asset) self.mid_price = 100 self.bid_spread = 0.01 self.ask_spread = 0.01 self.order_refresh_time = 30 self.book_data.set_balanced_order_book(mid_price=self.mid_price, min_price=1, max_price=200, price_step_size=1, volume_step_size=10) self.market.add_data(self.book_data) self.market.set_balance("HBOT", 500) self.market.set_balance("ETH", 5000) self.market.set_quantization_param( QuantizationParams( self.trading_pair, 6, 6, 6, 6 ) ) self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, self.base_asset, self.quote_asset) self.clock.add_iterator(self.market) self.order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.market.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) self.one_level_strategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5.0, filled_order_delay=5.0, order_refresh_tolerance_pct=-1, minimum_spread=-1, ) self.multi_levels_strategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5.0, filled_order_delay=5.0, order_refresh_tolerance_pct=-1, order_levels=3, order_level_spread=Decimal("0.01"), order_level_amount=Decimal("1"), minimum_spread=-1, ) self.order_override_strategy = PureMarketMakingStrategy( self.market_info, bid_spread=Decimal("0.01"), ask_spread=Decimal("0.01"), order_amount=Decimal("1"), order_refresh_time=5.0, filled_order_delay=5.0, order_refresh_tolerance_pct=-1, order_levels=3, order_level_spread=Decimal("0.01"), order_level_amount=Decimal("1"), minimum_spread=-1, order_override={"order_one": ["buy", 0.5, 0.7], "order_two": ["buy", 1.3, 1.1], "order_three": ["sell", 1.1, 2]}, ) self.ext_market: BacktestMarket = BacktestMarket() self.ext_data: MockOrderBookLoader = MockOrderBookLoader(self.trading_pair, self.base_asset, self.quote_asset) self.ext_market_info: MarketTradingPairTuple = MarketTradingPairTuple( self.ext_market, self.trading_pair, self.base_asset, self.quote_asset ) self.ext_data.set_balanced_order_book(mid_price=50, min_price=1, max_price=400, price_step_size=1, volume_step_size=10) self.ext_market.add_data(self.ext_data) self.order_book_asset_del = OrderBookAssetPriceDelegate(self.ext_market, self.trading_pair) trade_fill_sql = SQLConnectionManager( SQLConnectionType.TRADE_FILLS, db_path="" ) self.inventory_cost_price_del = InventoryCostPriceDelegate(trade_fill_sql, self.trading_pair)