Merge branch 'freqtrade:develop' into feat/telegram-profit-direction

This commit is contained in:
qqqqqf
2025-07-15 16:49:02 +08:00
committed by GitHub
21 changed files with 86 additions and 41 deletions

View File

@@ -1,5 +1,10 @@
<!-- Thank you for sending your pull request. But first, have you included <!-- Thank you for sending your pull request. But first, have you included
unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md) unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
Did you use AI to create your changes?
If so, please state it clearly in the PR description (failing to do so may result in your PR being closed).
Also, please do a line by line review of the changes you made before submitting the PR, reverting all unnecessary changes.
--> -->
## Summary ## Summary

View File

@@ -30,7 +30,7 @@ repos:
- types-filelock==3.2.7 - types-filelock==3.2.7
- types-requests==2.32.4.20250611 - types-requests==2.32.4.20250611
- types-tabulate==0.9.0.20241207 - 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 - scipy-stubs==1.16.0.2
- SQLAlchemy==2.0.41 - SQLAlchemy==2.0.41
# stages: [push] # stages: [push]
@@ -44,7 +44,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: 'v0.12.2' rev: 'v0.12.3'
hooks: hooks:
- id: ruff - id: ruff
- id: ruff-format - id: ruff-format
@@ -70,7 +70,7 @@ repos:
)$ )$
- repo: https://github.com/stefmolin/exif-stripper - repo: https://github.com/stefmolin/exif-stripper
rev: 1.0.0 rev: 1.1.0
hooks: hooks:
- id: strip-exif - id: strip-exif

View File

@@ -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. * 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). * 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 ### 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). 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).

View File

@@ -18,10 +18,7 @@ from freqtrade.constants import Config
from freqtrade.enums import ( from freqtrade.enums import (
NON_UTIL_MODES, NON_UTIL_MODES,
TRADE_MODES, TRADE_MODES,
CandleType,
MarginMode,
RunMode, RunMode,
TradingMode,
) )
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.loggers import setup_logging from freqtrade.loggers import setup_logging
@@ -397,11 +394,6 @@ class Configuration:
self._args_to_config( self._args_to_config(
config, argname="trading_mode", logstring="Detected --trading-mode: {}" 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( self._args_to_config(
config, argname="candle_types", logstring="Detected --candle-types: {}" config, argname="candle_types", logstring="Detected --candle-types: {}"
) )

View File

@@ -44,4 +44,5 @@ from freqtrade.exchange.kraken import Kraken
from freqtrade.exchange.kucoin import Kucoin from freqtrade.exchange.kucoin import Kucoin
from freqtrade.exchange.lbank import Lbank from freqtrade.exchange.lbank import Lbank
from freqtrade.exchange.luno import Luno from freqtrade.exchange.luno import Luno
from freqtrade.exchange.modetrade import Modetrade
from freqtrade.exchange.okx import Okx from freqtrade.exchange.okx import Okx

View File

@@ -63,7 +63,7 @@ class Binance(Exchange):
} }
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _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.MARGIN, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.CROSS), (TradingMode.FUTURES, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED), (TradingMode.FUTURES, MarginMode.ISOLATED),

View File

@@ -64,9 +64,9 @@ class Bybit(Exchange):
} }
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _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.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED)
] ]
@property @property

View File

@@ -169,7 +169,8 @@ class Exchange:
_ft_has_futures: FtHas = {} _ft_has_futures: FtHas = {}
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _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__( def __init__(
@@ -198,13 +199,19 @@ class Exchange:
self.loop = self._init_async_loop() self.loop = self._init_async_loop()
self._config: Config = {} self._config: Config = {}
self._config.update(config)
# Leverage properties # Leverage properties
self.trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT) self.trading_mode: TradingMode = TradingMode(
self.margin_mode: MarginMode = ( config.get("trading_mode", self._supported_trading_mode_margin_pairs[0][0])
MarginMode(config.get("margin_mode")) if config.get("margin_mode") else MarginMode.NONE
) )
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) self.liquidation_buffer = config.get("liquidation_buffer", 0.05)
exchange_conf: ExchangeConfig = exchange_config if exchange_config else config["exchange"] exchange_conf: ExchangeConfig = exchange_config if exchange_config else config["exchange"]
@@ -2596,10 +2603,12 @@ class Exchange:
if ticks and cache: if ticks and cache:
idx = -2 if drop_incomplete and len(ticks) > 1 else -1 idx = -2 if drop_incomplete and len(ticks) > 1 else -1
self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[idx][0] 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( 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 cache:
if (pair, timeframe, c_type) in self._klines: if (pair, timeframe, c_type) in self._klines:
old = self._klines[(pair, timeframe, c_type)] old = self._klines[(pair, timeframe, c_type)]

View File

@@ -27,7 +27,7 @@ from freqtrade.exchange.common import (
SUPPORTED_EXCHANGES, SUPPORTED_EXCHANGES,
) )
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_minutes, timeframe_to_prev_date 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 from freqtrade.util import FtPrecise
@@ -110,7 +110,7 @@ def _build_exchange_list_entry(
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
} }
if resolved := exchangeClasses.get(mapped_exchange_name): 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} {"trading_mode": tm.value, "margin_mode": mm.value}
for tm, mm in resolved["class"]._supported_trading_mode_margin_pairs for tm, mm in resolved["class"]._supported_trading_mode_margin_pairs
] ]

View File

@@ -55,10 +55,10 @@ class Gate(Exchange):
} }
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _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.MARGIN, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED) (TradingMode.FUTURES, MarginMode.ISOLATED),
] ]
@retrier @retrier

View File

@@ -28,6 +28,7 @@ class Hyperliquid(Exchange):
"stoploss_on_exchange": False, "stoploss_on_exchange": False,
"exchange_has_overrides": {"fetchTrades": False}, "exchange_has_overrides": {"fetchTrades": False},
"marketOrderRequiresPrice": True, "marketOrderRequiresPrice": True,
"ws_enabled": True,
} }
_ft_has_futures: FtHas = { _ft_has_futures: FtHas = {
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
@@ -40,7 +41,8 @@ class Hyperliquid(Exchange):
} }
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
(TradingMode.FUTURES, MarginMode.ISOLATED) (TradingMode.SPOT, MarginMode.NONE),
(TradingMode.FUTURES, MarginMode.ISOLATED),
] ]
@property @property

View File

@@ -35,7 +35,7 @@ class Kraken(Exchange):
} }
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _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.MARGIN, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.CROSS) # (TradingMode.FUTURES, MarginMode.CROSS)
] ]

View File

@@ -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),
# ]

View File

@@ -49,7 +49,7 @@ class Okx(Exchange):
} }
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _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.MARGIN, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED), (TradingMode.FUTURES, MarginMode.ISOLATED),

View File

@@ -8,4 +8,4 @@ from freqtrade.ft_types.backtest_result_type import (
get_BacktestResultType_default, get_BacktestResultType_default,
) )
from freqtrade.ft_types.plot_annotation_type import AnnotationType 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

View File

@@ -1,3 +1,3 @@
# Requirements for freqtrade client library # Requirements for freqtrade client library
requests==2.32.4 requests==2.32.4
python-rapidjson==1.20 python-rapidjson==1.21

View File

@@ -6,7 +6,7 @@
-r requirements-freqai-rl.txt -r requirements-freqai-rl.txt
-r docs/requirements-docs.txt -r docs/requirements-docs.txt
ruff==0.12.2 ruff==0.12.3
mypy==1.16.1 mypy==1.16.1
pre-commit==4.2.0 pre-commit==4.2.0
pytest==8.4.1 pytest==8.4.1
@@ -29,4 +29,4 @@ types-cachetools==6.0.0.20250525
types-filelock==3.2.7 types-filelock==3.2.7
types-requests==2.32.4.20250611 types-requests==2.32.4.20250611
types-tabulate==0.9.0.20241207 types-tabulate==0.9.0.20241207
types-python-dateutil==2.9.0.20250516 types-python-dateutil==2.9.0.20250708

View File

@@ -1,5 +1,5 @@
numpy==2.3.1 numpy==2.3.1
pandas==2.3.0 pandas==2.3.1
bottleneck==1.5.0 bottleneck==1.5.0
numexpr==2.11.0 numexpr==2.11.0
# Indicator libraries # Indicator libraries
@@ -7,9 +7,9 @@ ft-pandas-ta==0.3.15
ta-lib==0.5.5 ta-lib==0.5.5
technical==1.5.1 technical==1.5.1
ccxt==4.4.92 ccxt==4.4.94
cryptography==45.0.5 cryptography==45.0.5
aiohttp==3.12.13 aiohttp==3.12.14
SQLAlchemy==2.0.41 SQLAlchemy==2.0.41
python-telegram-bot==22.2 python-telegram-bot==22.2
# can't be hard-pinned due to telegram-bot pinning httpx with ~ # can't be hard-pinned due to telegram-bot pinning httpx with ~
@@ -18,7 +18,7 @@ humanize==4.12.3
cachetools==6.1.0 cachetools==6.1.0
requests==2.32.4 requests==2.32.4
urllib3==2.5.0 urllib3==2.5.0
certifi==2025.6.15 certifi==2025.7.14
jsonschema==4.24.0 jsonschema==4.24.0
tabulate==0.9.0 tabulate==0.9.0
pycoingecko==3.2.0 pycoingecko==3.2.0
@@ -28,7 +28,7 @@ rich==14.0.0
pyarrow==20.0.0; platform_machine != 'armv7l' pyarrow==20.0.0; platform_machine != 'armv7l'
# Load ticker files 30% faster # Load ticker files 30% faster
python-rapidjson==1.20 python-rapidjson==1.21
# Properly format api responses # Properly format api responses
orjson==3.10.18 orjson==3.10.18
@@ -36,7 +36,7 @@ orjson==3.10.18
sdnotify==0.3.2 sdnotify==0.3.2
# API Server # API Server
fastapi==0.115.14 fastapi==0.116.1
pydantic==2.11.7 pydantic==2.11.7
uvicorn==0.35.0 uvicorn==0.35.0
pyjwt==2.10.1 pyjwt==2.10.1

View File

@@ -258,6 +258,7 @@ def patch_exchange(
"._supported_trading_mode_margin_pairs", "._supported_trading_mode_margin_pairs",
PropertyMock( PropertyMock(
return_value=[ return_value=[
(TradingMode.SPOT, MarginMode.NONE),
(TradingMode.MARGIN, MarginMode.CROSS), (TradingMode.MARGIN, MarginMode.CROSS),
(TradingMode.MARGIN, MarginMode.ISOLATED), (TradingMode.MARGIN, MarginMode.ISOLATED),
(TradingMode.FUTURES, MarginMode.CROSS), (TradingMode.FUTURES, MarginMode.CROSS),

View File

@@ -10,7 +10,6 @@ from freqtrade.configuration.timerange import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.enums.candletype import CandleType
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from freqtrade.optimize.backtesting import Backtesting 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") 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("exchange", {}).update({"pair_whitelist": ["XRP/USDT:USDT"]})
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update( freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
{"include_timeframes": ["5m", "1h"], "include_corr_pairlist": []} {"include_timeframes": ["5m", "1h"], "include_corr_pairlist": []}

View File

@@ -354,6 +354,7 @@ def test_informative_decorator(mocker, default_conf_usdt, trading_mode):
default_conf_usdt["strategy"] = "InformativeDecoratorTest" default_conf_usdt["strategy"] = "InformativeDecoratorTest"
strategy = StrategyResolver.load_strategy(default_conf_usdt) strategy = StrategyResolver.load_strategy(default_conf_usdt)
exchange = get_patched_exchange(mocker, default_conf_usdt) exchange = get_patched_exchange(mocker, default_conf_usdt)
default_conf_usdt["candle_type_def"] = candle_def
strategy.dp = DataProvider({}, exchange, None) strategy.dp = DataProvider({}, exchange, None)
mocker.patch.object( mocker.patch.object(
strategy.dp, "current_whitelist", return_value=["XRP/USDT", "LTC/USDT", "NEO/USDT"] strategy.dp, "current_whitelist", return_value=["XRP/USDT", "LTC/USDT", "NEO/USDT"]