def test_inventory_skew(self, estimate_fee_mock):
        """
        When inventory_skew_enabled is true, the strategy will try to balance the amounts of base to match it
        """
        estimate_fee_mock.return_value = AddedToCostTradeFee(
            percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))]
        )

        # initiate with similar balances so the skew is obvious
        usdt_balance = 1000
        busd_balance = 1000
        eth_balance = 1000
        btc_balance = 1000

        trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BUSD", "BTC"]))
        market, market_infos = self.create_market(trading_pairs, 100, {"USDT": usdt_balance, "BUSD": busd_balance, "ETH": eth_balance, "BTC": btc_balance})

        skewed_base_strategy = LiquidityMiningStrategy()
        skewed_base_strategy.init_params(
            exchange=market,
            market_infos=market_infos,
            token="ETH",
            order_amount=Decimal(2),
            spread=Decimal(0.0005),
            inventory_skew_enabled=True,
            target_base_pct=Decimal(0.1),  # less base, more quote
            order_refresh_time=5,
            order_refresh_tolerance_pct=Decimal(0.1),  # tolerance of 10 % change
        )

        unskewed_strategy = LiquidityMiningStrategy()
        unskewed_strategy.init_params(
            exchange=market,
            market_infos=market_infos,
            token="ETH",
            order_amount=Decimal(2),
            spread=Decimal(0.0005),
            inventory_skew_enabled=False,
            order_refresh_time=5,
            target_base_pct=Decimal(0.1),  # this does nothing when inventory_skew_enabled is False
            order_refresh_tolerance_pct=Decimal(0.1),  # tolerance of 10 % change
        )

        self.clock.add_iterator(skewed_base_strategy)
        self.clock.backtest_til(self.start_timestamp + 10)
        self.clock.add_iterator(unskewed_strategy)
        self.clock.backtest_til(self.start_timestamp + 20)

        # iterate through pairs in skewed and unskewed strategy
        for unskewed_order in unskewed_strategy.active_orders:
            for skewed_base_order in skewed_base_strategy.active_orders:
                # if the trading_pair and trade type are the same, compare them
                if skewed_base_order.trading_pair == unskewed_order.trading_pair and \
                        skewed_base_order.is_buy == unskewed_order.is_buy:
                    if skewed_base_order.is_buy:
                        # the skewed strategy tries to buy more quote thant the unskewed one
                        self.assertGreater(skewed_base_order.price, unskewed_order.price)
                    else:
                        # trying to keep less base
                        self.assertLessEqual(skewed_base_order.price, unskewed_order.price)
    def setUp(self) -> None:
        self.clock_tick_size = 1
        self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp)

        self.mid_price = 100
        self.bid_spread = 0.01
        self.ask_spread = 0.01
        self.order_refresh_time = 1

        trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BTC"]))
        market, market_infos = self.create_market(trading_pairs, self.mid_price, {"USDT": 5000, "ETH": 500, "BTC": 100})
        self.market = market
        self.market_infos = market_infos

        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.default_strategy = LiquidityMiningStrategy()
        self.default_strategy.init_params(
            exchange=self.market,
            market_infos=self.market_infos,
            token="ETH",
            order_amount=Decimal(2),
            spread=Decimal(0.0005),
            inventory_skew_enabled=False,
            target_base_pct=Decimal(0.5),
            order_refresh_time=5,
            order_refresh_tolerance_pct=Decimal(0.1),  # tolerance of 10 % change
            max_order_age=3,
        )
Ejemplo n.º 3
0
    def test_budget_allocation(self, estimate_fee_mock):
        """
        Liquidity mining strategy budget allocation is different from pmm, it depends on the token base and it splits
        its budget between the quote tokens.
        """
        estimate_fee_mock.return_value = AddedToCostTradeFee(
            percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))])

        # initiate
        usdt_balance = 1000
        busd_balance = 900
        eth_balance = 100
        btc_balance = 10

        trading_pairs = list(
            map(lambda quote_asset: "ETH-" + quote_asset,
                ["USDT", "BUSD", "BTC"]))
        market, market_infos = self.create_market(
            trading_pairs, 100, {
                "USDT": usdt_balance,
                "BUSD": busd_balance,
                "ETH": eth_balance,
                "BTC": btc_balance
            })

        strategy = LiquidityMiningStrategy()
        strategy.init_params(
            exchange=market,
            market_infos=market_infos,
            token="ETH",
            order_amount=Decimal(2),
            spread=Decimal(0.0005),
            inventory_skew_enabled=False,
            target_base_pct=Decimal(0.5),
            order_refresh_time=5,
            order_refresh_tolerance_pct=Decimal(
                0.1),  # tolerance of 10 % change
        )

        self.clock.add_iterator(strategy)
        self.clock.backtest_til(self.start_timestamp + 10)

        # there should be a buy and sell budget for each pair
        self.assertEqual(len(strategy.sell_budgets), 3)
        self.assertEqual(len(strategy.buy_budgets), 3)

        # the buy budgets use all of the available balance for the quote tokens
        self.assertEqual(strategy.buy_budgets["ETH-USDT"], usdt_balance)
        self.assertEqual(strategy.buy_budgets["ETH-BTC"], btc_balance)
        self.assertEqual(strategy.buy_budgets["ETH-BUSD"], busd_balance)

        # the sell budget tries to evenly split the base token between the quote tokens
        self.assertLess(strategy.sell_budgets["ETH-USDT"], eth_balance * 0.4)
        self.assertLess(strategy.sell_budgets["ETH-BTC"], eth_balance * 0.4)
        self.assertLess(strategy.sell_budgets["ETH-BUSD"], eth_balance * 0.4)
Ejemplo n.º 4
0
    def test_volatility(self, estimate_fee_mock, get_mid_price_mock):
        """
        Assert that volatility information is updated after the expected number of intervals
        """
        estimate_fee_mock.return_value = AddedToCostTradeFee(
            percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))])

        # initiate with similar balances so the skew is obvious
        usdt_balance = 1000
        eth_balance = 1000

        trading_pairs = list(
            map(lambda quote_asset: "ETH-" + quote_asset, ["USDT"]))
        market, market_infos = self.create_market(trading_pairs, 100, {
            "USDT": usdt_balance,
            "ETH": eth_balance
        })

        strategy = LiquidityMiningStrategy()
        strategy.init_params(
            exchange=market,
            market_infos=market_infos,
            token="ETH",
            order_amount=Decimal(2),
            spread=Decimal(0.0005),
            inventory_skew_enabled=False,
            target_base_pct=Decimal(0.5),  # less base, more quote
            order_refresh_time=1,
            order_refresh_tolerance_pct=Decimal(
                0.1),  # tolerance of 10 % change
            # volatility_interval=2,
            # avg_volatility_period=2,
            # volatility_to_spread_multiplier=2,
        )

        get_mid_price_mock.return_value = Decimal(100.0)
        self.clock.add_iterator(strategy)
        self.clock.backtest_til(self.start_timestamp + 1)

        # update prices to create volatility after 2 intervals
        get_mid_price_mock.return_value = Decimal(105.0)
        self.clock.backtest_til(self.start_timestamp + 2)

        get_mid_price_mock.return_value = Decimal(110)
        self.clock.backtest_til(self.start_timestamp + 3)

        # assert that volatility is none zero
        self.assertAlmostEqual(float(
            strategy.market_status_df().loc[0, 'Volatility'].strip('%')),
                               10.00,
                               delta=0.1)
Ejemplo n.º 5
0
    def test_strategy_sends_in_app_notifications(
            self, cli_class_mock, main_application_function_mock):
        messages = []
        cli_logs = []

        cli_instance = cli_class_mock.return_value
        cli_instance.log.side_effect = lambda message: cli_logs.append(message)

        notifier_mock = unittest.mock.MagicMock()
        notifier_mock.add_msg_to_queue.side_effect = lambda message: messages.append(
            message)

        hummingbot_application = HummingbotApplication()
        hummingbot_application.notifiers.append(notifier_mock)
        main_application_function_mock.return_value = hummingbot_application

        strategy = self.default_strategy = LiquidityMiningStrategy()
        self.default_strategy.init_params(
            exchange=self.market,
            market_infos=self.market_infos,
            token="ETH",
            order_amount=Decimal(2),
            spread=Decimal(0.0005),
            inventory_skew_enabled=False,
            target_base_pct=Decimal(0.5),
            order_refresh_time=5,
            order_refresh_tolerance_pct=Decimal(
                0.1),  # tolerance of 10 % change
            max_order_age=3,
            hb_app_notification=True)

        timestamp = self.start_timestamp + 10
        self.clock.add_iterator(strategy)
        self.clock.backtest_til(timestamp)

        self.default_strategy.notify_hb_app("Test message")
        self.default_strategy.notify_hb_app_with_timestamp("Test message 2")

        self.assertIn("Test message", cli_logs)
        self.assertIn("Test message", messages)

        self.assertIn(
            f"({pd.Timestamp.fromtimestamp(timestamp)}) Test message 2",
            cli_logs)
        self.assertIn(
            f"({pd.Timestamp.fromtimestamp(timestamp)}) Test message 2",
            messages)