#!/usr/bin/env python3 """L3 — live VST validation of the LIMIT execution path. The policy is MARKET-only (execution-infra scope), so LIMIT is validated by injecting a LIMIT KernelIntent into the live kernel. This places a *non-marketable* resting SHORT LIMIT (5% above market, so it will not fill), confirms the exchange holds it as an open LIMIT order (i.e. the L2 wiring actually placed a LIMIT, not a MARKET that would have filled), then cancels it and confirms the account is flat with no dangling orders. The LIMIT fill -> async-fill-pump -> settle/persist path is deterministically covered offline (test_pink_async_fill_pump.py); live we validate placement + resting + cancel against the real venue. Gates: BINGX_SMOKE_LIVE, BINGX_SMOKE_ALLOW_TRADE, PINK_DITA_E2E, PINK_RUNTHROUGH. Run on a FLAT account, from repo root with PYTHONPATH=/mnt/dolphinng5_predict. """ from __future__ import annotations import asyncio import os from datetime import datetime, timezone import pytest for _gate in ("BINGX_SMOKE_LIVE", "BINGX_SMOKE_ALLOW_TRADE", "PINK_DITA_E2E", "PINK_RUNTHROUGH"): if not os.environ.get(_gate): pytest.skip(f"{_gate} not set", allow_module_level=True) from prod.tests.test_pink_bingx_dita_live_e2e import ( # noqa: E402 _build_config, _pick_sym, _snap, _check_open_orders, _verify, _flatten, ) from prod.bingx.http import BingxHttpClient # noqa: E402 from prod.clean_arch.dita_v2.launcher import build_launcher_bundle # noqa: E402 from prod.clean_arch.dita_v2.contracts import ( # noqa: E402 KernelCommandType, KernelIntent, TradeSide, TradeStage, ) def _intent(action, *, asset, price, size, order_type="MARKET", limit_price=0.0, reason="LIMIT_TEST"): return KernelIntent( timestamp=datetime.now(timezone.utc), intent_id=f"{reason}-{int(datetime.now().timestamp()*1000)}", trade_id=f"{reason}-T", slot_id=0, asset=asset, side=TradeSide.SHORT, action=action, reference_price=price, target_size=size, leverage=1.0, exit_leg_ratios=(1.0,), reason=reason, order_type=order_type, limit_price=limit_price, ) async def _run() -> dict: bundle = build_launcher_bundle(venue_mode="BINGX", max_slots=1, bingx_config=_build_config()) k = bundle.kernel k.account.snapshot.capital = 25_000.0 k.account.snapshot.peak_capital = 25_000.0 k.account.snapshot.equity = 25_000.0 client = BingxHttpClient(_build_config()) sym = await _pick_sym(k, client) snap, vsym = await _snap(client, sym) p = float(snap.price) assert p > 0, f"no live price for {sym}" k.venue.connect() try: assert k.slot(0).is_free(), f"slot not free (state={k.slot(0).fsm_state}); flatten the account first" # Non-marketable resting SHORT LIMIT: sell 5% above market -> will not fill. # Size to clear the exchange minimum order amount (~$25 notional); # _format_quantity only quantizes to step, it does NOT floor to the min. limit_px = round(p * 1.05, 6) size = round(25.0 / p, 3) out = k.process_intent(_intent( KernelCommandType.ENTER, asset=sym, price=limit_px, size=size, order_type="LIMIT", limit_price=limit_px, )) assert out.accepted, f"LIMIT entry rejected: {out.diagnostic_code} {out.details}" await asyncio.sleep(1.5) slot = k.slot(0) # A real resting LIMIT must NOT fill synchronously (a MARKET would have). assert slot.fsm_state == TradeStage.ENTRY_WORKING, f"expected ENTRY_WORKING, got {slot.fsm_state}" assert abs(slot.size) < 1e-9, f"resting LIMIT should not be filled, size={slot.size}" # Exchange truth: a resting LIMIT order exists for the symbol. oos = await _check_open_orders(client, vsym) types = [str(o.get("type", "")).upper() for o in oos] assert oos, "no open order on the exchange — LIMIT was not placed (or filled as MARKET)" assert any(t == "LIMIT" for t in types), f"open order is not a LIMIT: {types}" # Cancel the working LIMIT -> back to IDLE, account flat, no dangling order. k.process_intent(_intent(KernelCommandType.CANCEL, asset=sym, price=limit_px, size=0.001, reason="LIMIT_CANCEL")) await asyncio.sleep(1.5) assert k.slot(0).is_free(), f"slot not free after cancel: {k.slot(0).fsm_state}" result = {"symbol": sym, "limit_px": limit_px, "open_order_types": types} finally: # Safety net: cancel/flatten anything left. try: if not k.slot(0).is_free(): _flatten(k, sym, p, "limit-post") except Exception: pass try: k.venue.disconnect() except Exception: pass vr = await _verify(client, vsym) assert vr.positions_flat, f"exchange not flat after cancel: {vr.error}" return result def test_pink_limit_order_rests_and_cancels() -> None: result = asyncio.run(_run()) print(f"[PINK limit live] {result}")