From 961eaef2a5b7b6f5133e752700035f1e99fee5b5 Mon Sep 17 00:00:00 2001 From: ABS <53243996+ABSllk@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:19:09 +0800 Subject: [PATCH 1/9] fix: align pairlock backtesting expiration condition with live/dry run --- freqtrade/persistence/pairlock_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index 02642c463..94544928d 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -86,7 +86,7 @@ class PairLocks: lock for lock in PairLocks.locks if ( - lock.lock_end_time >= now + lock.lock_end_time > now and lock.active is True and (pair is None or lock.pair == pair) and (side is None or lock.side == "*" or lock.side == side) From 5f585e330fbccec9cc14a8a85a851e6f10ac83d1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Feb 2026 07:20:25 +0100 Subject: [PATCH 2/9] docs: Add discord server Badge --- README.md | 1 + docs/index.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2d3d35e4..e982f8559 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![DOI](https://joss.theoj.org/papers/10.21105/joss.04864/status.svg)](https://doi.org/10.21105/joss.04864) [![codecov](https://codecov.io/gh/freqtrade/freqtrade/branch/develop/graph/badge.svg?token=AD5BG3ATKI)](https://codecov.io/gh/freqtrade/freqtrade) [![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io) +[![Discord Server](https://img.shields.io/badge/Freqtrade_Discord-18181B?logo=discord)](https://discord.gg/p7nuUNVfP7) Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram or webUI. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning. diff --git a/docs/index.md b/docs/index.md index 5bcd71118..17a2f9756 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ [![DOI](https://joss.theoj.org/papers/10.21105/joss.04864/status.svg)](https://doi.org/10.21105/joss.04864) [![codecov](https://codecov.io/gh/freqtrade/freqtrade/branch/develop/graph/badge.svg?token=AD5BG3ATKI)](https://codecov.io/gh/freqtrade/freqtrade) [![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io) - +[![Discord Server](https://img.shields.io/badge/Freqtrade_Discord-18181B?logo=discord)](https://discord.gg/p7nuUNVfP7) [:octicons-star-16: Star](https://github.com/freqtrade/freqtrade){ .md-button .md-button--sm } From 402ef216c3080ae64f35894401a812da37d40c5f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Feb 2026 07:24:36 +0100 Subject: [PATCH 3/9] chore: bump dockerfile from 3.13.11 to 3.13.12 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 138775c4b..ade667989 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.13.11-slim-trixie AS base +FROM python:3.13.12-slim-trixie AS base # Setup env ENV LANG=C.UTF-8 From f0dd3e4ab8e86da074a0d07eca6072c0db02dddf Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 25 Feb 2026 16:17:35 +0900 Subject: [PATCH 4/9] move pair prefixes to constants --- freqtrade/constants.py | 3 +++ freqtrade/plugins/pairlist/MarketCapPairList.py | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 5a57f773f..cef5e1a33 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -239,3 +239,6 @@ IntOrInf = float EntryExecuteMode = Literal["initial", "pos_adjust", "replace"] + +# Prefixes for low-priced coins like 1000PEPE/USDDT:USDT or KPEPE/USDC (hyperliquid) +PairPrefixes = ["1000", "1000000", "1M", "K", "M"] diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index ecb287090..49224c9e6 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -7,6 +7,7 @@ Provides dynamic pair list based on Market Cap import logging import math +from freqtrade.constants import PairPrefixes from freqtrade.exceptions import OperationalException from freqtrade.exchange.exchange_types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting @@ -162,9 +163,6 @@ class MarketCapPairList(IPairList): return pairlist - # Prefixes to test to discover coins like 1000PEPE/USDDT:USDT or KPEPE/USDC (hyperliquid) - prefixes = ("1000", "K") - def resolve_marketcap_pair( self, pair: str, @@ -179,7 +177,7 @@ class MarketCapPairList(IPairList): return pair if pair not in markets: - for prefix in self.prefixes: + for prefix in PairPrefixes: test_prefix = f"{prefix}{pair}" if test_prefix in pairlist: From 7030b0bfcc76ad214626cf5406fc9538e62c6996 Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Thu, 26 Feb 2026 04:13:10 +0000 Subject: [PATCH 5/9] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 170 ++++++++++++++++-- 1 file changed, 154 insertions(+), 16 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 62e4d4e7b..88a274324 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -62743,15 +62743,15 @@ "symbol": "OM/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 2500.0, - "maintenanceMarginRate": 0.025, + "maxNotional": 2000.0, + "maintenanceMarginRate": 0.03, "maxLeverage": 20.0, "info": { "bracket": 1, "initialLeverage": 20, - "notionalCap": 2500, + "notionalCap": 2000, "notionalFloor": 0, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.03, "cum": 0.0 } }, @@ -62759,24 +62759,24 @@ "tier": 2.0, "symbol": "OM/USDT:USDT", "currency": "USDT", - "minNotional": 2500.0, - "maxNotional": 20000.0, + "minNotional": 2000.0, + "maxNotional": 15000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": 2, "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 2500, + "notionalCap": 15000, + "notionalFloor": 2000, "maintMarginRatio": 0.05, - "cum": 62.5 + "cum": 40.0 } }, { "tier": 3.0, "symbol": "OM/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, + "minNotional": 15000.0, "maxNotional": 60000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, @@ -62784,9 +62784,9 @@ "bracket": 3, "initialLeverage": 5, "notionalCap": 60000, - "notionalFloor": 20000, + "notionalFloor": 15000, "maintMarginRatio": 0.1, - "cum": 1062.5 + "cum": 790.0 } }, { @@ -62803,7 +62803,7 @@ "notionalCap": 150000, "notionalFloor": 60000, "maintMarginRatio": 0.125, - "cum": 2562.5 + "cum": 2290.0 } }, { @@ -62820,7 +62820,7 @@ "notionalCap": 300000, "notionalFloor": 150000, "maintMarginRatio": 0.1667, - "cum": 8817.5 + "cum": 8545.0 } }, { @@ -62837,7 +62837,7 @@ "notionalCap": 1000000, "notionalFloor": 300000, "maintMarginRatio": 0.25, - "cum": 33807.5 + "cum": 33535.0 } }, { @@ -62854,7 +62854,7 @@ "notionalCap": 1500000, "notionalFloor": 1000000, "maintMarginRatio": 0.5, - "cum": 283807.5 + "cum": 283535.0 } } ], @@ -64287,6 +64287,144 @@ } } ], + "OPN/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 1, + "initialLeverage": 25, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.02, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 2, + "initialLeverage": 20, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.025, + "cum": 25.0 + } + }, + { + "tier": 3.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 3, + "initialLeverage": 10, + "notionalCap": 25000, + "notionalFloor": 10000, + "maintMarginRatio": 0.05, + "cum": 275.0 + } + }, + { + "tier": 4.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 4, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 25000, + "maintMarginRatio": 0.1, + "cum": 1525.0 + } + }, + { + "tier": 5.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 5, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 2775.0 + } + }, + { + "tier": 6.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 6, + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 6945.0 + } + }, + { + "tier": 7.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 7, + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 27770.0 + } + }, + { + "tier": 8.0, + "symbol": "OPN/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 8, + "initialLeverage": 1, + "notionalCap": 800000, + "notionalFloor": 500000, + "maintMarginRatio": 0.5, + "cum": 152770.0 + } + } + ], "ORBS/USDT:USDT": [ { "tier": 1.0, From ab9b6989be5b2483af0e781580577f762a8b7ce7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Feb 2026 07:01:20 +0100 Subject: [PATCH 6/9] fix: historic precision must also workf or big numbers --- .../data/btanalysis/historic_precision.py | 6 +- tests/data/test_historic_precision.py | 91 ++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis/historic_precision.py b/freqtrade/data/btanalysis/historic_precision.py index 9aa43fa6a..a0a1c144a 100644 --- a/freqtrade/data/btanalysis/historic_precision.py +++ b/freqtrade/data/btanalysis/historic_precision.py @@ -1,3 +1,4 @@ +from numpy import format_float_positional from pandas import DataFrame, Series @@ -11,7 +12,10 @@ def get_tick_size_over_time(candles: DataFrame) -> Series: # count the number of significant digits for the open and close prices for col in ["open", "high", "low", "close"]: candles[f"{col}_count"] = ( - candles[col].round(14).apply("{:.15f}".format).str.extract(r"\.(\d*[1-9])")[0].str.len() + candles[col] + .apply(format_float_positional, precision=14, unique=False, fractional=False, trim="-") + .str.extract(r"\.(\d*[1-9])")[0] + .str.len() ) candles["max_count"] = candles[["open_count", "close_count", "high_count", "low_count"]].max( axis=1 diff --git a/tests/data/test_historic_precision.py b/tests/data/test_historic_precision.py index c4a499180..2972411c6 100644 --- a/tests/data/test_historic_precision.py +++ b/tests/data/test_historic_precision.py @@ -172,7 +172,6 @@ def test_get_tick_size_over_time_small_numbers(): # January should have 5 significant digits (based on 1.23456789 being the most precise value) # which should be converted to 0.00001 - assert result.asof("2020-01-01 00:00:00+00:00") == 0.000000000001 assert result.asof("2020-01-01 00:00:00+00:00") == 0.000000000001 assert result.asof("2020-02-25 00:00:00+00:00") == 0.00000000001 assert result.asof("2020-03-25 00:00:00+00:00") == 0.000000001 @@ -181,3 +180,93 @@ def test_get_tick_size_over_time_small_numbers(): assert result.asof("2025-04-01 00:00:00+00:00") == 0.000000001 assert result.iloc[0] == 0.000000000001 + + +def test_get_tick_size_over_time_big_numbers(): + """ + Test the get_tick_size_over_time function with predefined data + """ + # Create test dataframe with different levels of precision + data = { + "date": [ + Timestamp("2020-01-01 00:00:00", tz=UTC), + Timestamp("2020-01-02 00:00:00", tz=UTC), + Timestamp("2020-01-03 00:00:00", tz=UTC), + Timestamp("2020-01-15 00:00:00", tz=UTC), + Timestamp("2020-01-16 00:00:00", tz=UTC), + Timestamp("2020-01-31 00:00:00", tz=UTC), + Timestamp("2020-02-01 00:00:00", tz=UTC), + Timestamp("2020-02-15 00:00:00", tz=UTC), + Timestamp("2020-03-15 00:00:00", tz=UTC), + ], + "open": [ + 12345.123456, + 12345.1234, + 12345.123, + 12345.12, + 12345.123456, + 12345.1234, + 12345.23456, + 12345, + 12345.234, + ], + "high": [ + 12345.123457, + 12345.1235, + 12345.124, + 12345.13, + 12345.123456, + 12345.1235, + 12345.23457, + 12345, + 12345.234, + ], + "low": [ + 12345.123455, + 12345.1233, + 12345.122, + 12345.11, + 12345.123456, + 12345.1233, + 12345.23455, + 12345, + 12345.234, + ], + "close": [ + 12345.123456, + 12345.1234, + 12345.123, + 12345.12, + 12345.123456, + 12345.1234, + 12345.23456, + 12345, + 12345.234, + ], + "volume": [100, 200, 300, 400, 500, 600, 700, 800, 900], + } + + candles = DataFrame(data) + + # Calculate significant digits + result = get_tick_size_over_time(candles) + + # Check that the result is a pandas Series + assert isinstance(result, pd.Series) + + # Check that we have three months of data (Jan, Feb and March 2020 ) + assert len(result) == 3 + + # Before + assert result.asof("2019-01-01 00:00:00+00:00") is nan + # January should have 5 significant digits (based on 1.23456789 being the most precise value) + # which should be converted to 0.00001 + + assert result.asof("2020-01-01 00:00:00+00:00") == 0.000001 + assert result.asof("2020-02-25 00:00:00+00:00") == 0.00001 + assert result.asof("2020-03-25 00:00:00+00:00") == 0.001 + assert result.asof("2020-04-01 00:00:00+00:00") == 0.001 + # Value far past the last date should be the last value + assert result.asof("2025-04-01 00:00:00+00:00") == 0.001 + + assert result.iloc[0] == 0.000001 From 3a775a5b65dacca37bfd286d1d2223c39c1ec7b3 Mon Sep 17 00:00:00 2001 From: ABS <53243996+ABSllk@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:23:42 +0800 Subject: [PATCH 7/9] test: align backtest_pricecontours expected trades with correctly fixed pairlock expiry duration --- tests/optimize/test_backtesting.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 270c5b33d..0a8d2fb45 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1346,11 +1346,11 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) tests = [ - ["sine", 9], - ["raise", 10], + ["sine", 10], + ["raise", 11], ["lower", 0], - ["sine", 9], - ["raise", 10], + ["sine", 10], + ["raise", 11], ] backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -1380,11 +1380,11 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad (None, "lower", 0), (None, "sine", 35), (None, "raise", 19), - ([{"method": "CooldownPeriod", "stop_duration": 3}], "sine", 9), - ([{"method": "CooldownPeriod", "stop_duration": 3}], "raise", 10), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "sine", 10), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "raise", 11), ([{"method": "CooldownPeriod", "stop_duration": 3}], "lower", 0), - ([{"method": "CooldownPeriod", "stop_duration": 3}], "sine", 9), - ([{"method": "CooldownPeriod", "stop_duration": 3}], "raise", 10), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "sine", 10), + ([{"method": "CooldownPeriod", "stop_duration": 3}], "raise", 11), ], ) def test_backtest_pricecontours( From 166476b21c6d4e52a8d386b9fdef3b30cf9c88df Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Feb 2026 20:17:35 +0100 Subject: [PATCH 8/9] refactor: remove unused method --- .../data/history/datahandlers/idatahandler.py | 22 -------------- tests/data/test_datahandler.py | 30 ------------------- 2 files changed, 52 deletions(-) diff --git a/freqtrade/data/history/datahandlers/idatahandler.py b/freqtrade/data/history/datahandlers/idatahandler.py index 9e7d9de3a..59904e39f 100644 --- a/freqtrade/data/history/datahandlers/idatahandler.py +++ b/freqtrade/data/history/datahandlers/idatahandler.py @@ -70,28 +70,6 @@ class IDataHandler(ABC): if match and len(match.groups()) > 1 ] - @classmethod - def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> list[str]: - """ - Returns a list of all pairs with ohlcv data available in this datadir - for the specified timeframe - :param datadir: Directory to search for ohlcv files - :param timeframe: Timeframe to search pairs for - :param candle_type: Any of the enum CandleType (must match trading mode!) - :return: List of Pairs - """ - candle = "" - if candle_type != CandleType.SPOT: - datadir = datadir.joinpath("futures") - candle = f"-{candle_type}" - ext = cls._get_file_extension() - _tmp = [ - re.search(r"^(\S+)(?=\-" + timeframe + candle + f".{ext})", p.name) - for p in datadir.glob(f"*{timeframe}{candle}.{ext}") - ] - # Check if regex found something and only return these results - return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match] - @abstractmethod def ohlcv_store( self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType diff --git a/tests/data/test_datahandler.py b/tests/data/test_datahandler.py index 9bab3e797..fdbffac87 100644 --- a/tests/data/test_datahandler.py +++ b/tests/data/test_datahandler.py @@ -24,36 +24,6 @@ from freqtrade.exceptions import OperationalException from tests.conftest import log_has, log_has_re -def test_datahandler_ohlcv_get_pairs(testdatadir): - pairs = FeatherDataHandler.ohlcv_get_pairs(testdatadir, "5m", candle_type=CandleType.SPOT) - # Convert to set to avoid failures due to sorting - assert set(pairs) == { - "UNITTEST/BTC", - "XLM/BTC", - "ETH/BTC", - "TRX/BTC", - "LTC/BTC", - "XMR/BTC", - "ZEC/BTC", - "ADA/BTC", - "ETC/BTC", - "NXT/BTC", - "DASH/BTC", - "XRP/ETH", - "BTC/USDT", - "XRP/USDT", - } - - pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, "8m", candle_type=CandleType.SPOT) - assert set(pairs) == {"UNITTEST/BTC"} - - pairs = FeatherDataHandler.ohlcv_get_pairs(testdatadir, "1h", candle_type=CandleType.MARK) - assert set(pairs) == {"UNITTEST/USDT:USDT", "XRP/USDT:USDT"} - - pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, "1h", candle_type=CandleType.FUTURES) - assert set(pairs) == {"XRP/USDT:USDT"} - - @pytest.mark.parametrize( "filename,pair,timeframe,candletype", [ From 09c60b95437297547c0af612b1d72b0e9cb1ffbe Mon Sep 17 00:00:00 2001 From: Stefano Date: Fri, 27 Feb 2026 14:17:46 +0900 Subject: [PATCH 9/9] remove M --- freqtrade/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index cef5e1a33..1e93e1f7a 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -241,4 +241,4 @@ IntOrInf = float EntryExecuteMode = Literal["initial", "pos_adjust", "replace"] # Prefixes for low-priced coins like 1000PEPE/USDDT:USDT or KPEPE/USDC (hyperliquid) -PairPrefixes = ["1000", "1000000", "1M", "K", "M"] +PairPrefixes = ["1000", "1000000", "1M", "K"]