Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Staggered] fix order sizing when no recent trades are available #1428

Merged
merged 1 commit into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 169 additions & 1 deletion Trading/Mode/grid_trading_mode/tests/test_grid_trading_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😮

)
)
)
))

def _get_spread_missing_order_quantity(
self, average_order_quantity, side, i, orders_count, price, selling, limiting_amount_from_this_order,
Expand Down
Loading