docs(snapshot): sync workspace documentation

This commit is contained in:
u1
2026-03-29 13:14:30 +02:00
parent 93a6e4aa2f
commit 0d3d110026
37 changed files with 8500 additions and 123 deletions

359
bots.md Normal file
View File

@@ -0,0 +1,359 @@
# Boty (auto-trading) na Drift PERP — architektura VPS + Vast
Ten dokument opisuje docelową architekturę “botów”, które:
- potrafią otworzyć pozycję na wybranym rynku PERP,
- mierzą i publikują statsy (m.in. DLOB/L2, spread, depth, slippage),
- mogą zostać **zatrzymane / zmodyfikowane / natychmiast spłaszczone** (exit) w każdej chwili,
- działają na VPS (k3s) bez ręcznych zmian “na żywo” (snapshot deploy; patrz `doc/workflow.md`).
## Założenia i cele
### Cele
- **Separation of concerns:** model/strategia nie ma dostępu do kluczy; wykonanie transakcji jest tylko na VPS.
- **Kill switch:** jeden przełącznik ma natychmiast zatrzymać bota i wyjść z pozycji (cancel + close/reduce-only).
- **Desired-state loop:** bot nie “wydaje komend” adhoc; utrzymuje stan docelowy (np. target exposure) i zbiega do niego.
- **Obserwowalność:** każda decyzja i akcja ma log/metrykę/event w DB.
Słownik pojęć (DLOB/L1…L10): `doc/dlob-basics.md`.
Strategia MVP (sekundy → minuty): `doc/bot-strategy-reversal.md`.
### Non-goals (na start)
- HFT / ultra-low-latency (na początku stawiamy na stabilność i kontrolę).
- Multi-venue routing i arbitraż między giełdami.
- Zarządzanie portfelem wielu subkont per user (to może wejść później).
## Warstwy systemu (docelowo)
### 1) Data plane (VPS/k3s): market data + statsy
Cel: zapewnić botom i UI spójne, “lokalne” źródła danych.
Aktualne elementy (już istnieją w repo/stacku):
- `dlob-publisher-hot` → buduje hot-path DLOB z Solany i publikuje `dlob-hot:*` do `dlob-redis`.
- `dlob-publisher-all` → buduje pełny feed rynku i publikuje `dlob-all:*` do `dlob-redis`.
- `dlob-hot-redis-to-postgres-raw-writer` → zapisuje raw `hot` do PostgreSQL.
- `dlob-hot-postgres-to-postgres-derived-writer` → buduje derived `hot`.
- `dlob-all-redis-to-postgres-derived-writer` → buduje derived `all`.
- `Hasura` → wystawia te dane jako GraphQL + subscriptions dla UI i botów.
Konfiguracja rynków:
- `DLOB_MARKETS` (symbole: np. `SOL-PERP`, `BTC-PERP`, `1MBONK-PERP`).
- `PERP_MARKETS_TO_LOAD` (indeksy rynków ładowane przez `dlob-publisher-hot` / `dlob-publisher-all`).
### 2) Execution plane (VPS/k3s): bot executor (klucze + transakcje)
Cel: jedyne miejsce, gdzie występują klucze prywatne i podpisywanie transakcji.
Warianty uruchomienia:
- **A) 1 Deployment na bota**: proste izolowanie i rollout per bot.
- **B) 1 Deployment “executor” obsługujący wiele botów**: łatwiejszy pooling połączeń/RPC, ale większy blast radius.
Minimalne wymagania executora:
- Czyta konfigurację bota (desired state) z DB (Hasura/Postgres).
- Subskrybuje statsy/telemetrię z Hasury (GraphQL + subscriptions) lub odpytuje okresowo.
- Wykonuje akcje tradingowe (place/modify/cancel/close) z ograniczeniami ryzyka.
- Ma **hard kill switch** (patrz niżej).
## Maszyneria bota (to jest kluczowe)
To, czy bot “działa bezpiecznie”, zależy mniej od samego modelu, a bardziej od maszynerii: pętli kontrolnej, zarządzania orderami i bezpiecznego wyjścia.
Poniżej minimalny podział odpowiedzialności wewnątrz executora (nawet jeśli to jest jeden proces):
- **Config store**: odczyt `bot_config` (desired state) + zapis `bot_state`.
- **Market data adapter**: pobiera features z Hasury (np. `dlob_stats_latest`, `dlob_depth_bps_latest`, `dlob_slippage_latest`, candles).
- **Strategy adapter**: tworzy propozycję “desired” (lokalnie lub z Vast). Nie robi transakcji.
- **Risk engine**: weryfikuje limity (max pozycja, max slippage, max order rate, timeouts, freshness danych).
- **Order manager**: mapuje “desired” → konkretne akcje: place/modify/cancel; pilnuje idempotencji i retry.
- **Position reconciler**: porównuje stan konta (pozycje/ordery) z DB i “desired”; wykrywa rozjazdy.
- **Kill switch**: zawsze dostępny; wymusza cancel + close i przejście do `mode=off`.
### Stan bota jako state machine (proponowane)
Traktuj to jak automat stanów, żeby uniknąć chaosu “ifów”:
- `off`: bot nie wysyła transakcji (może tylko heartbeat).
- `observe`: liczy/loguje decyzje, ale nie handluje.
- `trade`: aktywny, utrzymuje desired-state.
- `panic`: tryb awaryjny; cancel + close; potem `off`.
Opcjonalne stany pośrednie (jeśli potrzebne): `entering`, `managing`, `exiting`.
### Minimalny model danych (MVP)
Docelowo boty muszą być “sterowane” i audytowalne z DB:
- `bot_config` (sterowanie):
- `mode`: `off|observe|trade`
- `market_name` (np. `SOL-PERP`)
- `target_exposure_usd` / `target_base`
- limity: `max_position_usd`, `max_slippage_bps`, `max_orders_per_min`, `cooldown_ms`
- `kill_switch` (bool)
- `strategy`: `{ type, params, model_endpoint, model_version }`
- `bot_state` (status runtime):
- `last_heartbeat_at`, `last_error`, `last_action_at`
- `position_snapshot` (opcjonalnie)
- `bot_events` (audit log):
- `decision` (wejście modelu/strategii + features hash + wersja)
- `order_sent`, `order_ack`, `order_filled`, `order_canceled`
- `panic_exit`, `error`
W MVP możesz zacząć od: `bot_config` + `bot_events`, a `bot_state` dodać po pierwszym działającym loopie.
### 3) Model plane (Vast): sygnały i inference (bez kluczy)
Cel: trenowanie/inference modeli na Vast, ale bez dostępu do środków.
Kontrakt między VPS a Vast:
- VPS wysyła “features snapshot” (np. statsy DLOB + candles + pozycja).
- Vast zwraca “decision” (np. `target_base`, `target_notional_usd`, `side`, `confidence`, `params`).
- VPS decyduje o wykonaniu i podpisuje transakcje.
Zasada: Vast **nigdy** nie dostaje sekretów (RPC keys, prywatne klucze, tokeny).
### 4) RPC: dedykowany Solana RPC/WS (VPS)
Cel: stabilność + mniejsze opóźnienia dla `dlob-publisher` i executora.
Założenia:
- `dlob-publisher` i executor używają tego samego RPC/WS (lub dwóch endpointów tej samej infrastruktury).
- W przyszłości możliwy fallback na publiczny endpoint (z ograniczeniami).
## Desired-state loop (core idea)
Bot działa jako pętla kontrolna:
1) Odczytaj `desired_state` (z DB) + bieżący stan (pozycja, ordery, rynek).
2) Policz różnicę (`delta`) i oceń ryzyko/limity.
3) Wykonaj minimalny zestaw akcji, żeby przybliżyć się do `desired_state`.
4) Zapisz eventy (co zrobił i dlaczego).
Przykładowe `desired_state` (na start):
- `mode`: `off | observe | trade`
- `market`: `SOL-PERP` (lub lista)
- `target_exposure_usd` albo `target_base`
- `max_position_usd`, `max_leverage`, `max_slippage_bps`
- `order_policy`: `market | limit | post_only | chase`
- `cooldown_ms`, `max_orders_per_min`
## Kill switch (must-have)
Kill switch ma działać bez udziału modelu i bez UI.
Źródła kill switch (kolejność priorytetu):
1) **Env var** executora (np. `BOT_KILL_SWITCH=1`) → natychmiastowy “panic mode”.
2) Flaga w DB (np. `bot_config.kill_switch=true`).
3) Warunek bezpieczeństwa (np. RPC lag, brak danych, brak świeżości L2, zbyt duży drawdown).
Akcja kill switch (docelowy runbook):
- `cancel_all` na rynku (lub subkoncie),
- `close_position` (preferowane: reduce-only, kontrola slippage),
- przejście bota do `mode=off` + zapis eventu “panic_exit”.
## Awaria VPS: co się dzieje z transakcjami i jak się zabezpieczyć
Kluczowe fakty:
- **Nie da się “ubić” wysłanej transakcji** na Solanie. Jeśli tx została przyjęta do sieci i wyląduje w bloku, jej efekt jest on-chain.
- To, co można zrobić po awarii, to **kolejne transakcje korygujące**: `cancel` / `close` / `reduce-only`.
W praktyce awarie dzielą się na 3 przypadki:
1) **Tx nie weszła (dropped/expired)** → po restarcie robisz reconcile i jedziesz dalej.
2) **Tx weszła i postawiła order** → po restarcie reconcile zobaczy otwarte ordery i może je utrzymać/zmodyfikować/cancel.
3) **Tx weszła i zmieniła pozycję** → po restarcie reconcile zobaczy pozycję i może ją domknąć lub dopasować do desired-state.
### Reconcile po starcie (must-have)
Każdy bot po restarcie powinien:
- wczytać `bot_config` (desired state),
- pobrać stan on-chain (pozycje + otwarte ordery dla rynku/subkonta),
- porównać “observed” vs “desired” i wykonać minimalne akcje korekcyjne.
To chroni przed sytuacją: “wysłaliśmy akcję, VPS padł przed zapisaniem eventu”.
## Guardian / Safety bot (żeby wyjść mimo padnięcia executora)
Jeśli wymaganie brzmi: “w każdej chwili możemy wyjść”, to kill switch nie może mieszkać tylko w tym samym VPS/klastrze co executor.
Proponowany wzorzec:
- Osobny, mały serwis **`bot-guardian`** uruchomiony **poza** głównym VPS (drugi VPS / inny klaster / inna VM).
- Guardian ma dostęp do:
- RPC/WS,
- klucza lub uprawnień do cancel/close (patrz uwagi bezpieczeństwa),
- sygnału “czy executor żyje” (np. `bot_state.last_heartbeat_at` w DB albo endpoint health).
Zachowanie:
- Jeśli heartbeat executora jest starszy niż `T_fail` (np. 1030s) → guardian włącza “panic mode”:
- `cancel_all_orders`,
- `close_position` (reduce-only; z limitem slippage),
- zapis eventu `panic_exit` (jeśli ma dostęp do DB).
Uwaga: guardian nie potrzebuje modelu/AI. Ma być “głupi i niezawodny”.
### Minimalne bezpieczeństwo guardiana
- Klucze: trzymane jako secret tylko na hoście guardiana; nie w repo.
- Zakres: jeśli to możliwe, ogranicz klucz do subkonta / minimalnych uprawnień (w zależności od możliwości Drift/Solana).
- Rate-limit: zabezpieczenie przed pętlą panic (np. `panic_cooldown_ms`).
## HA executora (opcjonalnie): leader election
Jeśli chcesz 2 repliki executora (na jednym lub wielu węzłach), potrzebujesz single-writer:
- tylko **leader** składa transakcje,
- **follower** obserwuje i jest gotowy przejąć.
Wzorzec:
- lease w DB/Redis (np. rekord “lock” z TTL),
- leader odnawia lease co N sekund,
- brak odnowienia → follower przejmuje i robi reconcile.
To ogranicza ryzyko “double-trading”, ale nie zastępuje guardiana (bo oba mogą paść razem).
## Model kosztów (expected vs realized) — entry/exit/modify
Bot powinien umieć policzyć:
- **expected costs** (przed wejściem/wyjściem): czy trade ma sens i czy mieści się w limitach,
- **realized costs** (po fakcie): ile faktycznie kosztowało wejście/wyjście + ile “spaliły” modyfikacje.
W praktyce koszty dzielą się na 3 kubełki:
1) koszt mikrostruktury (spread/slippage/market impact),
2) fee maker/taker + funding (opcjonalnie),
3) koszt tx na Solanie (network fee + priority) oraz koszt modyfikacji (cancel+place).
### 1) Koszt wejścia/wyjścia z orderbooka (spread/slippage)
Źródła danych:
- `dlob_stats_latest`: `mid_price`, `spread_bps`, `best_bid_price`, `best_ask_price`
- `dlob_slippage_latest`: `impact_bps`, `vwap_price`, `fill_pct` dla `size_usd` i `side`
Warianty:
- **Taker/market (lub agresywny limit krzyżujący)**: używaj `impact_bps` z `dlob_slippage_latest` jako “expected execution cost vs mid”.
- **Maker/post-only**: policz 2 scenariusze:
- “maker-fill”: koszt vs mid wynikający z `limit_price` (zwykle lepszy niż taker),
- “fallback-taker”: jeśli nie fill → koszt = `impact_bps` (jak taker).
Przykładowa definicja kosztu vs mid (bps), jeśli liczysz z cen:
- buy: `cost_bps = (fill_price / mid_at_submit - 1) * 10_000`
- sell: `cost_bps = (1 - fill_price / mid_at_submit) * 10_000`
Uwaga: `mid` się rusza, więc to jest miara “kosztu wykonania względem chwilowego środka rynku” (przydatna do strojenia), a nie pełne PnL.
### 2) Fee maker/taker i funding (opcjonalnie)
Na MVP trzymaj fee jako parametry konfiguracyjne:
- `fee_maker_bps`
- `fee_taker_bps`
Realized fee licz po fill:
- `fee_usd = fill_notional_usd * fee_rate_bps / 10_000`
Funding dla holdów “minuty” zwykle jest mały; można dodać później.
### 3) Koszt transakcyjny (Solana fee + priority) i koszt modyfikacji
Fakty:
- “modyfikacja” ordera to zazwyczaj **cancel + place** → zwykle 2 transakcje.
- Nie da się “ubić” wysłanej transakcji; można tylko wysłać korektę (cancel/close) kolejną tx.
Definicje:
- `tx_fee_usd` = koszt jednej transakcji w USD (network fee + priority fee).
- `tx_fee_bps = (tx_fee_usd / notional_usd) * 10_000`
Koszt modyfikacji (expected):
- `expected_modify_cost_usd ≈ expected_reprices * (tx_fee_cancel_usd + tx_fee_place_usd)`
- `expected_modify_cost_bps = (expected_modify_cost_usd / entry_notional_usd) * 10_000`
Źródło `tx_fee_usd`:
- na start: stałe estymaty w `bot_config`,
- później: estymata “priority fee” z feedu/endpointu + przelicznik SOL→USD.
### Expected total cost (proponowana metryka bramkująca)
Na ticku decyzji licz:
- `expected_total_cost_bps = execution_bps + fee_bps + tx_bps + expected_modify_bps (+ funding_bps)`
I bramkuj wejście/wyjście:
- `expected_total_cost_bps <= max_expected_total_cost_bps`
## Parametry kosztów (do `bot_config`) — propozycja
Minimalny zestaw:
- `fee_maker_bps`, `fee_taker_bps`
- `max_expected_total_cost_bps`
- `max_expected_modify_bps` (opcjonalnie osobny limit na “cancel+place spam”)
- `tx_fee_usd_est` (albo `tx_fee_sol_est` + `sol_price_usd_est`)
- `expected_reprices_per_entry`
To działa zarówno dla strategii regułowych, jak i ML (model może dać `urgency`, ale executor i tak liczy koszty).
## Logowanie kosztów (do `bot_events`) — propozycja
Żeby stroić progi i ocenić jakość wejść, zapisuj:
### Na decyzji (przed wysłaniem orderów)
- `mid_at_submit`, `spread_bps_at_submit`
- `slippage_quote`: `impact_bps`, `vwap_price`, `fill_pct` (dla danego `size_usd`)
- `expected_cost_bps_breakdown`: `{ execution_bps, fee_bps, tx_bps, expected_modify_bps, total_bps }`
- `order_intent`: `{ policy, limit_price, size_usd, side }`
### Po fill / wyjściu
- `realized_execution_bps` (vs `mid_at_submit`), osobno entry i exit
- `realized_fee_usd`, `realized_tx_usd`
- `modify_count`: liczba cancel+place w trakcie wejścia/zarządzania
- `time_to_fill_ms`, `hold_time_s`
To daje realną pętlę feedbacku: “koszt vs jakość sygnału” i “czy chase zjada edge”.
## Obserwowalność i audyt (DB + logi)
W DB trzymamy:
- Konfigurację (`bot_config`) — desired state i limity.
- Stan (`bot_state`) — ostatnia decyzja, last heartbeat, ostatni błąd.
- Eventy (`bot_events`) — “decision”, “order_sent”, “order_filled”, “panic_exit”, “error”.
MVP może zacząć od samego `bot_events` + `bot_config`, a resztę dodać po pierwszym działającym loopie.
## Interfejsy (kontrakty)
### VPS ↔ Hasura
- Executor czyta: `dlob_*_latest` + `drift_ticks`/candles + własne tabele botów.
- Executor pisze: `bot_events`, `bot_state` (oraz ewentualnie `bot_orders`).
### VPS ↔ Vast
- HTTP endpoint (lub gRPC) “predict”.
- Kontrakty muszą być wersjonowane (`model_version`) i logowane w `bot_events`.
## Bezpieczeństwo (sekrety)
Zasady:
- Prywatne klucze i tokeny są tylko w K8s Secret (na VPS) i nigdy nie trafiają do repo.
- Vast nie dostaje sekretów.
- Logi nie mogą wypisywać URL z api-key ani payloadów z sekretami.
## Deployment (k3s + GitOps)
Zasady deployu: snapshoty, brak ręcznych zmian na VPS (`doc/workflow.md`).
Docelowe komponenty w `trade-deploy` (do dopięcia):
- `Deployment/bot-executor` (albo `Deployment/bot-<name>`),
- `Secret/bot-executor-keys` (klucze/konfiguracja),
- NetworkPolicy (opcjonalnie) ograniczająca ruch do Vast i RPC.
## Plan budowy (iteracyjnie)
### Etap 0: Observe-only (bez handlu)
- `bot_config` + `bot_events`.
- Executor subskrybuje `dlob_stats_latest` i loguje “decision” bez transakcji.
- Kill switch działa (przełącza tryb i loguje).
### Etap 1: Paper trading / dry-run
- Executor wylicza ordery i zapisuje je do DB, ale nie wysyła na chain.
- UI pokazuje “co by zrobił”.
### Etap 2: Live trading (minimal)
- Wejście/wyjście market/limit z restrykcjami ryzyka.
- Panic exit: cancel + close.
### Etap 3: Integracja z modelem (Vast)
- Predictor endpoint, wersjonowanie modeli, fallback (jeśli Vast down → observe/off).
## Otwarte pytania (do uzupełnienia)
- Czy bot ma działać na jednym rynku PERP czy wielu jednocześnie?
- Jaki tryb wejścia/wyjścia: market vs limit vs post-only/chase?
- Jakie KPI/limity: max drawdown, max slippage, max position, timeouts?
- Jak rozdzielamy konta/subkonta (1 bot = 1 subkonto?) i jak to audytujemy?