mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-03-05 13:24:20 +00:00
Merge branch 'freqtrade:develop' into main-stash
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[](https://doi.org/10.21105/joss.04864)
|
||||
[](https://codecov.io/gh/freqtrade/freqtrade)
|
||||
[](https://www.freqtrade.io)
|
||||
[](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.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://doi.org/10.21105/joss.04864)
|
||||
[](https://codecov.io/gh/freqtrade/freqtrade)
|
||||
[](https://www.freqtrade.io)
|
||||
|
||||
[](https://discord.gg/p7nuUNVfP7)
|
||||
|
||||
<!-- GitHub action buttons -->
|
||||
[:octicons-star-16: Star](https://github.com/freqtrade/freqtrade){ .md-button .md-button--sm }
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
[
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user