diff --git a/binance/helpers.py b/binance/helpers.py index 8e02d0ea..75f0f190 100644 --- a/binance/helpers.py +++ b/binance/helpers.py @@ -1,5 +1,5 @@ import asyncio -from decimal import Decimal +from decimal import Decimal, ROUND_DOWN import json from typing import Union, Optional, Dict @@ -60,7 +60,9 @@ def interval_to_milliseconds(interval: str) -> Optional[int]: def round_step_size( - quantity: Union[float, Decimal], step_size: Union[float, Decimal] + quantity: Union[float, Decimal], + step_size: Union[float, Decimal], + rounding=ROUND_DOWN, ) -> float: """Rounds a given quantity to a specific step size @@ -70,7 +72,8 @@ def round_step_size( :return: decimal """ quantity = Decimal(str(quantity)) - return float(quantity - quantity % Decimal(str(step_size))) + step_size = Decimal(str(step_size)) + return float(quantity.quantize(step_size, rounding=rounding)) def convert_ts_str(ts_str): diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 00000000..a7b14262 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,63 @@ +import pytest +from decimal import Decimal, InvalidOperation +from binance.helpers import round_step_size + + +@pytest.mark.parametrize( + "quantity,step_size,expected", + [ + # Basic cases + (1.23456, 0.1, 1.2), + (1.23456, 0.01, 1.23), + (1.23456, 1, 1), + # Edge cases + (0.0, 0.1, 0.0), + (0.1, 0.1, 0.1), + (1.0, 1, 1.0), + # Large numbers + (100.123456, 0.1, 100.1), + (1000.123456, 1, 1000), + # Small step sizes + (1.123456, 0.0001, 1.1234), + (1.123456, 0.00001, 1.12345), + # Decimal inputs + (Decimal("1.23456"), Decimal("0.1"), 1.2), + (Decimal("1.23456"), 0.01, 1.23), + # String conversion edge cases + (1.23456, Decimal("0.01"), 1.23), + ("1.23456", "0.01", 1.23), + ], +) +def test_round_step_size(quantity, step_size, expected): + """Test round_step_size with various inputs""" + result = round_step_size(quantity, step_size) + assert result == expected + assert isinstance(result, float) + + +def test_round_step_size_precision(): + """Test that rounding maintains proper precision""" + # Should maintain step size precision + assert round_step_size(1.123456, 0.0001) == 1.1234 + assert round_step_size(1.123456, 0.001) == 1.123 + assert round_step_size(1.123456, 0.01) == 1.12 + assert round_step_size(1.123456, 0.1) == 1.1 + + +def test_round_step_size_always_rounds_down(): + """Test that values are always rounded down""" + assert round_step_size(1.19, 0.1) == 1.1 + assert round_step_size(1.99, 1.0) == 1.9 + assert round_step_size(0.99999, 0.1) == 0.9 + + +def test_round_step_size_invalid_inputs(): + """Test error handling for invalid inputs""" + with pytest.raises(InvalidOperation): + round_step_size(None, 0.1) # type: ignore + + with pytest.raises((ValueError, InvalidOperation)): + round_step_size("invalid", 0.1) # type: ignore + + with pytest.raises((ValueError, InvalidOperation)): + round_step_size(1.23, "invalid") # type: ignore