from __future__ import annotations from crypto_spot_bot.bybit import BybitClient, websocket_subscribe_message, _looks_like_leveraged_token, _looks_like_stablecoin def test_leveraged_token_filter() -> None: assert _looks_like_leveraged_token("BTC3L") assert _looks_like_leveraged_token("ETHDOWN") assert not _looks_like_leveraged_token("BTC") def test_stablecoin_filter() -> None: assert _looks_like_stablecoin("USDC") assert _looks_like_stablecoin("FDUSD") assert not _looks_like_stablecoin("BTC") def test_spot_instrument_uses_min_order_amt_and_base_precision(make_settings, tmp_path) -> None: client = BybitClient(make_settings(tmp_path)) client.public_get = lambda *_args, **_kwargs: { "list": [ { "symbol": "BTCUSDT", "baseCoin": "BTC", "quoteCoin": "USDT", "status": "Trading", "priceFilter": {"tickSize": "0.01"}, "lotSizeFilter": { "basePrecision": "0.000001", "minOrderQty": "0.000001", "minOrderAmt": "5", }, } ] } instrument = client.instruments()["BTCUSDT"] assert instrument.qty_step == 0.000001 assert instrument.min_notional_value == 5.0 def test_live_spot_order_explicitly_disables_leverage(make_settings, tmp_path) -> None: client = BybitClient(make_settings(tmp_path)) captured = {} def fake_private_post(path, payload): captured["path"] = path captured["payload"] = payload return {"orderId": "test"} client.private_post = fake_private_post client.place_spot_market_order("BTCUSDT", "Buy", 10, "quoteCoin", "order-1") assert captured["path"] == "/v5/order/create" assert captured["payload"]["category"] == "spot" assert captured["payload"]["isLeverage"] == 0 assert captured["payload"]["orderFilter"] == "Order" def test_private_get_signs_the_same_query_it_sends(make_settings, tmp_path) -> None: client = BybitClient(make_settings(tmp_path)) captured = {} class Response: def raise_for_status(self): return None def json(self): return {"retCode": 0, "result": {"ok": True}} class Session: def get(self, url, params, headers, timeout): captured["url"] = url captured["params"] = params captured["headers"] = headers captured["timeout"] = timeout return Response() client.session = Session() assert client.wallet_balance(coin=None) == {"ok": True} assert captured["params"] == [("accountType", "UNIFIED")] assert "coin" not in dict(captured["params"]) assert captured["headers"]["X-BAPI-SIGN"] def test_websocket_subscribe_uses_configured_kline_interval() -> None: payload = websocket_subscribe_message(["BTCUSDT"], interval="60") assert "kline.60.BTCUSDT" in payload assert "kline.1.BTCUSDT" not in payload