feat(live): disaster-SL on-book con lifecycle completo (idempotente) nel loop di esecuzione
ensure_disaster_sl(): garantisce UN solo STOP_MARKET reduce_only a ~-30% coerente con la posizione, ad ogni run del loop, per asset: - flat -> cancella i bracket orfani; - long -> assicura lo stop (size = posizione, prezzo al tick); - gia' coerente (1 bracket, amount~=, stop entro 5%) -> lascia com'e' (niente churn ne' gap di protezione fra cancel e place). - deribit.py: open_orders (merge type all+trigger_all), disaster_stop_price. - execution.py: cancel_order + ensure_disaster_sl. - live_execute.py: gestione bracket ogni run, gated come l'esecuzione. Validato armato: flat -> disaster-SL 'flat' (cleanup), zero ordini. Test 28/28. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ def load_config() -> dict:
|
||||
cfg.setdefault("execution_enabled", False)
|
||||
cfg.setdefault("max_notional_per_asset_usd", 300.0)
|
||||
cfg.setdefault("min_order_usd", 5.0)
|
||||
cfg.setdefault("disaster_sl_pct", 0.30)
|
||||
return cfg
|
||||
|
||||
|
||||
@@ -53,6 +54,7 @@ def main():
|
||||
do_execute = want_execute and enabled
|
||||
max_notional = float(cfg["max_notional_per_asset_usd"])
|
||||
min_order = float(cfg["min_order_usd"])
|
||||
sl_pct = float(cfg["disaster_sl_pct"])
|
||||
|
||||
r = shadow_report() # targets causali + conto/posizioni reali (online)
|
||||
equity = r["equity"]
|
||||
@@ -65,7 +67,7 @@ def main():
|
||||
print(f" modo : {mode}")
|
||||
print(f" gate : execution_enabled={enabled} | --execute={want_execute}")
|
||||
print(f" conto reale : ${r['real_equity']:,.2f}" if r["real_equity"] else f" conto: {r['eq_basis']}")
|
||||
print(f" sizing base : ${equity:,.2f} | cap/asset ${max_notional:.0f} | min ordine ${min_order:.0f}")
|
||||
print(f" sizing base : ${equity:,.2f} | cap/asset ${max_notional:.0f} | min ${min_order:.0f} | disaster-SL -{sl_pct*100:.0f}%")
|
||||
print(f" ultima barra : {r['last_data']}\n")
|
||||
|
||||
if not r["online"]:
|
||||
@@ -88,16 +90,19 @@ def main():
|
||||
act = f"REDUCE ${-delta:,.0f}"
|
||||
print(f" {asset:<3} target {frac:+.3f}x -> ${tgt:,.0f} | pos ${cur:,.0f} | delta ${delta:+,.0f} -> {act}")
|
||||
|
||||
if do_execute and not act.startswith("HOLD"):
|
||||
fills = trader.rebalance_to(INSTRUMENT[asset], tgt, mark, min_usd=min_order)
|
||||
newpos = trader.position_usd(INSTRUMENT[asset])
|
||||
for f in fills:
|
||||
print(f" -> {f.side.upper()} {f.filled:.4f} @ ${f.price:,.1f} fee {f.fee_usdc:.5f} "
|
||||
f"({'OK' if f.verified else 'NON VERIFICATO: ' + f.notes})")
|
||||
log_event(dict(ts_utc=str(pd.Timestamp(r['last_data'])), asset=asset, action=act,
|
||||
side=f.side, filled=f.filled, price=f.price, fee=f.fee_usdc,
|
||||
verified=f.verified, notes=f.notes, pos_after=newpos))
|
||||
print(f" reconcile: pos ${newpos:,.0f}")
|
||||
if do_execute:
|
||||
if not act.startswith("HOLD"):
|
||||
fills = trader.rebalance_to(INSTRUMENT[asset], tgt, mark, min_usd=min_order)
|
||||
newpos = trader.position_usd(INSTRUMENT[asset])
|
||||
for f in fills:
|
||||
print(f" -> {f.side.upper()} {f.filled:.4f} @ ${f.price:,.1f} fee {f.fee_usdc:.5f} "
|
||||
f"({'OK' if f.verified else 'NON VERIFICATO: ' + f.notes})")
|
||||
log_event(dict(ts_utc=str(pd.Timestamp(r['last_data'])), asset=asset, action=act,
|
||||
side=f.side, filled=f.filled, price=f.price, fee=f.fee_usdc,
|
||||
verified=f.verified, notes=f.notes, pos_after=newpos))
|
||||
print(f" reconcile: pos ${newpos:,.0f}")
|
||||
ds = trader.ensure_disaster_sl(INSTRUMENT[asset], sl_pct) # bracket: piazza se long, pulisce se flat
|
||||
print(f" disaster-SL: {ds.get('state')}" + (f" @ ${ds['stop']:,.1f}" if ds.get("stop") else ""))
|
||||
actions.append(act)
|
||||
|
||||
print()
|
||||
|
||||
Reference in New Issue
Block a user