Skip to content

[pull] develop from freqtrade:develop #1285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 10, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: use hsitoric price precision for improved accuracy
  • Loading branch information
xmatthias committed May 3, 2025
commit 5a6b43da46c1fdd74d6c142c070c950d43c079c2
45 changes: 34 additions & 11 deletions freqtrade/optimize/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@
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
Expand All @@ -35,7 +39,7 @@
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,
Expand Down Expand Up @@ -121,6 +125,7 @@ def __init__(self, config: Config, exchange: Exchange | None = None) -> None:
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] = {}
Expand Down Expand Up @@ -189,7 +194,6 @@ def __init__(self, config: Config, exchange: Exchange | None = None) -> None:
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
Expand Down Expand Up @@ -316,6 +320,9 @@ def load_bt_data(self) -> tuple[dict[str, DataFrame], TimeRange]:

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:
Expand Down Expand Up @@ -385,6 +392,22 @@ def _load_bt_data_detail(self) -> None:
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)

Expand Down Expand Up @@ -793,7 +816,7 @@ def _get_exit_for_signal(
)
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
Expand Down Expand Up @@ -926,6 +949,7 @@ def get_valid_price_and_stake(
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(
Expand All @@ -941,9 +965,7 @@ def get_valid_price_and_stake(
# 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:
Expand Down Expand Up @@ -1036,7 +1058,7 @@ def _enter_trade(
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,
Expand All @@ -1049,6 +1071,7 @@ def _enter_trade(
trade,
order_type,
precision_price,
precision_mode_price,
)

# replace proposed rate if another rate was requested
Expand Down Expand Up @@ -1124,7 +1147,7 @@ def _enter_trade(
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=[],
)
Expand Down