def test_create_profit_taking_proposal_logs_when_one_way_mode_and_multiple_positions(self):
        positions = [
            Position(
                trading_pair=(self.trading_pair),
                position_side=PositionSide.LONG,
                unrealized_pnl=Decimal(1000),
                entry_price=Decimal(50000),
                amount=Decimal(1),
                leverage=Decimal(10)
            ),
            Position(
                trading_pair=(self.trading_pair),
                position_side=PositionSide.SHORT,
                unrealized_pnl=Decimal(1000),
                entry_price=Decimal(50000),
                amount=Decimal(1),
                leverage=Decimal(10)
            )]
        self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions)

        self.assertTrue(
            self._is_logged(
                "ERROR",
                "More than one open position in ONEWAY position mode. "
                "Kindly ensure you do not interact with the exchange through other platforms and"
                " restart this strategy."))
 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."))
    def test_status_text_with_one_open_position_and_no_orders_alive(self):
        position = Position(trading_pair=(self.trading_pair),
                            position_side=PositionSide.LONG,
                            unrealized_pnl=Decimal(1000),
                            entry_price=self.market.get_price(
                                self.trading_pair, True),
                            amount=Decimal(1),
                            leverage=Decimal(10))
        self.market.account_positions[self.trading_pair] = position

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

        expected_status = (
            "\n  Markets:"
            "\n             Exchange         Market  Best Bid  Best Ask  Ref Price (MidPrice)"
            "\n    MockPerpConnector COINALPHA-HBOT      99.5     100.5                   100"
            "\n\n  Assets:"
            "\n                       HBOT"
            "\n    Total Balance     50000"
            "\n    Available Balance 50000"
            "\n\n  No active maker orders."
            "\n\n  Positions:"
            "\n            Symbol Type Entry Price Amount Leverage Unrealized PnL"
            "\n    COINALPHA-HBOT LONG      100.50      1       10           0.00"
        )
        status = self.strategy.format_status()

        self.assertEqual(expected_status, status)
    def test_create_profit_taking_proposal_for_short_position_cancel_old_exit_orders(
            self):
        order_id = self.strategy.buy_with_specific_market(
            market_trading_pair_tuple=self.market_info,
            amount=Decimal(1),
            order_type=OrderType.LIMIT,
            price=Decimal(self.initial_mid_price))
        self.strategy._exit_orders[order_id] = 1000

        position = Position(trading_pair=(self.trading_pair),
                            position_side=PositionSide.LONG,
                            unrealized_pnl=Decimal(1000),
                            entry_price=self.initial_mid_price + Decimal(20),
                            amount=Decimal(-1),
                            leverage=Decimal(10))
        positions = [position]

        self.market.set_balanced_order_book(trading_pair=self.trading_pair,
                                            mid_price=self.initial_mid_price +
                                            10,
                                            min_price=1,
                                            max_price=200,
                                            price_step_size=1,
                                            volume_step_size=10)

        self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions)

        self.assertEqual(order_id,
                         self.cancel_order_logger.event_log[0].order_id)
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"Initiated cancellation of previous take profit order {order_id} "
                f"in favour of new take profit order."))
        self.assertEqual(0, len(self.strategy.active_orders))
    def test_create_profit_taking_proposal_for_short_position(self):
        position = Position(trading_pair=(self.trading_pair),
                            position_side=PositionSide.LONG,
                            unrealized_pnl=Decimal(1000),
                            entry_price=self.initial_mid_price + Decimal(20),
                            amount=Decimal(-1),
                            leverage=Decimal(10))
        positions = [position]

        self.market.set_balanced_order_book(trading_pair=self.trading_pair,
                                            mid_price=self.initial_mid_price -
                                            10,
                                            min_price=1,
                                            max_price=200,
                                            price_step_size=1,
                                            volume_step_size=10)

        close_proposal = self.strategy.profit_taking_proposal(
            PositionMode.ONEWAY, positions)

        self.assertEqual(0, len(close_proposal.sells))
        self.assertEqual(
            position.entry_price *
            (Decimal(1) - self.short_profit_taking_spread),
            close_proposal.buys[0].price)
        self.assertEqual(Decimal("1"), close_proposal.buys[0].size)
    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_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.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 + 1)
     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_profit_taking_proposal_for_one_way_cancels_other_possible_exit_orders(
            self):
        order_id = self.strategy.buy_with_specific_market(
            market_trading_pair_tuple=self.market_info,
            amount=Decimal(1),
            order_type=OrderType.LIMIT,
            price=Decimal(50000))
        positions = [
            Position(trading_pair=(self.trading_pair),
                     position_side=PositionSide.LONG,
                     unrealized_pnl=Decimal(1000),
                     entry_price=Decimal(50000),
                     amount=Decimal(-1),
                     leverage=Decimal(10))
        ]

        self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions)

        self.assertEqual(0, len(self.market.limit_orders))
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"Initiated cancellation of buy order {order_id} in favour of take profit order."
            ))

        order_id = self.strategy.sell_with_specific_market(
            market_trading_pair_tuple=self.market_info,
            amount=Decimal(1),
            order_type=OrderType.LIMIT,
            price=Decimal(50000))
        positions = [
            Position(trading_pair=(self.trading_pair),
                     position_side=PositionSide.LONG,
                     unrealized_pnl=Decimal(1000),
                     entry_price=Decimal(50000),
                     amount=Decimal(1),
                     leverage=Decimal(10))
        ]

        self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions)

        self.assertEqual(0, len(self.market.limit_orders))
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"Initiated cancellation of sell order {order_id} in favour of take profit order."
            ))
示例#9
0
 def test_account_positions(self):
     """
     Test getting account positions by manually adding a position to the class member
     """
     pt: PerpetualTrading = PerpetualTrading()
     aPos: Position = Position("market1", PositionSide.LONG, Decimal("0"), Decimal("100"), Decimal("1"), Decimal("5"))
     pt._account_positions["market1"] = aPos
     self.assertEqual(len(pt.account_positions), 1)
     self.assertEqual(pt.account_positions["market1"], aPos)
     self.assertEqual(pt.get_position("market1"), aPos)
     self.assertEqual(pt.get_position("market2"), None)
    async def _update_positions(self):
        position_tasks = []
        for pair in self._trading_pairs:
            position_tasks.append(
                self._api_request(
                    "post", "perpfi/position",
                    {"pair": convert_to_exchange_trading_pair(pair)}))
        positions = await safe_gather(*position_tasks, return_exceptions=True)
        for trading_pair, position in zip(self._trading_pairs, positions):
            position = position.get("position", {})
            amount = self.quantize_order_amount(trading_pair,
                                                Decimal(position.get("size")))
            if amount != Decimal("0"):
                position_side = PositionSide.LONG if amount > 0 else PositionSide.SHORT
                unrealized_pnl = self.quantize_order_amount(
                    trading_pair, Decimal(position.get("pnl")))
                entry_price = self.quantize_order_price(
                    trading_pair, Decimal(position.get("entryPrice")))
                leverage = self._leverage[trading_pair]
                self._account_positions[self.position_key(
                    trading_pair)] = Position(trading_pair=trading_pair,
                                              position_side=position_side,
                                              unrealized_pnl=unrealized_pnl,
                                              entry_price=entry_price,
                                              amount=amount,
                                              leverage=leverage)
            else:
                if self.position_key(trading_pair) in self._account_positions:
                    del self._account_positions[self.position_key(
                        trading_pair)]

                payment = Decimal(str(position.get("fundingPayment")))
                oldPayment = self._fundingPayment.get(trading_pair, 0)
                if payment != oldPayment:
                    self._fundingPayment[trading_pair] = oldPayment
                    action = "paid" if payment < 0 else "received"
                    if payment != Decimal("0"):
                        self.logger().info(
                            f"Funding payment of {payment} {action} on {trading_pair} market."
                        )
                        self.trigger_event(
                            MarketEvent.FundingPaymentCompleted,
                            FundingPaymentCompletedEvent(
                                timestamp=time.time(),
                                market=self.name,
                                funding_rate=self._funding_info[trading_pair].
                                rate,
                                trading_pair=trading_pair,
                                amount=payment))
    def test_stop_loss_order_recreated_after_wait_time_for_short_position(
            self):
        position = Position(trading_pair=(self.trading_pair),
                            position_side=PositionSide.LONG,
                            unrealized_pnl=Decimal(1000),
                            entry_price=self.initial_mid_price - Decimal(30),
                            amount=Decimal(-1),
                            leverage=Decimal(10))
        self.market.account_positions[self.trading_pair] = position

        initial_stop_loss_price = self.initial_mid_price + Decimal("0.1")
        initial_stop_loss_order_id = self.strategy.buy_with_specific_market(
            market_trading_pair_tuple=self.market_info,
            amount=Decimal(1),
            order_type=OrderType.LIMIT,
            price=initial_stop_loss_price)

        # Simulate first stop loss was created at timestamp 1000
        self.strategy._exit_orders[
            initial_stop_loss_order_id] = self.start_timestamp

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

        self.assertEqual(1, len(self.strategy.active_orders))
        self.assertEqual(initial_stop_loss_order_id,
                         self.strategy.active_orders[0].client_order_id)

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

        self.assertEqual(1, len(self.strategy.active_orders))
        self.assertEqual(initial_stop_loss_order_id,
                         self.strategy.active_orders[0].client_order_id)

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

        self.assertEqual(initial_stop_loss_order_id,
                         self.cancel_order_logger.event_log[0].order_id)
        self.assertEqual(1, len(self.strategy.active_orders))
        new_stop_loss_order = self.strategy.active_orders[0]
        self.assertNotEqual(initial_stop_loss_order_id,
                            new_stop_loss_order.client_order_id)
        self.assertTrue(new_stop_loss_order.is_buy)
        self.assertEqual(abs(position.amount), new_stop_loss_order.quantity)
        self.assertEqual(
            initial_stop_loss_price *
            (Decimal(1) + self.stop_loss_slippage_buffer),
            new_stop_loss_order.price)
    def test_create_stop_loss_proposal_for_short_position(self):
        position = Position(
            trading_pair=(self.trading_pair),
            position_side=PositionSide.LONG,
            unrealized_pnl=Decimal(1000),
            entry_price=self.initial_mid_price - Decimal(30),
            amount=Decimal(-1),
            leverage=Decimal(10))
        positions = [position]
        proposal = self.strategy.stop_loss_proposal(PositionMode.ONEWAY, positions)

        self.assertEqual(0, len(self.market.limit_orders))
        self.assertEqual(0, len(proposal.sells))
        self.assertEqual(position.entry_price *
                         (Decimal(1) + self.stop_loss_spread) *
                         (Decimal(1) + self.stop_loss_slippage_buffer), proposal.buys[0].price)
        self.assertEqual(abs(position.amount), proposal.buys[0].size)
    def test_strategy_starts_with_existing_position(self):
        """
        Tests if the strategy can start
        """

        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 + 1)
        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)
示例#14
0
 async def _update_positions(self):
     # local_position_names = set(self._account_positions.keys())
     # remote_position_names = set()
     positions = await self.request(path="/fapi/v2/positionRisk", add_timestamp=True, is_signed=True)
     for position in positions:
         trading_pair = position.get("symbol")
         position_side = PositionSide[position.get("positionSide")]
         unrealized_pnl = Decimal(position.get("unRealizedProfit"))
         entry_price = Decimal(position.get("entryPrice"))
         amount = Decimal(position.get("positionAmt"))
         leverage = Decimal(position.get("leverage"))
         if amount != 0:
             self._account_positions[trading_pair + position_side.name] = Position(
                 trading_pair=convert_from_exchange_trading_pair(trading_pair),
                 position_side=position_side,
                 unrealized_pnl=unrealized_pnl,
                 entry_price=entry_price,
                 amount=amount,
                 leverage=leverage
             )
         else:
             if (trading_pair + position_side.name) in self._account_positions:
                 del self._account_positions[trading_pair + position_side.name]
    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))