Files
trade-frontend/doc/drift-perp-contract.md
2026-02-01 21:44:45 +01:00

263 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
1) opisuje *intent* (wejście + prowadzenie + wyjście),
2) mapuje intent na 1..N orderów na Drift,
3) jest **idempotentny** (nie dubluje orderów po restarcie),
4) jest **modyfikowalny** (cancel+place / zmiana desired state),
5) jest **kończony** (exit policy lub kill-switch),
6) 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
```jsonc
{
"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 do `target_exposure_usd=0` i zamykanie,
- `panic`: natychmiast cancel + close (reduce-only), potem `off`.
- `desired.target_exposure_usd` jest źródłem prawdy, ale executor ma **hard cap** `max_position_usd` (model może sugerować, executor egzekwuje).
- `limit_offset`:
- `ref=best_bid` dla wejścia long (maker),
- `ref=best_ask` dla 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 mapowania `market_name`)
- `direction`: `long|short`
- `orderType`: `market|limit` (+ `trigger*` jeśli później dodamy SL/TP jako ordery trigger)
- `baseAssetAmount` (z `size_usd` przeliczone do base i zaokrąglone do `baseStepSize`)
- `price` (dla limit): z `limit_offset` + aktualne top-of-book, zaokrąglone do `priceTickSize`
- `postOnly`: true, jeśli `order_type=post_only_limit`
- `reduceOnly`: true na wyjściu (`exit_order_type=reduce_only_*`)
- `immediateOrCancel`: true, jeśli `time_in_force=IOC`
- `clientOrderId` / `userOrderId`: deterministycznie z `decision_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_id` wywiedzione z `decision_id`,
- jeśli Drift nie wspiera pełnego “modify”, executor robi `cancel + place` ale zachowuje spójne ID (np. `decision_id` + suffix `-r1`, `-r2` dla 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; potem `off`)
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_bps`
- `impact_bps` dla docelowych `size_usd` (buy/sell)
- `depth_bid_usd`, `depth_ask_usd` (topN)
- 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_ms`
- `fill_pct`, `avg_fill_price`
- `reprice_count`, `cancel_count`
- `expected_execution_bps` (z DLOB w chwili decyzji) vs `realized_execution_bps`
- “churn cost”: `tx_count` i (jeśli liczymy) `priority_fee` sumarycznie
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)
1) Dodać tabele TS dla warstw (min. 7 dni retencji).
2) Dodać tabele i logi kontraktów (`bot_contracts`, `bot_events`, opcjonalnie `bot_intents_ts`).
3) Dodać “executor” (observe → dry-run → live) oraz integrację Vast.