Files
trade-doc/observer-sol-v1.md

5.3 KiB

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:

{
  "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:

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

{
  "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:

{
  "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