16 KiB
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” ad‑hoc; 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 publikujedlob-hot:*dodlob-redis.dlob-publisher-all→ buduje pełny feed rynku i publikujedlob-all:*dodlob-redis.dlob-hot-redis-to-postgres-raw-writer→ zapisuje rawhotdo PostgreSQL.dlob-hot-postgres-to-postgres-derived-writer→ buduje derivedhot.dlob-all-redis-to-postgres-derived-writer→ buduje derivedall.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 przezdlob-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) + zapisbot_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; potemoff.
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|trademarket_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_atposition_snapshot(opcjonalnie)
bot_events(audit log):decision(wejście modelu/strategii + features hash + wersja)order_sent,order_ack,order_filled,order_canceledpanic_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-publisheri 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:
- Odczytaj
desired_state(z DB) + bieżący stan (pozycja, ordery, rynek). - Policz różnicę (
delta) i oceń ryzyko/limity. - Wykonaj minimalny zestaw akcji, żeby przybliżyć się do
desired_state. - Zapisz eventy (co zrobił i dlaczego).
Przykładowe desired_state (na start):
mode:off | observe | trademarket:SOL-PERP(lub lista)target_exposure_usdalbotarget_basemax_position_usd,max_leverage,max_slippage_bpsorder_policy:market | limit | post_only | chasecooldown_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):
- Env var executora (np.
BOT_KILL_SWITCH=1) → natychmiastowy “panic mode”. - Flaga w DB (np.
bot_config.kill_switch=true). - Warunek bezpieczeństwa (np. RPC lag, brak danych, brak świeżości L2, zbyt duży drawdown).
Akcja kill switch (docelowy runbook):
cancel_allna 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:
- Tx nie weszła (dropped/expired) → po restarcie robisz reconcile i jedziesz dalej.
- Tx weszła i postawiła order → po restarcie reconcile zobaczy otwarte ordery i może je utrzymać/zmodyfikować/cancel.
- 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-guardianuruchomiony 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_atw DB albo endpoint health).
Zachowanie:
- Jeśli heartbeat executora jest starszy niż
T_fail(np. 10–30s) → 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:
- koszt mikrostruktury (spread/slippage/market impact),
- fee maker/taker + funding (opcjonalnie),
- 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_pricedlob_slippage_latest:impact_bps,vwap_price,fill_pctdlasize_usdiside
Warianty:
- Taker/market (lub agresywny limit krzyżujący):
używaj
impact_bpszdlob_slippage_latestjako “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).
- “maker-fill”: koszt vs mid wynikający z
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_bpsfee_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_bpsmax_expected_total_cost_bpsmax_expected_modify_bps(opcjonalnie osobny limit na “cancel+place spam”)tx_fee_usd_est(albotx_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_submitslippage_quote:impact_bps,vwap_price,fill_pct(dla danegosize_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(vsmid_at_submit), osobno entry i exitrealized_fee_usd,realized_tx_usdmodify_count: liczba cancel+place w trakcie wejścia/zarządzaniatime_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 ewentualniebot_orders).
VPS ↔︎ Vast
- HTTP endpoint (lub gRPC) “predict”.
- Kontrakty muszą być wersjonowane (
model_version) i logowane wbot_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(alboDeployment/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_latesti 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?