Add max-slippage limiting for dry-run orders to avoid insane market order fills
This commit is contained in:
parent
0b6aedbc4c
commit
61c076563f
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user