8.3 KiB
Drift PERP “kontrakt bota” (SOL-PERP) — spec intent → egzekucja → audyt
Ten dokument definiuje przyszłościowy kontrakt między:
- Vast (model/transformer na GPU): generuje trade intent (bez sekretów),
- k3s/VPS (executor): waliduje ryzyko, wystawia i prowadzi zlecenia na Drift, loguje zdarzenia,
- UI (visualizer): tylko wizualizuje warstwy i stan kontraktów (live + historia).
Kluczowa zasada: model nigdy nie ma kluczy i nie “handluje”. Handluje tylko executor w k3s.
Powiązane:
- Strategia “eskalacja horyzontu” (1m→5m→15m→30m→1h z bramkami):
doc/strategy-eskalacja-horyzontu.md
1) Co nazywamy “kontraktem” (u nas vs Drift)
Na Drift istnieją:
- orders (zlecenia): limit/market/trigger, post-only, reduce-only, IOC/GTC itd.
- position (pozycja): rozmiar, kierunek, średnia cena, PnL itd.
- konto/margin: collateral i health.
W naszym systemie kontrakt bota to byt aplikacyjny (DB + logika), który:
- opisuje intent (wejście + prowadzenie + wyjście),
- mapuje intent na 1..N orderów na Drift,
- jest idempotentny (nie dubluje orderów po restarcie),
- jest modyfikowalny (cancel+place / zmiana desired state),
- jest kończony (exit policy lub kill-switch),
- jest audytowalny (pełny log decyzji i akcji).
2) Wybór “lepszy i przyszłościowy”
A) Cena jako offset, nie absolutna cena (recommended)
Model zwraca cenę wejścia/wyjścia jako offset (ticks/bps) względem top-of-book / mid, a nie jako limit_price.
Dlaczego:
- odporniejsze na latency (cena się przesuwa, offset pozostaje sensowny),
- łatwiejsze “reprice” (edit policy jest naturalna),
- mniejsze ryzyko “starej ceny” przy krótkim TTL.
Executor i tak zna:
best_bid/best_ask/mid,- tick size i step size,
- aktualne gates (spread/slippage/depth/freshness).
B) Desired-state jako rdzeń (recommended)
Kontrakt jest prowadzony jako desired-state loop:
- model/kontrakt mówi “co chcę mieć” (np.
target_exposure_usd), - executor porównuje “observed vs desired” i wykonuje minimalne akcje.
To upraszcza:
- edycję (zmiana target, update policy),
- reconcile po restarcie,
- panic exit.
3) Role: Vast vs executor (k3s)
Vast (model) zwraca
- kompletny trade_intent: parametry wejścia/prowadzenia/wyjścia,
- sugestie gates (np. spread/slippage/depth), ale nie może ich omijać,
confidence/urgency(metadata).
Executor (k3s) jest “single source of execution”
- waliduje gates i limity,
- normalizuje do tick/step,
- nadaje idempotentne
client_order_id, - składa/canceluje/zamyka (reduce-only),
- prowadzi state machine,
- loguje eventy i mierzy koszty.
4) Spec: trade_intent (Vast → k3s)
Format jest wersjonowany: intent_schema_version.
4.1 Minimalny szkielet
{
"intent_schema_version": 1,
"decision_id": "ulid-or-uuid",
"bot_id": "bot-sol-perp-01",
"ts": "2026-01-31T00:00:00.000Z",
"ttl_ms": 15000,
"market_name": "SOL-PERP",
"subaccount_id": 0,
"mode": "enter|manage|exit|panic",
"confidence": 0.0,
"urgency": 0.0,
"desired": {
"target_exposure_usd": 0,
"max_position_usd": 200,
"min_trade_usd": 5
},
"entry": {
"side": "long|short",
"order_type": "post_only_limit|limit|market",
"size_usd": 25,
"limit_offset": { "ref": "best_bid|best_ask|mid", "ticks": 1 },
"time_in_force": "GTC|IOC",
"cancel_if_not_filled_ms": 8000
},
"manage": {
"reprice_after_ms": 750,
"reprice_offset_ticks": 1,
"max_reprices_per_min": 30,
"cooldown_ms": 250
},
"exit": {
"max_hold_s": 180,
"stop_loss_bps": 25,
"take_profit_bps": 35,
"exit_order_type": "reduce_only_limit|reduce_only_market",
"exit_limit_offset": { "ref": "best_bid|best_ask|mid", "ticks": 1 }
},
"gates": {
"freshness_max_ms": 1500,
"max_spread_bps": 10,
"max_slippage_bps": 25,
"min_depth_topn_usd": 5000,
"min_depth_band": { "band_bps": 20, "min_usd": 8000 }
}
}
4.2 Zasady interpretacji
ttl_ms: po tym czasie executor ma prawo zignorować intent (stary sygnał).mode:enter: wolno otwierać/rozszerzać pozycję,manage: tylko zarządzanie już istniejącą pozycją/ordreami (bez zwiększania ryzyka),exit: przejście dotarget_exposure_usd=0i zamykanie,panic: natychmiast cancel + close (reduce-only), potemoff.
desired.target_exposure_usdjest źródłem prawdy, ale executor ma hard capmax_position_usd(model może sugerować, executor egzekwuje).limit_offset:ref=best_biddla wejścia long (maker),ref=best_askdla wejścia short,- ticks/bps są zaokrąglane do tick size rynku.
5) Mapowanie trade_intent → Drift order params (PERP)
Executor buduje “order template” (pseudopola):
marketIndex(wynik mapowaniamarket_name)direction:long|shortorderType:market|limit(+trigger*jeśli później dodamy SL/TP jako ordery trigger)baseAssetAmount(zsize_usdprzeliczone do base i zaokrąglone dobaseStepSize)price(dla limit): zlimit_offset+ aktualne top-of-book, zaokrąglone dopriceTickSizepostOnly: true, jeśliorder_type=post_only_limitreduceOnly: true na wyjściu (exit_order_type=reduce_only_*)immediateOrCancel: true, jeślitime_in_force=IOCclientOrderId/userOrderId: deterministycznie zdecision_id(patrz niżej)
5.1 Idempotencja: client_order_id
Wymaganie: po restarcie executora nie może dojść do “double order”.
Zasada:
- każde wejście/wyjście ma stabilne
client_order_idwywiedzione zdecision_id, - jeśli Drift nie wspiera pełnego “modify”, executor robi
cancel + placeale zachowuje spójne ID (np.decision_id+ suffix-r1,-r2dla reprices).
6) State machine kontraktu (minimal)
Rekomendowane stany:
off(nie handluje)pending_entry(intent zaakceptowany, order wysłany)entered(pozycja ≠ 0 lub entry fill)managing(utrzymuje desired state / repricing)exiting(reduce-only close w toku)closed(pozycja 0, brak orderów)rejected(gates fail / TTL expired)panic(cancel+close; potemoff)
Każda zmiana stanu = event do DB.
7) Co UI wizualizuje (warstwy) a co liczy backend
UI nie liczy. UI:
- subskrybuje
*_latest(live), - pobiera
*_ts(historia), - renderuje warstwy i kontrakty.
Backend liczy:
- DLOB:
dlob_*_latest+ (docelowo)dlob_*_ts - ticks/candles:
drift_ticks+get_drift_candles(...) - kontrakty:
bot_intents/bot_contracts/bot_events(+ TS wersje)
8) Metryki do strojenia (co mierzyć i jakie okna czasowe)
Cel: stroić gates, politykę repricing i parametry exit.
8.1 Mikrostruktura (gates) — z czego stroimy progi
Źródła: dlob_stats_*, dlob_depth_*, dlob_slippage_*.
Mierz (per market, live + TS):
spread_bpsimpact_bpsdla docelowychsize_usd(buy/sell)depth_bid_usd,depth_ask_usd(top‑N)- depth w bandach (
band_bps):bid_usd/ask_usd freshness_ms(now - updated_at)
Okno strojenia:
- start: 7 dni TS (wystarczy do percentyli i pór doby),
- docelowo: 30+ dni (downsample 1m/5m) dla stabilniejszych reżimów.
Jak stroić:
- progi na percentylach (P90/P95), nie na średniej,
- osobne percentyle per “godzina doby” (płynność) i per “vol regime”.
8.2 Jakość egzekucji (czy entry/manage działa)
Źródła: bot_events (audyt) + snapshoty z momentu decyzji.
Mierz per kontrakt:
time_to_first_fill_ms,time_to_full_fill_msfill_pct,avg_fill_pricereprice_count,cancel_countexpected_execution_bps(z DLOB w chwili decyzji) vsrealized_execution_bps- “churn cost”:
tx_counti (jeśli liczymy)priority_feesumarycznie
Okno strojenia:
- 24h (szybki smoke po deployu),
- 7 dni (tuning),
- 30 dni (stabilizacja).
8.3 Wynik i ryzyko (czy strategia ma edge)
Mierz:
hold_time_s- reason exit:
tp|sl|time|regime|panic - MAE/MFE w bps w trakcie hold
- PnL (jeśli macie komplet danych) albo proxy w bps
Okno:
- 7 dni minimalnie, ale sensowniej 30+ dni (reżimy).
9) Następny krok implementacyjny (po akceptacji)
- Dodać tabele TS dla warstw (min. 7 dni retencji).
- Dodać tabele i logi kontraktów (
bot_contracts,bot_events, opcjonalniebot_intents_ts). - Dodać “executor” (observe → dry-run → live) oraz integrację Vast.