Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.4.124] - 2024-11-17
### Updated
[Exchanges] Handle limit order in converter
[Exchanges] Handle more ccxt missed orders

## [2.4.123] - 2024-11-16
### Updated
[Requirements] Bump cachetools and make version more flexible
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# OctoBot-Trading [2.4.123](https://github.com/Drakkar-Software/OctoBot-Trading/blob/master/CHANGELOG.md)
# OctoBot-Trading [2.4.124](https://github.com/Drakkar-Software/OctoBot-Trading/blob/master/CHANGELOG.md)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/903b6b22bceb4661b608a86fea655f69)](https://app.codacy.com/gh/Drakkar-Software/OctoBot-Trading?utm_source=github.com&utm_medium=referral&utm_content=Drakkar-Software/OctoBot-Trading&utm_campaign=Badge_Grade_Dashboard)
[![PyPI](https://img.shields.io/pypi/v/OctoBot-Trading.svg)](https://pypi.python.org/pypi/OctoBot-Trading/)
[![Coverage Status](https://coveralls.io/repos/github/Drakkar-Software/OctoBot-Trading/badge.svg?branch=master)](https://coveralls.io/github/Drakkar-Software/OctoBot-Trading?branch=master)
Expand Down
2 changes: 1 addition & 1 deletion octobot_trading/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
# License along with this library.

PROJECT_NAME = "OctoBot-Trading"
VERSION = "2.4.123" # major.minor.revision
VERSION = "2.4.124" # major.minor.revision
35 changes: 24 additions & 11 deletions octobot_trading/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@
# License along with this library.


class MissingFunds(Exception):
class OctoBotExchangeError(Exception):
"""
Parent class of local exceptions raised when communicating with exchanges
"""


class MissingFunds(OctoBotExchangeError):
"""
Raised upon placing an order while having insufficient funds
"""


class MissingMinimalExchangeTradeVolume(Exception):
class MissingMinimalExchangeTradeVolume(OctoBotExchangeError):
"""
Raised when a new order is impossible to create due to exchange minimal funds restrictions
"""
Expand All @@ -33,19 +39,19 @@ class TradingModeIncompatibility(Exception):
"""


class OrderCreationError(Exception):
class OrderCreationError(OctoBotExchangeError):
"""
Raised upon a failed order creation
"""


class OrderEditError(Exception):
class OrderEditError(OctoBotExchangeError):
"""
Raised upon a failed order edition
"""


class OrderCancelError(Exception):
class OrderCancelError(OctoBotExchangeError):
"""
Raised upon a failed order cancel
"""
Expand Down Expand Up @@ -93,37 +99,44 @@ class NotSupported(Exception):
"""


class FailedRequest(Exception):
class FailedRequest(OctoBotExchangeError):
"""
Raised upon a failed request on an exchange API
"""


class RateLimitExceeded(Exception):
class RateLimitExceeded(OctoBotExchangeError):
"""
Raised upon an exchange API rate limit error
"""


class UnavailableOrderTypeForMarketError(Exception):
class UnavailableOrderTypeForMarketError(OctoBotExchangeError):
"""
Raised when an exchange refuses to create a given type of order that should normally be supported
"""


class AuthenticationError(Exception):
class AuthenticationError(OctoBotExchangeError):
"""
Raised when an exchange failed to authenticate
"""


class ExchangeCompliancyError(Exception):
class ExchangeInternalSyncError(OctoBotExchangeError):
"""
Raised when an exchange is returning an error due to its internal sync process
(ex: when an order is filled but portfolio has not yet been updated)
"""


class ExchangeCompliancyError(OctoBotExchangeError):
"""
Raised when an exchange failed to execute the given request because of compliance rules for the current user account
"""


class ExchangeAccountSymbolPermissionError(Exception):
class ExchangeAccountSymbolPermissionError(OctoBotExchangeError):
"""
Raised when an exchange failed to execute the given request because of allowed traded symbols
on the current user account
Expand Down
41 changes: 34 additions & 7 deletions octobot_trading/exchanges/types/rest_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ class RestExchange(abstract_exchange.AbstractExchange):
EXCHANGE_PERMISSION_ERRORS: typing.List[typing.Iterable[str]] = []
# text content of errors due to account compliancy issues
EXCHANGE_COMPLIANCY_ERRORS: typing.List[typing.Iterable[str]] = []
# text content of errors due to exchange internal synch (like when portfolio is not yet up to date after a trade)
EXCHANGE_INTERNAL_SYNC_ERRORS: typing.List[typing.Iterable[str]] = []
# text content of errors due to missing fnuds when creating an order (when not identified as such by ccxt)
EXCHANGE_MISSING_FUNDS_ERRORS: typing.List[typing.Iterable[str]] = []
# text content of errors due to exchange local account permissions (ex: accounts from X country can't trade XYZ)
# text content of errors due to traded assets for account
EXCHANGE_ACCOUNT_TRADED_SYMBOL_PERMISSION_ERRORS: typing.List[typing.Iterable[str]] = []
Expand Down Expand Up @@ -209,15 +213,18 @@ async def _edit_order(self, exchange_order_id: str, order_type: enums.TraderOrde
quantity, price, stop_price, side,
current_price, params)

def _on_missing_funds_err(self, err, order_type, symbol, quantity, price, stop_price):
self.log_order_creation_error(err, order_type, symbol, quantity, price, stop_price)
if self.__class__.PRINT_DEBUG_LOGS:
self.logger.warning(str(err))
raise errors.MissingFunds(err) from err

@contextlib.asynccontextmanager
async def _order_operation(self, order_type, symbol, quantity, price, stop_price):
try:
yield
except ccxt.InsufficientFunds as e:
self.log_order_creation_error(e, order_type, symbol, quantity, price, stop_price)
if self.__class__.PRINT_DEBUG_LOGS:
self.logger.warning(str(e))
raise errors.MissingFunds(e) from e
self._on_missing_funds_err(e, order_type, symbol, quantity, price, stop_price)
except ccxt.NotSupported as err:
raise errors.NotSupported from err
except (errors.AuthenticationError, ccxt.AuthenticationError) as err:
Expand All @@ -237,7 +244,8 @@ async def _order_operation(self, order_type, symbol, quantity, price, stop_price
if self.should_log_on_ddos_exception(e):
self.connector.log_ddos_error(e)
raise errors.FailedRequest(f"Failed to order operation: {e.__class__.__name__} {e}") from e
except errors.ExchangeAccountSymbolPermissionError:
except errors.OctoBotExchangeError:
# custom error: forward it
raise
except Exception as e:
if not self.is_market_open_for_order_type(symbol, order_type):
Expand Down Expand Up @@ -311,9 +319,18 @@ async def _create_order_with_retry(self, order_type, symbol, quantity: decimal.D
raise errors.ExchangeAccountSymbolPermissionError(
f"Error when creating {symbol} {order_type} order on {self.exchange_manager.exchange_name}: {err}"
) from err
if self.is_exchange_internal_sync_error(err):
raise errors.ExchangeInternalSyncError(
f"Error when handling order {err}. Exchange is refusing this order request because of sync error "
f"({err})."
) from err
if self.is_missing_funds_error(err):
self._on_missing_funds_err(err, order_type, symbol, quantity, price, stop_price)
# can be raised when exchange precision/limits rules change
self.logger.debug(f"Failed to create order ({err}) : order_type: {order_type}, symbol: {symbol}. "
f"This might be due to an update on {self.name} market rules. Fetching updated rules.")
self.logger.warning(
f"Failed to create order ({err}) : order_type: {order_type}, symbol: {symbol}. "
f"This might be due to an update on {self.name} market rules. Fetching updated rules."
)
await self.connector.load_symbol_markets(reload=True, market_filter=self.exchange_manager.market_filter)
# retry order creation with updated markets (ccxt will use the updated market values)
return await self._create_specific_order(order_type, symbol, quantity, price=price,
Expand Down Expand Up @@ -948,6 +965,16 @@ def is_exchange_rules_compliancy_error(self, error: BaseException) -> bool:
return exchanges_util.is_error_on_this_type(error, self.EXCHANGE_COMPLIANCY_ERRORS)
return False

def is_exchange_internal_sync_error(self, error: BaseException) -> bool:
if self.EXCHANGE_INTERNAL_SYNC_ERRORS:
return exchanges_util.is_error_on_this_type(error, self.EXCHANGE_INTERNAL_SYNC_ERRORS)
return False

def is_missing_funds_error(self, error: BaseException) -> bool:
if self.EXCHANGE_MISSING_FUNDS_ERRORS:
return exchanges_util.is_error_on_this_type(error, self.EXCHANGE_MISSING_FUNDS_ERRORS)
return False

def is_exchange_account_traded_symbol_permission_error(self, error: BaseException) -> bool:
if self.EXCHANGE_ACCOUNT_TRADED_SYMBOL_PERMISSION_ERRORS:
return exchanges_util.is_error_on_this_type(error, self.EXCHANGE_ACCOUNT_TRADED_SYMBOL_PERMISSION_ERRORS)
Expand Down