diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 7eca242f7..4dfa97b28 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -248,6 +248,39 @@ def amount_to_contract_precision( return amount +def __price_to_precision_significant_digits( + price: float, + price_precision: float, + *, + rounding_mode: int = ROUND, +) -> float: + """ + Implementation of ROUND_UP/Round_down for significant digits mode. + """ + from decimal import ROUND_DOWN as dec_ROUND_DOWN + from decimal import ROUND_UP as dec_ROUND_UP + from decimal import Decimal + dec = Decimal(str(price)) + string = f'{dec:f}' + precision = round(price_precision) + + q = precision - dec.adjusted() - 1 + sigfig = Decimal('10') ** -q + if q < 0: + string_to_precision = string[:precision] + # string_to_precision is '' when we have zero precision + below = sigfig * Decimal(string_to_precision if string_to_precision else '0') + above = below + sigfig + res = above if rounding_mode == ROUND_UP else below + precise = f'{res:f}' + else: + precise = '{:f}'.format(dec.quantize( + sigfig, + rounding=dec_ROUND_DOWN if rounding_mode == ROUND_DOWN else dec_ROUND_UP) + ) + return float(precise) + + def price_to_precision( price: float, price_precision: Optional[float], @@ -289,7 +322,7 @@ def price_to_precision( res = price_str - missing return round(float(str(res)), 14) return price - elif precisionMode in (SIGNIFICANT_DIGITS, DECIMAL_PLACES): + elif precisionMode == DECIMAL_PLACES: ndigits = round(price_precision) ticks = price * (10**ndigits) @@ -299,5 +332,11 @@ def price_to_precision( return floor(ticks) / (10**ndigits) raise ValueError(f"Unknown rounding_mode {rounding_mode}") + elif precisionMode == SIGNIFICANT_DIGITS: + if rounding_mode in (ROUND_UP, ROUND_DOWN): + return __price_to_precision_significant_digits( + price, price_precision, rounding_mode=rounding_mode + ) + raise ValueError(f"Unknown precisionMode {precisionMode}") return price diff --git a/tests/exchange/test_exchange_utils.py b/tests/exchange/test_exchange_utils.py index 1dff64a60..377514468 100644 --- a/tests/exchange/test_exchange_utils.py +++ b/tests/exchange/test_exchange_utils.py @@ -288,6 +288,25 @@ def test_amount_to_precision(amount, precision_mode, precision, expected,): (2.9909, SIGNIFICANT_DIGITS, 2, 2.9, TRUNCATE), (0.00000777, SIGNIFICANT_DIGITS, 2, 0.0000077, TRUNCATE), (0.00000729, SIGNIFICANT_DIGITS, 2, 0.0000072, TRUNCATE), + # ROUND + (722.2, SIGNIFICANT_DIGITS, 1, 700.0, ROUND), + (790.2, SIGNIFICANT_DIGITS, 1, 800.0, ROUND), + (722.2, SIGNIFICANT_DIGITS, 2, 720.0, ROUND), + (722.2, SIGNIFICANT_DIGITS, 1, 800.0, ROUND_UP), + (722.2, SIGNIFICANT_DIGITS, 2, 730.0, ROUND_UP), + (777.7, SIGNIFICANT_DIGITS, 2, 780.0, ROUND_UP), + (777.7, SIGNIFICANT_DIGITS, 3, 778.0, ROUND_UP), + (722.2, SIGNIFICANT_DIGITS, 1, 700.0, ROUND_DOWN), + (722.2, SIGNIFICANT_DIGITS, 2, 720.0, ROUND_DOWN), + (777.7, SIGNIFICANT_DIGITS, 2, 770.0, ROUND_DOWN), + (777.7, SIGNIFICANT_DIGITS, 3, 777.0, ROUND_DOWN), + + (0.000007222, SIGNIFICANT_DIGITS, 1, 0.000008, ROUND_UP), + (0.000007222, SIGNIFICANT_DIGITS, 2, 0.0000073, ROUND_UP), + (0.000007777, SIGNIFICANT_DIGITS, 2, 0.0000078, ROUND_UP), + (0.000007222, SIGNIFICANT_DIGITS, 1, 0.000007, ROUND_DOWN), + (0.000007222, SIGNIFICANT_DIGITS, 2, 0.0000072, ROUND_DOWN), + (0.000007777, SIGNIFICANT_DIGITS, 2, 0.0000077, ROUND_DOWN), ]) def test_price_to_precision(price, precision_mode, precision, expected, rounding_mode): assert price_to_precision(