Skip to content

Commit 2a0be71

Browse files
committed
[Order] add MinimumOrderCostNotReachedError
[Order] handle MinimumOrderCostNotReachedError
1 parent 0abddc5 commit 2a0be71

File tree

4 files changed

+69
-43
lines changed

4 files changed

+69
-43
lines changed

octobot_trading/errors.py

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ class MissingFunds(Exception):
2121
"""
2222

2323

24+
class MinimumOrderCostNotReachedError(Exception):
25+
"""
26+
Raised upon placing an order with size less than minimum order size
27+
"""
28+
29+
2430
class MissingMinimalExchangeTradeVolume(Exception):
2531
"""
2632
Raised when a new order is impossible to create due to exchange minimal funds restrictions

octobot_trading/exchanges/traders/trader.py

+52-40
Original file line numberDiff line numberDiff line change
@@ -483,17 +483,22 @@ async def _sell_everything(self, symbol, inverted, timeout=None):
483483
quantity = 0
484484
else:
485485
quantity = current_symbol_holding
486-
for order_quantity, order_price in decimal_order_adapter.decimal_check_and_adapt_order_details_if_necessary(
487-
quantity, price,
488-
symbol_market):
489-
current_order = order_factory.create_order_instance(trader=self,
490-
order_type=order_type,
491-
symbol=symbol,
492-
current_price=order_price,
493-
quantity=order_quantity,
494-
price=order_price)
495-
created_orders.append(
496-
await self.create_order(current_order))
486+
try:
487+
for order_quantity, order_price in decimal_order_adapter.decimal_check_and_adapt_order_details_if_necessary(
488+
quantity, price,
489+
symbol_market):
490+
current_order = order_factory.create_order_instance(trader=self,
491+
order_type=order_type,
492+
symbol=symbol,
493+
current_price=order_price,
494+
quantity=order_quantity,
495+
price=order_price)
496+
created_orders.append(
497+
await self.create_order(current_order))
498+
except errors.MinimumOrderCostNotReachedError as error:
499+
self.logger.exception(
500+
error, True,
501+
f"Failed to sell everything - error: {error}")
497502
return created_orders
498503

499504
async def sell_all(self, currencies_to_sell=None, timeout=None):
@@ -553,35 +558,42 @@ async def close_position(self, position, limit_price=None, timeout=1, emit_tradi
553558
_, _, _, price, symbol_market = await order_util.get_pre_order_data(self.exchange_manager,
554559
position.symbol,
555560
timeout=timeout)
556-
for order_quantity, order_price in decimal_order_adapter.decimal_check_and_adapt_order_details_if_necessary(
557-
position.get_quantity_to_close().copy_abs(), price, symbol_market):
558-
559-
if limit_price is not None:
560-
order_type = enums.TraderOrderType.SELL_LIMIT \
561-
if position.is_long() else enums.TraderOrderType.BUY_LIMIT
562-
else:
563-
order_type = enums.TraderOrderType.SELL_MARKET \
564-
if position.is_long() else enums.TraderOrderType.BUY_MARKET
565-
566-
current_order = order_factory.create_order_instance(trader=self,
567-
order_type=order_type,
568-
symbol=position.symbol,
569-
current_price=order_price,
570-
quantity=order_quantity,
571-
price=limit_price
572-
if limit_price is not None else order_price,
573-
reduce_only=True,
574-
close_position=True)
575-
async with signals.remote_signal_publisher(self.exchange_manager, current_order.symbol,
576-
emit_trading_signals):
577-
order = await signals.create_order(
578-
self.exchange_manager,
579-
emit_trading_signals and signals.should_emit_trading_signal(self.exchange_manager),
580-
current_order,
581-
wait_for_creation=wait_for_creation,
582-
creation_timeout=creation_timeout
583-
)
584-
created_orders.append(order)
561+
try:
562+
for order_quantity, order_price in decimal_order_adapter.decimal_check_and_adapt_order_details_if_necessary(
563+
position.get_quantity_to_close().copy_abs(), price, symbol_market):
564+
565+
if limit_price is not None:
566+
order_type = enums.TraderOrderType.SELL_LIMIT \
567+
if position.is_long() else enums.TraderOrderType.BUY_LIMIT
568+
else:
569+
order_type = enums.TraderOrderType.SELL_MARKET \
570+
if position.is_long() else enums.TraderOrderType.BUY_MARKET
571+
572+
current_order = order_factory.create_order_instance(trader=self,
573+
order_type=order_type,
574+
symbol=position.symbol,
575+
current_price=order_price,
576+
quantity=order_quantity,
577+
price=limit_price
578+
if limit_price is not None else order_price,
579+
reduce_only=True,
580+
close_position=True)
581+
async with signals.remote_signal_publisher(self.exchange_manager, current_order.symbol,
582+
emit_trading_signals):
583+
order = await signals.create_order(
584+
self.exchange_manager,
585+
emit_trading_signals and signals.should_emit_trading_signal(self.exchange_manager),
586+
current_order,
587+
wait_for_creation=wait_for_creation,
588+
creation_timeout=creation_timeout
589+
)
590+
created_orders.append(order)
591+
except errors.MinimumOrderCostNotReachedError as error:
592+
# this error should never happen as you are
593+
# able to always close a position
594+
self.logger.exception(
595+
error, True,
596+
f"Failed close position - error: {error}")
585597
return created_orders
586598

587599
async def withdraw(self, amount, currency):

octobot_trading/personal_data/orders/decimal_order_adapter.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,11 @@ def decimal_check_and_adapt_order_details_if_necessary(quantity, price, symbol_m
188188

189189
# check total_order_price not < min_cost
190190
if not personal_data.check_cost(float(total_order_price), min_cost):
191-
return []
191+
symbol = f"{symbol_market['symbol']} - " if 'symbol' in symbol_market else ""
192+
raise errors.MinimumOrderCostNotReachedError(
193+
f"Order value ({symbol}value: "
194+
f"{total_order_price}) must be at least {min_cost} to open a order "
195+
)
192196

193197
# check total_order_price not > max_cost and valid_quantity not > max_quantity
194198
elif (max_cost is not None and total_order_price > max_cost) or \

tests/personal_data/orders/test_decimal_order_adapter.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from octobot_trading.enums import ExchangeConstantsMarketStatusColumns as Ecmsc
2121
import octobot_trading.personal_data as personal_data
2222
import octobot_trading.constants as constants
23+
import octobot_trading.errors as errors
2324
from tests import event_loop
2425

2526
# All test coroutines will be treated as marked.
@@ -170,8 +171,11 @@ async def test_decimal_check_and_adapt_order_details_if_necessary():
170171
# invalid cost <
171172
quantity = decimal.Decimal(str(0.5))
172173
price = decimal.Decimal(str(1))
173-
assert personal_data.decimal_check_and_adapt_order_details_if_necessary(quantity, price, symbol_market) == []
174-
174+
try:
175+
personal_data.decimal_check_and_adapt_order_details_if_necessary(quantity, price, symbol_market)
176+
assert False
177+
except errors.MinimumOrderCostNotReachedError:
178+
assert True
175179
# invalid cost >
176180
quantity = decimal.Decimal(str(10))
177181
price = decimal.Decimal(str(49))

0 commit comments

Comments
 (0)