diff --git a/Trading/Mode/grid_trading_mode/tests/test_grid_trading_mode.py b/Trading/Mode/grid_trading_mode/tests/test_grid_trading_mode.py index d4dd3bba6..957be02d5 100644 --- a/Trading/Mode/grid_trading_mode/tests/test_grid_trading_mode.py +++ b/Trading/Mode/grid_trading_mode/tests/test_grid_trading_mode.py @@ -911,7 +911,7 @@ async def test_start_after_offline_x_filled_and_price_back_should_buy_to_recreat _check_created_orders(producer, trading_api.get_open_orders(exchange_manager), 200) -async def test_start_after_offline_x_filled_and_missing_should_recreate_sell(): +async def test_start_after_offline_x_filled_and_missing_should_recreate_1_sell(): symbol = "BTC/USDT" async with _get_tools(symbol) as (producer, _, exchange_manager): # forced config @@ -981,6 +981,174 @@ async def test_start_after_offline_x_filled_and_missing_should_recreate_sell(): assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.SELL]) == 6 # there is now 4 buy orders assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.BUY]) == 4 + # quantity is preserved + assert all( + decimal.Decimal("0.00028") < order.origin_quantity < decimal.Decimal("0.00029") + for order in open_orders + ) + _check_created_orders(producer, trading_api.get_open_orders(exchange_manager), initial_price) + + +async def test_start_after_offline_x_filled_and_missing_should_recreate_5_sell_orders_no_recent_trade(): + symbol = "BTC/USDT" + async with _get_tools(symbol) as (producer, _, exchange_manager): + # forced config + producer.buy_funds = producer.sell_funds = 0 + producer.allow_order_funds_redispatch = True + producer.buy_orders_count = producer.sell_orders_count = 5 + producer.compensate_for_missed_mirror_order = True + producer.enable_trailing_down = False + producer.enable_trailing_up = True + producer.flat_increment = decimal.Decimal(100) + producer.flat_spread = decimal.Decimal(300) + producer.reinvest_profits = False + producer.sell_volume_per_order = producer.buy_volume_per_order = False + producer.starting_price = 0 + producer.use_existing_orders_only = False + producer.use_fixed_volume_for_mirror_orders = False + + orders_count = producer.buy_orders_count + producer.sell_orders_count + + initial_price = decimal.Decimal("105278.1") + trading_api.force_set_mark_price(exchange_manager, producer.symbol, initial_price) + btc_pf = trading_api.get_portfolio_currency(exchange_manager, "BTC") + usdt_pf = trading_api.get_portfolio_currency(exchange_manager, "USDT") + btc_pf.available = decimal.Decimal("0.00141858") + btc_pf.total = decimal.Decimal("0.00141858") + usdt_pf.available = decimal.Decimal("150.505098") + usdt_pf.total = decimal.Decimal("150.505098") + + await producer._ensure_staggered_orders() + await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count)) + original_orders = copy.copy(trading_api.get_open_orders(exchange_manager)) + assert len(original_orders) == orders_count + assert sorted([ + order.origin_price for order in original_orders + ]) == [ + # buy orders + decimal.Decimal('104728.1'), decimal.Decimal('104828.1'), decimal.Decimal('104928.1'), + decimal.Decimal('105028.1'), decimal.Decimal('105128.1'), + # sell orders + decimal.Decimal('105428.1'), decimal.Decimal('105528.1'), decimal.Decimal('105628.1'), + decimal.Decimal('105728.1'), decimal.Decimal('105828.1') + ] + + # price goes down to 104720, all buy order get filled + price = decimal.Decimal("104720") + offline_filled = [order for order in original_orders if order.origin_price <= decimal.Decimal('105128.1')] + assert len(offline_filled) == 5 + assert all(o.side == trading_enums.TradeOrderSide.BUY for o in offline_filled) + for order in offline_filled: + await _fill_order(order, exchange_manager, trigger_update_callback=False, producer=producer) + assert len(trading_api.get_open_orders(exchange_manager)) == orders_count - len(offline_filled) + assert btc_pf.available == decimal.Decimal("0.00143356799") + assert btc_pf.total == decimal.Decimal("0.00284915799") + assert usdt_pf.available == decimal.Decimal("0.247225519") + assert usdt_pf.total == decimal.Decimal("0.247225519") + + # clear trades + exchange_manager.exchange_personal_data.trades_manager.trades.clear() + + # back online: restore orders according to current price + trading_api.force_set_mark_price(exchange_manager, producer.symbol, price) + with _assert_missing_orders_count(producer, len(offline_filled)): + await producer._ensure_staggered_orders() + # create buy orders equivalent sell orders + await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count)) + open_orders = trading_api.get_open_orders(exchange_manager) + # there is now 10 sell orders + assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.SELL]) == 10 + # quantity is preserved + assert all( + decimal.Decimal("0.00028") < order.origin_quantity < decimal.Decimal("0.00029") + for order in open_orders + ) + # there is now 0 buy order + assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.BUY]) == 0 + _check_created_orders(producer, trading_api.get_open_orders(exchange_manager), initial_price) + + assert btc_pf.available == decimal.Decimal("0.00001571799") + assert btc_pf.total == decimal.Decimal("0.00284915799") + assert usdt_pf.available == decimal.Decimal("0.247225519") + assert usdt_pf.total == decimal.Decimal("0.247225519") + + +async def test_start_after_offline_x_filled_and_missing_should_recreate_5_buy_orders_no_recent_trade(): + symbol = "BTC/USDT" + async with _get_tools(symbol) as (producer, _, exchange_manager): + # forced config + producer.buy_funds = producer.sell_funds = 0 + producer.allow_order_funds_redispatch = True + producer.buy_orders_count = producer.sell_orders_count = 5 + producer.compensate_for_missed_mirror_order = True + producer.enable_trailing_down = False + producer.enable_trailing_up = True + producer.flat_increment = decimal.Decimal(100) + producer.flat_spread = decimal.Decimal(300) + producer.reinvest_profits = False + producer.sell_volume_per_order = producer.buy_volume_per_order = False + producer.starting_price = 0 + producer.use_existing_orders_only = False + producer.use_fixed_volume_for_mirror_orders = False + + orders_count = producer.buy_orders_count + producer.sell_orders_count + + initial_price = decimal.Decimal("105278.1") + trading_api.force_set_mark_price(exchange_manager, producer.symbol, initial_price) + btc_pf = trading_api.get_portfolio_currency(exchange_manager, "BTC") + usdt_pf = trading_api.get_portfolio_currency(exchange_manager, "USDT") + btc_pf.available = decimal.Decimal("0.00141858") + btc_pf.total = decimal.Decimal("0.00141858") + usdt_pf.available = decimal.Decimal("150.505098") + usdt_pf.total = decimal.Decimal("150.505098") + + await producer._ensure_staggered_orders() + await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count)) + original_orders = copy.copy(trading_api.get_open_orders(exchange_manager)) + assert len(original_orders) == orders_count + assert sorted([ + order.origin_price for order in original_orders + ]) == [ + # buy orders + decimal.Decimal('104728.1'), decimal.Decimal('104828.1'), decimal.Decimal('104928.1'), + decimal.Decimal('105028.1'), decimal.Decimal('105128.1'), + # sell orders + decimal.Decimal('105428.1'), decimal.Decimal('105528.1'), decimal.Decimal('105628.1'), + decimal.Decimal('105728.1'), decimal.Decimal('105828.1') + ] + + # price goes up to 105838, all sell order get filled + price = decimal.Decimal("105838") + offline_filled = [order for order in original_orders if order.origin_price > decimal.Decimal('105128.1')] + assert len(offline_filled) == 5 + assert all(o.side == trading_enums.TradeOrderSide.SELL for o in offline_filled) + for order in offline_filled: + await _fill_order(order, exchange_manager, trigger_update_callback=False, producer=producer) + assert len(trading_api.get_open_orders(exchange_manager)) == orders_count - len(offline_filled) + assert btc_pf.available == decimal.Decimal("0.00000299") + assert btc_pf.total == decimal.Decimal("0.00000299") + assert usdt_pf.available == decimal.Decimal("149.623458838921") + assert usdt_pf.total == decimal.Decimal("299.881331319921") + + # clear trades + exchange_manager.exchange_personal_data.trades_manager.trades.clear() + + # back online: restore orders according to current price + trading_api.force_set_mark_price(exchange_manager, producer.symbol, price) + with _assert_missing_orders_count(producer, len(offline_filled)): + await producer._ensure_staggered_orders() + # create buy orders equivalent sell orders + await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count)) + open_orders = trading_api.get_open_orders(exchange_manager) + # there is now 0 sell order + assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.SELL]) == 0 + # there is now 10 buy orders + assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.BUY]) == 10 + # quantity is preserved + assert all( + decimal.Decimal("0.00028") < order.origin_quantity < decimal.Decimal("0.00029") + for order in open_orders + ) _check_created_orders(producer, trading_api.get_open_orders(exchange_manager), initial_price) diff --git a/Trading/Mode/staggered_orders_trading_mode/staggered_orders_trading.py b/Trading/Mode/staggered_orders_trading_mode/staggered_orders_trading.py index b39aa6439..503b33c3b 100644 --- a/Trading/Mode/staggered_orders_trading_mode/staggered_orders_trading.py +++ b/Trading/Mode/staggered_orders_trading_mode/staggered_orders_trading.py @@ -1502,7 +1502,7 @@ def _fill_missing_orders( # missing order between similar orders quantity = self._get_surrounded_missing_order_quantity( previous_o, following_o, max_quant_per_order, decimal_missing_order_price, recent_trades, - current_price, sorted_orders + current_price, sorted_orders, side ) orders.append(OrderData(missing_order_side, quantity, decimal_missing_order_price, self.symbol, False)) @@ -1578,9 +1578,9 @@ def _fill_missing_orders( def _get_surrounded_missing_order_quantity( self, previous_order, following_order, max_quant_per_order, order_price, recent_trades, - current_price, sorted_orders + current_price, sorted_orders, side ): - selling = previous_order.side == trading_enums.TradeOrderSide.SELL + selling = side == trading_enums.TradeOrderSide.SELL if sorted_orders: if quantity := self._get_quantity_from_existing_orders( order_price, sorted_orders, selling @@ -1594,10 +1594,9 @@ def _get_surrounded_missing_order_quantity( min( data_util.mean([previous_order.origin_quantity, following_order.origin_quantity]) if following_order else previous_order.origin_quantity, - max_quant_per_order / order_price + (max_quant_per_order if selling else max_quant_per_order / order_price) ) - ) - ) + )) def _get_spread_missing_order_quantity( self, average_order_quantity, side, i, orders_count, price, selling, limiting_amount_from_this_order,