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 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 } diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 68fada77e..659b90f72 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -240,3 +240,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"] 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/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/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, 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) 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: 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", [ 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 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(