Files
trade-doc/bots.md

16 KiB
Raw Blame History

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?