Files
trade-doc/bots.md

360 lines
16 KiB
Markdown
Raw 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.

# 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?