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_tick_size_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,11 @@ 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+ if pair in data :
326+ # Load price precision logic
327+ self .price_pair_prec [pair ] = get_tick_size_over_time (data [pair ])
319328 return data , self .timerange
320329
321330 def _load_bt_data_detail (self ) -> None :
@@ -385,6 +394,22 @@ def _load_bt_data_detail(self) -> None:
385394 else :
386395 self .futures_data = {}
387396
397+ def get_pair_precision (self , pair : str , current_time : datetime ) -> tuple [float | None , int ]:
398+ """
399+ Get pair precision at that moment in time
400+ :param pair: Pair to get precision for
401+ :param current_time: Time to get precision for
402+ :return: tuple of price precision, precision_mode_price for the pair at that given time.
403+ """
404+ precision_series = self .price_pair_prec .get (pair )
405+ if precision_series is not None :
406+ precision = precision_series .asof (current_time )
407+
408+ if not isnan (precision ):
409+ # Force tick size if we define the precision
410+ return precision , TICK_SIZE
411+ return self .exchange .get_precision_price (pair ), self .exchange .precision_mode_price
412+
388413 def disable_database_use (self ):
389414 disable_database_use (self .timeframe )
390415
@@ -793,7 +818,7 @@ def _get_exit_for_signal(
793818 )
794819 if rate is not None and rate != close_rate :
795820 close_rate = price_to_precision (
796- rate , trade .price_precision , self .precision_mode_price
821+ rate , trade .price_precision , trade .precision_mode_price
797822 )
798823 # We can't place orders lower than current low.
799824 # freqtrade does not support this in live, and the order would fill immediately
@@ -926,6 +951,7 @@ def get_valid_price_and_stake(
926951 trade : LocalTrade | None ,
927952 order_type : str ,
928953 price_precision : float | None ,
954+ precision_mode_price : int ,
929955 ) -> tuple [float , float , float , float ]:
930956 if order_type == "limit" :
931957 new_rate = strategy_safe_wrapper (
@@ -941,9 +967,7 @@ def get_valid_price_and_stake(
941967 # We can't place orders higher than current high (otherwise it'd be a stop limit entry)
942968 # which freqtrade does not support in live.
943969 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- )
970+ propose_rate = price_to_precision (new_rate , price_precision , precision_mode_price )
947971 if direction == "short" :
948972 propose_rate = max (propose_rate , row [LOW_IDX ])
949973 else :
@@ -1036,7 +1060,7 @@ def _enter_trade(
10361060 pos_adjust = trade is not None and requested_rate is None
10371061
10381062 stake_amount_ = stake_amount or (trade .stake_amount if trade else 0.0 )
1039- precision_price = self .exchange . get_precision_price (pair )
1063+ precision_price , precision_mode_price = self .get_pair_precision (pair , current_time )
10401064
10411065 propose_rate , stake_amount , leverage , min_stake_amount = self .get_valid_price_and_stake (
10421066 pair ,
@@ -1049,6 +1073,7 @@ def _enter_trade(
10491073 trade ,
10501074 order_type ,
10511075 precision_price ,
1076+ precision_mode_price ,
10521077 )
10531078
10541079 # replace proposed rate if another rate was requested
@@ -1124,7 +1149,7 @@ def _enter_trade(
11241149 amount_precision = precision_amount ,
11251150 price_precision = precision_price ,
11261151 precision_mode = self .precision_mode ,
1127- precision_mode_price = self . precision_mode_price ,
1152+ precision_mode_price = precision_mode_price ,
11281153 contract_size = contract_size ,
11291154 orders = [],
11301155 )
0 commit comments