feat(live): TP reale = LIMIT reduce-only al livello (fix +235bps slippage exit)
All'apertura reale il worker piazza un limit reduce-only al TP della strategia (quota del SOLO worker, prezzo quantizzato al tick); alla chiusura sim cancella il resting, riconcilia i fill (anche parziali) via get_trade_history per order_id e chiude a market solo il residuo. real_tp_order_id persistito (resume-safe). SL resta market-on-poll (deliberato: trigger Deribit = nuovo order_id al trigger, fill non verificabile; e il rimbalzo sul SL lavora a favore). Smoke testnet 2 scenari OK (resting+cancel / fill immediato). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
"""Smoke della catena SHADOW dentro lo StrategyWorker (testnet, ordini reali minimi).
|
||||
|
||||
Apre e chiude la quota di UN worker fade come farebbe il runner:
|
||||
_open_position (sim + REAL_OPEN reale su BTC_USDC) → _close_position (sim +
|
||||
REAL_CLOSE reduce-only) → controlla che real_capital sia aggiornato dal fill reale.
|
||||
Apre e chiude la quota di UN worker fade come farebbe il runner, esercitando i
|
||||
DUE percorsi del LIMIT reduce-only al TP (fix divergenza sim/reale 2026-06-04):
|
||||
|
||||
A) TP lontano → REAL_TP_RESTING in book; exit sim non-TP → cancel + market
|
||||
reduce-only di fallback (REAL_CLOSE con tp_filled_amount=0);
|
||||
B) TP gia' oltre il prezzo → il limit crossa e filla SUBITO; la chiusura
|
||||
riconcilia il fill dal trade history (order_id) SENZA ordine market
|
||||
(REAL_CLOSE con market_amount=0).
|
||||
|
||||
Non tocca lo stato di produzione (data_dir temporanea). Costo testnet = €0.
|
||||
|
||||
@@ -40,27 +45,45 @@ def main() -> None:
|
||||
)
|
||||
print(f"execution_enabled={w.execution_enabled} notional atteso=${100*0.15*2:.0f}")
|
||||
|
||||
# OPEN long (sim + reale)
|
||||
sig = Signal(idx=0, direction=1, entry_price=price, metadata={})
|
||||
# --- Scenario A: TP lontano → resting in book, exit non-TP → cancel + market ---
|
||||
print("\n[A] TP lontano (resting) → exit time_limit → cancel + market fallback")
|
||||
sig = Signal(idx=0, direction=1, entry_price=price,
|
||||
metadata={"tp": price * 1.05, "sl": price * 0.50, "max_bars": 6})
|
||||
w._open_position(sig, price)
|
||||
print(f" real_in_position={w.real_in_position} side={w.real_side} "
|
||||
f"amount={w.real_amount} entry={w.real_entry_price} "
|
||||
f"entry_fee=${w.real_entry_fee_usd:.5f} notional=${w.real_entry_notional:.2f}")
|
||||
assert w.real_in_position, "OPEN reale non verificato"
|
||||
print(f" TP resting order_id={w.real_tp_order_id!r}")
|
||||
assert w.real_tp_order_id, "LIMIT reduce-only al TP non piazzato"
|
||||
|
||||
# CLOSE (sim + reale reduce-only) a un prezzo leggermente diverso
|
||||
exit_price = (w.entry_price or price) * 1.001
|
||||
cap_before = w.real_capital
|
||||
w._close_position(exit_price, "smoke_close")
|
||||
w._close_position((w.entry_price or price) * 1.001, "time_limit")
|
||||
print(f" real_capital {cap_before:.4f} -> {w.real_capital:.4f} "
|
||||
f"(Δ {w.real_capital - cap_before:+.4f}) real_trades={w.real_trades}")
|
||||
assert not w.real_in_position, "posizione reale non chiusa"
|
||||
assert not w.real_tp_order_id, "order_id TP non resettato dopo la chiusura"
|
||||
|
||||
# --- Scenario B: TP gia' oltre il prezzo → il limit crossa e filla subito ---
|
||||
print("\n[B] TP gia' crossato (fill immediato del limit) → close riconcilia da history")
|
||||
price = ex._mark_price(instrument) or price
|
||||
sig = Signal(idx=0, direction=1, entry_price=price,
|
||||
metadata={"tp": price * 0.995, "sl": price * 0.50, "max_bars": 6})
|
||||
w._open_position(sig, price)
|
||||
assert w.real_in_position, "OPEN reale non verificato (B)"
|
||||
assert w.real_tp_order_id, "LIMIT TP non piazzato (B)"
|
||||
|
||||
cap_before = w.real_capital
|
||||
w._close_position(w.tp, "take_profit")
|
||||
print(f" real_capital {cap_before:.4f} -> {w.real_capital:.4f} "
|
||||
f"(Δ {w.real_capital - cap_before:+.4f}) real_trades={w.real_trades}")
|
||||
assert not w.real_in_position, "posizione reale non chiusa (B)"
|
||||
|
||||
# verifica finale: il conto e' flat sullo strumento (nessuna quota residua del worker)
|
||||
pos = ex._position_size(instrument)
|
||||
print(f" posizione netta {instrument}: {pos}")
|
||||
print("✓ catena shadow OK — ordine reale aperto, verificato, chiuso reduce-only, "
|
||||
"fee reali nel ledger reale")
|
||||
print(f"\n posizione netta {instrument}: {pos}")
|
||||
print("✓ catena shadow OK — open reale, LIMIT TP resting (A: cancel+market, "
|
||||
"B: fill immediato riconciliato), fee reali nel ledger reale")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user