Files
PythagorasGoal/docs/diary/2026-06-08-pairs-executor.md
T
Adriano Dal Pastro c655604131 feat(live): executor a 2 gambe per i pairs (PairsExecutionClient, shadow) — pronto, spento
PairsExecutionClient (compone ExecutionClient): open_pair (2 market long A/short B,
verifica per-gamba, LEG-RISK unwind se una sola filla), close_pair (2 reduce-only via
close_amount, MAI close_position), register_contract (fetch spec USDC). Spec LTC/ADA/SOL
aggiunti. PairsWorker: ledger reale shadow a 2 gambe resume-safe (_real_open_pair/
_real_close_pair, PnL per gamba dir A=+d/B=-d, doppio arrotondamento riportato). Runner:
pairs_executor gated su execution.pairs_enabled (false di default).

Validazione: test 92/92 (open/close, leg-risk unwind, resume) + smoke testnet
end-to-end (open 2 gambe verificate, close reduce-only, PnL reale -0.039 vs sim -0.036,
conto flat). Smoke ora aborta se ci sono posizioni di produzione + usa solo close_amount.

NB incidente testnet documentato (diario): pulizia manuale con close_position ha flattato
le quote shadow dei fade sul conto condiviso -> auto-riconciliazione al prossimo close.
Lezione: mai close_position su strumenti condivisi.

pairs_enabled resta FALSE: accendere con finestra a conto flat + osservazione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:19:56 +00:00

3.2 KiB
Raw Blame History

2026-06-08 — Executor a 2 gambe per i pairs (PairsExecutionClient)

Implementato il pezzo di esecuzione reale più impegnativo rimasto: l'esecuzione shadow a 2 gambe per la famiglia PAIRS (29% del portafoglio, finora solo simulata).

Cosa

  • execution.PairsExecutionClient: compone l'ExecutionClient single-leg.
    • open_pair(inst_a, inst_b, direction, notional): piazza le 2 gambe (long A/short B o viceversa) a market, verifica per-gamba sul trade; LEG-RISK: se UNA sola gamba filla → UNWIND (richiude la fillata reduce-only) per non restare direzionali → verified=False.
    • close_pair(...): chiude entrambe reduce-only (solo close_amount della quota, MAI close_position), riconcilia fee/prezzi.
    • register_contract: fetch dinamico spec USDC da Deribit per strumenti non hardcodati.
    • Strumenti = lineari USDC (payoff lineare = matematica del backtest a 2 gambe). Aggiunti spec LTC/ADA/SOL_USDC (step 0.1/0.2/0.1).
  • PairsWorker shadow: ledger reale parallelo (real_capital, real_dir, real_amount_a/b, real_entry_a/b, real_notional_a/b, fee), persistito e resume-safe; _real_open_pair/_real_close_pair agganciati a _open/_close; PnL reale per gamba (dir A=+d, dir B=d). Doppio arrotondamento → piccolo sbilanciamento notional, riportato.
  • runner: pairs_executor (PairsExecutionClient su stesso ExecutionClient dei fade), _pairs_exec_for, gate su execution.pairs_enabled. Config portfolios.yml: instruments estesi (LTC/ADA/SOL) + pairs_enabled: false (capability pronta, SPENTA).

Validazione

  • Test (executor finto): 92/92 — open/close a 2 gambe, leg-risk unwind, ledger reale persiste e resume.
  • Smoke testnet end-to-end (live_pairs_smoke.py, €0): open 2 gambe verificate (long ETH 0.011 @1666 / short BTC 0.0003 @63263, fee $0.019), close 2 gambe reduce-only, riconciliazione PnL reale 0.039 vs sim 0.036 (coerente), conto flat dopo.

Incidente operativo (testnet, €0) — da ricordare

Durante la prima esecuzione, lo smoke è crashato (bug del test: prezzi sim=0 → divisione per zero nel ramo sim) lasciando una posizione aperta. Per pulirla ho usato a mano ExecutionClient.close() (= close_position) che flatta l'intero strumento → ho chiuso anche le 3 posizioni reali SHADOW dei fade del runner (BTC/ETH_USDC condivisi). Impatto: testnet, shadow (non guida le decisioni), €0; 3 worker fade con real_in_position=True su conto flat → si auto-riconciliano al prossimo close sim (close_amount su flat → verified=False, reset; "shadow pulito dalla prossima apertura"). I TP resting erano già stati cancellati da close_position (cancel → order_not_found).

Lezione (CLAUDE.md la documentava già): MAI close_position su strumenti condivisi. Fix permanente: lo smoke ora (a) usa solo close_amount della quota, (b) ABORTA se ci sono posizioni di produzione aperte sugli strumenti, (c) usa prezzi sim reali.

Stato: PRONTO ma SPENTO (pairs_enabled: false)

L'executor è validato (test + smoke). NON attivato in produzione: accenderlo richiede una finestra a conto flat e un periodo di osservazione, dato il conto condiviso coi fade. Quando si accende: i 5 pairs eseguiranno reale a 2 gambe accanto al sim.