Skip to content

Commit

Permalink
[Staggered] fix order sizing when no recent trades are available
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeDSM committed Jan 27, 2025
1 parent 71af244 commit 3df16bf
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 7 deletions.
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)
)
)
)
))

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

0 comments on commit 3df16bf

Please sign in to comment.