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 sell order creation close to spread #1080

Merged
merged 1 commit into from
Oct 13, 2023
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
161 changes: 148 additions & 13 deletions Trading/Mode/grid_trading_mode/tests/test_grid_trading_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,8 @@ async def test_start_after_offline_filled_orders_without_recent_trades():
# back online: restore orders according to current price
price = 96
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available <= post_portfolio
Expand All @@ -480,7 +481,8 @@ async def test_start_after_offline_filled_orders_without_recent_trades():

# back online: restore orders according to current price
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available
Expand Down Expand Up @@ -516,7 +518,8 @@ async def test_start_after_offline_filled_orders_with_recent_trades():
# back online: restore orders according to current price
price = 95
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available <= post_portfolio
Expand All @@ -538,7 +541,8 @@ async def test_start_after_offline_filled_orders_with_recent_trades():

# back online: restore orders according to current price
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available
Expand Down Expand Up @@ -578,7 +582,8 @@ async def test_start_after_offline_filled_orders_close_to_price_with_recent_trad
# back online: restore orders according to current price => create sell missing order
price = 29127.16
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled_orders)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available
Expand Down Expand Up @@ -627,7 +632,8 @@ async def test_start_after_offline_full_sell_side_filled_orders_with_recent_trad
# back online: restore orders according to current price
price = max(order.origin_price for order in offline_filled) * 2
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
assert producer.operational_depth > orders_count
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available <= post_portfolio
Expand Down Expand Up @@ -671,7 +677,8 @@ async def test_start_after_offline_full_sell_side_filled_orders_price_back():
# simulate current price as back to average origin sell orders
price = offline_filled[len(offline_filled)//2].origin_price
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders (and create up to 50 orders as all orders can be created)
assert producer.operational_depth > orders_count
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
Expand Down Expand Up @@ -714,7 +721,8 @@ async def test_start_after_offline_full_buy_side_filled_orders_price_back_with_r
# simulate current price as back to average origin buy orders
price = offline_filled[len(offline_filled)//2].origin_price
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available
Expand Down Expand Up @@ -756,7 +764,8 @@ async def test_start_after_offline_buy_side_10_filled():
# simulate current price as back to average origin buy orders
price = offline_filled[len(offline_filled)//2].origin_price + 1
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available
Expand Down Expand Up @@ -809,7 +818,8 @@ async def test_start_after_offline_x_filled_and_price_back_should_sell_to_recrea
# create buy orders between 150 and 180
price = decimal.Decimal(180)
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "BTC").available <= post_btc_portfolio
Expand Down Expand Up @@ -861,7 +871,8 @@ async def test_start_after_offline_x_filled_and_price_back_should_buy_to_recreat
# create sell orders between 220 and 250
price = decimal.Decimal(220)
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available <= post_usdt_portfolio
Expand All @@ -873,6 +884,108 @@ 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_1_filled_should_create_buy():
symbol = "BTC/USDT"
async with _get_tools(symbol) as (producer, _, exchange_manager):
price = decimal.Decimal("26616.7")
producer.flat_spread = decimal.Decimal(275)
producer.flat_increment = decimal.Decimal(125)
producer.buy_orders_count = 30
producer.sell_orders_count = 30

orders_count = producer.buy_orders_count + producer.sell_orders_count

trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
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
pre_btc_portfolio = trading_api.get_portfolio_currency(exchange_manager, "BTC").available
pre_usdt_portfolio = trading_api.get_portfolio_currency(exchange_manager, "USDT").available

# offline simulation: orders get filled but not replaced => price moved to 26756.2
open_orders = trading_api.get_open_orders(exchange_manager)
offline_filled = [
o
for o in open_orders
if o.side == trading_enums.TradeOrderSide.SELL and o.origin_price <= decimal.Decimal("26756.2")
]
# this is 1 order
assert len(offline_filled) == 1
assert offline_filled[0].origin_price == decimal.Decimal("26754.2")
for order in offline_filled:
await _fill_order(order, exchange_manager, trigger_update_callback=False, producer=producer)
post_btc_portfolio = trading_api.get_portfolio_currency(exchange_manager, "BTC").available
post_usdt_portfolio = trading_api.get_portfolio_currency(exchange_manager, "USDT").available
# buy orders filled: available BTC is constant
assert pre_btc_portfolio == post_btc_portfolio
# no sell order filled, available USDT increased
assert pre_usdt_portfolio <= post_usdt_portfolio
assert len(trading_api.get_open_orders(exchange_manager)) == orders_count - len(offline_filled)

# back online: restore orders according to current price
# simulate current price at 26753.8
price = decimal.Decimal("26753.8")
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
with _assert_missing_orders_count(producer, 1):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert 0 <= trading_api.get_portfolio_currency(exchange_manager, "USDT").available <= post_usdt_portfolio
open_orders = trading_api.get_open_orders(exchange_manager)
# 1 sell order is filled
assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.SELL]) == 30 - 1
# 1 buy order is added
assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.BUY]) == 30 + 1
_check_created_orders(producer, trading_api.get_open_orders(exchange_manager), decimal.Decimal("26616.7"))


async def test_start_after_offline_1_filled_should_create_sell():
symbol = "BTC/USDT"
async with _get_tools(symbol) as (producer, _, exchange_manager):
price = decimal.Decimal("26616.7")
producer.flat_spread = decimal.Decimal(275)
producer.flat_increment = decimal.Decimal(125)
producer.buy_orders_count = 30
producer.sell_orders_count = 30

orders_count = producer.buy_orders_count + producer.sell_orders_count

trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
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
# offline simulation: orders get filled but not replaced => price moved to 26756.2
open_orders = trading_api.get_open_orders(exchange_manager)
offline_filled = [
o
for o in open_orders
if o.side == trading_enums.TradeOrderSide.BUY and o.origin_price >= decimal.Decimal("26459.2")
]
# this is 1 order
assert len(offline_filled) == 1
assert offline_filled[0].origin_price == decimal.Decimal("26479.2")
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)

# back online: restore orders according to current price
# simulate current price at 26409.2
price = decimal.Decimal("26409.2")
trading_api.force_set_mark_price(exchange_manager, producer.symbol, price)
with _assert_missing_orders_count(producer, 1):
await producer._ensure_staggered_orders()
# restored orders
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
open_orders = trading_api.get_open_orders(exchange_manager)
# 1 sell order is filled
assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.SELL]) == 30 + 1
# 1 buy order is added
assert len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.BUY]) == 30 - 1
_check_created_orders(producer, trading_api.get_open_orders(exchange_manager), decimal.Decimal("26616.7"))


async def test_start_after_offline_with_added_funds_increasing_orders_count():
symbol = "BTC/USDT"
async with _get_tools(symbol) as (producer, _, exchange_manager):
Expand All @@ -897,7 +1010,8 @@ async def test_start_after_offline_with_added_funds_increasing_orders_count():
)
previous_orders = original_orders
# 1. offline simulation: nothing happens: orders are not replaced
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, 0):
await producer._ensure_staggered_orders()
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
assert all(order.is_open() for order in previous_orders)

Expand Down Expand Up @@ -1050,7 +1164,8 @@ def _increase_funds(asset, multiplier):
post_portfolio = trading_api.get_portfolio_currency(exchange_manager, "BTC").available
assert pre_portfolio < post_portfolio
assert len(trading_api.get_open_orders(exchange_manager)) == orders_count - len(offline_filled)
await producer._ensure_staggered_orders()
with _assert_missing_orders_count(producer, len(offline_filled)):
await producer._ensure_staggered_orders()
await asyncio.create_task(_check_open_orders_count(exchange_manager, orders_count))
new_orders = copy.copy(trading_api.get_open_orders(exchange_manager))
assert len(new_orders) == orders_count
Expand All @@ -1072,6 +1187,26 @@ def _increase_funds(asset, multiplier):
initial_sell_orders_average_cost * decimal.Decimal(str(2.5)) * decimal.Decimal(2)


@contextlib.contextmanager
def _assert_missing_orders_count(trading_mode_producer, expected_count):
origin_analyse_current_orders_situation = trading_mode_producer._analyse_current_orders_situation
missing_orders = []

def _local_analyse_current_orders_situation(*args, **kwargs):
return_vals = origin_analyse_current_orders_situation(*args, **kwargs)
created_missing_orders = return_vals[0]
for order in created_missing_orders:
missing_orders.append(order)
return return_vals

with mock.patch.object(trading_mode_producer, "_analyse_current_orders_situation", mock.Mock(
side_effect=_local_analyse_current_orders_situation
)) as _local_analyse_current_orders_situation_mock:
yield
_local_analyse_current_orders_situation_mock.assert_called_once()
assert len(missing_orders) == expected_count


async def _wait_for_orders_creation(orders_count=1):
for _ in range(orders_count):
await asyncio_tools.wait_asyncio_next_cycle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ async def _cancel_orders(self, orders_descriptions, symbol):
try:
await self._cancel_order_on_exchange(order)
except (errors.OrderCancelError, errors.UnexpectedExchangeSideOrderStateError) as err:
self.logger.warning(f"Skipping order cancel: {err}")
self.logger.warning(f"Skipping order cancel: {err} ({err.__class__.__name__})")
cancelled_count += 1
return cancelled_count

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1454,10 +1454,11 @@ def _bootstrap_parameters(self, sorted_orders, recently_closed_trades, lower_bou
# missing orders around spread point: symmetrical orders were not created when
# orders were filled => re-create them
next_missing_order_price = previous_order.origin_price + increment
spread_lower_boundary = self.current_price
spread_lower_boundary = order.origin_price - inferred_spread
Copy link
Member

Choose a reason for hiding this comment

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

👍


# re-create buy orders starting from the closest buy up to spread
while next_missing_order_price < spread_lower_boundary:
while next_missing_order_price < self.current_price and \
next_missing_order_price <= spread_lower_boundary:
# missing buy order
if not self._is_just_closed_order(next_missing_order_price,
recently_closed_trades):
Expand Down