diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 81510d047..9531014c8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -13,6 +13,7 @@ from datetime import UTC, datetime, timedelta from math import floor, isnan from threading import Lock from typing import Any, Literal, TypeGuard, TypeVar +from uuid import uuid4 import ccxt import ccxt.pro as ccxt_pro @@ -1152,7 +1153,7 @@ class Exchange: stop_price: float | None = None, ) -> CcxtOrder: now = dt_now() - order_id = f"dry_run_{side}_{pair}_{now.timestamp()}" + order_id = f"dry_run_{side}_{pair}_{uuid4()}" # Rounding here must respect to contract sizes _amount = self._contracts_to_amount( pair, self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 022c410f0..187a5f64e 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1,5 +1,6 @@ import copy import logging +import re from copy import deepcopy from datetime import UTC, datetime, timedelta from random import randint @@ -1077,6 +1078,24 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag assert order["cost"] == 1 * 200 +def test_create_dry_run_order_id_unique_with_same_timestamp(default_conf, mocker, time_machine): + exchange = get_patched_exchange(mocker, default_conf) + + time_machine.move_to("2026-04-27T04:49:57.438232Z", tick=False) + order1 = exchange.create_dry_run_order( + pair="ETH/USDT", ordertype="limit", side="sell", amount=1, rate=2.05, leverage=1.0 + ) + order2 = exchange.create_dry_run_order( + pair="ETH/USDT", ordertype="limit", side="sell", amount=1, rate=2.05, leverage=1.0 + ) + + assert order1["id"] != order2["id"] + assert re.match( + r"^dry_run_sell_ETH/USDT_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$", + order1["id"], + ) + + @pytest.mark.parametrize( "side,is_short,order_reason", [