# SOL-PERP Observer V1 Ten dokument definiuje pierwszy działający observer bota dla `SOL-PERP`. Celem nie jest jeszcze trading, tylko: - stabilny loop `1s` - powtarzalny snapshot feature'ów - decyzja `long|flat|short` - heartbeat i audyt w `bot_state` / `bot_events` ## Zakres - rynek: `SOL-PERP` - tryb: `observe` - źródło danych v1: `Hasura` - bez kluczy prywatnych - bez składania transakcji ## Architektura - `bot_config` pozostaje desired state i źródłem parametrów - `bot-observer` czyta `bot_config`, pobiera snapshot rynku, liczy feature'y i zapisuje wynik - `bot_state` przechowuje heartbeat, ostatni błąd i ostatni wynik loopa - `bot_events` przechowuje append-only audyt decyzji i błędów ## Źródła danych Observer czyta z Hasury: - `get_drift_candles(...)` - `dlob_hot_derived_latest` dla hot marketów z fallbackiem do `dlob_all_derived_latest` Depth bands i slippage nie są już czytane z osobnych legacy tabel. Observer liczy je lokalnie z `bids_norm` / `asks_norm` nowego derived read-model. Wersja v1 zakłada małe query tylko dla jednego rynku. ## Parametry w `bot_config.params` Minimalny kontrakt: ```json { "strategy": { "type": "predictive_observer_v1" }, "loop": { "decision_interval_ms": 1000, "candle_bucket_seconds": 1, "candles_limit": 64 }, "features": { "mom_fast_s": 3, "mom_mid_s": 10, "mom_slow_s": 30, "vol_window_s": 30, "depth_band_bps": 10, "slippage_size_usd": 500 }, "gates": { "freshness_max_ms": 800, "spread_max_bps": 8, "slippage_max_bps": 12, "depth_band_min_usd": 3000 }, "sizing": { "target_notional_usd": 500 }, "decision": { "threshold": 1.2, "horizon_s": 60 }, "scoring": { "weights": { "mom_fast": 0.9, "mom_mid": 0.35, "mom_slow_reversal": 0.55, "imbalance": 5.0, "mark_vs_oracle": 0.08, "spread": 0.15, "slippage": 0.12 } } } ``` ## Feature Snapshot Na każdy tick observer liczy: - `mark_price` - `oracle_price` - `mid_price` - `spread_bps` - `stats_imbalance` - `depth_bid_usd` - `depth_ask_usd` - `depth_imbalance` - `buy_slippage_bps` - `sell_slippage_bps` - `mark_vs_oracle_bps` - `mom_3s` - `mom_10s` - `mom_30s` - `vol_30s` - `data_age_ms` ## Gates Jeśli którykolwiek gate nie przechodzi, observer nie produkuje sygnału kierunkowego i zapisuje event `skip`. Wersja v1: - `data_age_ms <= freshness_max_ms` - `spread_bps <= spread_max_bps` - `max(buy_slippage_bps, sell_slippage_bps) <= slippage_max_bps` - `min(depth_bid_usd, depth_ask_usd) >= depth_band_min_usd` ## Model decyzji V1 nie używa ML. To baseline scoring do zebrania danych i benchmarku. Long side: ```text long_score = mom_fast * w_mom_fast + mom_mid * w_mom_mid - mom_slow * w_mom_slow_reversal + depth_imbalance * w_imbalance - mark_vs_oracle_bps * w_mark_vs_oracle - spread_bps * w_spread - buy_slippage_bps * w_slippage ``` Short side jest lustrzanym odbiciem long side. Decyzja: - jeśli gates fail -> `flat` - jeśli `max(abs(long_score), abs(short_score)) < threshold` -> `flat` - w przeciwnym razie `long` albo `short` ## Event `decision` ```json { "version": "observer-sol-v1", "bot": { "id": "uuid", "name": "sol-observer", "mode": "observe", "market_name": "SOL-PERP", "kill_switch": false }, "runtime": { "service": "bot-observer", "observe_only": true, "loop_ms": 1000, "query_latency_ms": 82, "loop_latency_ms": 96 }, "snapshot": { "data_age_ms": 214, "stats_updated_at": "2026-03-23T20:12:31.112Z", "depth_updated_at": "2026-03-23T20:12:31.112Z", "buy_slippage_updated_at": "2026-03-23T20:12:31.112Z", "sell_slippage_updated_at": "2026-03-23T20:12:31.112Z" }, "features": { "mom_3s": 3.8, "mom_10s": 6.1, "mom_30s": -12.5, "vol_30s": 5.9, "spread_bps": 2.1, "depth_imbalance": 0.18, "buy_slippage_bps": 4.2, "sell_slippage_bps": 4.0, "mark_vs_oracle_bps": -1.3 }, "gates": { "fresh": true, "spread_ok": true, "slippage_ok": true, "depth_ok": true }, "decision": { "side": "long", "confidence": 0.72, "long_score": 1.91, "short_score": -2.34, "target_notional_usd": 500, "horizon_s": 60 } } ``` ## Event `skip` Observer zapisuje `skip`, gdy: - dane są stale - brakuje wymaganych metryk - gates nie przechodzą - bot jest w trybie innym niż aktywny observe loop Payload `skip` ma ten sam nagłówek co `decision`, ale z polem: ```json { "skip_reason": "gate_failed:freshness" } ``` ## `bot_state.state` V1 zapisuje w `bot_state.state` znormalizowany stan loopa: - `service` - `version` - `observe_only` - `market_name` - `mode` - `status` - `last_decision` - `last_features` - `last_gates` - `last_snapshot` - `counters` ## Zmienne środowiskowe Najważniejsze: - `BOT_ID` albo `BOT_NAME` - `HASURA_GRAPHQL_URL` - `HASURA_ADMIN_SECRET` - `DLOB_SOURCE` opcjonalnie, jeśli chcesz zawęzić derived read-model do konkretnego `source` - `CANDLES_SOURCE` opcjonalnie, jeśli chcesz zawęzić candles do konkretnego źródła ticków - `OBSERVER_PORT` opcjonalnie, domyślnie `8791` ## Cel wersji v1 V1 ma odpowiedzieć na trzy pytania: - czy Hasura daje stabilny loop `1s` dla jednego rynku - czy feature snapshot jest wystarczająco świeży - czy baseline scoring daje sensowny materiał do dalszego strojenia i późniejszego ML