feat: use hsitoric price precision for improved accuracy

closes #11203
This commit is contained in:
Matthias
2025-05-03 20:37:57 +02:00
parent 7d3fa41911
commit 5a6b43da46

View File

@@ -9,14 +9,18 @@ from collections import defaultdict
from copy import deepcopy
from datetime import datetime, timedelta
from numpy import nan
from pandas import DataFrame
from numpy import isnan, nan
from pandas import DataFrame, Series
from freqtrade import constants
from freqtrade.configuration import TimeRange, validate_config_consistency
from freqtrade.constants import DATETIME_PRINT_FORMAT, Config, IntOrInf, LongShort
from freqtrade.data import history
from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe
from freqtrade.data.btanalysis import (
find_existing_backtest_stats,
get_significant_digits_over_time,
trade_list_to_dataframe,
)
from freqtrade.data.converter import trim_dataframe, trim_dataframes
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.metrics import combined_dataframes_with_rel_mean
@@ -35,7 +39,7 @@ from freqtrade.exchange import (
price_to_precision,
timeframe_to_seconds,
)
from freqtrade.exchange.exchange import Exchange
from freqtrade.exchange.exchange import TICK_SIZE, Exchange
from freqtrade.ft_types import (
BacktestContentType,
BacktestContentTypeIcomplete,
@@ -121,6 +125,7 @@ class Backtesting:
self.order_id_counter: int = 0
config["dry_run"] = True
self.price_pair_prec: dict[str, Series] = {}
self.run_ids: dict[str, str] = {}
self.strategylist: list[IStrategy] = []
self.all_bt_content: dict[str, BacktestContentType] = {}
@@ -189,7 +194,6 @@ class Backtesting:
self.fee = max(fee for fee in fees if fee is not None)
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
self.precision_mode = self.exchange.precisionMode
self.precision_mode_price = self.exchange.precision_mode_price
if self.config.get("freqai_backtest_live_models", False):
from freqtrade.freqai.utils import get_timerange_backtest_live_models
@@ -316,6 +320,9 @@ class Backtesting:
self.progress.set_new_value(1)
self._load_bt_data_detail()
self.price_pair_prec = {}
for pair in self.pairlists.whitelist:
self.price_pair_prec[pair] = get_significant_digits_over_time(data[pair])
return data, self.timerange
def _load_bt_data_detail(self) -> None:
@@ -385,6 +392,22 @@ class Backtesting:
else:
self.futures_data = {}
def get_pair_precision(self, pair: str, current_time: datetime) -> tuple[float, int]:
"""
Get pair precision at that moment in time
:param pair: Pair to get precision for
:param current_time: Time to get precision for
:return: tuple of price precision, precision_mode_price for the pair at that given time.
"""
precision_series = self.price_pair_prec.get(pair)
if precision_series is not None:
precision = precision_series.asof(current_time)
if not isnan(precision):
# Force tick size if we define the precision
return precision, TICK_SIZE
return self.exchange.get_precision_price(pair), self.exchange.precision_mode_price
def disable_database_use(self):
disable_database_use(self.timeframe)
@@ -793,7 +816,7 @@ class Backtesting:
)
if rate is not None and rate != close_rate:
close_rate = price_to_precision(
rate, trade.price_precision, self.precision_mode_price
rate, trade.price_precision, trade.precision_mode_price
)
# We can't place orders lower than current low.
# freqtrade does not support this in live, and the order would fill immediately
@@ -926,6 +949,7 @@ class Backtesting:
trade: LocalTrade | None,
order_type: str,
price_precision: float | None,
precision_mode_price: int,
) -> tuple[float, float, float, float]:
if order_type == "limit":
new_rate = strategy_safe_wrapper(
@@ -941,9 +965,7 @@ class Backtesting:
# We can't place orders higher than current high (otherwise it'd be a stop limit entry)
# which freqtrade does not support in live.
if new_rate is not None and new_rate != propose_rate:
propose_rate = price_to_precision(
new_rate, price_precision, self.precision_mode_price
)
propose_rate = price_to_precision(new_rate, price_precision, precision_mode_price)
if direction == "short":
propose_rate = max(propose_rate, row[LOW_IDX])
else:
@@ -1036,7 +1058,7 @@ class Backtesting:
pos_adjust = trade is not None and requested_rate is None
stake_amount_ = stake_amount or (trade.stake_amount if trade else 0.0)
precision_price = self.exchange.get_precision_price(pair)
precision_price, precision_mode_price = self.get_pair_precision(pair, current_time)
propose_rate, stake_amount, leverage, min_stake_amount = self.get_valid_price_and_stake(
pair,
@@ -1049,6 +1071,7 @@ class Backtesting:
trade,
order_type,
precision_price,
precision_mode_price,
)
# replace proposed rate if another rate was requested
@@ -1124,7 +1147,7 @@ class Backtesting:
amount_precision=precision_amount,
price_precision=precision_price,
precision_mode=self.precision_mode,
precision_mode_price=self.precision_mode_price,
precision_mode_price=precision_mode_price,
contract_size=contract_size,
orders=[],
)