Merge pull request #11711 from freqtrade/feat/bt_historic_precision
Improve price precision logic and add significant digits calculationpull/11739/head
commit
c3f6aa17ee
@ -0,0 +1,27 @@
|
||||
from pandas import DataFrame, Series
|
||||
|
||||
|
||||
def get_tick_size_over_time(candles: DataFrame) -> Series:
|
||||
"""
|
||||
Calculate the number of significant digits for candles over time.
|
||||
It's using the Monthly maximum of the number of significant digits for each month.
|
||||
:param candles: DataFrame with OHLCV data
|
||||
:return: Series with the average number of significant digits for each month
|
||||
"""
|
||||
# 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).astype(str).str.extract(r"\.(\d*[1-9])")[0].str.len()
|
||||
)
|
||||
candles["max_count"] = candles[["open_count", "close_count", "high_count", "low_count"]].max(
|
||||
axis=1
|
||||
)
|
||||
|
||||
candles1 = candles.set_index("date", drop=True)
|
||||
# Group by month and calculate the average number of significant digits
|
||||
monthly_count_avg1 = candles1["max_count"].resample("MS").max()
|
||||
# monthly_open_count_avg
|
||||
# convert monthly_open_count_avg from 5.0 to 0.00001, 4.0 to 0.0001, ...
|
||||
monthly_open_count_avg = 1 / 10**monthly_count_avg1
|
||||
|
||||
return monthly_open_count_avg
|
||||
@ -0,0 +1,92 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
|
||||
from datetime import timezone
|
||||
|
||||
import pandas as pd
|
||||
from numpy import nan
|
||||
from pandas import DataFrame, Timestamp
|
||||
|
||||
from freqtrade.data.btanalysis.historic_precision import get_tick_size_over_time
|
||||
|
||||
|
||||
def test_get_tick_size_over_time():
|
||||
"""
|
||||
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=timezone.utc),
|
||||
Timestamp("2020-01-02 00:00:00", tz=timezone.utc),
|
||||
Timestamp("2020-01-03 00:00:00", tz=timezone.utc),
|
||||
Timestamp("2020-01-15 00:00:00", tz=timezone.utc),
|
||||
Timestamp("2020-01-16 00:00:00", tz=timezone.utc),
|
||||
Timestamp("2020-01-31 00:00:00", tz=timezone.utc),
|
||||
Timestamp("2020-02-01 00:00:00", tz=timezone.utc),
|
||||
Timestamp("2020-02-15 00:00:00", tz=timezone.utc),
|
||||
Timestamp("2020-03-15 00:00:00", tz=timezone.utc),
|
||||
],
|
||||
"open": [1.23456, 1.234, 1.23, 1.2, 1.23456, 1.234, 2.3456, 2.34, 2.34],
|
||||
"high": [1.23457, 1.235, 1.24, 1.3, 1.23456, 1.235, 2.3457, 2.34, 2.34],
|
||||
"low": [1.23455, 1.233, 1.22, 1.1, 1.23456, 1.233, 2.3455, 2.34, 2.34],
|
||||
"close": [1.23456, 1.234, 1.23, 1.2, 1.23456, 1.234, 2.3456, 2.34, 2.34],
|
||||
"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.00001
|
||||
assert result.asof("2020-01-01 00:00:00+00:00") == 0.00001
|
||||
assert result.asof("2020-02-25 00:00:00+00:00") == 0.0001
|
||||
assert result.asof("2020-03-25 00:00:00+00:00") == 0.01
|
||||
assert result.asof("2020-04-01 00:00:00+00:00") == 0.01
|
||||
# Value far past the last date should be the last value
|
||||
assert result.asof("2025-04-01 00:00:00+00:00") == 0.01
|
||||
|
||||
assert result.iloc[0] == 0.00001
|
||||
|
||||
|
||||
def test_get_tick_size_over_time_real_data(testdatadir):
|
||||
"""
|
||||
Test the get_tick_size_over_time function with real data from the testdatadir
|
||||
"""
|
||||
from freqtrade.data.history import load_pair_history
|
||||
|
||||
# Load some test data from the testdata directory
|
||||
pair = "UNITTEST/BTC"
|
||||
timeframe = "1m"
|
||||
|
||||
candles = load_pair_history(
|
||||
datadir=testdatadir,
|
||||
pair=pair,
|
||||
timeframe=timeframe,
|
||||
)
|
||||
|
||||
# Make sure we have test data
|
||||
assert not candles.empty, "No test data found, cannot run test"
|
||||
|
||||
# Calculate significant digits
|
||||
result = get_tick_size_over_time(candles)
|
||||
|
||||
assert isinstance(result, pd.Series)
|
||||
|
||||
# Verify that all values are between 0 and 1 (valid precision values)
|
||||
assert all(result > 0)
|
||||
assert all(result < 1)
|
||||
|
||||
assert all(result <= 0.0001)
|
||||
assert all(result >= 0.00000001)
|
||||
Loading…
Reference in new issue