Add max-slippage limiting for dry-run orders to avoid insane market order fills

This commit is contained in:
Matthias 2021-08-11 12:11:29 +02:00
parent 0b6aedbc4c
commit 61c076563f
2 changed files with 26 additions and 10 deletions

View File

@ -618,6 +618,8 @@ class Exchange:
if self.exchange_has('fetchL2OrderBook'): if self.exchange_has('fetchL2OrderBook'):
ob = self.fetch_l2_order_book(pair, 20) ob = self.fetch_l2_order_book(pair, 20)
ob_type = 'asks' if side == 'buy' else 'bids' ob_type = 'asks' if side == 'buy' else 'bids'
slippage = 0.05
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))
remaining_amount = amount remaining_amount = amount
filled_amount = 0 filled_amount = 0
@ -626,7 +628,9 @@ class Exchange:
book_entry_coin_volume = book_entry[1] book_entry_coin_volume = book_entry[1]
if remaining_amount > 0: if remaining_amount > 0:
if remaining_amount < book_entry_coin_volume: if remaining_amount < book_entry_coin_volume:
# Orderbook at this slot bigger than remaining amount
filled_amount += remaining_amount * book_entry_price filled_amount += remaining_amount * book_entry_price
break
else: else:
filled_amount += book_entry_coin_volume * book_entry_price filled_amount += book_entry_coin_volume * book_entry_price
remaining_amount -= book_entry_coin_volume remaining_amount -= book_entry_coin_volume
@ -635,7 +639,14 @@ class Exchange:
else: else:
# If remaining_amount wasn't consumed completely (break was not called) # If remaining_amount wasn't consumed completely (break was not called)
filled_amount += remaining_amount * book_entry_price filled_amount += remaining_amount * book_entry_price
forecast_avg_filled_price = filled_amount / amount forecast_avg_filled_price = max(filled_amount, 0) / amount
# Limit max. slippage to specified value
if side == 'buy':
forecast_avg_filled_price = min(forecast_avg_filled_price, max_slippage_val)
else:
forecast_avg_filled_price = max(forecast_avg_filled_price, max_slippage_val)
return self.price_to_precision(pair, forecast_avg_filled_price) return self.price_to_precision(pair, forecast_avg_filled_price)
return rate return rate

View File

@ -984,16 +984,21 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice,
assert order['fee'] assert order['fee']
@pytest.mark.parametrize("side,amount,endprice", [ @pytest.mark.parametrize("side,rate,amount,endprice", [
("buy", 1, 25.566), # spread is 25.263-25.266
("buy", 100, 25.5672), # Requires interpolation ("buy", 25.564, 1, 25.566),
("buy", 1000, 25.575), # More than orderbook return ("buy", 25.564, 100, 25.5672), # Requires interpolation
("sell", 1, 25.563), ("buy", 25.590, 100, 25.5672), # Price above spread ... average is lower
("sell", 100, 25.5625), # Requires interpolation ("buy", 25.564, 1000, 25.575), # More than orderbook return
("sell", 1000, 25.5555), # More than orderbook return ("buy", 24.000, 100000, 25.200), # Run into max_slippage of 5%
("sell", 25.564, 1, 25.563),
("sell", 25.564, 100, 25.5625), # Requires interpolation
("sell", 25.510, 100, 25.5625), # price below spread - average is higher
("sell", 25.564, 1000, 25.5555), # More than orderbook return
("sell", 27, 10000, 25.65), # max-slippage 5%
]) ])
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_market_fill(default_conf, mocker, side, amount, endprice, def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amount, endprice,
exchange_name, order_book_l2_usd): exchange_name, order_book_l2_usd):
default_conf['dry_run'] = True default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
@ -1003,7 +1008,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, amount, en
) )
order = exchange.create_dry_run_order( order = exchange.create_dry_run_order(
pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=25.5) pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=rate)
assert 'id' in order assert 'id' in order
assert f'dry_run_{side}_' in order["id"] assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side assert order["side"] == side