Files
QuantDinger/backend_api_python/tests/test_grid_quantity_normalization.py

155 lines
4.3 KiB
Python

import pytest
from app.services import instrument_rules
from app.services.grid.exchange_orders import normalize_grid_order_quantity
from app.services.instrument_rules import InstrumentRules
from app.services.live_trading.base import BaseRestClient
from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient
from app.services.live_trading.htx import HtxClient
def _gate_client(order_size_min="1", multiplier="0.0001"):
client = GateUsdtFuturesClient.__new__(GateUsdtFuturesClient)
client.get_contract = lambda **_kwargs: {
"order_size_min": order_size_min,
"quanto_multiplier": multiplier,
}
return client
def test_gate_futures_never_rounds_sub_minimum_quantity_up():
client = _gate_client()
size, headers = client._resolve_order_size(
contract="BTC_USDT",
side="sell",
base_size=0.00005,
)
assert size == "0"
assert headers is None
def test_gate_grid_quantity_is_floored_and_returned_in_base_units():
client = _gate_client()
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=0.00039,
market_type="swap",
) == 0.0003
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=0.00005,
market_type="swap",
) == 0.0
def test_htx_futures_never_rounds_sub_contract_quantity_up():
client = HtxClient.__new__(HtxClient)
client.get_contract_info = lambda **_kwargs: {"contract_size": "0.001"}
assert client._base_to_contracts(symbol="BTC/USDT", qty=0.0005) == 0
def test_gate_spot_grid_rejects_float_dust_and_floors_native_precision():
client = GateSpotClient.__new__(GateSpotClient)
client.get_currency_pair = lambda **_kwargs: {
"amount_precision": 6,
"precision": 2,
"min_base_amount": "0.0001",
"min_quote_amount": "1",
}
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=3.3532799999999997e-07,
market_type="spot",
price=100_000,
) == 0.0
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=0.0001239,
market_type="spot",
price=100_000,
) == pytest.approx(0.000123)
def test_htx_spot_grid_rejects_float_dust_and_minimum_notional():
client = HtxClient.__new__(HtxClient)
client.get_spot_symbol_info = lambda **_kwargs: {
"amount-precision": 6,
"price-precision": 2,
"min-order-amt": "0.0001",
"min-order-value": "1",
}
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=3.3532799999999997e-07,
market_type="spot",
price=100_000,
) == 0.0
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=0.0001,
market_type="spot",
price=5_000,
) == 0.0
@pytest.mark.parametrize("exchange_id", ["binance", "okx", "bitget", "bybit", "gate", "htx"])
@pytest.mark.parametrize("market_type", ["spot", "swap"])
def test_all_grid_exchanges_apply_common_minimum_rules(
monkeypatch,
exchange_id,
market_type,
):
rules = InstrumentRules(
key=f"Crypto:BTC/USDT@{exchange_id}:{market_type}",
exchange_id=exchange_id,
market_type=market_type,
symbol="BTC/USDT",
amount_step=0.001,
min_amount=0.01,
min_notional=5.0,
)
class FakeProvider:
def get_rules(self, *_args, **_kwargs):
return rules
monkeypatch.setattr(instrument_rules, "get_instrument_rules_provider", lambda: FakeProvider())
client = BaseRestClient("https://example.invalid")
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=0.0099,
market_type=market_type,
exchange_config={"exchange_id": exchange_id},
price=1_000,
) == 0.0
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=0.0129,
market_type=market_type,
exchange_config={"exchange_id": exchange_id},
price=100,
) == 0.0
assert normalize_grid_order_quantity(
client,
symbol="BTC/USDT",
quantity=0.1239,
market_type=market_type,
exchange_config={"exchange_id": exchange_id},
price=100,
) == pytest.approx(0.123)