diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 631f5587d..83db916bd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -416,7 +416,17 @@ class Exchange: @property def precisionMode(self) -> int: - """exchange ccxt precisionMode""" + """Exchange ccxt precisionMode""" + return self._api.precisionMode + + @property + def precision_mode_price(self) -> int: + """ + Exchange ccxt precisionMode used for price + Workaround for ccxt limitation to not have precisionMode for price + if it differs for an exchange + Might need to be updated if https://github.com/ccxt/ccxt/issues/20408 is fixed. + """ return self._api.precisionMode def additional_exchange_init(self) -> None: @@ -913,7 +923,10 @@ class Exchange: For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts. """ return price_to_precision( - price, self.get_precision_price(pair), self.precisionMode, rounding_mode=rounding_mode + price, + self.get_precision_price(pair), + self.precision_mode_price, + rounding_mode=rounding_mode, ) def price_get_one_pip(self, pair: str, price: float) -> float: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5a33e9fa6..c970e440b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -374,6 +374,7 @@ class FreqtradeBot(LoggingMixin): if trade.exchange != self.exchange.id: continue trade.precision_mode = self.exchange.precisionMode + trade.precision_mode_price = self.exchange.precision_mode_price trade.amount_precision = self.exchange.get_precision_amount(trade.pair) trade.price_precision = self.exchange.get_precision_price(trade.pair) trade.contract_size = self.exchange.get_contract_size(trade.pair) @@ -992,6 +993,7 @@ class FreqtradeBot(LoggingMixin): amount_precision=self.exchange.get_precision_amount(pair), price_precision=self.exchange.get_precision_price(pair), precision_mode=self.exchange.precisionMode, + precision_mode_price=self.exchange.precision_mode_price, contract_size=self.exchange.get_contract_size(pair), ) stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2edb65e1b..1487d9f10 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -181,6 +181,7 @@ 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 @@ -785,7 +786,7 @@ class Backtesting: ) if rate is not None and rate != close_rate: close_rate = price_to_precision( - rate, trade.price_precision, self.precision_mode + rate, trade.price_precision, self.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 @@ -929,7 +930,9 @@ 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) + propose_rate = price_to_precision( + new_rate, price_precision, self.precision_mode_price + ) if direction == "short": propose_rate = max(propose_rate, row[LOW_IDX]) else: @@ -1109,6 +1112,7 @@ class Backtesting: amount_precision=precision_amount, price_precision=precision_price, precision_mode=self.precision_mode, + precision_mode_price=self.precision_mode_price, contract_size=contract_size, orders=[], ) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index e2e3b2175..2150d76bc 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -147,6 +147,9 @@ def migrate_trades_and_orders_table( price_precision = get_column_def(cols, "price_precision", "null") precision_mode = get_column_def(cols, "precision_mode", "null") contract_size = get_column_def(cols, "contract_size", "null") + precision_mode_price = get_column_def( + cols, "precision_mode_price", get_column_def(cols, "precision_mode", "null") + ) # Schema migration necessary with engine.begin() as connection: @@ -177,7 +180,7 @@ def migrate_trades_and_orders_table( timeframe, open_trade_value, close_profit_abs, trading_mode, leverage, liquidation_price, is_short, interest_rate, funding_fees, funding_fee_running, realized_profit, - amount_precision, price_precision, precision_mode, contract_size, + amount_precision, price_precision, precision_mode, precision_mode_price, contract_size, max_stake_amount ) select id, lower(exchange), pair, {base_currency} base_currency, @@ -207,8 +210,8 @@ def migrate_trades_and_orders_table( {funding_fees} funding_fees, {funding_fee_running} funding_fee_running, {realized_profit} realized_profit, {amount_precision} amount_precision, {price_precision} price_precision, - {precision_mode} precision_mode, {contract_size} contract_size, - {max_stake_amount} max_stake_amount + {precision_mode} precision_mode, {precision_mode_price} precision_mode_price, + {contract_size} contract_size, {max_stake_amount} max_stake_amount from {trade_back_name} """ ) @@ -348,8 +351,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # if ('orders' not in previous_tables # or not has_column(cols_orders, 'funding_fee')): migrating = False - # if not has_column(cols_trades, 'funding_fee_running'): - if not has_column(cols_orders, "ft_order_tag"): + if not has_column(cols_trades, "precision_mode_price"): + # if not has_column(cols_orders, "ft_order_tag"): migrating = True logger.info( f"Running database migration for trades - " diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 7ec781cdc..4e7f01906 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -433,6 +433,7 @@ class LocalTrade: amount_precision: Optional[float] = None price_precision: Optional[float] = None precision_mode: Optional[int] = None + precision_mode_price: Optional[int] = None contract_size: Optional[float] = None # Leverage trading properties @@ -730,6 +731,7 @@ class LocalTrade: "amount_precision": self.amount_precision, "price_precision": self.price_precision, "precision_mode": self.precision_mode, + "precision_mode_price": self.precision_mode_price, "contract_size": self.contract_size, "has_open_orders": self.has_open_orders, "orders": orders_json, @@ -810,7 +812,7 @@ class LocalTrade: stop_loss_norm = price_to_precision( new_loss, self.price_precision, - self.precision_mode, + self.precision_mode_price, rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP, ) # no stop loss assigned yet @@ -819,7 +821,7 @@ class LocalTrade: self.initial_stop_loss = price_to_precision( stop_loss_norm, self.price_precision, - self.precision_mode, + self.precision_mode_price, rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP, ) self.initial_stop_loss_pct = -1 * abs(stoploss) @@ -1562,6 +1564,7 @@ class LocalTrade: amount_precision=data.get("amount_precision", None), price_precision=data.get("price_precision", None), precision_mode=data.get("precision_mode", None), + precision_mode_price=data.get("precision_mode_price", data.get("precision_mode", None)), contract_size=data.get("contract_size", None), ) for order in data["orders"]: @@ -1695,6 +1698,9 @@ class Trade(ModelBase, LocalTrade): ) price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore + precision_mode_price: Mapped[Optional[int]] = mapped_column( # type: ignore + Integer, nullable=True + ) contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore # Leverage trading properties diff --git a/tests/conftest.py b/tests/conftest.py index c0a9f2485..8f15388ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -243,6 +243,7 @@ def patch_exchange( mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange)) mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title())) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2)) + mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=2)) # Temporary patch ... mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers") diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 168157cea..4a1865658 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -365,6 +365,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio exchange = get_patched_exchange(mocker, default_conf, exchange="binance") mocker.patch(f"{EXMS}.markets", markets) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode)) + mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=precision_mode)) pair = "ETH/BTC" assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index e50f1f8ce..05160c74a 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -1404,6 +1404,7 @@ def test_to_json(fee): exchange="binance", enter_tag=None, precision_mode=1, + precision_mode_price=1, amount_precision=8.0, price_precision=7.0, contract_size=1, @@ -1473,6 +1474,7 @@ def test_to_json(fee): "amount_precision": 8.0, "price_precision": 7.0, "precision_mode": 1, + "precision_mode_price": 1, "contract_size": 1, "orders": [], "has_open_orders": False, @@ -1493,6 +1495,7 @@ def test_to_json(fee): enter_tag="buys_signal_001", exchange="binance", precision_mode=2, + precision_mode_price=1, amount_precision=7.0, price_precision=8.0, contract_size=1, @@ -1562,6 +1565,7 @@ def test_to_json(fee): "amount_precision": 7.0, "price_precision": 8.0, "precision_mode": 2, + "precision_mode_price": 1, "contract_size": 1, "orders": [], "has_open_orders": False, diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index a0c235cd5..e0eaabe24 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -96,6 +96,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: "amount_precision": 8.0, "price_precision": 8.0, "precision_mode": 2, + "precision_mode_price": 2, "contract_size": 1, "has_open_orders": False, "orders": [ diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index ab87e110e..5459a0ff2 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -579,6 +579,7 @@ def test_ft_stoploss_reached( liquidation_price=liq, price_precision=4, precision_mode=2, + precision_mode_price=2, ) trade.adjust_min_max_rates(trade.open_rate, trade.open_rate) strategy.trailing_stop = trailing