Merge branch 'develop' into fix/remove_deprecation_warning

This commit is contained in:
Matthias
2025-10-20 20:23:08 +02:00
committed by GitHub
15 changed files with 268 additions and 77 deletions

View File

@@ -1,10 +1,10 @@
from datetime import timedelta
from unittest.mock import MagicMock
from unittest.mock import MagicMock, PropertyMock
import pytest
from freqtrade.enums import CandleType
from freqtrade.exceptions import RetryableOrderError
from freqtrade.enums import CandleType, MarginMode, TradingMode
from freqtrade.exceptions import OperationalException, RetryableOrderError
from freqtrade.exchange.common import API_RETRY_COUNT
from freqtrade.util import dt_now, dt_ts
from tests.conftest import EXMS, get_patched_exchange
@@ -120,3 +120,76 @@ def test_bitget_ohlcv_candle_limit(mocker, default_conf_usdt):
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == length
assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == length
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 200
def test_additional_exchange_init_bitget(default_conf, mocker):
default_conf["dry_run"] = False
default_conf["trading_mode"] = TradingMode.FUTURES
default_conf["margin_mode"] = MarginMode.ISOLATED
api_mock = MagicMock()
api_mock.set_position_mode = MagicMock(return_value={})
get_patched_exchange(mocker, default_conf, exchange="bitget", api_mock=api_mock)
assert api_mock.set_position_mode.call_count == 1
ccxt_exceptionhandlers(
mocker, default_conf, api_mock, "bitget", "additional_exchange_init", "set_position_mode"
)
def test_dry_run_liquidation_price_cross_bitget(default_conf, mocker):
default_conf["dry_run"] = True
default_conf["trading_mode"] = TradingMode.FUTURES
default_conf["margin_mode"] = MarginMode.CROSS
api_mock = MagicMock()
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", MagicMock(return_value=(0.005, 0.0)))
exchange = get_patched_exchange(mocker, default_conf, exchange="bitget", api_mock=api_mock)
with pytest.raises(
OperationalException, match="Freqtrade currently only supports isolated futures for bitget"
):
exchange.dry_run_liquidation_price(
"ETH/USDT:USDT",
100_000,
False,
0.1,
100,
10,
100,
[],
)
def test__lev_prep_bitget(default_conf, mocker):
api_mock = MagicMock()
api_mock.set_margin_mode = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={"setMarginMode": True, "setLeverage": True})
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bitget")
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
assert api_mock.set_margin_mode.call_count == 0
assert api_mock.set_leverage.call_count == 0
# test in futures mode
api_mock.set_margin_mode.reset_mock()
api_mock.set_leverage.reset_mock()
default_conf["dry_run"] = False
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bitget")
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
assert api_mock.set_margin_mode.call_count == 0
assert api_mock.set_leverage.call_count == 1
api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=3.2)
api_mock.reset_mock()
exchange._lev_prep("BTC/USDC:USDC", 19.99, "sell")
assert api_mock.set_margin_mode.call_count == 0
assert api_mock.set_leverage.call_count == 1
api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=19.99)

View File

@@ -5942,29 +5942,32 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
assert exchange.get_max_leverage("TIA/USDT:USDT", 130.008) == 40
@pytest.mark.parametrize("exchange_name", ["binance", "kraken", "gate", "okx", "bybit"])
def test__get_params(mocker, default_conf, exchange_name):
@pytest.mark.parametrize(
"exchange_name, add_params_spot, add_params_futures",
[
("binance", {}, {}),
("kraken", {}, {"leverage": 3.0}),
("gate", {}, {}),
("okx", {}, {"tdMode": "isolated", "posSide": "net"}),
("bybit", {}, {"position_idx": 0}),
("bitget", {}, {"marginMode": "isolated"}),
],
)
def test__get_params(mocker, default_conf, exchange_name, add_params_spot, add_params_futures):
api_mock = MagicMock()
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange._params = {"test": True}
params1 = {"test": True}
params2 = {
params1.update(add_params_spot)
params_fut = {
"test": True,
"timeInForce": "IOC",
"reduceOnly": True,
}
if exchange_name == "kraken":
params2["leverage"] = 3.0
if exchange_name == "okx":
params2["tdMode"] = "isolated"
params2["posSide"] = "net"
if exchange_name == "bybit":
params2["position_idx"] = 0
params_fut.update(add_params_futures)
assert (
exchange._get_params(
@@ -6012,7 +6015,7 @@ def test__get_params(mocker, default_conf, exchange_name):
time_in_force="IOC",
leverage=3.0,
)
== params2
== params_fut
)

View File

@@ -422,6 +422,10 @@ EXCHANGES = {
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 1000,
"futures": True,
"futures_pair": "BTC/USDT:USDT",
"leverage_tiers_public": True,
"leverage_in_spot_market": True,
},
"coinex": {
"pair": "BTC/USDT",

View File

@@ -1132,7 +1132,9 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path, fee) -> None:
def test_in_strategy_auto_hyperopt_with_parallel(
mocker, hyperopt_conf, tmp_path, fee, caplog
) -> None:
mocker.patch(f"{EXMS}.validate_config", MagicMock())
mocker.patch(f"{EXMS}.get_fee", fee)
mocker.patch(f"{EXMS}.reload_markets")
@@ -1175,6 +1177,8 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path
assert len(list(buy_rsi_range)) == 51
hyperopt.start()
# Test logs from parallel workers are shown.
assert log_has("Test: Bot loop started", caplog)
def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmp_path, fee) -> None:

View File

@@ -1,5 +1,7 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import logging
from pandas import DataFrame
from strategy_test_v3 import StrategyTestV3
@@ -7,6 +9,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter
logger = logging.getLogger(__name__)
class HyperoptableStrategy(StrategyTestV3):
"""
Default Strategy provided by freqtrade bot.
@@ -16,6 +21,7 @@ class HyperoptableStrategy(StrategyTestV3):
for samples and inspiration.
"""
INTERFACE_VERSION = 3
buy_params = {
"buy_rsi": 35,
# Intentionally not specified, so "default" is tested
@@ -54,34 +60,13 @@ class HyperoptableStrategy(StrategyTestV3):
def bot_loop_start(self, **kwargs):
self.bot_loop_started = True
logger.info("Test: Bot loop started")
def bot_start(self, **kwargs) -> None:
"""
Parameters can also be defined here ...
"""
self.bot_started = True
self.buy_rsi = IntParameter([0, 50], default=30, space="buy")
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["rsi"] < self.buy_rsi.value)
@@ -90,18 +75,12 @@ class HyperoptableStrategy(StrategyTestV3):
& (dataframe["plus_di"] > self.buy_plusdi.value)
)
| ((dataframe["adx"] > 65) & (dataframe["plus_di"] > self.buy_plusdi.value)),
"buy",
"enter_long",
] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with sell column
"""
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(
@@ -112,6 +91,6 @@ class HyperoptableStrategy(StrategyTestV3):
& (dataframe["minus_di"] > 0)
)
| ((dataframe["adx"] > 70) & (dataframe["minus_di"] > self.sell_minusdi.value)),
"sell",
"exit_long",
] = 1
return dataframe