diff --git a/CHANGELOG.md b/CHANGELOG.md index 2404f7759..fa0dd75a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index f3c8b5890..5fc640ec1 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/octobot_trading/__init__.py b/octobot_trading/__init__.py index e9c70b23c..16860d309 100644 --- a/octobot_trading/__init__.py +++ b/octobot_trading/__init__.py @@ -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 diff --git a/octobot_trading/errors.py b/octobot_trading/errors.py index 078a7436c..9aaa352c7 100644 --- a/octobot_trading/errors.py +++ b/octobot_trading/errors.py @@ -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 """ @@ -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 """ @@ -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 diff --git a/octobot_trading/exchanges/types/rest_exchange.py b/octobot_trading/exchanges/types/rest_exchange.py index 9c8202b51..acc0c56d7 100644 --- a/octobot_trading/exchanges/types/rest_exchange.py +++ b/octobot_trading/exchanges/types/rest_exchange.py @@ -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]] = [] @@ -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: @@ -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): @@ -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, @@ -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)