|
|
@@ -76,6 +76,19 @@ def candles_response() -> DummyResponse:
|
|
|
)
|
|
|
|
|
|
|
|
|
+def descending_candles_response() -> DummyResponse:
|
|
|
+ return DummyResponse(
|
|
|
+ {
|
|
|
+ "code": "0",
|
|
|
+ "msg": "",
|
|
|
+ "data": [
|
|
|
+ ["1710000001000", "25100", "25200", "25000", "25150", "110", "1100", "1100", "1"],
|
|
|
+ ["1710000000000", "25000", "25100", "24900", "25050", "100", "1000", "1000", "1"],
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
def instrument_response() -> DummyResponse:
|
|
|
return DummyResponse(
|
|
|
{
|
|
|
@@ -94,6 +107,24 @@ def instrument_response() -> DummyResponse:
|
|
|
)
|
|
|
|
|
|
|
|
|
+def large_min_size_instrument_response() -> DummyResponse:
|
|
|
+ return DummyResponse(
|
|
|
+ {
|
|
|
+ "code": "0",
|
|
|
+ "msg": "",
|
|
|
+ "data": [
|
|
|
+ {
|
|
|
+ "instId": "BTC-USDT-SWAP",
|
|
|
+ "instType": "SWAP",
|
|
|
+ "ctVal": "0.01",
|
|
|
+ "lotSz": "1",
|
|
|
+ "minSz": "100",
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
def ticker_response(last: str) -> DummyResponse:
|
|
|
return DummyResponse({"code": "0", "msg": "", "data": [{"instId": "BTC-USDT-SWAP", "last": last}]})
|
|
|
|
|
|
@@ -135,6 +166,29 @@ def positions_response() -> DummyResponse:
|
|
|
)
|
|
|
|
|
|
|
|
|
+def positions_with_zero_size_response() -> DummyResponse:
|
|
|
+ return DummyResponse(
|
|
|
+ {
|
|
|
+ "code": "0",
|
|
|
+ "msg": "",
|
|
|
+ "data": [
|
|
|
+ {
|
|
|
+ "instId": "BTC-USDT-SWAP",
|
|
|
+ "posSide": "long",
|
|
|
+ "pos": "0",
|
|
|
+ "avgPx": "25000",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "instId": "BTC-USDT-SWAP",
|
|
|
+ "posSide": "short",
|
|
|
+ "pos": "3",
|
|
|
+ "avgPx": "24900",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
def market_long_signal() -> TradeSignal:
|
|
|
return TradeSignal(
|
|
|
action="long",
|
|
|
@@ -186,6 +240,15 @@ def test_signed_demo_request_attaches_headers():
|
|
|
assert request.headers["OK-ACCESS-PASSPHRASE"] == "passphrase"
|
|
|
|
|
|
|
|
|
+def test_get_candles_returns_chronological_ascending_order():
|
|
|
+ session = DummySession([descending_candles_response()])
|
|
|
+ client = OkxClient(config=sample_config(), session=session)
|
|
|
+
|
|
|
+ candles = client.get_candles(symbol="BTC-USDT-SWAP", bar="1H", limit=20)
|
|
|
+
|
|
|
+ assert [candle.ts for candle in candles] == [1710000000000, 1710000001000]
|
|
|
+
|
|
|
+
|
|
|
def test_build_contract_size_rounds_down_to_lot_size():
|
|
|
metadata = InstrumentMeta(ct_val=0.01, lot_sz=0.1, min_sz=0.1)
|
|
|
assert build_contract_size(notional=251, price=25_000, metadata=metadata) == 1.0
|
|
|
@@ -234,6 +297,26 @@ def test_place_demo_order_fails_when_not_hedge_mode():
|
|
|
client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100)
|
|
|
|
|
|
|
|
|
+def test_place_demo_order_validates_size_before_setting_leverage():
|
|
|
+ session = DummySession(
|
|
|
+ [
|
|
|
+ large_min_size_instrument_response(),
|
|
|
+ ticker_response(last="25000"),
|
|
|
+ account_config_response(pos_mode="long_short_mode"),
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ client = OkxClient(config=sample_config(), session=session)
|
|
|
+
|
|
|
+ with pytest.raises(ValueError, match="contract size below minimum"):
|
|
|
+ client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100)
|
|
|
+
|
|
|
+ assert session.request_paths == [
|
|
|
+ "/api/v5/public/instruments",
|
|
|
+ "/api/v5/market/ticker",
|
|
|
+ "/api/v5/account/config",
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
def test_limit_short_order_uses_sell_and_short_pos_side():
|
|
|
session = DummySession(
|
|
|
[
|
|
|
@@ -380,3 +463,14 @@ def test_get_positions_returns_normalized_positions():
|
|
|
assert positions[0].pos_side == "long"
|
|
|
assert positions[0].size == 8.0
|
|
|
assert positions[0].avg_price == 25000.0
|
|
|
+
|
|
|
+
|
|
|
+def test_get_positions_filters_zero_size_rows():
|
|
|
+ session = DummySession([positions_with_zero_size_response()])
|
|
|
+ client = OkxClient(config=sample_config(), session=session)
|
|
|
+
|
|
|
+ positions = client.get_positions(symbol="BTC-USDT-SWAP")
|
|
|
+
|
|
|
+ assert len(positions) == 1
|
|
|
+ assert positions[0].pos_side == "short"
|
|
|
+ assert positions[0].size == 3.0
|