async def _test_strategy_ping_pong_on_bid_fill(self): try: script_file = realpath(join(__file__, "../../scripts/ping_pong_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.one_level_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.one_level_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) await self.turn_clock(2) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) old_ask = strategy.active_sells[0] await self.turn_clock(8) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(1, len(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 != strategy.active_sells[0].client_order_id) self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) await self.turn_clock(10) await self.turn_clock(15) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock)
async def _test_price_band_price_floor_breach_async(self): try: script_file = realpath( join(__file__, "../../../../scripts/price_band_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 85, self.mid_price) await self.turn_clock(2) await self.turn_clock(7) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock)
async def _test_dynamic_price_band_price_async(self): try: script_file = realpath( join(__file__, "../../scripts/dynamic_price_band_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(4) # self.book_data.order_book.apply_diffs([OrderBookRow(99.97, 30, 2)], [OrderBookRow(100.3, 30, 2)], 2) simulate_order_book_widening(self.book_data.order_book, 85, self.mid_price) mid_price = self.market.get_mid_price(self.trading_pair) await self.turn_clock(10) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) self.assertLess(mid_price, Decimal(100 * 0.97)) # mid_price is now below 3% from the original (at 92.5), but band script won't kick in until at least 50s self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(55) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 75, self.mid_price) self.book_data.order_book.apply_diffs([], [OrderBookRow(76, 30, 2)], 2) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) await self.turn_clock(80) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells)) # after another 40 ticks, the mid price avg is now at 75.25, both buys and sells back on the market await self.turn_clock(110) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 76, 90) self.book_data.order_book.apply_diffs([OrderBookRow(85, 30, 2)], [OrderBookRow(86, 30, 2)], 3) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) # Market now move up over 10% await self.turn_clock(120) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) # As no further movement in market prices, avg starts to catch up to the mid price await self.turn_clock(160) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock)
async def _test_update_parameters(self): try: script_file = realpath( join(__file__, "../../scripts/update_parameters_test_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(6) strategy.buy_levels = 1 strategy.sell_levels = 2 strategy.order_levels = 3 strategy.bid_spread = Decimal("0.1") strategy.ask_spread = Decimal("0.2") strategy.hanging_orders_cancel_pct = Decimal("0.3") strategy.hanging_orders_enabled = True strategy.filled_order_delay = 50.0 strategy.order_refresh_tolerance_pct = Decimal("0.01") strategy.order_refresh_time = 10.0 strategy.order_level_amount = Decimal("4") strategy.order_level_spread = Decimal("0.05") strategy.order_amount = Decimal("20") finally: self._script_iterator.stop(self.clock)
async def start_market_making( self, # type: HummingbotApplication strategy_name: str, restore: Optional[bool] = False): start_strategy: Callable = get_strategy_starter_file(strategy_name) if strategy_name in settings.STRATEGIES: start_strategy(self) else: raise NotImplementedError try: config_path: str = self.strategy_file_name self.start_time = time.time() * 1e3 # Time in milliseconds self.clock = Clock(ClockMode.REALTIME) if self.wallet is not None: self.clock.add_iterator(self.wallet) for market in self.markets.values(): if market is not None: self.clock.add_iterator(market) self.markets_recorder.restore_market_states( config_path, market) if len(market.limit_orders) > 0: if restore is False: self._notify( f"Cancelling dangling limit orders on {market.name}..." ) await market.cancel_all(5.0) else: self._notify( f"Restored {len(market.limit_orders)} limit orders on {market.name}..." ) if self.strategy: self.clock.add_iterator(self.strategy) if global_config_map["script_enabled"].value: script_file = global_config_map["script_file_path"].value folder = dirname(script_file) if folder == "": script_file = join(settings.SCRIPTS_PATH, script_file) if self.strategy_name != "pure_market_making": self._notify( "Error: script feature is only available for pure_market_making strategy (for now)." ) else: self._script_iterator = ScriptIterator( script_file, list(self.markets.values()), self.strategy, 0.1) self.clock.add_iterator(self._script_iterator) self._notify(f"Script ({script_file}) started.") # TODO: HAHA this is where everything starts self.strategy_task: asyncio.Task = safe_ensure_future( self._run_clock(), loop=self.ev_loop) self._notify(f"\n'{strategy_name}' strategy started.\n" f"Run `status` command to query the progress.") self.logger().info("start command initiated.") if self._trading_required: self.kill_switch = KillSwitch(self) await self.wait_till_ready(self.kill_switch.start) except Exception as e: self.logger().error(str(e), exc_info=True)
async def _test_spreads_adjusted_on_volatility_async(self): try: script_file = realpath( join( __file__, "../../../../scripts/spreads_adjusted_on_volatility_script.py" )) self._script_iterator = ScriptIterator(script_file, [self.market], self.one_level_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.one_level_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertEqual(Decimal("0.01"), strategy.bid_spread) self.assertEqual(Decimal("0.01"), strategy.ask_spread) await self.turn_clock(155) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 100, 105) self.book_data.order_book.apply_diffs([OrderBookRow(100, 30, 2)], [], 2) await self.turn_clock(160) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) # The median volatility over the long period is at 0 # The average volatility over the short period is now at 0.00916 # So the adjustment is 0.0075 (rounded by 0.0025 increment) # await self.turn_clock(161) self.assertEqual(Decimal("0.0175"), strategy.bid_spread) self.assertEqual(Decimal("0.0175"), strategy.ask_spread) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) await self.turn_clock(185) # No more further price movement, which means volatility is now back to 0. # Spreads are adjusted back to the originals self.assertEqual(Decimal("0.01"), strategy.bid_spread) self.assertEqual(Decimal("0.01"), strategy.ask_spread) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock)
class ScriptIteratorUnitTest(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._last_clock_tick = 0 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.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._ev_loop = asyncio.get_event_loop() async def turn_clock(self, seconds_from_start: float, delay_between_ticks: float = 0.05): """ turns the clock back test one second at a time, with a delay between ticks. this is st. the messages in the queues are relayed in a proper sequence. """ for i in range(self._last_clock_tick, seconds_from_start): self.clock.backtest_til(self.start_timestamp + self._last_clock_tick + 1) self._last_clock_tick += 1 await asyncio.sleep(delay_between_ticks) def test_update_parameters(self): self._ev_loop.run_until_complete(self._test_update_parameters()) async def _test_update_parameters(self): try: script_file = realpath(join(__file__, "../../scripts/update_parameters_test_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(6) strategy.buy_levels = 1 strategy.sell_levels = 2 strategy.order_levels = 3 strategy.bid_spread = Decimal("0.1") strategy.ask_spread = Decimal("0.2") strategy.hanging_orders_cancel_pct = Decimal("0.3") strategy.hanging_orders_enabled = True strategy.filled_order_delay = 50.0 strategy.order_refresh_tolerance_pct = Decimal("0.01") strategy.order_refresh_time = 10.0 strategy.order_level_amount = Decimal("4") strategy.order_level_spread = Decimal("0.05") strategy.order_amount = Decimal("20") strategy.inventory_skew_enabled = True strategy.inventory_range_multiplier = 2 strategy.inventory_target_base_pct = 0.6 strategy.order_override = {"order_1": ["buy", 0.5, 100], "order_2": ["sell", 0.55, 102]} finally: self._script_iterator.stop(self.clock) def test_price_band_price_ceiling_breach(self): self._ev_loop.run_until_complete(self._test_price_band_price_ceiling_breach_async()) async def _test_price_band_price_ceiling_breach_async(self): try: script_file = realpath(join(__file__, "../../scripts/price_band_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, self.mid_price, 115, ) await self.turn_clock(2) await self.turn_clock(7) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock) def test_price_band_price_floor_breach_async(self): self._ev_loop.run_until_complete(self._test_price_band_price_floor_breach_async()) async def _test_price_band_price_floor_breach_async(self): try: script_file = realpath(join(__file__, "../../scripts/price_band_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 85, self.mid_price) await self.turn_clock(2) await self.turn_clock(7) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock) def test_strategy_ping_pong_on_ask_fill(self): self._ev_loop.run_until_complete(self._test_strategy_ping_pong_on_ask_fill()) async def _test_strategy_ping_pong_on_ask_fill(self): try: script_file = realpath(join(__file__, "../../scripts/ping_pong_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.one_level_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.one_level_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) await self.turn_clock(2) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells)) old_bid = strategy.active_buys[0] await self.turn_clock(8) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(0, len(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 != strategy.active_buys[0].client_order_id) self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) await self.turn_clock(10) await self.turn_clock(15) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock) def test_strategy_ping_pong_on_bid_fill(self): self._ev_loop.run_until_complete(self._test_strategy_ping_pong_on_bid_fill()) async def _test_strategy_ping_pong_on_bid_fill(self): try: script_file = realpath(join(__file__, "../../scripts/ping_pong_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.one_level_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.one_level_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) await self.turn_clock(2) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) old_ask = strategy.active_sells[0] await self.turn_clock(8) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(1, len(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 != strategy.active_sells[0].client_order_id) self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) await self.turn_clock(10) await self.turn_clock(15) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock) def test_dynamic_price_band_price(self): self._ev_loop.run_until_complete(self._test_dynamic_price_band_price_async()) async def _test_dynamic_price_band_price_async(self): try: script_file = realpath(join(__file__, "../../scripts/dynamic_price_band_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(4) # self.book_data.order_book.apply_diffs([OrderBookRow(99.97, 30, 2)], [OrderBookRow(100.3, 30, 2)], 2) simulate_order_book_widening(self.book_data.order_book, 85, self.mid_price) mid_price = self.market.get_mid_price(self.trading_pair) await self.turn_clock(10) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) self.assertLess(mid_price, Decimal(100 * 0.97)) # mid_price is now below 3% from the original (at 92.5), but band script won't kick in until at least 50s self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(55) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 75, self.mid_price) self.book_data.order_book.apply_diffs([], [OrderBookRow(76, 30, 2)], 2) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) await self.turn_clock(80) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells)) # after another 40 ticks, the mid price avg is now at 75.25, both buys and sells back on the market await self.turn_clock(110) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 76, 90) self.book_data.order_book.apply_diffs([OrderBookRow(85, 30, 2)], [OrderBookRow(86, 30, 2)], 3) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) # Market now move up over 10% await self.turn_clock(120) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) # As no further movement in market prices, avg starts to catch up to the mid price await self.turn_clock(160) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock) def test_spreads_adjusted_on_volatility(self): self._ev_loop.run_until_complete(self._test_spreads_adjusted_on_volatility_async()) async def _test_spreads_adjusted_on_volatility_async(self): try: script_file = realpath(join(__file__, "../../scripts/spreads_adjusted_on_volatility_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.one_level_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.one_level_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertEqual(Decimal("0.01"), strategy.bid_spread) self.assertEqual(Decimal("0.01"), strategy.ask_spread) await self.turn_clock(155) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 100, 105) self.book_data.order_book.apply_diffs([OrderBookRow(100, 30, 2)], [], 2) await self.turn_clock(160) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) # The median volatility over the long period is at 0 # The average volatility over the short period is now at 0.00916 # So the adjustment is 0.0075 (rounded by 0.0025 increment) # await self.turn_clock(161) self.assertEqual(Decimal("0.0175"), strategy.bid_spread) self.assertEqual(Decimal("0.0175"), strategy.ask_spread) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) await self.turn_clock(185) # No more further price movement, which means volatility is now back to 0. # Spreads are adjusted back to the originals self.assertEqual(Decimal("0.01"), strategy.bid_spread) self.assertEqual(Decimal("0.01"), strategy.ask_spread) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock)