Skip to content

Commit

Permalink
Fix insufficient balance error
Browse files Browse the repository at this point in the history
There was a flaw in logic which allowed to bypass a check for minimal
allowed order size. As a result, worker tried to place too small order
causing an error `bitsharesapi.exceptions.UnhandledRPCError: Assert
Exception: d.get_balance( *_seller, *_sell_asset ) >= op.amount_to_sell:
insufficient balance`

Closes: Codaone#649
  • Loading branch information
bitphage committed Sep 7, 2019
1 parent 9eea0f4 commit ef4039b
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 70 deletions.
88 changes: 40 additions & 48 deletions dexbot/strategies/staggered_orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,6 @@ def maintain_strategy(self, *args, **kwargs):
# Store balance entry for profit estimation if needed
self.store_profit_estimation_data()

# Calculate minimal orders amounts based on asset precision
if not self.order_min_base or not self.order_min_quote:
self.calculate_min_amounts()

# Calculate asset thresholds once
if not self.quote_asset_threshold or not self.base_asset_threshold:
self.calculate_asset_thresholds()
Expand Down Expand Up @@ -1519,34 +1515,16 @@ def place_closer_order(self, asset, order, place_order=True, allow_partial=False
limiter = 0
if asset == 'base':
# Define amounts in terms of BASE and QUOTE
base_amount = own_asset_amount
base_amount = limiter = own_asset_amount
quote_amount = opposite_asset_amount
limiter = base_amount
elif asset == 'quote':
quote_amount = own_asset_amount
limiter = quote_amount
quote_amount = limiter = own_asset_amount
price = price ** -1

# Make sure new order is bigger than allowed minimum
hard_limit = 0
if place_order:
corrected_quote_amount = self.check_min_order_size(quote_amount, price)
if corrected_quote_amount > quote_amount:
self.log.debug('Correcting closer order amount to minimal allowed')
quote_amount = corrected_quote_amount
base_amount = quote_amount * price
if asset == 'base':
hard_limit = base_amount
elif asset == 'quote':
hard_limit = quote_amount
limiter = hard_limit

# Check whether new order will exceed available balance
# Check whether new order will excess the limiter. Limiter is set based on own_aseet_limit or
# opposite_asset_limit kwargs
if balance < limiter:
# Closer order should not be less than threshold
if (
allow_partial and
balance > hard_limit) or (
if allow_partial or (
# Accept small inaccuracy for full-sized closer order
place_order and not allow_partial and limiter - balance < 20 * 10 ** -precision
):
Expand All @@ -1556,14 +1534,26 @@ def place_closer_order(self, asset, order, place_order=True, allow_partial=False
quote_amount = balance / price
elif asset == 'quote':
quote_amount = balance

elif place_order and not allow_partial:
self.log.debug('Not enough balance to place closer {} order; need/avail: {:.{prec}f}/{:.{prec}f}'
.format(order_type, limiter, balance, prec=precision))
place_order = False
elif place_order:

# Make sure new order is bigger than allowed minimum
hard_limit = 0
if place_order:
corrected_quote_amount = self.check_min_order_size(quote_amount, price)
if corrected_quote_amount > quote_amount:
self.log.debug('Correcting closer order amount to minimal allowed')
quote_amount = corrected_quote_amount
base_amount = quote_amount * price
if asset == 'base':
hard_limit = base_amount
elif asset == 'quote':
hard_limit = quote_amount
if balance < hard_limit:
self.log.debug('Not enough balance to place minimal allowed order: {:.{prec}f}/{:.{prec}f} {}'
.format(balance, limiter, symbol, prec=precision))
.format(balance, hard_limit, symbol, prec=precision))
place_order = False

if place_order and asset == 'base':
Expand Down Expand Up @@ -1647,37 +1637,35 @@ def place_further_order(self, asset, order, place_order=True, allow_partial=Fals
limiter = quote_amount
price = price ** -1

# Make sure new order is bigger than allowed minimum
hard_limit = 0
if place_order:
corrected_quote_amount = self.check_min_order_size(quote_amount, price)
if corrected_quote_amount > quote_amount:
self.log.debug('Correcting further order amount to minimal allowed: {} -> {}'
.format(quote_amount, corrected_quote_amount))
quote_amount = corrected_quote_amount
base_amount = quote_amount * price
if asset == 'base':
hard_limit = base_amount
elif asset == 'quote':
hard_limit = quote_amount
limiter = hard_limit

# Check whether new order will exceed available balance
if balance < limiter:
if place_order and not allow_partial:
self.log.debug('Not enough balance to place further {} order; need/avail: {:.{prec}f}/{:.{prec}f}'
.format(order_type, limiter, balance, prec=precision))
place_order = False
elif allow_partial and balance > hard_limit:
elif allow_partial:
self.log.debug('Limiting {} order amount to available asset balance: {:.{prec}f} {}'
.format(order_type, balance, symbol, prec=precision))
if asset == 'base':
quote_amount = balance / price
elif asset == 'quote':
quote_amount = balance
elif place_order:

# Make sure new order is bigger than allowed minimum
hard_limit = 0
if place_order:
corrected_quote_amount = self.check_min_order_size(quote_amount, price)
if corrected_quote_amount > quote_amount:
self.log.debug('Correcting further order amount to minimal allowed')
quote_amount = corrected_quote_amount
base_amount = quote_amount * price
if asset == 'base':
hard_limit = base_amount
elif asset == 'quote':
hard_limit = quote_amount
if balance < hard_limit:
self.log.debug('Not enough balance to place minimal allowed order: {:.{prec}f}/{:.{prec}f} {}'
.format(balance, limiter, symbol, prec=precision))
.format(balance, hard_limit, symbol, prec=precision))
place_order = False

if place_order and asset == 'base':
Expand Down Expand Up @@ -1953,6 +1941,10 @@ def check_min_order_size(self, amount, price):
:param float | price: Order price in BASE
:return float | new_amount: passed amount or minimal allowed amount
"""
# Calculate minimal orders amounts based on asset precision
if not self.order_min_base or not self.order_min_quote:
self.calculate_min_amounts()

if (amount < self.order_min_quote or
amount * price < self.order_min_base):
self.log.debug('Too small order, base: {:.8f}/{:.8f}, quote: {}/{}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,28 +292,6 @@ def test_place_closer_order_allow_partial_hard_limit(orders2, asset):
assert num_orders_before == num_orders_after


@pytest.mark.parametrize('asset', ['base', 'quote'])
def test_place_closer_order_allow_partial_soft_limit(orders2, asset):
""" Test place_closer_order with allow_partial=True when avail balance is less than self.partial_fill_threshold
restriction
"""
worker = orders2

if asset == 'base':
order = worker.buy_orders[0]
# Pretend we have balance smaller than soft limit
worker.base_balance['amount'] = order['base']['amount'] * worker.partial_fill_threshold / 1.1
elif asset == 'quote':
order = worker.sell_orders[0]
worker.quote_balance['amount'] = order['base']['amount'] * worker.partial_fill_threshold / 1.1

num_orders_before = len(worker.own_orders)
worker.place_closer_order(asset, order, place_order=True, allow_partial=True)
num_orders_after = len(worker.own_orders)
# Expect that order was not placed
assert num_orders_before == num_orders_after


@pytest.mark.parametrize('asset', ['base', 'quote'])
def test_place_closer_order_allow_partial(orders2, asset):
""" Test place_closer_order with allow_partial=True when avail balance is more than self.partial_fill_threshold
Expand Down

0 comments on commit ef4039b

Please sign in to comment.