Skip to content

Commit 5a6b43d

Browse files
committed
feat: use hsitoric price precision for improved accuracy
closes freqtrade#11203
1 parent 7d3fa41 commit 5a6b43d

File tree

1 file changed

+34
-11
lines changed

1 file changed

+34
-11
lines changed

freqtrade/optimize/backtesting.py

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
from copy import deepcopy
1010
from datetime import datetime, timedelta
1111

12-
from numpy import nan
13-
from pandas import DataFrame
12+
from numpy import isnan, nan
13+
from pandas import DataFrame, Series
1414

1515
from freqtrade import constants
1616
from freqtrade.configuration import TimeRange, validate_config_consistency
1717
from freqtrade.constants import DATETIME_PRINT_FORMAT, Config, IntOrInf, LongShort
1818
from freqtrade.data import history
19-
from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe
19+
from freqtrade.data.btanalysis import (
20+
find_existing_backtest_stats,
21+
get_significant_digits_over_time,
22+
trade_list_to_dataframe,
23+
)
2024
from freqtrade.data.converter import trim_dataframe, trim_dataframes
2125
from freqtrade.data.dataprovider import DataProvider
2226
from freqtrade.data.metrics import combined_dataframes_with_rel_mean
@@ -35,7 +39,7 @@
3539
price_to_precision,
3640
timeframe_to_seconds,
3741
)
38-
from freqtrade.exchange.exchange import Exchange
42+
from freqtrade.exchange.exchange import TICK_SIZE, Exchange
3943
from freqtrade.ft_types import (
4044
BacktestContentType,
4145
BacktestContentTypeIcomplete,
@@ -121,6 +125,7 @@ def __init__(self, config: Config, exchange: Exchange | None = None) -> None:
121125
self.order_id_counter: int = 0
122126

123127
config["dry_run"] = True
128+
self.price_pair_prec: dict[str, Series] = {}
124129
self.run_ids: dict[str, str] = {}
125130
self.strategylist: list[IStrategy] = []
126131
self.all_bt_content: dict[str, BacktestContentType] = {}
@@ -189,7 +194,6 @@ def __init__(self, config: Config, exchange: Exchange | None = None) -> None:
189194
self.fee = max(fee for fee in fees if fee is not None)
190195
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
191196
self.precision_mode = self.exchange.precisionMode
192-
self.precision_mode_price = self.exchange.precision_mode_price
193197

194198
if self.config.get("freqai_backtest_live_models", False):
195199
from freqtrade.freqai.utils import get_timerange_backtest_live_models
@@ -316,6 +320,9 @@ def load_bt_data(self) -> tuple[dict[str, DataFrame], TimeRange]:
316320

317321
self.progress.set_new_value(1)
318322
self._load_bt_data_detail()
323+
self.price_pair_prec = {}
324+
for pair in self.pairlists.whitelist:
325+
self.price_pair_prec[pair] = get_significant_digits_over_time(data[pair])
319326
return data, self.timerange
320327

321328
def _load_bt_data_detail(self) -> None:
@@ -385,6 +392,22 @@ def _load_bt_data_detail(self) -> None:
385392
else:
386393
self.futures_data = {}
387394

395+
def get_pair_precision(self, pair: str, current_time: datetime) -> tuple[float, int]:
396+
"""
397+
Get pair precision at that moment in time
398+
:param pair: Pair to get precision for
399+
:param current_time: Time to get precision for
400+
:return: tuple of price precision, precision_mode_price for the pair at that given time.
401+
"""
402+
precision_series = self.price_pair_prec.get(pair)
403+
if precision_series is not None:
404+
precision = precision_series.asof(current_time)
405+
406+
if not isnan(precision):
407+
# Force tick size if we define the precision
408+
return precision, TICK_SIZE
409+
return self.exchange.get_precision_price(pair), self.exchange.precision_mode_price
410+
388411
def disable_database_use(self):
389412
disable_database_use(self.timeframe)
390413

@@ -793,7 +816,7 @@ def _get_exit_for_signal(
793816
)
794817
if rate is not None and rate != close_rate:
795818
close_rate = price_to_precision(
796-
rate, trade.price_precision, self.precision_mode_price
819+
rate, trade.price_precision, trade.precision_mode_price
797820
)
798821
# We can't place orders lower than current low.
799822
# freqtrade does not support this in live, and the order would fill immediately
@@ -926,6 +949,7 @@ def get_valid_price_and_stake(
926949
trade: LocalTrade | None,
927950
order_type: str,
928951
price_precision: float | None,
952+
precision_mode_price: int,
929953
) -> tuple[float, float, float, float]:
930954
if order_type == "limit":
931955
new_rate = strategy_safe_wrapper(
@@ -941,9 +965,7 @@ def get_valid_price_and_stake(
941965
# We can't place orders higher than current high (otherwise it'd be a stop limit entry)
942966
# which freqtrade does not support in live.
943967
if new_rate is not None and new_rate != propose_rate:
944-
propose_rate = price_to_precision(
945-
new_rate, price_precision, self.precision_mode_price
946-
)
968+
propose_rate = price_to_precision(new_rate, price_precision, precision_mode_price)
947969
if direction == "short":
948970
propose_rate = max(propose_rate, row[LOW_IDX])
949971
else:
@@ -1036,7 +1058,7 @@ def _enter_trade(
10361058
pos_adjust = trade is not None and requested_rate is None
10371059

10381060
stake_amount_ = stake_amount or (trade.stake_amount if trade else 0.0)
1039-
precision_price = self.exchange.get_precision_price(pair)
1061+
precision_price, precision_mode_price = self.get_pair_precision(pair, current_time)
10401062

10411063
propose_rate, stake_amount, leverage, min_stake_amount = self.get_valid_price_and_stake(
10421064
pair,
@@ -1049,6 +1071,7 @@ def _enter_trade(
10491071
trade,
10501072
order_type,
10511073
precision_price,
1074+
precision_mode_price,
10521075
)
10531076

10541077
# replace proposed rate if another rate was requested
@@ -1124,7 +1147,7 @@ def _enter_trade(
11241147
amount_precision=precision_amount,
11251148
price_precision=precision_price,
11261149
precision_mode=self.precision_mode,
1127-
precision_mode_price=self.precision_mode_price,
1150+
precision_mode_price=precision_mode_price,
11281151
contract_size=contract_size,
11291152
orders=[],
11301153
)

0 commit comments

Comments
 (0)