439 lines
21 KiB
Python
439 lines
21 KiB
Python
|
|
#!/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]}")
|