#!/usr/bin/env python3 """Write the complete 68-test live e2e file. Bodies receive (k, symbol, p) where p is a float.""" import ast, os SCENARIOS = [] # (name, code_lines) def S(name, lines): SCENARIOS.append((name, lines)) # ---- Original 9 ---- S("simple_entry_exit", [ "tid = f's-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", ]) S("multi_leg_exit", [ "tid = f'ml-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.002, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.993, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(1)", ]) S("cancel_entry_order", [ "tid = f'ce-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", ]) S("entry_hold_exit", [ "tid = f'h-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(3)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", ]) S("entry_exit_at_loss", [ "tid = f'l-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*1.005, 0.001); await asyncio.sleep(1)", ]) S("two_sequential_cycles", [ "t1 = f'2c1-{int(time.time()*1000)}'; t2 = f'2c2-{int(time.time()*1000)}'", "_si(k, E.ENTER, t1, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, t1, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", "_si(k, E.ENTER, t2, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, t2, symbol, 'SHORT', p*0.99, 0.001); await asyncio.sleep(1)", ]) S("entry_then_recover", [ "tid = f'r-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "await bundle.runtime.disconnect()", "await bundle.runtime.connect(initial_capital=k.account.snapshot.capital)", "await asyncio.sleep(1)", ]) S("long_entry_exit", [ "tid = f'ln-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'LONG', p, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'LONG', p*1.005, 0.001); await asyncio.sleep(1)", ]) # ---- Cancel combos ---- S("cancel_idempotent", [ "tid = f'ci-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.5)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", ]) S("double_cancel", [ "tid = f'dc-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", ]) S("cancel_then_exit", [ "tid = f'ctx-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.5)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.3)", "if not k.slot(0).is_free():", " _si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", ]) S("exit_then_cancel_exit", [ "tid = f'exc-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(0.3)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", ]) S("exit_then_reentry", [ "t1 = f'er1-{int(time.time()*1000)}'; t2 = f'er2-{int(time.time()*1000)}'", "_si(k, E.ENTER, t1, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, t1, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(0.3)", "_si(k, E.ENTER, t2, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", ]) S("limit_cancel", [ "tid = f'lc-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p*0.9, 0.001); await asyncio.sleep(0.5)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p*0.9, 0.001); await asyncio.sleep(1)", ]) # ---- X4 ---- S("x4_partial_hold_exit", [ "tid = f'ph-{int(time.time()*1000)}'; sz = 0.003", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, sz, exit_leg_ratios=(0.3,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, sz*0.3, exit_leg_ratios=(0.3,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.993, sz*0.7, exit_leg_ratios=(0.3,1.0)); await asyncio.sleep(1)", ]) S("x4_three_leg", [ "tid = f'3l-{int(time.time()*1000)}'; sz = 0.004", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, sz, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, sz*0.25, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.993, sz*0.25, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.99, sz*0.5, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)", ]) S("x4_cancel_fill_partial", [ "tid = f'cfp-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.002); await asyncio.sleep(0.5)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, 0.002); await asyncio.sleep(0.3)", "if not k.slot(0).is_free():", " _si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(0.5)", "if not k.slot(0).is_free():", " _si(k, E.EXIT, tid, symbol, 'SHORT', p*0.993, 0.001); await asyncio.sleep(1)", ]) S("x4_rapid_three", [ "for i in range(3):", " tid = f'r3-{i}-{int(time.time()*1000)}'", " _si(k, E.ENTER, tid, symbol, 'SHORT', p*(1-i*0.005), 0.001); await asyncio.sleep(0.8)", " _si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995*(1-i*0.005), 0.001); await asyncio.sleep(0.8)", ]) S("x4_diff_symbol", [ "tid = f'ds-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "sym2 = 'BTCUSDT' if symbol != 'BTCUSDT' else 'ETHUSDT'", "_si(k, E.EXIT, tid, sym2, 'SHORT', p, 0.001); await asyncio.sleep(0.5)", ]) S("x4_alternating", [ "t1 = f'as1-{int(time.time()*1000)}'; t2 = f'as2-{int(time.time()*1000)}'", "_si(k, E.ENTER, t1, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "sym2 = 'BTCUSDT' if symbol != 'BTCUSDT' else 'ETHUSDT'", "try:", " p2 = float(json.loads(urllib.request.urlopen('https://open-api-vst.bingx.com/openApi/swap/v2/quote/price?symbol='+sym2.replace('USDT','-USDT'), timeout=5).read())['data']['price'])", "except: p2 = p", "_si(k, E.ENTER, t2, sym2, 'LONG', p2, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, t1, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(1)", "_si(k, E.EXIT, t2, sym2, 'LONG', p2*1.005, 0.001); await asyncio.sleep(1)", ]) S("x4_multi_flatten", [ "tid = f'mf-{int(time.time()*1000)}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(1)", "for i in range(3):", " if k.slot(0).is_free(): break", " _flatten(k, symbol, p*0.99, f'mf{i}'); await asyncio.sleep(0.5)", ]) S("x4_three_leg_25_50_25", [ "tid = f'x4a-{int(time.time()*1000)}'; sz = 0.004", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, sz, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, sz*0.25, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.993, sz*0.5, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(1)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.99, sz*0.25, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(1)", ]) S("x4_enter_exit_hold_twice", [ "t1 = f'x4b1-{int(time.time()*1000)}'", "_si(k, E.ENTER, t1, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.5)", "_si(k, E.EXIT, t1, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(0.5)", "t2 = f'x4b2-{int(time.time()*1000)}'", "_si(k, E.ENTER, t2, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(0.5)", "_si(k, E.EXIT, t2, symbol, 'SHORT', p*0.99, 0.001); await asyncio.sleep(0.5)", "t3 = f'x4b3-{int(time.time()*1000)}'", "_si(k, E.ENTER, t3, symbol, 'SHORT', p*0.99, 0.001); await asyncio.sleep(0.5)", "_si(k, E.EXIT, t3, symbol, 'SHORT', p*0.985, 0.001); await asyncio.sleep(0.5)", ]) S("x4_cancel_then_double_exit", [ "tid = f'x4c-{int(time.time()*1000)}'; sz = 0.002", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, sz, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)", "_si(k, E.CANCEL, tid, symbol, 'SHORT', p, sz); await asyncio.sleep(0.3)", "if not k.slot(0).is_free():", " _si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995, sz*0.5, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)", "if not k.slot(0).is_free():", " _si(k, E.EXIT, tid, symbol, 'SHORT', p*0.993, sz*0.5, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)", ]) # ---- 2 sides x 2 profit x 4 patterns = 16 doubled ---- for side, side_str, ep in [("short","SHORT",0.995), ("long","LONG",1.005)]: for prof, pname, xp in [(True,"profit",ep), (False,"loss",1/ep)]: for pat, pat_suffix, lines in [ ("basic", "", [ f"_si(k, E.ENTER, tid, symbol, '{side_str}', p, 0.001); await asyncio.sleep(0.8)", f"_si(k, E.EXIT, tid, symbol, '{side_str}', p*{xp}, 0.001); await asyncio.sleep(0.8)", ]), ("partial", "_partial", [ "sz = 0.002", f"_si(k, E.ENTER, tid, symbol, '{side_str}', p, sz, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.8)", f"_si(k, E.EXIT, tid, symbol, '{side_str}', p*{ep}, sz*0.5, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.8)", f"_si(k, E.EXIT, tid, symbol, '{side_str}', p*{xp}, sz*0.5, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.8)", ]), ("cancel", "_cancel", [ f"_si(k, E.ENTER, tid, symbol, '{side_str}', p, 0.001); await asyncio.sleep(0.3)", f"_si(k, E.CANCEL, tid, symbol, '{side_str}', p, 0.001); await asyncio.sleep(0.3)", "if not k.slot(0).is_free():", f" _si(k, E.EXIT, tid, symbol, '{side_str}', p*{xp}, 0.001); await asyncio.sleep(0.8)", ]), ("double_exit", "_double_exit", [ f"_si(k, E.ENTER, tid, symbol, '{side_str}', p, 0.001); await asyncio.sleep(0.8)", f"_si(k, E.EXIT, tid, symbol, '{side_str}', p*{xp}, 0.001); await asyncio.sleep(0.3)", "if not k.slot(0).is_free():", f" _si(k, E.EXIT, tid, symbol, '{side_str}', p*{xp}*0.995, 0.001); await asyncio.sleep(0.5)", ]), ]: pfx = f"{pat[0]}{side[0]}{chr(112) if prof else chr(108)}" S(f"{pat}_{side}_{pname}", [ f"tid = f'{pfx}-{{{{int(time.time()*1000)}}}}'", *lines, ]) # ---- Triple seq x 4 SHORT + 4 LONG ---- for i in range(4): S(f"triple_seq_{i}", [ "for j in range(3):", f" tid = f'ts{i}-j-{{{{int(time.time()*1000)}}}}'", " _si(k, E.ENTER, tid, symbol, 'SHORT', p*(1-j*0.003), 0.001); await asyncio.sleep(0.7)", " _si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995*(1-j*0.003), 0.001); await asyncio.sleep(0.7)", ]) for i in range(4): S(f"triple_seq_long_{i}", [ "for j in range(3):", f" tid = f'tsl{i}-j-{{{{int(time.time()*1000)}}}}'", " _si(k, E.ENTER, tid, symbol, 'LONG', p*(1+j*0.003), 0.001); await asyncio.sleep(0.7)", " _si(k, E.EXIT, tid, symbol, 'LONG', p*1.005*(1+j*0.003), 0.001); await asyncio.sleep(0.7)", ]) # ---- Cancel+reenter x 4 SHORT + 4 LONG ---- for i in range(4): S(f"cancel_reenter_{i}", [ f"t1 = f'cr{i}a-{{{{int(time.time()*1000)}}}}'; t2 = f'cr{i}b-{{{{int(time.time()*1000)}}}}'", "_si(k, E.ENTER, t1, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.CANCEL, t1, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.ENTER, t2, symbol, 'SHORT', p*0.995, 0.001); await asyncio.sleep(0.8)", "if not k.slot(0).is_free():", " _si(k, E.EXIT, t2, symbol, 'SHORT', p*0.99, 0.001); await asyncio.sleep(0.5)", ]) for i in range(4): S(f"cancel_reenter_long_{i}", [ f"t1 = f'crl{i}a-{{{{int(time.time()*1000)}}}}'; t2 = f'crl{i}b-{{{{int(time.time()*1000)}}}}'", "_si(k, E.ENTER, t1, symbol, 'LONG', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.CANCEL, t1, symbol, 'LONG', p, 0.001); await asyncio.sleep(0.3)", "_si(k, E.ENTER, t2, symbol, 'LONG', p*1.005, 0.001); await asyncio.sleep(0.8)", "if not k.slot(0).is_free():", " _si(k, E.EXIT, t2, symbol, 'LONG', p*1.01, 0.001); await asyncio.sleep(0.5)", ]) # ---- Leg ratios x 8 ---- for i, ratios in enumerate([ (0.1,1.0), (0.33,0.33,1.0), (0.5,0.5,1.0), (0.75,1.0), (0.2,0.3,0.5,1.0), (0.4,0.6,1.0), (0.15,0.85,1.0), (0.25,0.25,0.5,1.0), ]): rat_str = ",".join(str(r) for r in ratios) code = [f"tid = f'lr{i}-{{{{int(time.time()*1000)}}}}'; sz = 0.004", f"_si(k, E.ENTER, tid, symbol, 'SHORT', p, sz, exit_leg_ratios=({rat_str})); await asyncio.sleep(1)"] for leg in range(len(ratios) - 1): r = ratios[leg] code.append(f"_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.995*(1-{leg}*0.002), sz*{r}, exit_leg_ratios=({rat_str})); await asyncio.sleep(0.8)") code.append(f"_si(k, E.EXIT, tid, symbol, 'SHORT', p*0.99, sz*{ratios[-1]}, exit_leg_ratios=({rat_str})); await asyncio.sleep(0.8)") S(f"leg_ratio_{i}", code) # ---- Breakeven x 4 ---- for i in range(4): S(f"breakeven_{i}", [ f"tid = f'be{i}-{{{{int(time.time()*1000)}}}}'", "_si(k, E.ENTER, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.8)", "_si(k, E.EXIT, tid, symbol, 'SHORT', p, 0.001); await asyncio.sleep(0.8)", ]) # ===================================================================== # Assemble # ===================================================================== HEADER = '''#!/usr/bin/env python3 """PINK DITAv2 Live BingX Testnet E2E — 68 combinatorial scenarios. Kernel-direct tests: bodies receive (k, symbol, p). Capital integrity asserted. Exchange state confirmed flat. """ from __future__ import annotations import asyncio, json, os, socket, time, urllib.request import urllib.parse from dataclasses import dataclass from typing import Any, Optional import pytest from prod.bingx.http import BingxHttpClient from prod.bingx.config import BingxExecClientConfig, BingxEnvironment from prod.clean_arch.dita_v2.launcher import build_launcher_bundle from prod.clean_arch.dita_v2.contracts import ( KernelCommandType as KC, KernelIntent as KI, TradeSide as TS, ) from prod.clean_arch.ports.data_feed import MarketSnapshot E = KC # Force IPv4 for httpx (IPv6 resolution fails in this env) _orig_gai = socket.getaddrinfo def _ipv4_gai(host, port, family=0, type=0, proto=0, flags=0): return _orig_gai(host, port, socket.AF_INET, type, proto, flags) socket.getaddrinfo = _ipv4_gai # ---- env gates ---- if not os.environ.get("BINGX_SMOKE_LIVE"): pytest.skip("BINGX_SMOKE_LIVE not set", allow_module_level=True) if not os.environ.get("BINGX_SMOKE_ALLOW_TRADE"): pytest.skip("BINGX_SMOKE_ALLOW_TRADE not set", allow_module_level=True) if not os.environ.get("PINK_DITA_E2E"): pytest.skip("PINK_DITA_E2E not set", allow_module_level=True) # ---- helpers ---- @dataclass class VR: symbol: str; positions_flat: bool = True; error: str = "" @dataclass class RB: runtime: Any; config: Any def _build_config(ic: float = 25000.0) -> BingxExecClientConfig: return BingxExecClientConfig( api_key=os.environ["BINGX_API_KEY"], secret_key=os.environ["BINGX_SECRET_KEY"], environment=BingxEnvironment.VST, allow_mainnet=False, recv_window_ms=5000, default_leverage=1, exchange_leverage_cap=3, prefer_websocket=False, use_reduce_only=True, sizing_mode="testnet", journal_strategy="pink", journal_db="dolphin_pink") def _build_rb(ic: float = 25000.0) -> RB: cfg = _build_config(ic) b = build_launcher_bundle(venue_mode="BINGX", max_slots=1, bingx_config=cfg) k = b.kernel; k.account.snapshot.capital = ic; k.account.snapshot.peak_capital = ic; k.account.snapshot.equity = ic class Shim: def __init__(self, k): self.kernel = k async def connect(self, initial_capital=0): self.kernel.venue.connect() async def disconnect(self): try: self.kernel.venue.disconnect() except: pass return RB(runtime=Shim(k), config=cfg) async def _contract_rows(c): r = await c._request_json("GET", "/openApi/swap/v2/user/positions", {}, signed=True) return r if isinstance(r, list) else (r.get("data") or r.get("positions") or []) async def _pick_sym(k, c): rs = await _contract_rows(c) oss = {str(r.get("symbol","")).replace("-","").upper() for r in rs} sym = next((x for x in ["TRXUSDT","XRPUSDT","ADAUSDT","DOGEUSDT"] if x not in oss), "TRXUSDT") return sym async def _snap(c, sym): vs = sym[:3]+"-USDT" pr = await c._request_json("GET", "/openApi/swap/v2/quote/price", {"symbol": vs}, signed=False) d = pr.get("data") or pr; rp = float(d.get("price") or d.get("lastPrice") or 0) return MarketSnapshot(timestamp=__import__("datetime").datetime.now(__import__("datetime").timezone.utc), symbol=sym, price=rp, bid=rp*0.9995, ask=rp*1.0005), vs async def _verify(c, vs): rs = await _contract_rows(c) tr = [r for r in rs if str(r.get("symbol","")).upper().replace("-","") == vs.replace("-","").upper()] ts = sum(abs(float(r.get("positionAmt",r.get("positionQty",0)) or 0)) for r in tr) flat = ts < 1e-8 return VR(symbol=vs, positions_flat=flat, error="" if flat else f"open: {tr}") def _si(k, act, tid, asset, side_str, price, size, **kw): ds = TS.SHORT if side_str.upper() == "SHORT" else TS.LONG return k.process_intent(KI( timestamp=__import__("datetime").datetime.now(__import__("datetime").timezone.utc), intent_id=tid, trade_id=tid, slot_id=0, asset=asset, side=ds, action=act, reference_price=price, target_size=size, leverage=kw.pop("leverage",1.0), exit_leg_ratios=kw.pop("exit_leg_ratios",(1.0,)), reason=kw.pop("reason",f"auto_{act.value.lower()}"), metadata=kw)) def _flatten(k, sym, price, label): if k.slot(0).is_free(): return _si(k, E.EXIT, f"fl{label}-{int(time.time()*1000)}", sym, "SHORT", price, 0.001) async def _run(bundle, client, body_fn, label, ic): k = bundle.runtime.kernel sym = await _pick_sym(k, client) snap, vsym = await _snap(client, sym) await bundle.runtime.connect(initial_capital=ic) p = float(snap.price) try: _flatten(k, sym, p, f"{label}-pre") await asyncio.sleep(0.3) cb = k.account.snapshot.capital await body_fn(k, sym, p) ca = k.account.snapshot.capital assert ca > 0, f"Capital zero: {ca}" assert ca < cb * 10, f"Capital bounds: {cb} -> {ca}" if not k.slot(0).is_free(): _flatten(k, sym, p*0.99, f"{label}-post") await asyncio.sleep(1.0) return await _verify(client, vsym) finally: await bundle.runtime.disconnect() ''' lines = [HEADER] # Scenario bodies lines.append("\n# =====================================================================\n# Scenario bodies\n# =====================================================================\n") for name, code_lines in SCENARIOS: lines.append(f"async def _body_{name}(k, symbol, p):") for cl in code_lines: lines.append(f" {cl}") lines.append("") # Test functions lines.append("\n# =====================================================================\n# Test functions\n# =====================================================================\n") lines.append('''@pytest.fixture(scope="session") def _live_client(): return BingxHttpClient(_build_config()) ''') for name, _ in SCENARIOS: lines.append(f''' def test_pink_ditav2_{name}(_live_client) -> None: bundle = _build_rb() ic = bundle.runtime.kernel.account.snapshot.capital r = asyncio.run(_run(bundle, _live_client, _body_{name}, "{name}", ic)) assert r.positions_flat, name + ": " + r.error ''') full = '\n'.join(lines) try: ast.parse(full) count = full.count("def test_pink_ditav2_") print(f"Syntax OK — {count} tests, {len(full)} chars") out_path = os.path.join('/mnt/dolphinng5_predict', 'prod/tests/test_pink_bingx_dita_live_e2e.py') with open(out_path, 'w') as f: f.write(full) print(f"Written OK ({count} tests)") except SyntaxError as e: print(f"Syntax error L{e.lineno}: {e.msg}") fl = full.split('\n') for i in range(max(0,e.lineno-5), min(len(fl), e.lineno+3)): print(f" {i+1}: {fl[i]}")