First commit of the previously-untracked PINK-on-DITAv2 migration system (execution moves to the Rust kernel; policy stays on legacy DITA, so Alpha Engine algorithmic integrity is preserved). BLUE is untouched. Sprint 0 (safety snapshot + flaw-fix verification, MARKET single-leg scope): - Verified Rust FSM fixes (flaws 2,4,10,11,13) by source read of lib.rs. - Hardened 5 vacuous/guarded assertions in test_flaws.py so each flaw test genuinely exercises its fix. Most important: Flaw 5 now asserts capital moves by EXACTLY realized PnL (was entering/exiting at the same price). - Offline suites: 533 passed, 0 failed (35 flaws + 402 kernel/accounting/ bridge + 96 runtime/persistence/multi-exit/restart/seams). - GATE PASS: MARKET-path-critical flaws 1,2,5 confirmed fixed + green. - Added SPRINT0_FLAW_VERIFICATION.md report and _rust_kernel/.gitignore (excludes Rust target/ build artifacts). LIMIT/partial-fill remain explicitly out of scope (MARKET-only bring-up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1245 lines
60 KiB
Python
1245 lines
60 KiB
Python
"""Generate the complete pink e2e test file."""
|
|
import sys
|
|
sys.path.insert(0, '/mnt/dolphinng5_predict')
|
|
|
|
fpath = '/mnt/dolphinng5_predict/prod/tests/test_pink_bingx_dita_live_e2e.py'
|
|
|
|
lines = []
|
|
|
|
def emit(s=""):
|
|
lines.append(s)
|
|
|
|
# ---- IMPORTS ----
|
|
emit('#!/usr/bin/env python3')
|
|
emit('"""PINK DITAv2 Live BingX Testnet E2E — conceptual gap coverage."""')
|
|
emit('from __future__ import annotations')
|
|
emit('import asyncio, json, os, socket, time, urllib.request')
|
|
emit('import urllib.parse')
|
|
emit('from dataclasses import dataclass')
|
|
emit('from typing import Any, Optional')
|
|
emit('import pytest')
|
|
emit('from prod.bingx.http import BingxHttpClient')
|
|
emit('from prod.bingx.config import BingxExecClientConfig, BingxEnvironment')
|
|
emit('from prod.clean_arch.dita_v2.launcher import build_launcher_bundle')
|
|
emit('from prod.clean_arch.dita_v2.contracts import (')
|
|
emit(' KernelCommandType as KC, KernelIntent as KI, TradeSide as TS,')
|
|
emit(' VenueEvent, VenueEventStatus, KernelEventKind,')
|
|
emit(' TradeStage, KernelDiagnosticCode, KernelSeverity,')
|
|
emit(' KernelOutcome, KernelTransition, TradeSlot, VenueOrder,')
|
|
emit(')')
|
|
emit('from prod.clean_arch.ports.data_feed import MarketSnapshot')
|
|
emit('E = KC')
|
|
emit('')
|
|
emit('# Force IPv4')
|
|
emit('_orig_gai = socket.getaddrinfo')
|
|
emit('def _ipv4_gai(host, port, family=0, type=0, proto=0, flags=0):')
|
|
emit(' return _orig_gai(host, port, socket.AF_INET, type, proto, flags)')
|
|
emit('socket.getaddrinfo = _ipv4_gai')
|
|
emit('')
|
|
emit('_last_finish: float = 0.0')
|
|
emit('def _throttle(min_gap: float = 3.0) -> None:')
|
|
emit(' global _last_finish')
|
|
emit(' now = __import__("time").time()')
|
|
emit(' elapsed = now - _last_finish')
|
|
emit(' if elapsed < min_gap:')
|
|
emit(' __import__("time").sleep(min_gap - elapsed)')
|
|
emit(' _last_finish = __import__("time").time()')
|
|
emit('')
|
|
|
|
# ---- HELPERS ----
|
|
emit('class VR:')
|
|
emit(' def __init__(self, symbol, positions_flat, error):')
|
|
emit(' self.symbol = symbol; self.positions_flat = positions_flat; self.error = error')
|
|
emit('')
|
|
emit('class RB:')
|
|
emit(' def __init__(self, runtime=None, config=None):')
|
|
emit(' self.runtime = runtime; self.config = config')
|
|
emit('')
|
|
emit('def _build_config(ic: float = 25000.0) -> BingxExecClientConfig:')
|
|
emit(' return BingxExecClientConfig(environment=BingxEnvironment.TESTNET,')
|
|
emit(' api_key=os.environ["BINGX_API_KEY"], secret_key=os.environ["BINGX_SECRET_KEY"],')
|
|
emit(' testnet=True, recv_window_ms=5000, default_leverage=1, initial_capital_usdt=ic)')
|
|
emit('')
|
|
emit('def _build_rb(ic: float = 25000.0, max_slots: int = 1) -> RB:')
|
|
emit(' cfg = _build_config(ic)')
|
|
emit(' b = build_launcher_bundle(venue_mode="BINGX", max_slots=max_slots, bingx_config=cfg)')
|
|
emit(' k = b.kernel; k.account.snapshot.capital = ic')
|
|
emit(' k.account.snapshot.peak_capital = ic; k.account.snapshot.equity = ic')
|
|
emit(' class Shim:')
|
|
emit(' def __init__(self, k): self.kernel = k')
|
|
emit(' async def connect(self, initial_capital=0): self.kernel.venue.connect()')
|
|
emit(' async def disconnect(self):')
|
|
emit(' try: self.kernel.venue.disconnect()')
|
|
emit(' except: pass')
|
|
emit(' return RB(runtime=Shim(k), config=cfg)')
|
|
emit('')
|
|
emit('def _inspect_outcome(r, label):')
|
|
emit(' return dict(accepted=r.accepted, state=r.state.value if r.state else "",')
|
|
emit(' diagnostic=r.diagnostic_code.value if r.diagnostic_code else "",')
|
|
emit(' severity=r.severity.value if r.severity else "",')
|
|
emit(' transitions=[(t.prev_state.value, t.next_state.value) for t in (r.transitions or ())],')
|
|
emit(' event_kinds=[e.kind.value for e in (r.emitted_events or ())],')
|
|
emit(' details=dict(r.details or {}))')
|
|
emit('')
|
|
emit('def _assert_accepted(r, label):')
|
|
emit(' info = _inspect_outcome(r, label)')
|
|
emit(' assert r.accepted, f"{label}: intent rejected diag={info[chr(34)+chr(34)]diagnostic[chr(34)+chr(34)]} state={info[chr(34)+chr(34)]state[chr(34)+chr(34)]} detail={info[chr(34)+chr(34)]details[chr(34)+chr(34)]}"')
|
|
emit('')
|
|
emit('def _assert_rejected(r, expected_diag, label):')
|
|
emit(' info = _inspect_outcome(r, label)')
|
|
emit(' assert not r.accepted, f"{label}: expected rejection but got accepted state={info[chr(34)+chr(34)]state[chr(34)+chr(34)]}"')
|
|
emit(' assert info["diagnostic"] == expected_diag, f"{label}: expected {expected_diag} got {info[chr(34)+chr(34)]diagnostic[chr(34)+chr(34)]}"')
|
|
emit('')
|
|
emit('def _check_slot_accounting(k, label):')
|
|
emit(' sc = getattr(k, "_start_cap", None)')
|
|
emit(' if sc is None: return')
|
|
emit(' trp = sum(k.slot(i).realized_pnl for i in range(k.max_slots))')
|
|
emit(' tup = sum(k.slot(i).unrealized_pnl for i in range(k.max_slots))')
|
|
emit(' expected = sc + trp + tup')
|
|
emit(' actual = k.account.snapshot.capital')
|
|
emit(' assert abs(actual - expected) < 0.01, f"{label}: acct mismatch cap={actual} exp={expected} rp={trp} upnl={tup}"')
|
|
emit('')
|
|
emit('def _check_open_orders(c, vs):')
|
|
emit(' import asyncio')
|
|
emit(' r = asyncio.run(c._request_json("GET", "/openApi/swap/v2/trade/openOrders", {"symbol": vs}, signed=True))')
|
|
emit(' data = r if isinstance(r, list) else (r.get("data") or r.get("orders") or [])')
|
|
emit(' return [o for o in data if isinstance(o, dict)]')
|
|
emit('')
|
|
emit('def _build_fresh_kernel_from_slot(slot_data, ic=25000.0):')
|
|
emit(' from prod.clean_arch.dita_v2.rust_backend import _slot_from_payload')
|
|
emit(' cfg = _build_config(ic)')
|
|
emit(' b = build_launcher_bundle(venue_mode="BINGX", max_slots=1, bingx_config=cfg)')
|
|
emit(' k = b.kernel; k.account.snapshot.capital = ic')
|
|
emit(' k.account.snapshot.peak_capital = ic; k.account.snapshot.equity = ic')
|
|
emit(' restored = _slot_from_payload(slot_data)')
|
|
emit(' k.reconcile_from_slots([restored])')
|
|
emit(' class Shim:')
|
|
emit(' def __init__(self, k): self.kernel = k')
|
|
emit(' async def connect(self, ic=0): self.kernel.venue.connect()')
|
|
emit(' async def disconnect(self):')
|
|
emit(' try: self.kernel.venue.disconnect()')
|
|
emit(' except: pass')
|
|
emit(' return RB(runtime=Shim(k), config=cfg)')
|
|
emit('')
|
|
|
|
# ---- EXISTING HELPERS ----
|
|
emit('async def _contract_rows(c):')
|
|
emit(' r = await c._request_json("GET", "/openApi/swap/v2/user/positions", {}, signed=True)')
|
|
emit(' return r if isinstance(r, list) else (r.get("data") or r.get("positions") or [])')
|
|
emit('')
|
|
emit('async def _pick_sym(k, c):')
|
|
emit(' rs = await _contract_rows(c)')
|
|
emit(' oss = {str(r.get("symbol","")).replace("-","").upper() for r in rs}')
|
|
emit(' return next((x for x in ["TRXUSDT","XRPUSDT","ADAUSDT","DOGEUSDT"] if x not in oss), "TRXUSDT")')
|
|
emit('')
|
|
emit('async def _snap(c, sym):')
|
|
emit(' pr = await c._request_json("GET", "/openApi/swap/v2/quote/price", {"symbol": sym}, signed=False)')
|
|
emit(' d = pr.get("data") or pr; rp = float(d.get("price") or d.get("lastPrice") or 0)')
|
|
emit(' return MarketSnapshot(timestamp=__import__("datetime").datetime.now(__import__("datetime").timezone.utc),')
|
|
emit(' symbol=sym, price=rp, bid=rp*0.9995, ask=rp*1.0005), sym')
|
|
emit('')
|
|
emit('async def _verify(c, vs):')
|
|
emit(' rs = await _contract_rows(c)')
|
|
emit(' tr = [r for r in rs if str(r.get("symbol","")).upper().replace("-","") == vs.replace("-","").upper()]')
|
|
emit(' ts = sum(abs(float(r.get("positionAmt",r.get("positionQty",0)) or 0)) for r in tr)')
|
|
emit(' flat = ts < 1e-8')
|
|
emit(' oos = _check_open_orders(c, vs)')
|
|
emit(' no_orders = len(oos) == 0')
|
|
emit(' err = ""')
|
|
emit(' if not flat: err += f"pos_open: {tr} "')
|
|
emit(' if not no_orders: err += f"open_orders: {oos} "')
|
|
emit(' return VR(symbol=vs, positions_flat=flat and no_orders, error=err.strip())')
|
|
emit('')
|
|
emit('def _si(k, act, tid, asset, side_str, price, size, **kw):')
|
|
emit(' ds = TS.SHORT if side_str.upper() == "SHORT" else TS.LONG')
|
|
emit(' slot_id = kw.pop("slot_id", 0)')
|
|
emit(' return k.process_intent(KI(timestamp=__import__("datetime").datetime.now(__import__("datetime").timezone.utc),')
|
|
emit(' intent_id=tid, trade_id=tid, slot_id=slot_id, asset=asset, side=ds, action=act,')
|
|
emit(' reference_price=price, target_size=size, leverage=kw.pop("leverage",1.0),')
|
|
emit(' exit_leg_ratios=kw.pop("exit_leg_ratios",(1.0,)),')
|
|
emit(' reason=kw.pop("reason",f"auto_{act.value.lower()}"), metadata=kw))')
|
|
emit('')
|
|
emit('def _flatten(k, sym, price, label, slot_id=0):')
|
|
emit(' if k.slot(slot_id).is_free(): return')
|
|
emit(' ts = int(time.time()*1000)')
|
|
emit(' _si(k, E.EXIT, f"fl{label}-{ts}", sym, "SHORT", price, 0.001, slot_id=slot_id)')
|
|
emit(' if not k.slot(slot_id).is_free():')
|
|
emit(' _si(k, E.EXIT, f"fl{label}b-{ts}", sym, "LONG", price, 0.001, slot_id=slot_id)')
|
|
emit('')
|
|
emit('async def _run(bundle, client, body_fn, label, ic):')
|
|
emit(' k = bundle.runtime.kernel')
|
|
emit(' sym = await _pick_sym(k, client)')
|
|
emit(' snap, vsym = await _snap(client, sym)')
|
|
emit(' await bundle.runtime.connect(initial_capital=ic)')
|
|
emit(' p = float(snap.price)')
|
|
emit(' try:')
|
|
emit(' for si in range(k.max_slots):')
|
|
emit(' if not k.slot(si).is_free():')
|
|
emit(' _flatten(k, sym, p*0.99 if si == 0 else p*1.005, f"{label}-pre-{si}")')
|
|
emit(' await asyncio.sleep(0.3)')
|
|
emit(' k._start_cap = k.account.snapshot.capital')
|
|
emit(' cb = k.account.snapshot.capital')
|
|
emit(' await body_fn(k, sym, p)')
|
|
emit(' ca = k.account.snapshot.capital')
|
|
emit(' assert ca > 0, f"Capital zero: {ca}"')
|
|
emit(' max_change = max(1.0, cb * 0.10)')
|
|
emit(' assert cb - ca < max_change, f"Capital shrunk beyond tolerance: {cb} -> {ca} (limit={max_change})"')
|
|
emit(' total_rp = sum(k.slot(i).realized_pnl for i in range(k.max_slots))')
|
|
emit(' if abs(total_rp) > 0.0001:')
|
|
emit(' assert abs(total_rp) < abs(cb - ca) + 0.01, f"{label}: rp={total_rp} != cap_change={cb-ca}"')
|
|
emit(' for si in range(k.max_slots):')
|
|
emit(' if not k.slot(si).is_free():')
|
|
emit(' _flatten(k, sym, p*0.99 if si == 0 else p*1.005, f"{label}-post-{si}")')
|
|
emit(' await asyncio.sleep(1.0)')
|
|
emit(' _throttle(3.0)')
|
|
emit(' return await _verify(client, vsym)')
|
|
emit(' finally:')
|
|
emit(' await bundle.runtime.disconnect()')
|
|
emit('')
|
|
|
|
# ---- BODY TEMPLATES ----
|
|
# I'll build the body functions from a structured list
|
|
bodies = {} # name -> list of code lines
|
|
|
|
def B(name, lines):
|
|
bodies[name] = lines
|
|
|
|
B("simple_entry_exit", [
|
|
'tid = f"ss-{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)',
|
|
])
|
|
|
|
B("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)',
|
|
])
|
|
|
|
B("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)',
|
|
])
|
|
|
|
B("entry_hold_exit", [
|
|
'tid = f"eh-{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)',
|
|
])
|
|
|
|
B("entry_exit_at_loss", [
|
|
'tid = f"el-{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)',
|
|
])
|
|
|
|
B("two_sequential_cycles", [
|
|
't1 = f"sq1-{int(time.time()*1000)}"; t2 = f"sq2-{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)',
|
|
])
|
|
|
|
B("entry_then_recover", [
|
|
'tid = f"r-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(1)',
|
|
'_si(k, E.CANCEL, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.5)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(1)',
|
|
])
|
|
|
|
B("long_entry_exit", [
|
|
'tid = f"l-{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)',
|
|
])
|
|
|
|
B("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)',
|
|
])
|
|
|
|
B("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)',
|
|
])
|
|
|
|
B("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)',
|
|
])
|
|
|
|
B("exit_then_cancel_exit", [
|
|
'tid = f"ecx-{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)',
|
|
])
|
|
|
|
B("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(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)',
|
|
])
|
|
|
|
B("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.3)',
|
|
'_si(k, E.CANCEL, tid, symbol, "SHORT", p*0.9, 0.001); await asyncio.sleep(1)',
|
|
])
|
|
|
|
B("x4_partial_hold_exit", [
|
|
'tid = f"x4ph-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.002, exit_leg_ratios=(0.3,1.0)); await asyncio.sleep(1)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.0006, exit_leg_ratios=(0.3,1.0)); await asyncio.sleep(1)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.0014, exit_leg_ratios=(0.3,1.0)); await asyncio.sleep(1)',
|
|
])
|
|
|
|
B("x4_three_leg", [
|
|
'tid = f"x4tl-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.003, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.00075, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.0015, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.991, 0.00075, exit_leg_ratios=(0.25,0.25,1.0)); await asyncio.sleep(1)',
|
|
])
|
|
|
|
B("x4_cancel_fill_partial", [
|
|
'tid = f"x4cf-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.002, exit_leg_ratios=(0.5,1.0)); 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, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)',
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("x4_rapid_three", [
|
|
"for j in range(3):",
|
|
' tid = f"x4r{j}-{int(time.time()*1000)}"',
|
|
' _si(k, E.ENTER, tid, symbol, "SHORT", p*(1-j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995*(1-j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("x4_diff_symbol", [
|
|
"ts = int(time.time()*1000)",
|
|
'_si(k, E.ENTER, f"x4ds1-{ts}", symbol, "SHORT", p, 0.001); await asyncio.sleep(0.5)',
|
|
'_si(k, E.EXIT, f"x4ds1-{ts}", "ZZZUSDT", "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("x4_alternating", [
|
|
"ts = int(time.time()*1000)",
|
|
'_si(k, E.ENTER, f"x4a1-{ts}", symbol, "SHORT", p, 0.001); await asyncio.sleep(0.5)',
|
|
'_si(k, E.EXIT, f"x4a1-{ts}", symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
'_si(k, E.ENTER, f"x4a2-{ts}", symbol, "LONG", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
'_si(k, E.EXIT, f"x4a2-{ts}", symbol, "LONG", p*1.002, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("x4_multi_flatten", [
|
|
'tid = f"x4mf-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"while not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.3)',
|
|
])
|
|
|
|
B("x4_three_leg_25_50_25", [
|
|
'tid = f"x4t3-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.002, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(1)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.0005, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(0.7)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.001, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(0.7)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.991, 0.0005, exit_leg_ratios=(0.25,0.5,1.0)); await asyncio.sleep(1)',
|
|
])
|
|
|
|
B("x4_enter_exit_hold_twice", [
|
|
"for j in range(3):",
|
|
' tid = f"x4ht{j}-{int(time.time()*1000)}"',
|
|
' _si(k, E.ENTER, tid, symbol, "SHORT", p*(1-j*0.002), 0.001); await asyncio.sleep(0.5)',
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995*(1-j*0.002), 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("x4_cancel_then_double_exit", [
|
|
'tid = f"x4cd-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.002, exit_leg_ratios=(0.5,1.0)); 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, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)',
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
def make_profit_loss_bodies():
|
|
for side, side_label in [("SHORT", "short"), ("LONG", "long")]:
|
|
for pl, pl_label, price_factor in [("profit", "profit", ("0.997" if side == "SHORT" else "1.003")), ("loss", "loss", ("1.003" if side == "SHORT" else "0.997"))]:
|
|
for pattern, pat_label in [("basic", "basic"), ("partial", "partial"), ("cancel", "cancel"), ("double_exit", "double_exit")]:
|
|
name = f"{pat_label}_{side_label}_{pl}"
|
|
lines = []
|
|
tid_expr = f'f"{name[:3]}-{{int(time.time()*1000)}}"'
|
|
lines.append(f'tid = {tid_expr}')
|
|
if pattern == "basic":
|
|
exit_price = f"p*{price_factor}"
|
|
lines.append(f'_si(k, E.ENTER, tid, symbol, "{side}", p, 0.001); await asyncio.sleep(0.8)')
|
|
lines.append(f'_si(k, E.EXIT, tid, symbol, "{side}", {exit_price}, 0.001); await asyncio.sleep(0.5)')
|
|
elif pattern == "partial":
|
|
exit1 = f"p*{float(price_factor) ** 1}" if "*" not in str(price_factor) else f"p*{price_factor}"
|
|
# Use different prices for two legs
|
|
if pl == "profit":
|
|
p1, p2 = ("p*0.995", "p*0.993") if side == "SHORT" else ("p*1.005", "p*1.007")
|
|
else:
|
|
p1, p2 = ("p*1.003", "p*1.005") if side == "SHORT" else ("p*0.997", "p*0.995")
|
|
lines.append('_si(k, E.ENTER, tid, symbol, "' + side + '", p, 0.002, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.8)')
|
|
lines.append(f'_si(k, E.EXIT, tid, symbol, "{side}", {p1}, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)')
|
|
lines.append(f'_si(k, E.EXIT, tid, symbol, "{side}", {p2}, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)')
|
|
elif pattern == "cancel":
|
|
lines.append(f'_si(k, E.ENTER, tid, symbol, "{side}", p, 0.001); await asyncio.sleep(0.5)')
|
|
lines.append(f'_si(k, E.CANCEL, tid, symbol, "{side}", p, 0.001); await asyncio.sleep(0.3)')
|
|
lines.append("if not k.slot(0).is_free():")
|
|
ef = f"p*{price_factor}"
|
|
lines.append(f' _si(k, E.EXIT, tid, symbol, "{side}", {ef}, 0.001); await asyncio.sleep(0.5)')
|
|
elif pattern == "double_exit":
|
|
if side == "SHORT":
|
|
p1, p2 = ("p*0.995", "p*0.993") if pl == "profit" else ("p*1.003", "p*1.005")
|
|
else:
|
|
p1, p2 = ("p*1.005", "p*1.007") if pl == "profit" else ("p*0.997", "p*0.995")
|
|
lines.append('_si(k, E.ENTER, tid, symbol, "' + side + '", p, 0.002, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.8)')
|
|
lines.append(f'_si(k, E.EXIT, tid, symbol, "{side}", {p1}, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)')
|
|
lines.append(f'_si(k, E.EXIT, tid, symbol, "{side}", {p2}, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)')
|
|
B(name, lines)
|
|
|
|
make_profit_loss_bodies()
|
|
|
|
# Triple seq
|
|
for i in range(4):
|
|
name = f"triple_seq_{i}"
|
|
B(name, [
|
|
"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.8)',
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995*(1-j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
for i in range(4):
|
|
name = f"triple_seq_long_{i}"
|
|
B(name, [
|
|
"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.8)',
|
|
' _si(k, E.EXIT, tid, symbol, "LONG", p*1.005*(1+j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# Cancel reenter
|
|
for i in range(4):
|
|
name = f"cancel_reenter_{i}"
|
|
better = ["p*0.997", "p*0.994", "p*0.991", "p*0.988"][i]
|
|
B(name, [
|
|
't1 = f"cr{}a-{}".format(' + str(i) + ', int(time.time()*1000))',
|
|
't2 = f"cr{}b-{}".format(' + str(i) + ', 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)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.3)',
|
|
f'_si(k, E.ENTER, t2, symbol, "SHORT", {better}, 0.001); await asyncio.sleep(0.8)',
|
|
f'_si(k, E.EXIT, t2, symbol, "SHORT", p*{0.995 + 0.001*i:.3f}, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
for i in range(4):
|
|
name = f"cancel_reenter_long_{i}"
|
|
better = ["p*1.003", "p*1.006", "p*1.009", "p*1.012"][i]
|
|
B(name, [
|
|
't1 = f"crl{}a-{}".format(' + str(i) + ', int(time.time()*1000))',
|
|
't2 = f"crl{}b-{}".format(' + str(i) + ', 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)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "LONG", p*1.005, 0.001); await asyncio.sleep(0.3)',
|
|
f'_si(k, E.ENTER, t2, symbol, "LONG", {better}, 0.001); await asyncio.sleep(0.8)',
|
|
f'_si(k, E.EXIT, t2, symbol, "LONG", p*{1.005 + 0.003*(i+1):.3f}, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# Leg ratio variants
|
|
ratios_data = [
|
|
("leg_ratio_0", [(0.1,1.0)], 0.002, [0.0002, 0.0018]),
|
|
("leg_ratio_1", [(0.33,0.33,1.0)], 0.003, [0.001, 0.001, 0.001]),
|
|
("leg_ratio_2", [(0.5,0.5,1.0)], 0.002, [0.001, 0.001]),
|
|
("leg_ratio_3", [(0.75,1.0)], 0.002, [0.0015, 0.0005]),
|
|
("leg_ratio_4", [(0.2,0.3,0.5,1.0)], 0.004, [0.0008, 0.0012, 0.002]),
|
|
("leg_ratio_5", [(0.4,0.6,1.0)], 0.002, [0.0008, 0.0012]),
|
|
("leg_ratio_6", [(0.15,0.85,1.0)], 0.002, [0.0003, 0.0017]),
|
|
("leg_ratio_7", [(0.25,0.25,0.5,1.0)], 0.002, [0.0005, 0.0005, 0.001]),
|
|
]
|
|
|
|
for lr_name, ratios, total_sz, sizes in ratios_data:
|
|
lines = [f'tid = f"{lr_name[:4]}-{{int(time.time()*1000)}}"']
|
|
lines.append(f'_si(k, E.ENTER, tid, symbol, "SHORT", p, {total_sz}, exit_leg_ratios={ratios[0]}); await asyncio.sleep(0.8)')
|
|
prices = [0.995, 0.993, 0.991, 0.989][:len(sizes)]
|
|
for i, (sz, pr) in enumerate(zip(sizes, prices)):
|
|
lines.append(f'_si(k, E.EXIT, tid, symbol, "SHORT", p*{pr}, {sz}, exit_leg_ratios={ratios[0]}); await asyncio.sleep(0.5)')
|
|
B(lr_name, lines)
|
|
|
|
# Breakeven
|
|
for i in range(4):
|
|
B(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.5)',
|
|
])
|
|
|
|
# Price-level variants
|
|
price_variants = [
|
|
("short_exit_one_pct_profit", "SHORT", "p*0.99"),
|
|
("short_exit_third_pct_profit", "SHORT", "p*0.997"),
|
|
("short_exit_third_pct_loss", "SHORT", "p*1.003"),
|
|
("short_exit_one_pct_loss", "SHORT", "p*1.01"),
|
|
("long_exit_one_pct_profit", "LONG", "p*1.01"),
|
|
("long_exit_third_pct_profit", "LONG", "p*1.003"),
|
|
("long_exit_third_pct_loss", "LONG", "p*0.997"),
|
|
("long_exit_one_pct_loss", "LONG", "p*0.99"),
|
|
]
|
|
for pn, ps, pe in price_variants:
|
|
B(pn, [
|
|
f'tid = f"{pn[:4]}-{{int(time.time()*1000)}}"',
|
|
f'_si(k, E.ENTER, tid, symbol, "{ps}", p, 0.001); await asyncio.sleep(0.8)',
|
|
f'_si(k, E.EXIT, tid, symbol, "{ps}", {pe}, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# Leverage
|
|
lev = [
|
|
("entry_exit_short_2x_profit", "SHORT", 2, "p*0.995"),
|
|
("entry_exit_long_2x_profit", "LONG", 2, "p*1.005"),
|
|
("entry_exit_short_3x_profit", "SHORT", 3, "p*0.995"),
|
|
("entry_exit_long_3x_profit", "LONG", 3, "p*1.005"),
|
|
("entry_exit_short_2x_loss", "SHORT", 2, "p*1.005"),
|
|
("entry_exit_long_2x_loss", "LONG", 2, "p*0.995"),
|
|
("entry_exit_short_3x_loss", "SHORT", 3, "p*1.005"),
|
|
("entry_exit_long_3x_loss", "LONG", 3, "p*0.995"),
|
|
]
|
|
for pn, ps, lv, pe in lev:
|
|
B(pn, [
|
|
f'tid = f"{pn[:4]}-{{int(time.time()*1000)}}"',
|
|
f'_si(k, E.ENTER, tid, symbol, "{ps}", p, 0.001, leverage={lv}); await asyncio.sleep(0.8)',
|
|
f'_si(k, E.EXIT, tid, symbol, "{ps}", {pe}, 0.001, leverage={lv}); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# Size
|
|
sz = [
|
|
("entry_exit_short_2x_size", "SHORT", 0.002),
|
|
("entry_exit_long_2x_size", "LONG", 0.002),
|
|
("entry_exit_short_3x_size", "SHORT", 0.003),
|
|
("entry_exit_long_3x_size", "LONG", 0.003),
|
|
("entry_exit_short_4x_size", "SHORT", 0.004),
|
|
("entry_exit_long_4x_size", "LONG", 0.004),
|
|
("entry_exit_short_5x_size", "SHORT", 0.005),
|
|
("entry_exit_long_5x_size", "LONG", 0.005),
|
|
]
|
|
for pn, ps, s in sz:
|
|
B(pn, [
|
|
f'tid = f"{pn[:4]}-{{int(time.time()*1000)}}"',
|
|
f'_si(k, E.ENTER, tid, symbol, "{ps}", p, {s}); await asyncio.sleep(0.8)',
|
|
f'_si(k, E.EXIT, tid, symbol, "{ps}", p*0.995 if "{ps}" == "SHORT" else p*1.005, {s}); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# Cycles
|
|
B("three_cycle_short", [
|
|
"for j in range(3):",
|
|
' tid = f"tcs{j}-{int(time.time()*1000)}"',
|
|
' _si(k, E.ENTER, tid, symbol, "SHORT", p*(1-j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.997*(1-j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("three_cycle_long", [
|
|
"for j in range(3):",
|
|
' tid = f"tcl{j}-{int(time.time()*1000)}"',
|
|
' _si(k, E.ENTER, tid, symbol, "LONG", p*(1+j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
' _si(k, E.EXIT, tid, symbol, "LONG", p*1.003*(1+j*0.003), 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# Partial ratio
|
|
prs = [
|
|
("partial_ratio_0_short", "SHORT", (0.5,0.5,1.0), 0.002, ["p*0.995","p*0.993"], [0.001, 0.001]),
|
|
("partial_ratio_0_long", "LONG", (0.5,0.5,1.0), 0.002, ["p*1.005","p*1.007"], [0.001, 0.001]),
|
|
("partial_ratio_1_short", "SHORT", (0.33,0.33,1.0), 0.003, ["p*0.995","p*0.993","p*0.991"], [0.001, 0.001, 0.001]),
|
|
("partial_ratio_1_long", "LONG", (0.33,0.33,1.0), 0.003, ["p*1.005","p*1.007","p*1.009"], [0.001, 0.001, 0.001]),
|
|
("partial_ratio_2_short", "SHORT", (0.1,0.9,1.0), 0.002, ["p*0.995","p*0.993"], [0.0002, 0.0018]),
|
|
("partial_ratio_2_long", "LONG", (0.1,0.9,1.0), 0.002, ["p*1.005","p*1.007"], [0.0002, 0.0018]),
|
|
("partial_ratio_3_short", "SHORT", (0.25,0.25,0.5,1.0), 0.004, ["p*0.995","p*0.993","p*0.991"], [0.001, 0.001, 0.002]),
|
|
("partial_ratio_3_long", "LONG", (0.25,0.25,0.5,1.0), 0.004, ["p*1.005","p*1.007","p*1.009"], [0.001, 0.001, 0.002]),
|
|
]
|
|
|
|
for pn, ps, rat, tsz, exits, szs in prs:
|
|
lines = [f'tid = f"{pn[:4]}-{{int(time.time()*1000)}}"']
|
|
lines.append(f'_si(k, E.ENTER, tid, symbol, "{ps}", p, {tsz}, exit_leg_ratios={rat}); await asyncio.sleep(0.8)')
|
|
for xp, xs in zip(exits, szs):
|
|
lines.append(f'_si(k, E.EXIT, tid, symbol, "{ps}", {xp}, {xs}, exit_leg_ratios={rat}); await asyncio.sleep(0.5)')
|
|
B(pn, lines)
|
|
|
|
# Other groups
|
|
B("cross_asset_short", [
|
|
'tid = f"cas-{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.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("cross_asset_long", [
|
|
'tid = f"cal-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "LONG", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "LONG", p*1.005, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("cancel_on_fill_short", [
|
|
'tid = f"cfs-{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)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("cancel_on_fill_long", [
|
|
'tid = f"cfl-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "LONG", p, 0.001); await asyncio.sleep(0.3)',
|
|
'_si(k, E.CANCEL, tid, symbol, "LONG", p, 0.001); await asyncio.sleep(0.3)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, tid, symbol, "LONG", p*1.005, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("entry_quick_exit_short", [
|
|
'tid = f"eqs-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("entry_quick_exit_long", [
|
|
'tid = f"eql-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "LONG", p, 0.001); await asyncio.sleep(0.3)',
|
|
'_si(k, E.EXIT, tid, symbol, "LONG", p*1.005, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("triple_leg_exit_short", [
|
|
'tid = f"tls-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.003, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.5)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.001, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.5)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.991, 0.001, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("triple_leg_exit_long", [
|
|
'tid = f"tll-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "LONG", p, 0.003, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "LONG", p*1.005, 0.001, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.5)',
|
|
'_si(k, E.EXIT, tid, symbol, "LONG", p*1.007, 0.001, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.5)',
|
|
'_si(k, E.EXIT, tid, symbol, "LONG", p*1.009, 0.001, exit_leg_ratios=(0.33,0.33,1.0)); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("cancel_reenter_exit_short", [
|
|
't1 = f"cres-{int(time.time()*1000)}"; t2 = f"cres2-{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)',
|
|
"if not k.slot(0).is_free():",
|
|
' _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.997, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, t2, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("cancel_reenter_exit_long", [
|
|
't1 = f"crel-{int(time.time()*1000)}"; t2 = f"crel2-{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)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "LONG", p*1.005, 0.001); await asyncio.sleep(0.3)',
|
|
'_si(k, E.ENTER, t2, symbol, "LONG", p*1.003, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, t2, symbol, "LONG", p*1.005, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("zero_capital_safety", [
|
|
'tid = f"zcs-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.5)',
|
|
"if not k.slot(0).is_free():",
|
|
' _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(0.5)',
|
|
])
|
|
|
|
B("position_survives_exit", [
|
|
'tid = f"pse-{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.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("double_entry_prevention", [
|
|
't1 = f"dep1-{int(time.time()*1000)}"; t2 = f"dep2-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, 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.3)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("negative_capital_check", [
|
|
'tid = f"nec-{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.5)',
|
|
])
|
|
|
|
# RECONCILE
|
|
B("reconcile_empty", [
|
|
"k.reconcile_from_slots([]); await asyncio.sleep(0.3)",
|
|
])
|
|
|
|
B("reconcile_after_entry", [
|
|
'tid = f"re-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"k.reconcile_from_slots([k.slot(0)]); 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)',
|
|
])
|
|
|
|
B("reconcile_after_exit", [
|
|
'tid = f"rx-{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.995, 0.001); await asyncio.sleep(0.5)',
|
|
"k.reconcile_from_slots([k.slot(0)]); await asyncio.sleep(0.3)",
|
|
])
|
|
|
|
B("reconcile_after_cancel", [
|
|
'tid = f"rcn-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.5)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.CANCEL, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"k.reconcile_from_slots([k.slot(0)]); 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)',
|
|
])
|
|
|
|
B("reconcile_twice", [
|
|
'tid = f"rtw-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"k.reconcile_from_slots([k.slot(0)]); await asyncio.sleep(0.3)",
|
|
"k.reconcile_from_slots([k.slot(0)]); 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)',
|
|
])
|
|
|
|
B("reconcile_then_cancel", [
|
|
'tid = f"rtc-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.5)',
|
|
"k.reconcile_from_slots([k.slot(0)]); await asyncio.sleep(0.3)",
|
|
"if not k.slot(0).is_free():",
|
|
' _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(0.5)',
|
|
])
|
|
|
|
# CHAOS
|
|
B("concurrent_enter_cancel", [
|
|
'tid = f"cc-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001)',
|
|
'_si(k, E.CANCEL, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.5)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("rapid_alternating", [
|
|
't1 = f"ras-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, t1, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.2)',
|
|
'_si(k, E.CANCEL, t1, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.2)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.3)',
|
|
't2 = f"ral-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, t2, symbol, "LONG", p, 0.001); await asyncio.sleep(0.2)',
|
|
'_si(k, E.CANCEL, t2, symbol, "LONG", p, 0.001); await asyncio.sleep(0.2)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t2, symbol, "LONG", p*1.005, 0.001); await asyncio.sleep(0.3)',
|
|
])
|
|
|
|
B("duplicate_trade_id", [
|
|
'tid = f"dt-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p*0.995, 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(0.5)',
|
|
])
|
|
|
|
B("slot_busy_double_entry", [
|
|
't1 = f"sb1-{int(time.time()*1000)}"; t2 = f"sb2-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, 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.3)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("exit_on_idle_slot", [
|
|
'_si(k, E.EXIT, f"exidle-{int(time.time()*1000)}", symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
'tid = f"eoi-{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.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("cancel_on_idle_slot", [
|
|
'_si(k, E.CANCEL, f"coi-{int(time.time()*1000)}", symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
'tid = f"cis-{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.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("rapid_ten_cycle", [
|
|
"for i in range(10):",
|
|
' tid = f"rc10-{i}-{int(time.time()*1000)}"',
|
|
' _si(k, E.ENTER, tid, symbol, "SHORT", p*(1-i*0.001), 0.001); await asyncio.sleep(0.4)',
|
|
" if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995*(1-i*0.001), 0.001); await asyncio.sleep(0.4)',
|
|
" else:",
|
|
" break",
|
|
])
|
|
|
|
B("cancel_after_exit_fill", [
|
|
'tid = f"caf-{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.995, 0.001); await asyncio.sleep(0.3)',
|
|
'_si(k, E.CANCEL, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# MULTI-SLOT
|
|
B("multi_slot_enter_exit", [
|
|
't0 = f"ms0-{int(time.time()*1000)}"; t1 = f"ms1-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, t0, symbol, "SHORT", p, 0.001, slot_id=0); await asyncio.sleep(0.4)',
|
|
'_si(k, E.ENTER, t1, symbol, "LONG", p, 0.001, slot_id=1); await asyncio.sleep(0.4)',
|
|
'_si(k, E.EXIT, t0, symbol, "SHORT", p*0.995, 0.001, slot_id=0); await asyncio.sleep(0.4)',
|
|
'_si(k, E.EXIT, t1, symbol, "LONG", p*1.005, 0.001, slot_id=1); await asyncio.sleep(0.4)',
|
|
])
|
|
|
|
B("multi_slot_cross_cancel", [
|
|
't0 = f"msx0-{int(time.time()*1000)}"; t1 = f"msx1-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, t0, symbol, "SHORT", p, 0.001, slot_id=0); await asyncio.sleep(0.3)',
|
|
'_si(k, E.ENTER, t1, symbol, "LONG", p, 0.001, slot_id=1); await asyncio.sleep(0.3)',
|
|
'_si(k, E.CANCEL, t0, symbol, "SHORT", p, 0.001, slot_id=0); await asyncio.sleep(0.3)',
|
|
'_si(k, E.CANCEL, t1, symbol, "LONG", p, 0.001, slot_id=1); await asyncio.sleep(0.3)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t0, symbol, "SHORT", p*0.995, 0.001, slot_id=0); await asyncio.sleep(0.3)',
|
|
"if not k.slot(1).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "LONG", p*1.005, 0.001, slot_id=1); await asyncio.sleep(0.3)',
|
|
])
|
|
|
|
B("multi_slot_rapid_cycle", [
|
|
"for i in range(5):",
|
|
' t0 = f"msc0-{i}-{int(time.time()*1000)}"; t1 = f"msc1-{i}-{int(time.time()*1000)}"',
|
|
' _si(k, E.ENTER, t0, symbol, "SHORT", p*(1-i*0.002), 0.001, slot_id=0); await asyncio.sleep(0.3)',
|
|
' _si(k, E.ENTER, t1, symbol, "LONG", p*(1+i*0.002), 0.001, slot_id=1); await asyncio.sleep(0.3)',
|
|
' _si(k, E.EXIT, t0, symbol, "SHORT", p*0.995*(1-i*0.002), 0.001, slot_id=0); await asyncio.sleep(0.3)',
|
|
' _si(k, E.EXIT, t1, symbol, "LONG", p*1.005*(1+i*0.002), 0.001, slot_id=1); await asyncio.sleep(0.3)',
|
|
])
|
|
|
|
# REJECTION
|
|
B("reject_wrong_symbol", [
|
|
'tid = f"rs-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, "ZZZUSDT", "SHORT", 0.001, 0.001); await asyncio.sleep(0.5)',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("reject_zero_size", [
|
|
'tid = f"rz-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.0); await asyncio.sleep(0.3)',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("reject_side_mismatch_cancel", [
|
|
'tid = f"rsm-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.CANCEL, tid, symbol, "LONG", 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(0.5)',
|
|
])
|
|
|
|
B("reject_negative_price", [
|
|
'tid = f"rn-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", -1.0, 0.001); await asyncio.sleep(0.5)',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# SNAPSHOT
|
|
B("snapshot_restore_empty", [
|
|
"s = k.snapshot(); await asyncio.sleep(0.1)",
|
|
"j = json.dumps(s); _ = json.loads(j)",
|
|
'tid = f"sre-{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.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("snapshot_restore_mid_trade", [
|
|
'tid = f"srm-{int(time.time()*1000)}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"s = k.snapshot(); await asyncio.sleep(0.1)",
|
|
"j = json.dumps(s); _ = json.loads(j)",
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("snapshot_restore_after_cancel", [
|
|
'tid = f"src-{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)',
|
|
"s = k.snapshot(); await asyncio.sleep(0.1)",
|
|
"j = json.dumps(s); _ = json.loads(j)",
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# LIMIT
|
|
B("limit_does_not_fill", [
|
|
'tid = "l0-" + str(int(time.time()*1000))',
|
|
"k.process_intent(KI(timestamp=__import__(\"datetime\").datetime.now(__import__(\"datetime\").timezone.utc),",
|
|
" intent_id=tid, trade_id=tid, slot_id=0, asset=symbol, side=TS.SHORT, action=E.ENTER,",
|
|
" reference_price=0.0, target_size=0.001, leverage=1.0, exit_leg_ratios=(1.0,),",
|
|
' reason="auto_zeroprice")); await asyncio.sleep(0.3)',
|
|
'tid2 = "l0r-" + str(int(time.time()*1000))',
|
|
'_si(k, E.ENTER, tid2, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid2, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("limit_immediate_fill", [
|
|
'tid = "ln-" + str(int(time.time()*1000))',
|
|
"k.process_intent(KI(timestamp=__import__(\"datetime\").datetime.now(__import__(\"datetime\").timezone.utc),",
|
|
" intent_id=tid, trade_id=tid, slot_id=0, asset=symbol, side=TS.SHORT, action=E.ENTER,",
|
|
" reference_price=p, target_size=-0.001, leverage=1.0, exit_leg_ratios=(1.0,),",
|
|
' reason="auto_negsize")); await asyncio.sleep(0.3)',
|
|
'tid2 = "lnr-" + str(int(time.time()*1000))',
|
|
'_si(k, E.ENTER, tid2, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid2, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# ===== FRESH KERNEL RECONCILE =====
|
|
B("fresh_kernel_reconcile_entry", [
|
|
'tid = "fk-" + str(int(time.time()*1000))',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"slot_data = k.slot(0).to_dict()",
|
|
"cb = k.account.snapshot.capital",
|
|
"fresh = _build_fresh_kernel_from_slot(slot_data, ic=cb)",
|
|
"k2 = fresh.runtime.kernel",
|
|
"s = k2.slot(0)",
|
|
'assert not s.is_free(), f"fresh kernel slot should not be free: {s.fsm_state}"',
|
|
'assert s.trade_id == tid, f"trade_id mismatch: {s.trade_id} vs {tid}"',
|
|
'_si(k2, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
'assert k2.slot(0).is_free(), "fresh kernel slot not free after exit"',
|
|
'assert abs(k2.account.snapshot.capital - cb) < 0.01, f"capital drift: {k2.account.snapshot.capital} vs {cb}"',
|
|
])
|
|
|
|
B("fresh_kernel_reconcile_after_cancel", [
|
|
'tid = "fkc-" + str(int(time.time()*1000))',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
'r = _si(k, E.CANCEL, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"slot_data = k.slot(0).to_dict()",
|
|
"cb = k.account.snapshot.capital",
|
|
"fresh = _build_fresh_kernel_from_slot(slot_data, ic=cb)",
|
|
"k2 = fresh.runtime.kernel",
|
|
'assert k2.slot(0).is_free(), f"cancelled slot not free: {k2.slot(0).fsm_state}"',
|
|
])
|
|
|
|
B("fresh_kernel_reconcile_after_exit", [
|
|
'tid = "fkx-" + str(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.995, 0.001); await asyncio.sleep(0.5)',
|
|
"slot_data = k.slot(0).to_dict()",
|
|
"cb = k.account.snapshot.capital",
|
|
"fresh = _build_fresh_kernel_from_slot(slot_data, ic=cb)",
|
|
"k2 = fresh.runtime.kernel",
|
|
'assert k2.slot(0).is_free(), f"closed slot not free: {k2.slot(0).fsm_state}"',
|
|
'assert k2.slot(0).closed, "slot should be marked closed"',
|
|
'assert abs(k2.account.snapshot.capital - cb) < 0.01, f"capital drift: {k2.account.snapshot.capital} vs {cb}"',
|
|
])
|
|
|
|
B("fresh_kernel_reconcile_partial_exit", [
|
|
'tid = "fkp-" + str(int(time.time()*1000))',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.002, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.5)',
|
|
"slot_data = k.slot(0).to_dict()",
|
|
"cb = k.account.snapshot.capital",
|
|
"fresh = _build_fresh_kernel_from_slot(slot_data, ic=cb)",
|
|
"k2 = fresh.runtime.kernel",
|
|
"s = k2.slot(0)",
|
|
'assert not s.is_free(), f"partial-exit slot not free: {s.fsm_state}"',
|
|
'assert s.realized_pnl != 0 or s.size > 0, "partial-exit slot should have remaining position or realized PnL"',
|
|
'_si(k2, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.001, exit_leg_ratios=(1.0,)); await asyncio.sleep(0.5)',
|
|
'assert k2.slot(0).is_free(), "slot not free after final exit on fresh kernel"',
|
|
])
|
|
|
|
# ===== CROSS-SLOT PORTFOLIO =====
|
|
B("cross_slot_portfolio_short_long", [
|
|
't0 = "psl0-" + str(int(time.time()*1000))',
|
|
't1 = "psl1-" + str(int(time.time()*1000))',
|
|
"cb = k.account.snapshot.capital",
|
|
'_si(k, E.ENTER, t0, symbol, "SHORT", p, 0.001, slot_id=0); await asyncio.sleep(0.4)',
|
|
'_si(k, E.ENTER, t1, symbol, "LONG", p, 0.001, slot_id=1); await asyncio.sleep(0.4)',
|
|
'assert not k.slot(0).is_free(), "slot 0 should be open"',
|
|
'assert not k.slot(1).is_free(), "slot 1 should be open"',
|
|
"rp0 = k.slot(0).realized_pnl; up0 = k.slot(0).unrealized_pnl",
|
|
"rp1 = k.slot(1).realized_pnl; up1 = k.slot(1).unrealized_pnl",
|
|
"expected = cb + rp0 + up0 + rp1 + up1",
|
|
"actual = k.account.snapshot.capital",
|
|
'assert abs(actual - expected) < 0.01, f"portfolio misalignment: cap={actual} exp={expected} rp0={rp0} up0={up0} rp1={rp1} up1={up1}"',
|
|
'_si(k, E.EXIT, t0, symbol, "SHORT", p*0.995, 0.001, slot_id=0); await asyncio.sleep(0.4)',
|
|
'assert k.slot(0).is_free(), "slot 0 should be free after exit"',
|
|
'_si(k, E.EXIT, t1, symbol, "LONG", p*1.005, 0.001, slot_id=1); await asyncio.sleep(0.4)',
|
|
'assert k.slot(1).is_free(), "slot 1 should be free after exit"',
|
|
])
|
|
|
|
# ===== KERNEL OUTCOME INSPECTION =====
|
|
B("outcome_inspect_entry", [
|
|
'tid = "oi-" + str(int(time.time()*1000))',
|
|
'r = _si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"_assert_accepted(r, 'entry')",
|
|
"info = _inspect_outcome(r, 'entry')",
|
|
'assert r.accepted, f"entry not accepted: {info}"',
|
|
'assert r.trade_id == tid, f"trade_id mismatch: {r.trade_id} vs {tid}"',
|
|
'assert r.slot_id == 0, f"slot_id: {r.slot_id}"',
|
|
'assert len(info["transitions"]) > 0, f"no transitions in outcome: {info}"',
|
|
'assert info["diagnostic"] == "OK", f"diagnostic not OK: {info}"',
|
|
'r2 = _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
"_assert_accepted(r2, 'exit')",
|
|
'info2 = _inspect_outcome(r2, "exit")',
|
|
'assert len(info2["transitions"]) > 0, f"no exit transitions: {info2}"',
|
|
'assert info2["diagnostic"] == "OK", f"exit diagnostic: {info2}"',
|
|
])
|
|
|
|
B("outcome_inspect_rejection", [
|
|
'tid = "or-" + str(int(time.time()*1000))',
|
|
'tid2 = "or2-" + str(int(time.time()*1000))',
|
|
'r1 = _si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"_assert_accepted(r1, 'first entry')",
|
|
'r2 = _si(k, E.ENTER, tid2, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"_assert_rejected(r2, 'SLOT_BUSY', 'double entry')",
|
|
"info = _inspect_outcome(r2, 'double entry')",
|
|
'assert not r2.accepted, f"second entry should be rejected: {info}"',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
B("outcome_inspect_exit_on_idle", [
|
|
'tid = "oei-" + str(int(time.time()*1000))',
|
|
'r = _si(k, E.EXIT, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"_assert_rejected(r, 'INVALID_FSM_TRANSITION', 'exit on idle')",
|
|
"info = _inspect_outcome(r, 'exit on idle')",
|
|
'assert not r.accepted, f"exit on idle should be rejected: {info}"',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# ===== EVENT DEDUP =====
|
|
B("dedup_duplicate_fill_event", [
|
|
'tid = "dd-" + str(int(time.time()*1000))',
|
|
'r = _si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"_assert_accepted(r, 'entry')",
|
|
"sl = k.slot(0)",
|
|
"ao = sl.active_entry_order if sl.active_entry_order else sl.active_exit_order",
|
|
"if ao:",
|
|
" dup = VenueEvent(",
|
|
" timestamp=__import__(\"datetime\").datetime.now(__import__(\"datetime\").timezone.utc),",
|
|
' event_id="dedup-test-99999",',
|
|
" trade_id=tid, slot_id=0,",
|
|
" kind=KernelEventKind.FULL_FILL,",
|
|
" status=VenueEventStatus.FILLED,",
|
|
" venue_order_id=ao.venue_order_id,",
|
|
" venue_client_id=ao.venue_client_id,",
|
|
" side=sl.side,",
|
|
" asset=symbol,",
|
|
" price=p,",
|
|
" size=0.001, filled_size=0.001, remaining_size=0.0,",
|
|
' reason="dedup_test",',
|
|
" )",
|
|
" r2 = k.on_venue_event(dup)",
|
|
" _assert_accepted(r2, 'dedup_fill')",
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# ===== FILL-PRICE DIVERGENCE =====
|
|
B("fill_price_divergence_1pct", [
|
|
'tid = "fd-" + str(int(time.time()*1000))',
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"sl = k.slot(0)",
|
|
"ao = sl.active_entry_order if sl.active_entry_order else sl.active_exit_order",
|
|
"if ao and str(sl.fsm_state) not in ('IDLE', 'CLOSED'):",
|
|
" divergent_price = p * 1.01",
|
|
" div_event = VenueEvent(",
|
|
" timestamp=__import__(\"datetime\").datetime.now(__import__(\"datetime\").timezone.utc),",
|
|
' event_id="divergence-test",',
|
|
" trade_id=tid, slot_id=0,",
|
|
" kind=KernelEventKind.FULL_FILL,",
|
|
" status=VenueEventStatus.FILLED,",
|
|
' venue_order_id=ao.venue_order_id if ao else "",',
|
|
' venue_client_id=ao.venue_client_id if ao else "",',
|
|
" side=sl.side,",
|
|
" asset=symbol,",
|
|
" price=divergent_price,",
|
|
" size=0.001, filled_size=0.001, remaining_size=0.0,",
|
|
' reason="divergence_test",',
|
|
" )",
|
|
" k.on_venue_event(div_event); await asyncio.sleep(0.3)",
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# ===== NEGATIVE CAPITAL =====
|
|
B("neg_cap_entry_rejected", [
|
|
'tid = "nc-" + str(int(time.time()*1000))',
|
|
"k.account.snapshot.capital = 0.0",
|
|
'r = _si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"info = _inspect_outcome(r, 'neg_cap')",
|
|
"k.account.snapshot.capital = 25000.0",
|
|
'_si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
'_si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
])
|
|
|
|
# ===== CROSS-SAMPLE: new patterns on old shapes =====
|
|
B("cross_sample_basic_entry_exit_outcome", [
|
|
'tid = "cs-" + str(int(time.time()*1000))',
|
|
"cb = k.account.snapshot.capital; k._start_cap = cb",
|
|
'r1 = _si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.8)',
|
|
"_assert_accepted(r1, 'cs_entry')",
|
|
"_check_slot_accounting(k, 'cs_after_entry')",
|
|
'r2 = _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
"_assert_accepted(r2, 'cs_exit')",
|
|
"_check_slot_accounting(k, 'cs_after_exit')",
|
|
"ca = k.account.snapshot.capital",
|
|
"max_change = max(1.0, cb * 0.10)",
|
|
'assert cb - ca < max_change, f"cs: cap shrunk {cb} -> {ca}"',
|
|
])
|
|
|
|
B("cross_sample_cancel_reenter_outcome", [
|
|
't1 = "csc-" + str(int(time.time()*1000))',
|
|
't2 = "csc2-" + str(int(time.time()*1000))',
|
|
"cb = k.account.snapshot.capital; k._start_cap = cb",
|
|
'r1 = _si(k, E.ENTER, t1, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"_assert_accepted(r1, 'cs_cancel_entry')",
|
|
'r2 = _si(k, E.CANCEL, t1, symbol, "SHORT", p, 0.001); await asyncio.sleep(0.3)',
|
|
"if not k.slot(0).is_free():",
|
|
' _si(k, E.EXIT, t1, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.3)',
|
|
"_check_slot_accounting(k, 'cs_after_cancel')",
|
|
'assert k.slot(0).is_free(), "slot should be free after cancel"',
|
|
'r3 = _si(k, E.ENTER, t2, symbol, "SHORT", p*0.997, 0.001); await asyncio.sleep(0.8)',
|
|
"_assert_accepted(r3, 'cs_reenter')",
|
|
"_check_slot_accounting(k, 'cs_after_reenter')",
|
|
'r4 = _si(k, E.EXIT, t2, symbol, "SHORT", p*0.995, 0.001); await asyncio.sleep(0.5)',
|
|
"_assert_accepted(r4, 'cs_reenter_exit')",
|
|
"_check_slot_accounting(k, 'cs_after_reenter_exit')",
|
|
])
|
|
|
|
B("cross_sample_multi_leg_outcome", [
|
|
'tid = "csm-" + str(int(time.time()*1000))',
|
|
"cb = k.account.snapshot.capital; k._start_cap = cb",
|
|
'r = _si(k, E.ENTER, tid, symbol, "SHORT", p, 0.002, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.8)',
|
|
"_assert_accepted(r, 'cs_ml_entry')",
|
|
'r = _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.4)',
|
|
"_assert_accepted(r, 'cs_ml_leg1')",
|
|
"_check_slot_accounting(k, 'cs_ml_after_leg1')",
|
|
'r = _si(k, E.EXIT, tid, symbol, "SHORT", p*0.993, 0.001, exit_leg_ratios=(0.5,1.0)); await asyncio.sleep(0.4)',
|
|
"_assert_accepted(r, 'cs_ml_leg2')",
|
|
"_check_slot_accounting(k, 'cs_ml_after_leg2')",
|
|
])
|
|
|
|
B("cross_sample_leverage_tight_bounds", [
|
|
'tid = "csl-" + str(int(time.time()*1000))',
|
|
"cb = k.account.snapshot.capital; k._start_cap = cb",
|
|
'r_ent = _si(k, E.ENTER, tid, symbol, "SHORT", p, 0.001, leverage=2); await asyncio.sleep(0.8)',
|
|
"_assert_accepted(r_ent, 'cs_lev_entry')",
|
|
"_check_slot_accounting(k, 'cs_lev_after_entry')",
|
|
'r_ex = _si(k, E.EXIT, tid, symbol, "SHORT", p*0.995, 0.001, leverage=2); await asyncio.sleep(0.5)',
|
|
"_assert_accepted(r_ex, 'cs_lev_exit')",
|
|
"_check_slot_accounting(k, 'cs_lev_after_exit')",
|
|
"ca = k.account.snapshot.capital",
|
|
"max_change = max(1.0, cb * 0.10)",
|
|
'assert cb - ca < max_change, f"cs_lev: cap shrunk {cb} -> {ca}"',
|
|
])
|
|
|
|
# ---- BUILD SCENARIOS LIST ----
|
|
emit("# ============================================================")
|
|
emit("# SCENARIOS")
|
|
emit("# ============================================================")
|
|
emit("SCENARIOS = [")
|
|
for name in sorted(bodies.keys()):
|
|
emit(f' pytest.param("{name}", _body_{name}, id="{name}"),')
|
|
emit("]")
|
|
emit("")
|
|
|
|
# ---- FIXTURE + TEST ----
|
|
emit("@pytest.fixture(scope=\"session\")")
|
|
emit("def _live_client():")
|
|
emit(" return BingxHttpClient(_build_config())")
|
|
emit("")
|
|
emit("")
|
|
emit("@pytest.mark.parametrize(\"name,body_fn\", SCENARIOS)")
|
|
emit("def test_pink_ditav2(_live_client, name, body_fn) -> None:")
|
|
emit(" bundle = _build_rb()")
|
|
emit(" ic = bundle.runtime.kernel.account.snapshot.capital")
|
|
emit(" r = asyncio.run(_run(bundle, _live_client, body_fn, name, ic))")
|
|
emit(" assert r.positions_flat, f\"{name}: {r.error}\"")
|
|
|
|
# ---- WRITE BODY FUNCTIONS ----
|
|
# Build the full file: header + helpers + body functions + scenarios + fixture/test
|
|
header = "\n".join(lines)
|
|
|
|
body_funcs = []
|
|
for name, blines in bodies.items():
|
|
body_funcs.append(f"async def _body_{name}(k, symbol, p):")
|
|
for bl in blines:
|
|
body_funcs.append(f" {bl}")
|
|
body_funcs.append("")
|
|
|
|
full = header + "\n".join(body_funcs) + "\n\n" + header[header.rindex("# =="):]
|
|
|
|
# Actually let me just assemble properly
|
|
# Split header at the body section comment
|
|
body_section_header = "# ============================================================\n# SCENARIO BODIES\n# Each receives (k, symbol, p) and exercises a slice of the FSM.\n# ============================================================\n\n"
|
|
|
|
all_body_text = ""
|
|
for name, blines in bodies.items():
|
|
all_body_text += f"async def _body_{name}(k, symbol, p):\n"
|
|
for bl in blines:
|
|
if bl.startswith(" "):
|
|
all_body_text += bl + "\n"
|
|
else:
|
|
all_body_text += " " + bl + "\n"
|
|
all_body_text += "\n"
|
|
|
|
# Find the pre-body section (imports + helpers)
|
|
body_start_idx = header.index("# SCENARIO BODIES")
|
|
pre_body = header
|
|
|
|
full_text = pre_body + all_body_text + """
|
|
# ============================================================
|
|
# SCENARIOS
|
|
# ============================================================
|
|
SCENARIOS = [
|
|
""" + "\n".join([f' pytest.param("{n}", _body_{n}, id="{n}"),' for n in sorted(bodies.keys())]) + """
|
|
]
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def _live_client():
|
|
return BingxHttpClient(_build_config())
|
|
|
|
|
|
@pytest.mark.parametrize("name,body_fn", SCENARIOS)
|
|
def test_pink_ditav2(_live_client, name, body_fn) -> None:
|
|
bundle = _build_rb()
|
|
ic = bundle.runtime.kernel.account.snapshot.capital
|
|
r = asyncio.run(_run(bundle, _live_client, body_fn, name, ic))
|
|
assert r.positions_flat, f"{name}: {r.error}"
|
|
"""
|
|
|
|
with open(fpath, 'w') as f:
|
|
f.write(full_text)
|
|
|
|
import py_compile
|
|
try:
|
|
py_compile.compile(fpath, doraise=True)
|
|
print(f"Compiles OK. {len(bodies)} scenarios")
|
|
except py_compile.PyCompileError as e:
|
|
print(f"Compile error: {e}")
|
|
# Show the broken area
|
|
import traceback
|
|
traceback.print_exc()
|