99from copy import deepcopy
1010from 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
1515from freqtrade import constants
1616from freqtrade .configuration import TimeRange , validate_config_consistency
1717from freqtrade .constants import DATETIME_PRINT_FORMAT , Config , IntOrInf , LongShort
1818from 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+ )
2024from freqtrade .data .converter import trim_dataframe , trim_dataframes
2125from freqtrade .data .dataprovider import DataProvider
2226from freqtrade .data .metrics import combined_dataframes_with_rel_mean
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
3943from 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