コード例 #1
0
    def setUp(self):
        self.order_fill_logger: EventLogger = EventLogger()
        self.cancel_order_logger: EventLogger = EventLogger()
        self.clock: Clock = Clock(ClockMode.BACKTEST, 1, self.start_timestamp, self.end_timestamp)
        self.spot_connector: BacktestMarket = BacktestMarket()
        self.spot_obook: MockOrderBookLoader = MockOrderBookLoader(self.trading_pair, self.base_asset, self.quote_asset)
        self.spot_obook.set_balanced_order_book(mid_price=100,
                                                min_price=1,
                                                max_price=200,
                                                price_step_size=1,
                                                volume_step_size=10)
        self.spot_connector.add_data(self.spot_obook)
        self.spot_connector.set_balance("HBOT", 500)
        self.spot_connector.set_balance("ETH", 5000)
        self.spot_connector.set_quantization_param(
            QuantizationParams(
                self.trading_pair, 6, 6, 6, 6
            )
        )
        self.spot_market_info = MarketTradingPairTuple(self.spot_connector, self.trading_pair,
                                                       self.base_asset, self.quote_asset)

        self.perp_connector: MockPerpConnector = MockPerpConnector()
        self.perp_obook: MockOrderBookLoader = MockOrderBookLoader(self.trading_pair, self.base_asset,
                                                                   self.quote_asset)
        self.perp_obook.set_balanced_order_book(mid_price=110,
                                                min_price=1,
                                                max_price=200,
                                                price_step_size=1,
                                                volume_step_size=10)
        self.perp_connector.add_data(self.perp_obook)
        self.perp_connector.set_balance("HBOT", 500)
        self.perp_connector.set_balance("ETH", 5000)
        self.perp_connector.set_quantization_param(
            QuantizationParams(
                self.trading_pair, 6, 6, 6, 6
            )
        )
        self.perp_market_info = MarketTradingPairTuple(self.perp_connector, self.trading_pair,
                                                       self.base_asset, self.quote_asset)

        self.clock.add_iterator(self.spot_connector)
        self.clock.add_iterator(self.perp_connector)

        self.spot_connector.add_listener(MarketEvent.OrderFilled, self.order_fill_logger)
        self.spot_connector.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger)
        self.perp_connector.add_listener(MarketEvent.OrderFilled, self.order_fill_logger)
        self.perp_connector.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger)

        self.strategy = SpotPerpetualArbitrageStrategy(
            self.spot_market_info,
            self.perp_market_info,
            order_amount=Decimal("1"),
            derivative_leverage=5,
            min_divergence=Decimal("0.05"),
            min_convergence=Decimal("0.01")
        )
コード例 #2
0
    def setUp(self):
        self.log_records = []
        self.order_fill_logger: EventLogger = EventLogger()
        self.cancel_order_logger: EventLogger = EventLogger()
        self.clock: Clock = Clock(ClockMode.BACKTEST, 1, self.start_timestamp,
                                  self.end_timestamp)
        self.spot_connector: MockPaperExchange = MockPaperExchange(
            client_config_map=ClientConfigAdapter(ClientConfigMap()))
        self.spot_connector.set_balanced_order_book(trading_pair=trading_pair,
                                                    mid_price=100,
                                                    min_price=1,
                                                    max_price=200,
                                                    price_step_size=1,
                                                    volume_step_size=10)
        self.spot_connector.set_balance(base_asset, 5)
        self.spot_connector.set_balance(quote_asset, 500)
        self.spot_connector.set_quantization_param(
            QuantizationParams(trading_pair, 6, 6, 6, 6))
        self.spot_market_info = MarketTradingPairTuple(self.spot_connector,
                                                       trading_pair,
                                                       base_asset, quote_asset)

        self.perp_connector: MockPerpConnector = MockPerpConnector(
            client_config_map=ClientConfigAdapter(ClientConfigMap()))
        self.perp_connector.set_leverage(trading_pair, 5)
        self.perp_connector.set_balanced_order_book(trading_pair=trading_pair,
                                                    mid_price=110,
                                                    min_price=1,
                                                    max_price=200,
                                                    price_step_size=1,
                                                    volume_step_size=10)
        self.perp_connector.set_balance(base_asset, 5)
        self.perp_connector.set_balance(quote_asset, 500)
        self.perp_connector.set_quantization_param(
            QuantizationParams(trading_pair, 6, 6, 6, 6))
        self.perp_market_info = MarketTradingPairTuple(self.perp_connector,
                                                       trading_pair,
                                                       base_asset, quote_asset)

        self.clock.add_iterator(self.spot_connector)
        self.clock.add_iterator(self.perp_connector)

        self.spot_connector.add_listener(MarketEvent.OrderFilled,
                                         self.order_fill_logger)
        self.spot_connector.add_listener(MarketEvent.OrderCancelled,
                                         self.cancel_order_logger)
        self.perp_connector.add_listener(MarketEvent.OrderFilled,
                                         self.order_fill_logger)
        self.perp_connector.add_listener(MarketEvent.OrderCancelled,
                                         self.cancel_order_logger)

        self.strategy = SpotPerpetualArbitrageStrategy()
        self.strategy.init_params(
            spot_market_info=self.spot_market_info,
            perp_market_info=self.perp_market_info,
            order_amount=Decimal("1"),
            perp_leverage=5,
            min_opening_arbitrage_pct=Decimal("0.05"),
            min_closing_arbitrage_pct=Decimal("0.01"),
            next_arbitrage_opening_delay=10,
        )
        self.strategy.logger().setLevel(1)
        self.strategy.logger().addHandler(self)
        self._last_tick = 0
コード例 #3
0
class TestSpotPerpetualArbitrage(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()
    level = 0
    log_records = []

    def handle(self, record):
        self.log_records.append(record)

    def _is_logged(self, log_level: str, message: str) -> bool:
        return any(
            record.levelname == log_level and message in record.getMessage()
            for record in self.log_records)

    def setUp(self):
        self.log_records = []
        self.order_fill_logger: EventLogger = EventLogger()
        self.cancel_order_logger: EventLogger = EventLogger()
        self.clock: Clock = Clock(ClockMode.BACKTEST, 1, self.start_timestamp,
                                  self.end_timestamp)
        self.spot_connector: MockPaperExchange = MockPaperExchange(
            client_config_map=ClientConfigAdapter(ClientConfigMap()))
        self.spot_connector.set_balanced_order_book(trading_pair=trading_pair,
                                                    mid_price=100,
                                                    min_price=1,
                                                    max_price=200,
                                                    price_step_size=1,
                                                    volume_step_size=10)
        self.spot_connector.set_balance(base_asset, 5)
        self.spot_connector.set_balance(quote_asset, 500)
        self.spot_connector.set_quantization_param(
            QuantizationParams(trading_pair, 6, 6, 6, 6))
        self.spot_market_info = MarketTradingPairTuple(self.spot_connector,
                                                       trading_pair,
                                                       base_asset, quote_asset)

        self.perp_connector: MockPerpConnector = MockPerpConnector(
            client_config_map=ClientConfigAdapter(ClientConfigMap()))
        self.perp_connector.set_leverage(trading_pair, 5)
        self.perp_connector.set_balanced_order_book(trading_pair=trading_pair,
                                                    mid_price=110,
                                                    min_price=1,
                                                    max_price=200,
                                                    price_step_size=1,
                                                    volume_step_size=10)
        self.perp_connector.set_balance(base_asset, 5)
        self.perp_connector.set_balance(quote_asset, 500)
        self.perp_connector.set_quantization_param(
            QuantizationParams(trading_pair, 6, 6, 6, 6))
        self.perp_market_info = MarketTradingPairTuple(self.perp_connector,
                                                       trading_pair,
                                                       base_asset, quote_asset)

        self.clock.add_iterator(self.spot_connector)
        self.clock.add_iterator(self.perp_connector)

        self.spot_connector.add_listener(MarketEvent.OrderFilled,
                                         self.order_fill_logger)
        self.spot_connector.add_listener(MarketEvent.OrderCancelled,
                                         self.cancel_order_logger)
        self.perp_connector.add_listener(MarketEvent.OrderFilled,
                                         self.order_fill_logger)
        self.perp_connector.add_listener(MarketEvent.OrderCancelled,
                                         self.cancel_order_logger)

        self.strategy = SpotPerpetualArbitrageStrategy()
        self.strategy.init_params(
            spot_market_info=self.spot_market_info,
            perp_market_info=self.perp_market_info,
            order_amount=Decimal("1"),
            perp_leverage=5,
            min_opening_arbitrage_pct=Decimal("0.05"),
            min_closing_arbitrage_pct=Decimal("0.01"),
            next_arbitrage_opening_delay=10,
        )
        self.strategy.logger().setLevel(1)
        self.strategy.logger().addHandler(self)
        self._last_tick = 0

    def test_strategy_fails_to_initialize_position_mode(self):
        self.clock.add_iterator(self.strategy)
        self.clock.backtest_til(self.start_timestamp + 1)
        self.strategy._strategy_initialized = True
        self.strategy._position_mode_ready = True
        # Setting ONEWAY in initial settings failed
        self.perp_connector.set_position_mode(PositionMode.HEDGE)
        self.clock.backtest_til(self.start_timestamp + 2)
        self.assertTrue(self._is_logged("INFO", "Markets are ready."))
        self.assertTrue(self._is_logged("INFO", "Trading started."))
        self.assertTrue(
            self._is_logged(
                "INFO",
                "This strategy supports only Oneway position mode. Attempting to switch ..."
            ))
        # assert the strategy stopped here
        # self.assertIsNone(self.strategy.clock)

    def test_strategy_starts_with_multiple_active_position(self):
        # arbitrary adding multiple actuve positions here, which should never happen in real trading on oneway mode
        self.strategy._position_mode_ready = True
        self.perp_connector._account_positions[
            trading_pair + "SHORT"] = Position(
                trading_pair, PositionSide.SHORT, Decimal("0"), Decimal("95"),
                Decimal("-1"), self.perp_connector.get_leverage(trading_pair))
        self.perp_connector._account_positions[
            trading_pair + "LONG"] = Position(
                trading_pair, PositionSide.LONG, Decimal("0"), Decimal("95"),
                Decimal("1"), self.perp_connector.get_leverage(trading_pair))
        self.clock.add_iterator(self.strategy)
        self.clock.backtest_til(self.start_timestamp + 2)
        self.assertTrue(self._is_logged("INFO", "Markets are ready."))
        self.assertTrue(self._is_logged("INFO", "Trading started."))
        # self.assertIsNone(self.strategy.clock)

    def test_strategy_starts_with_existing_position(self):
        """
        Tests if the strategy can start
        """

        self.strategy._position_mode_ready = True
        self.clock.add_iterator(self.strategy)
        self.perp_connector._account_positions[trading_pair] = Position(
            trading_pair, PositionSide.SHORT, Decimal("0"), Decimal("95"),
            Decimal("-1"), self.perp_connector.get_leverage(trading_pair))
        self.clock.backtest_til(self.start_timestamp + 2)
        self.assertTrue(self._is_logged("INFO", "Markets are ready."))
        self.assertTrue(self._is_logged("INFO", "Trading started."))
        self.assertTrue(
            self._is_logged(
                "INFO", f"There is an existing {trading_pair} "
                f"{PositionSide.SHORT.name} position. The bot resumes "
                f"operation to close out the arbitrage position"))
        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01))
        self.clock.backtest_til(self.start_timestamp + 2)

    def test_strategy_starts_with_existing_position_unmatched_pos_amount(self):
        """
        Tests if the strategy start then stop when there is an existing position where position amount doesn't match
        strategy order amount
        """
        self.strategy._position_mode_ready = True
        self.clock.add_iterator(self.strategy)
        self.perp_connector._account_positions[trading_pair] = Position(
            trading_pair, PositionSide.SHORT, Decimal("0"), Decimal("95"),
            Decimal("-10"), self.perp_connector.get_leverage(trading_pair))
        self.clock.backtest_til(self.start_timestamp + 2)
        self.assertTrue(self._is_logged("INFO", "Markets are ready."))
        self.assertTrue(self._is_logged("INFO", "Trading started."))
        self.assertTrue(
            self._is_logged(
                "INFO", f"There is an existing {trading_pair} "
                f"{PositionSide.SHORT.name} position with unmatched position amount. "
                f"Please manually close out the position before starting this "
                f"strategy."))
        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01))
        self.clock.backtest_til(self.start_timestamp + 2)
        # assert the strategy stopped here
        self.assertIsNone(self.strategy.clock)

    def test_create_base_proposals(self):
        asyncio.get_event_loop().run_until_complete(
            self._test_create_base_proposals())

    async def _test_create_base_proposals(self):
        self.clock.add_iterator(self.strategy)
        props = await self.strategy.create_base_proposals()
        self.assertEqual(2, len(props))
        self.assertEqual(True, props[0].spot_side.is_buy)
        self.assertEqual(Decimal("100.5"), props[0].spot_side.order_price)
        self.assertEqual(False, props[0].perp_side.is_buy)
        self.assertEqual(Decimal("109.5"), props[0].perp_side.order_price)
        self.assertEqual(Decimal("1"), props[0].order_amount)

        self.assertEqual(False, props[1].spot_side.is_buy)
        self.assertEqual(Decimal("99.5"), props[1].spot_side.order_price)
        self.assertEqual(True, props[1].perp_side.is_buy)
        self.assertEqual(Decimal("110.5"), props[1].perp_side.order_price)
        self.assertEqual(Decimal("1"), props[1].order_amount)

    def test_apply_slippage_buffers(self):
        proposal = ArbProposal(
            ArbProposalSide(self.spot_market_info, True, Decimal("100")),
            ArbProposalSide(self.perp_market_info, False, Decimal("100")),
            Decimal("1"))
        self.strategy._spot_market_slippage_buffer = Decimal("0.01")
        self.strategy._perp_market_slippage_buffer = Decimal("0.02")
        self.strategy.apply_slippage_buffers(proposal)
        self.assertEqual(Decimal("101"), proposal.spot_side.order_price)
        self.assertEqual(Decimal("98"), proposal.perp_side.order_price)

    def test_check_budget_available(self):
        self.spot_connector.set_balance(base_asset, 0)
        self.spot_connector.set_balance(quote_asset, 0)
        self.perp_connector.set_balance(base_asset, 0)
        self.perp_connector.set_balance(quote_asset, 10)
        # Since spot has 0 HBOT and 0 USDT, not enough to do any trade
        self.assertFalse(self.strategy.check_budget_available())

        self.spot_connector.set_balance(base_asset, 10)
        self.spot_connector.set_balance(quote_asset, 10)
        self.perp_connector.set_balance(base_asset, 10)
        self.perp_connector.set_balance(quote_asset, 0)
        # Since perp has 0, not enough to do any trade
        self.assertFalse(self.strategy.check_budget_available())

        self.spot_connector.set_balance(base_asset, 10)
        self.spot_connector.set_balance(quote_asset, 10)
        self.perp_connector.set_balance(base_asset, 10)
        self.perp_connector.set_balance(quote_asset, 10)
        # All assets are available
        self.assertTrue(self.strategy.check_budget_available())

    def test_check_budget_constraint(self):
        proposal = ArbProposal(
            ArbProposalSide(self.spot_market_info, False, Decimal("100")),
            ArbProposalSide(self.perp_market_info, True, Decimal("100")),
            Decimal("1"))
        self.spot_connector.set_balance(base_asset, 0.5)
        self.spot_connector.set_balance(quote_asset, 0)
        self.perp_connector.set_balance(base_asset, 0)
        self.perp_connector.set_balance(quote_asset, 21)
        # Since spot has 0.5 HBOT, not enough to sell on 1 order amount
        self.assertFalse(self.strategy.check_budget_constraint(proposal))

        self.spot_connector.set_balance(base_asset, 1)
        self.assertTrue(self.strategy.check_budget_constraint(proposal))

        # on perpetual you need at least 100/5 to open a position
        self.perp_connector.set_balance(quote_asset, 10)
        self.assertFalse(self.strategy.check_budget_constraint(proposal))

        # There is no balance required to close a position
        self.perp_connector._account_positions[trading_pair] = Position(
            trading_pair, PositionSide.SHORT, Decimal("0"), Decimal("95"),
            Decimal("-1"), self.perp_connector.get_leverage(trading_pair))
        self.assertTrue(self.strategy.check_budget_constraint(proposal))

    def test_no_arbitrage_opportunity(self):
        self.perp_connector.set_balanced_order_book(trading_pair=trading_pair,
                                                    mid_price=100,
                                                    min_price=1,
                                                    max_price=200,
                                                    price_step_size=1,
                                                    volume_step_size=10)
        self.clock.add_iterator(self.strategy)
        self.clock.backtest_til(self.start_timestamp + 1)
        asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01))
        taker_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders
        self.assertTrue(len(taker_orders) == 0)

    def test_arbitrage_buy_spot_sell_perp(self):
        self.strategy._position_mode_ready = True
        self.clock.add_iterator(self.strategy)
        self.assertEqual(StrategyState.Closed, self.strategy.strategy_state)
        self.turn_clock(2)
        # self.clock.backtest_til(self.start_timestamp + 1)
        # asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01))
        self.assertTrue(
            self._is_logged("INFO",
                            "Arbitrage position opening opportunity found."))
        self.assertTrue(
            self._is_logged(
                "INFO",
                "Profitability (8.96%) is now above min_opening_arbitrage_pct."
            ))
        self.assertTrue(
            self._is_logged(
                "INFO",
                "Placing BUY order for 1 HBOT at mock_paper_exchange at 100.500 price"
            ))
        self.assertTrue(
            self._is_logged(
                "INFO",
                "Placing SELL order for 1 HBOT at mock_perp_connector at 109.500 price "
                "to OPEN position."))
        placed_orders = self.strategy.tracked_market_orders
        self.assertEqual(2, len(placed_orders))
        spot_order = [
            order for market, order in placed_orders
            if market == self.spot_connector
        ][0]
        self.assertTrue(spot_order.is_buy)
        self.assertEqual(Decimal("1"), Decimal(str(spot_order.amount)))
        perp_order = [
            order for market, order in placed_orders
            if market == self.perp_connector
        ][0]
        self.assertFalse(perp_order.is_buy)
        self.assertEqual(Decimal("1"), Decimal(str(perp_order.amount)))
        self.assertEqual(StrategyState.Opening, self.strategy.strategy_state)

        self.trigger_order_complete(True, self.spot_connector, Decimal("1"),
                                    Decimal("100.5"), spot_order.order_id)
        self.trigger_order_complete(False, self.perp_connector, Decimal("1"),
                                    Decimal("109.5"), perp_order.order_id)
        self.perp_connector._account_positions[trading_pair] = Position(
            trading_pair, PositionSide.SHORT, Decimal("0"), Decimal("109.5"),
            Decimal("-1"), self.perp_connector.get_leverage(trading_pair))
        self.turn_clock(1)
        status = asyncio.get_event_loop().run_until_complete(
            self.strategy.format_status())
        expected_status = ("""
  Markets:
               Exchange    Market  Sell Price  Buy Price  Mid Price
    mock_paper_exchange HBOT-USDT        99.5      100.5        100
    mock_perp_connector HBOT-USDT       109.5      110.5        110

  Positions:
       Symbol  Type Entry Price Amount  Leverage Unrealized PnL
    HBOT-USDT SHORT       109.5     -1         5              0

  Assets:
                  Exchange Asset  Total Balance  Available Balance
    0  mock_paper_exchange  HBOT              5                  5
    1  mock_paper_exchange  USDT            500                500
    2  mock_perp_connector  HBOT              5                  5
    3  mock_perp_connector  USDT            500                500

  Opportunity:
    buy at mock_paper_exchange, sell at mock_perp_connector: 8.96%
    sell at mock_paper_exchange, buy at mock_perp_connector: -9.95%""")

        self.assertEqual(expected_status, status)

        self.assertEqual(StrategyState.Opened, self.strategy.strategy_state)
        self.perp_connector.set_balanced_order_book(trading_pair=trading_pair,
                                                    mid_price=90,
                                                    min_price=1,
                                                    max_price=200,
                                                    price_step_size=1,
                                                    volume_step_size=10)
        self.turn_clock(1)
        placed_orders = self.strategy.tracked_market_orders
        self.assertEqual(4, len(placed_orders))
        spot_order = [
            o for m, o in placed_orders
            if m == self.spot_connector and o.order_id != spot_order.order_id
        ][0]
        self.assertFalse(spot_order.is_buy)
        self.assertEqual(Decimal("1"), Decimal(str(spot_order.amount)))
        perp_order = [
            o for m, o in placed_orders
            if m == self.perp_connector and o.order_id != perp_order.order_id
        ][0]
        self.assertTrue(perp_order.is_buy)
        self.assertEqual(Decimal("1"), Decimal(str(perp_order.amount)))
        self.assertEqual(StrategyState.Closing, self.strategy.strategy_state)

        self.trigger_order_complete(False, self.spot_connector, Decimal("1"),
                                    Decimal("99.5"), spot_order.order_id)
        self.trigger_order_complete(True, self.perp_connector, Decimal("1"),
                                    Decimal("90.5"), perp_order.order_id)
        self.perp_connector._account_positions.clear()
        self.turn_clock(1)
        # Due to the next_arbitrage_opening_delay, new arb position is not opened yet
        self.assertEqual(StrategyState.Closed, self.strategy.strategy_state)
        # Set balance on perpetual to 0 to test the strategy shouldn't submit orders
        self.spot_connector.set_balance(base_asset, 0)
        self.turn_clock(12)
        self.assertEqual(StrategyState.Closed, self.strategy.strategy_state)
        self.assertEqual(4, len(self.strategy.tracked_market_orders))

        self.spot_connector.set_balance(base_asset, 10)
        self.turn_clock(1)
        # After next_arbitrage_opening_delay, new arb orders are submitted
        self.assertEqual(StrategyState.Opening, self.strategy.strategy_state)
        self.assertEqual(6, len(self.strategy.tracked_market_orders))

    def test_arbitrage_sell_spot_buy_perp_opening(self):
        self.strategy._position_mode_ready = True
        self.perp_connector.set_balanced_order_book(trading_pair=trading_pair,
                                                    mid_price=90,
                                                    min_price=1,
                                                    max_price=200,
                                                    price_step_size=1,
                                                    volume_step_size=10)
        self.clock.add_iterator(self.strategy)
        self.assertEqual(StrategyState.Closed, self.strategy.strategy_state)
        self.turn_clock(2)
        # self.clock.backtest_til(self.start_timestamp + 1)
        # asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01))
        self.assertTrue(
            self._is_logged("INFO",
                            "Arbitrage position opening opportunity found."))
        self.assertTrue(
            self._is_logged(
                "INFO",
                "Profitability (9.94%) is now above min_opening_arbitrage_pct."
            ))
        self.assertTrue(
            self._is_logged(
                "INFO",
                "Placing SELL order for 1 HBOT at mock_paper_exchange at 99.5000 price"
            ))
        self.assertTrue(
            self._is_logged(
                "INFO",
                "Placing BUY order for 1 HBOT at mock_perp_connector at 90.5000 price to "
                "OPEN position."))
        placed_orders = self.strategy.tracked_market_orders
        self.assertEqual(2, len(placed_orders))
        spot_order = [
            order for market, order in placed_orders
            if market == self.spot_connector
        ][0]
        self.assertFalse(spot_order.is_buy)
        self.assertEqual(Decimal("1"), Decimal(str(spot_order.amount)))
        perp_order = [
            order for market, order in placed_orders
            if market == self.perp_connector
        ][0]
        self.assertTrue(perp_order.is_buy)
        self.assertEqual(Decimal("1"), Decimal(str(perp_order.amount)))
        self.assertEqual(StrategyState.Opening, self.strategy.strategy_state)

    def turn_clock(self, no_ticks: int):
        for i in range(self._last_tick, self._last_tick + no_ticks + 1):
            self.clock.backtest_til(self.start_timestamp + i)
            asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01))
        self._last_tick += no_ticks

    @staticmethod
    def trigger_order_complete(is_buy: bool, connector: ConnectorBase,
                               amount: Decimal, price: Decimal, order_id: str):
        # This function triggers order complete event for our mock connector, this is to simulate scenarios more
        # precisely taker orders are fully filled.
        event_tag = MarketEvent.BuyOrderCompleted if is_buy else MarketEvent.SellOrderCompleted
        event_class = BuyOrderCompletedEvent if is_buy else SellOrderCompletedEvent
        connector.trigger_event(
            event_tag,
            event_class(connector.current_timestamp, order_id, base_asset,
                        quote_asset, amount, amount * price, OrderType.LIMIT))

    @patch(
        "hummingbot.connector.perpetual_trading.PerpetualTrading.set_position_mode"
    )
    def test_position_mode_change_success(self, set_position_mode_mock):
        self.strategy._position_mode_ready = False

        self.assertFalse(self.strategy._position_mode_ready)

        self.clock.backtest_til(self.start_timestamp + 1)

        self.assertFalse(self.strategy._position_mode_ready)

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

        self.strategy.did_change_position_mode_succeed(
            PositionModeChangeEvent(
                timestamp=self.start_timestamp + 11,
                trading_pair=trading_pair,
                position_mode=PositionMode.ONEWAY,
            ))

        self.assertTrue(self.strategy._position_mode_ready)

    @patch(
        "hummingbot.connector.perpetual_trading.PerpetualTrading.set_position_mode"
    )
    def test_position_mode_change_failure(self, set_position_mode_mock):
        self.strategy._position_mode_ready = False

        self.assertFalse(self.strategy._position_mode_ready)

        self.clock.backtest_til(self.start_timestamp + 1)

        self.assertFalse(self.strategy._position_mode_ready)

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

        self.strategy.did_change_position_mode_fail(
            PositionModeChangeEvent(
                timestamp=self.start_timestamp + 11,
                trading_pair=trading_pair,
                position_mode=PositionMode.ONEWAY,
                message="Error message",
            ))

        self.assertFalse(self.strategy._position_mode_ready)