diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 90a10d4da..2b957abcf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,10 @@ ## Summary diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 081056105..64fbdbb89 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: - types-filelock==3.2.7 - types-requests==2.32.4.20250611 - types-tabulate==0.9.0.20241207 - - types-python-dateutil==2.9.0.20250516 + - types-python-dateutil==2.9.0.20250708 - scipy-stubs==1.16.0.2 - SQLAlchemy==2.0.41 # stages: [push] @@ -44,7 +44,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.12.2' + rev: 'v0.12.3' hooks: - id: ruff - id: ruff-format @@ -70,7 +70,7 @@ repos: )$ - repo: https://github.com/stefmolin/exif-stripper - rev: 1.0.0 + rev: 1.1.0 hooks: - id: strip-exif diff --git a/docs/faq.md b/docs/faq.md index 07db8b6d7..9085a9226 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -159,6 +159,14 @@ This warning can point to one of the below problems: * Barely traded pair -> Check the pair on the exchange webpage, look at the timeframe your strategy uses. If the pair does not have any volume in some candles (usually visualized with a "volume 0" bar, and a "_" as candle), this pair did not have any trades in this timeframe. These pairs should ideally be avoided, as they can cause problems with order-filling. * API problem -> API returns wrong data (this only here for completeness, and should not happen with supported exchanges). +### I get the message "Couldn't reuse watch for xxx" in the log + +This is an informational message that the bot tried to use candles from the websocket, but the exchange didn't provide the right information. +This can happen if there was an interruption to the websocket connection - or if the pair didn't have any trades happen in the timeframe you are using. + +Freqtrade will handle this gracefully by falling back to the REST api. +While this makes the iteration slightly slower (due to the REST Api call) - it will not cause any problems to the bot's operation. + ### I'm getting the "Exchange XXX does not support market orders." message and cannot run my strategy As the message says, your exchange does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Your strategy was probably written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Gate.io). diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index b9da05d0c..c5e9b56e9 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -18,10 +18,7 @@ from freqtrade.constants import Config from freqtrade.enums import ( NON_UTIL_MODES, TRADE_MODES, - CandleType, - MarginMode, RunMode, - TradingMode, ) from freqtrade.exceptions import OperationalException from freqtrade.loggers import setup_logging @@ -397,11 +394,6 @@ class Configuration: self._args_to_config( config, argname="trading_mode", logstring="Detected --trading-mode: {}" ) - config["candle_type_def"] = CandleType.get_default( - config.get("trading_mode", "spot") or "spot" - ) - config["trading_mode"] = TradingMode(config.get("trading_mode", "spot") or "spot") - config["margin_mode"] = MarginMode(config.get("margin_mode", "") or "") self._args_to_config( config, argname="candle_types", logstring="Detected --candle-types: {}" ) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 8fd2ffb95..36aadcad1 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -44,4 +44,5 @@ from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.kucoin import Kucoin from freqtrade.exchange.lbank import Lbank from freqtrade.exchange.luno import Luno +from freqtrade.exchange.modetrade import Modetrade from freqtrade.exchange.okx import Okx diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index c9ed83b50..1a6bcc18f 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -63,7 +63,7 @@ class Binance(Exchange): } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ - # TradingMode.SPOT always supported and not required in this list + (TradingMode.SPOT, MarginMode.NONE), # (TradingMode.MARGIN, MarginMode.CROSS), (TradingMode.FUTURES, MarginMode.CROSS), (TradingMode.FUTURES, MarginMode.ISOLATED), diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index dfadee4b1..56aacf31f 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -64,9 +64,9 @@ class Bybit(Exchange): } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ - # TradingMode.SPOT always supported and not required in this list + (TradingMode.SPOT, MarginMode.NONE), + (TradingMode.FUTURES, MarginMode.ISOLATED), # (TradingMode.FUTURES, MarginMode.CROSS), - (TradingMode.FUTURES, MarginMode.ISOLATED) ] @property diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 31ece5ba4..5c6f2cc1e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -169,7 +169,8 @@ class Exchange: _ft_has_futures: FtHas = {} _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ - # TradingMode.SPOT always supported and not required in this list + # Non-defined exchanges only support spot mode. + (TradingMode.SPOT, MarginMode.NONE), ] def __init__( @@ -198,13 +199,19 @@ class Exchange: self.loop = self._init_async_loop() self._config: Config = {} - self._config.update(config) - # Leverage properties - self.trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT) - self.margin_mode: MarginMode = ( - MarginMode(config.get("margin_mode")) if config.get("margin_mode") else MarginMode.NONE + self.trading_mode: TradingMode = TradingMode( + config.get("trading_mode", self._supported_trading_mode_margin_pairs[0][0]) ) + self.margin_mode: MarginMode = MarginMode( + MarginMode(config.get("margin_mode")) + if config.get("margin_mode") + else self._supported_trading_mode_margin_pairs[0][1] + ) + config["trading_mode"] = self.trading_mode + config["margin_mode"] = self.margin_mode + config["candle_type_def"] = CandleType.get_default(self.trading_mode) + self._config.update(config) self.liquidation_buffer = config.get("liquidation_buffer", 0.05) exchange_conf: ExchangeConfig = exchange_config if exchange_config else config["exchange"] @@ -2596,10 +2603,12 @@ class Exchange: if ticks and cache: idx = -2 if drop_incomplete and len(ticks) > 1 else -1 self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[idx][0] - # keeping parsed dataframe in cache + has_cache = cache and (pair, timeframe, c_type) in self._klines + # in case of existing cache, fill_missing happens after concatenation ohlcv_df = ohlcv_to_dataframe( - ticks, timeframe, pair=pair, fill_missing=True, drop_incomplete=drop_incomplete + ticks, timeframe, pair=pair, fill_missing=not has_cache, drop_incomplete=drop_incomplete ) + # keeping parsed dataframe in cache if cache: if (pair, timeframe, c_type) in self._klines: old = self._klines[(pair, timeframe, c_type)] diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index a400fa123..550aab2c3 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -27,7 +27,7 @@ from freqtrade.exchange.common import ( SUPPORTED_EXCHANGES, ) from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_minutes, timeframe_to_prev_date -from freqtrade.ft_types import ValidExchangesType +from freqtrade.ft_types import TradeModeType, ValidExchangesType from freqtrade.util import FtPrecise @@ -110,7 +110,7 @@ def _build_exchange_list_entry( "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } if resolved := exchangeClasses.get(mapped_exchange_name): - supported_modes = [{"trading_mode": "spot", "margin_mode": ""}] + [ + supported_modes: list[TradeModeType] = [ {"trading_mode": tm.value, "margin_mode": mm.value} for tm, mm in resolved["class"]._supported_trading_mode_margin_pairs ] diff --git a/freqtrade/exchange/gate.py b/freqtrade/exchange/gate.py index db53fdd84..2bdb2fbc8 100644 --- a/freqtrade/exchange/gate.py +++ b/freqtrade/exchange/gate.py @@ -55,10 +55,10 @@ class Gate(Exchange): } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ - # TradingMode.SPOT always supported and not required in this list + (TradingMode.SPOT, MarginMode.NONE), # (TradingMode.MARGIN, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS), - (TradingMode.FUTURES, MarginMode.ISOLATED) + (TradingMode.FUTURES, MarginMode.ISOLATED), ] @retrier diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index ae1de3b64..a880a60df 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -28,6 +28,7 @@ class Hyperliquid(Exchange): "stoploss_on_exchange": False, "exchange_has_overrides": {"fetchTrades": False}, "marketOrderRequiresPrice": True, + "ws_enabled": True, } _ft_has_futures: FtHas = { "stoploss_on_exchange": True, @@ -40,7 +41,8 @@ class Hyperliquid(Exchange): } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ - (TradingMode.FUTURES, MarginMode.ISOLATED) + (TradingMode.SPOT, MarginMode.NONE), + (TradingMode.FUTURES, MarginMode.ISOLATED), ] @property diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index e3158a851..d113aedd8 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -35,7 +35,7 @@ class Kraken(Exchange): } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ - # TradingMode.SPOT always supported and not required in this list + (TradingMode.SPOT, MarginMode.NONE), # (TradingMode.MARGIN, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS) ] diff --git a/freqtrade/exchange/modetrade.py b/freqtrade/exchange/modetrade.py new file mode 100644 index 000000000..9c6baa9c9 --- /dev/null +++ b/freqtrade/exchange/modetrade.py @@ -0,0 +1,27 @@ +import logging + +# from freqtrade.enums import MarginMode, TradingMode +from freqtrade.exchange import Exchange +from freqtrade.exchange.exchange_types import FtHas + + +logger = logging.getLogger(__name__) + + +class Modetrade(Exchange): + """ + MOdetrade exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + + Please note that this exchange is not included in the list of exchanges + officially supported by the Freqtrade development team. So some features + may still not work as expected. + """ + + _ft_has: FtHas = { + "always_require_api_keys": True, # Requires API keys to fetch candles + } + + # _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ + # (TradingMode.FUTURES, MarginMode.ISOLATED), + # ] diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index bd25fccea..a30769b51 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -49,7 +49,7 @@ class Okx(Exchange): } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ - # TradingMode.SPOT always supported and not required in this list + (TradingMode.SPOT, MarginMode.NONE), # (TradingMode.MARGIN, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS), (TradingMode.FUTURES, MarginMode.ISOLATED), diff --git a/freqtrade/ft_types/__init__.py b/freqtrade/ft_types/__init__.py index 6cd74f7b2..87e2df4dd 100644 --- a/freqtrade/ft_types/__init__.py +++ b/freqtrade/ft_types/__init__.py @@ -8,4 +8,4 @@ from freqtrade.ft_types.backtest_result_type import ( get_BacktestResultType_default, ) from freqtrade.ft_types.plot_annotation_type import AnnotationType -from freqtrade.ft_types.valid_exchanges_type import ValidExchangesType +from freqtrade.ft_types.valid_exchanges_type import TradeModeType, ValidExchangesType diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt index 433c0f392..ceca8a373 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library requests==2.32.4 -python-rapidjson==1.20 +python-rapidjson==1.21 diff --git a/requirements-dev.txt b/requirements-dev.txt index e8b49e6b3..9bc51d288 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ -r requirements-freqai-rl.txt -r docs/requirements-docs.txt -ruff==0.12.2 +ruff==0.12.3 mypy==1.16.1 pre-commit==4.2.0 pytest==8.4.1 @@ -29,4 +29,4 @@ types-cachetools==6.0.0.20250525 types-filelock==3.2.7 types-requests==2.32.4.20250611 types-tabulate==0.9.0.20241207 -types-python-dateutil==2.9.0.20250516 +types-python-dateutil==2.9.0.20250708 diff --git a/requirements.txt b/requirements.txt index a41a39cfa..f862b5b02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==2.3.1 -pandas==2.3.0 +pandas==2.3.1 bottleneck==1.5.0 numexpr==2.11.0 # Indicator libraries @@ -7,9 +7,9 @@ ft-pandas-ta==0.3.15 ta-lib==0.5.5 technical==1.5.1 -ccxt==4.4.92 +ccxt==4.4.94 cryptography==45.0.5 -aiohttp==3.12.13 +aiohttp==3.12.14 SQLAlchemy==2.0.41 python-telegram-bot==22.2 # can't be hard-pinned due to telegram-bot pinning httpx with ~ @@ -18,7 +18,7 @@ humanize==4.12.3 cachetools==6.1.0 requests==2.32.4 urllib3==2.5.0 -certifi==2025.6.15 +certifi==2025.7.14 jsonschema==4.24.0 tabulate==0.9.0 pycoingecko==3.2.0 @@ -28,7 +28,7 @@ rich==14.0.0 pyarrow==20.0.0; platform_machine != 'armv7l' # Load ticker files 30% faster -python-rapidjson==1.20 +python-rapidjson==1.21 # Properly format api responses orjson==3.10.18 @@ -36,7 +36,7 @@ orjson==3.10.18 sdnotify==0.3.2 # API Server -fastapi==0.115.14 +fastapi==0.116.1 pydantic==2.11.7 uvicorn==0.35.0 pyjwt==2.10.1 diff --git a/tests/conftest.py b/tests/conftest.py index d1cbac549..20826cef9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -258,6 +258,7 @@ def patch_exchange( "._supported_trading_mode_margin_pairs", PropertyMock( return_value=[ + (TradingMode.SPOT, MarginMode.NONE), (TradingMode.MARGIN, MarginMode.CROSS), (TradingMode.MARGIN, MarginMode.ISOLATED), (TradingMode.FUTURES, MarginMode.CROSS), diff --git a/tests/freqai/test_freqai_backtesting.py b/tests/freqai/test_freqai_backtesting.py index c9be6ea05..fd825e106 100644 --- a/tests/freqai/test_freqai_backtesting.py +++ b/tests/freqai/test_freqai_backtesting.py @@ -10,7 +10,6 @@ from freqtrade.configuration.timerange import TimeRange from freqtrade.data import history from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import RunMode -from freqtrade.enums.candletype import CandleType from freqtrade.exceptions import OperationalException from freqtrade.freqai.data_kitchen import FreqaiDataKitchen from freqtrade.optimize.backtesting import Backtesting @@ -142,7 +141,7 @@ def test_freqai_backtest_consistent_timerange(mocker, freqai_conf): gbs = mocker.patch("freqtrade.optimize.backtesting.generate_backtest_stats") - freqai_conf["candle_type_def"] = CandleType.FUTURES + freqai_conf["trading_mode"] = "futures" freqai_conf.get("exchange", {}).update({"pair_whitelist": ["XRP/USDT:USDT"]}) freqai_conf.get("freqai", {}).get("feature_parameters", {}).update( {"include_timeframes": ["5m", "1h"], "include_corr_pairlist": []} diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index aeb46a4e4..605579191 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -354,6 +354,7 @@ def test_informative_decorator(mocker, default_conf_usdt, trading_mode): default_conf_usdt["strategy"] = "InformativeDecoratorTest" strategy = StrategyResolver.load_strategy(default_conf_usdt) exchange = get_patched_exchange(mocker, default_conf_usdt) + default_conf_usdt["candle_type_def"] = candle_def strategy.dp = DataProvider({}, exchange, None) mocker.patch.object( strategy.dp, "current_whitelist", return_value=["XRP/USDT", "LTC/USDT", "NEO/USDT"]