From dff3bfeef974f41b02335c48fcc9d39b6dea9285 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Fri, 27 Dec 2024 15:05:29 +0100 Subject: [PATCH] [Modes] reduce_only chained orders when trading futures --- Trading/Mode/daily_trading_mode/daily_trading.py | 7 +++++-- .../tests/test_daily_trading_mode_consumer.py | 9 +++++++++ Trading/Mode/dca_trading_mode/dca_trading.py | 7 +++++-- .../Mode/dca_trading_mode/tests/test_dca_trading_mode.py | 6 +++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Trading/Mode/daily_trading_mode/daily_trading.py b/Trading/Mode/daily_trading_mode/daily_trading.py index a658fc1c2..4d0ffa959 100644 --- a/Trading/Mode/daily_trading_mode/daily_trading.py +++ b/Trading/Mode/daily_trading_mode/daily_trading.py @@ -499,6 +499,8 @@ async def _create_order( chained_orders = [] is_long = current_order.side is trading_enums.TradeOrderSide.BUY exit_side = trading_enums.TradeOrderSide.SELL if is_long else trading_enums.TradeOrderSide.BUY + # tag chained orders as reduce_only when trading futures + reduce_only_chained_orders = self.exchange_manager.is_future if use_stop_loss_orders: if len(stop_loss_details) > 1: self.logger.error(f"Multiple stop loss orders is not supported.") @@ -509,7 +511,8 @@ async def _create_order( ) ) if (not stop_loss_details or stop_loss_details[0].price.is_nan()) else stop_loss_details[0].price param_update, chained_order = await self.register_chained_order( - current_order, stop_price, trading_enums.TraderOrderType.STOP_LOSS, exit_side, tag=tag + current_order, stop_price, trading_enums.TraderOrderType.STOP_LOSS, exit_side, + tag=tag, reduce_only=reduce_only_chained_orders ) params.update(param_update) chained_orders.append(chained_order) @@ -536,7 +539,7 @@ async def _create_order( ) param_update, chained_order = await self.register_chained_order( current_order, take_profit_price, order_type, exit_side, - quantity=take_profits_detail.quantity, tag=tag + quantity=take_profits_detail.quantity, tag=tag, reduce_only=reduce_only_chained_orders ) params.update(param_update) chained_orders.append(chained_order) diff --git a/Trading/Mode/daily_trading_mode/tests/test_daily_trading_mode_consumer.py b/Trading/Mode/daily_trading_mode/tests/test_daily_trading_mode_consumer.py index 1c12a772a..991744767 100644 --- a/Trading/Mode/daily_trading_mode/tests/test_daily_trading_mode_consumer.py +++ b/Trading/Mode/daily_trading_mode/tests/test_daily_trading_mode_consumer.py @@ -957,6 +957,7 @@ async def test_chained_stop_loss_and_take_profit_orders(tools): assert stop_order.is_waiting_for_chained_trigger is False assert stop_order.associated_entry_ids == [buy_order.order_id] assert stop_order.tag == "super" + assert stop_order.reduce_only is False assert stop_order.is_open() state = trading_enums.EvaluatorStates.LONG.value @@ -978,6 +979,7 @@ async def test_chained_stop_loss_and_take_profit_orders(tools): assert take_profit_order.associated_entry_ids == [buy_order.order_id] assert not take_profit_order.is_open() assert not take_profit_order.is_created() + assert take_profit_order.reduce_only is False # take profit only using ADDITIONAL_TAKE_PROFIT_PRICES_KEY data = { consumer.ADDITIONAL_TAKE_PROFIT_PRICES_KEY: [decimal.Decimal("100000")], @@ -995,6 +997,7 @@ async def test_chained_stop_loss_and_take_profit_orders(tools): assert take_profit_order.associated_entry_ids == [buy_order.order_id] assert not take_profit_order.is_open() assert not take_profit_order.is_created() + assert take_profit_order.reduce_only is False # stop loss and take profit data = { @@ -1023,6 +1026,7 @@ async def test_chained_stop_loss_and_take_profit_orders(tools): assert take_profit_order.associated_entry_ids == [buy_order.order_id] assert not take_profit_order.is_open() assert not take_profit_order.is_created() + assert take_profit_order.reduce_only is False assert isinstance(stop_order.order_group, trading_personal_data.OneCancelsTheOtherOrderGroup) assert take_profit_order.order_group is stop_order.order_group @@ -1044,6 +1048,7 @@ async def test_chained_stop_loss_and_take_profit_orders(tools): assert sell_limit.chained_orders == [] assert stop_loss.associated_entry_ids is None assert stop_loss.chained_orders == [] + assert stop_loss.reduce_only is True # True as force stop loss assert stop_loss.origin_price == decimal.Decimal("123") assert stop_loss.origin_quantity == decimal.Decimal("0.01") \ - trading_personal_data.get_fees_for_currency(sell_limit.fee, stop_loss.quantity_currency) @@ -1207,6 +1212,7 @@ async def test_target_profit_mode(tools): assert isinstance(take_profit_order, trading_personal_data.SellLimitOrder) assert take_profit_order.side is trading_enums.TradeOrderSide.SELL assert take_profit_order.origin_quantity == buy_order.origin_quantity + assert take_profit_order.reduce_only is False assert take_profit_order.origin_price == trading_personal_data.decimal_adapt_price( symbol_market, buy_order.origin_price * (trading_constants.ONE + consumer.TARGET_PROFIT_TAKE_PROFIT) @@ -1225,6 +1231,7 @@ async def test_target_profit_mode(tools): assert isinstance(stop_order, trading_personal_data.StopLossOrder) assert stop_order.side is trading_enums.TradeOrderSide.SELL assert stop_order.origin_quantity == buy_order.origin_quantity + assert stop_order.reduce_only is False assert stop_order.origin_price == trading_personal_data.decimal_adapt_price( symbol_market, buy_order.origin_price * (trading_constants.ONE - consumer.TARGET_PROFIT_STOP_LOSS) @@ -1307,12 +1314,14 @@ async def test_target_profit_mode_futures_trading(future_tools): assert isinstance(stop_loss_order, trading_personal_data.StopLossOrder) assert take_profit_order.side is trading_enums.TradeOrderSide.BUY assert take_profit_order.origin_quantity == sell_order.origin_quantity + assert take_profit_order.reduce_only is True assert take_profit_order.origin_price == trading_personal_data.decimal_adapt_price( symbol_market, sell_order.origin_price * (trading_constants.ONE - consumer.TARGET_PROFIT_TAKE_PROFIT) ) assert stop_loss_order.side is trading_enums.TradeOrderSide.BUY assert stop_loss_order.origin_quantity == sell_order.origin_quantity + assert stop_loss_order.reduce_only is True assert stop_loss_order.origin_price == trading_personal_data.decimal_adapt_price( symbol_market, sell_order.origin_price * (trading_constants.ONE + consumer.TARGET_PROFIT_STOP_LOSS) diff --git a/Trading/Mode/dca_trading_mode/dca_trading.py b/Trading/Mode/dca_trading_mode/dca_trading.py index 523dd7440..2bc2c9b0d 100644 --- a/Trading/Mode/dca_trading_mode/dca_trading.py +++ b/Trading/Mode/dca_trading_mode/dca_trading.py @@ -367,6 +367,7 @@ async def _create_entry_with_chained_exit_orders(self, entry_order, entry_price, symbol_market ) can_bundle_exit_orders = len(exit_quantities) == 1 + reduce_only_chained_orders = self.exchange_manager.is_future for i, exit_quantity in exit_quantities: order_couple = [] # stop loss @@ -374,7 +375,8 @@ async def _create_entry_with_chained_exit_orders(self, entry_order, entry_price, stop_price = trading_personal_data.decimal_adapt_price(symbol_market, stop_price) param_update, chained_order = await self.register_chained_order( entry_order, stop_price, trading_enums.TraderOrderType.STOP_LOSS, exit_side, - quantity=exit_quantity, allow_bundling=can_bundle_exit_orders + quantity=exit_quantity, allow_bundling=can_bundle_exit_orders, + reduce_only=reduce_only_chained_orders ) params.update(param_update) order_couple.append(chained_order) @@ -399,7 +401,8 @@ async def _create_entry_with_chained_exit_orders(self, entry_order, entry_price, ) param_update, chained_order = await self.register_chained_order( entry_order, take_profit_price, take_profit_order_type, None, - quantity=exit_quantity, allow_bundling=can_bundle_exit_orders + quantity=exit_quantity, allow_bundling=can_bundle_exit_orders, + reduce_only=reduce_only_chained_orders ) params.update(param_update) order_couple.append(chained_order) diff --git a/Trading/Mode/dca_trading_mode/tests/test_dca_trading_mode.py b/Trading/Mode/dca_trading_mode/tests/test_dca_trading_mode.py index 3506b608d..974e6e005 100644 --- a/Trading/Mode/dca_trading_mode/tests/test_dca_trading_mode.py +++ b/Trading/Mode/dca_trading_mode/tests/test_dca_trading_mode.py @@ -436,6 +436,7 @@ async def test_create_entry_with_chained_exit_orders(tools): assert stop_loss.origin_price == entry_price * (1 - mode.stop_loss_price_multiplier) assert stop_loss.triggered_by is entry_order assert stop_loss.order_group is None + assert stop_loss.reduce_only is False # reset values create_order_mock.reset_mock() entry_order.chained_orders = [] @@ -456,6 +457,7 @@ async def test_create_entry_with_chained_exit_orders(tools): assert take_profit.origin_price == entry_price * (1 + mode.exit_limit_orders_price_multiplier) assert take_profit.triggered_by is entry_order assert take_profit.order_group is None + assert take_profit.reduce_only is False # reset values create_order_mock.reset_mock() entry_order.chained_orders = [] @@ -531,7 +533,8 @@ async def test_create_entry_with_chained_exit_orders(tools): create_order_mock.reset_mock() entry_order.chained_orders = [] - # chained stop loss + # chained stop loss on futures + consumer.exchange_manager.is_future = True # 3 take profit (initial + 2 additional) mode.use_stop_loss = True mode.use_take_profit_exit_orders = True @@ -555,6 +558,7 @@ async def test_create_entry_with_chained_exit_orders(tools): # ensure only stop losses and take profits in chained orders assert len(stop_losses) == 1 assert len(take_profits) == 1 + assert all(order.reduce_only is True for order in entry_order.chained_orders) # futures: use reduce only assert stop_losses[0].origin_quantity == take_profits[0].origin_quantity == entry_order.origin_quantity