Merge branch 'develop' into feature/fetch-public-trades

This commit is contained in:
Matthias
2024-04-05 06:35:35 +02:00
99 changed files with 5849 additions and 1672 deletions

View File

@@ -28,9 +28,10 @@ from freqtrade.data.converter.orderflow import _calculate_ohlcv_candle_start_and
from freqtrade.data.converter.trade_converter import (trades_df_remove_duplicates,
trades_dict_to_list, trades_list_to_df)
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, RunMode, TradingMode
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
InvalidOrderException, OperationalException, PricingError,
RetryableOrderError, TemporaryError)
from freqtrade.exceptions import (ConfigurationError, DDosProtection, ExchangeError,
InsufficientFundsError, InvalidOrderException,
OperationalException, PricingError, RetryableOrderError,
TemporaryError)
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, remove_exchange_credentials,
retrier, retrier_async)
from freqtrade.exchange.exchange_utils import (ROUND, ROUND_DOWN, ROUND_UP, CcxtModuleType,
@@ -93,6 +94,8 @@ class Exchange:
"order_props_in_contracts": ['amount', 'filled', 'remaining'],
# Override createMarketBuyOrderRequiresPrice where ccxt has it wrong
"marketOrderRequiresPrice": False,
"exchange_has_overrides": {}, # Dictionary overriding ccxt's "has".
# Expected to be in the format {"fetchOHLCV": True} or {"fetchOHLCV": False}
}
_ft_has: Dict = {}
_ft_has_futures: Dict = {}
@@ -547,7 +550,7 @@ class Exchange:
)
quote_currencies = self.get_quote_currencies()
if stake_currency not in quote_currencies:
raise OperationalException(
raise ConfigurationError(
f"{stake_currency} is not available as stake on {self.name}. "
f"Available currencies are: {', '.join(quote_currencies)}")
@@ -615,7 +618,7 @@ class Exchange:
f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
if timeframe and (timeframe not in self.timeframes):
raise OperationalException(
raise ConfigurationError(
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
if (
@@ -623,7 +626,7 @@ class Exchange:
and self._config['runmode'] != RunMode.UTIL_EXCHANGE
and timeframe_to_minutes(timeframe) < 1
):
raise OperationalException("Timeframes < 1m are currently not supported by Freqtrade.")
raise ConfigurationError("Timeframes < 1m are currently not supported by Freqtrade.")
def validate_ordertypes(self, order_types: Dict) -> None:
"""
@@ -631,7 +634,7 @@ class Exchange:
"""
if any(v == 'market' for k, v in order_types.items()):
if not self.exchange_has('createMarketOrder'):
raise OperationalException(
raise ConfigurationError(
f'Exchange {self.name} does not support market orders.')
self.validate_stop_ordertypes(order_types)
@@ -641,7 +644,7 @@ class Exchange:
"""
if (order_types.get("stoploss_on_exchange")
and not self._ft_has.get("stoploss_on_exchange", False)):
raise OperationalException(
raise ConfigurationError(
f'On exchange stoploss is not supported for {self.name}.'
)
if self.trading_mode == TradingMode.FUTURES:
@@ -651,17 +654,17 @@ class Exchange:
and 'stoploss_price_type' in order_types
and order_types['stoploss_price_type'] not in price_mapping
):
raise OperationalException(
raise ConfigurationError(
f'On exchange stoploss price type is not supported for {self.name}.'
)
def validate_pricing(self, pricing: Dict) -> None:
if pricing.get('use_order_book', False) and not self.exchange_has('fetchL2OrderBook'):
raise OperationalException(f'Orderbook not available for {self.name}.')
raise ConfigurationError(f'Orderbook not available for {self.name}.')
if (not pricing.get('use_order_book', False) and (
not self.exchange_has('fetchTicker')
or not self._ft_has['tickers_have_price'])):
raise OperationalException(f'Ticker pricing not available for {self.name}.')
raise ConfigurationError(f'Ticker pricing not available for {self.name}.')
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
"""
@@ -669,7 +672,7 @@ class Exchange:
"""
if any(v.upper() not in self._ft_has["order_time_in_force"]
for k, v in order_time_in_force.items()):
raise OperationalException(
raise ConfigurationError(
f'Time in force policies are not supported for {self.name} yet.')
def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int:
@@ -691,12 +694,12 @@ class Exchange:
if required_candle_call_count > 5:
# Only allow 5 calls per pair to somewhat limit the impact
raise OperationalException(
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, "
"which is more than 5x "
f"the amount of candles {self.name} provides for {timeframe}.")
elif required_candle_call_count > 1:
raise OperationalException(
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, which is more than "
f"the amount of candles {self.name} provides for {timeframe}.")
if required_candle_call_count > 1:
@@ -737,6 +740,8 @@ class Exchange:
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
:return: bool
"""
if endpoint in self._ft_has.get('exchange_has_overrides', {}):
return self._ft_has['exchange_has_overrides'][endpoint]
return endpoint in self._api.has and self._api.has[endpoint]
def get_precision_amount(self, pair: str) -> Optional[float]:
@@ -3261,3 +3266,4 @@ class Exchange:
# describes the min amt for a tier, and the lowest tier will always go down to 0
else:
raise ExchangeError(f"Cannot get maintenance ratio using {self.name}")
raise ExchangeError(f"Cannot get maintenance ratio using {self.name}")