Skip to content

Periodic WS↔REST Orderbook Reconciliation — Design ​

Status: ACCEPTED 2026-05-13 (user-ratified architecture) Implementation status (2026-08-06): NOT IMPLEMENTED in pm-engine. Bootstrap reconciliation, reconnect reconciliation and orderbook REST self-heal paths are narrower mechanisms and do not constitute this periodic pool. Track implementation as PM-ENGINE-PERIODIC-RECONCILIATION in the tech-debt backlog. Fixes: BUG-11 (docs/tests/backlog/BUG-11-clob-periodic-reconciliation-missing.md) Related: BUG-#143 (dual-WS dedup) — отдельный fix для WS self-healing

1. Цель ​

  • Закрывает 10 rule-IDs §20 + R-CLOB-PURPOSE-RECONCILIATION §8.1 (R-RECON-SIZE-DUST-THRESHOLD отдельно — не применим).
  • Reconciliation = safety net для server-side аномалий Polymarket и сетевых reorder.
  • НЕ заменяет WS self-healing (тот fix — отдельный backlog item, см. #143).

2. Архитектура ​

2.1 ReconciliationPool ​

  • Dedicated tokio task (отдельный от scanner и strategy engine).
  • API:
    • subscribe(asset_id) — добавить ассет в очередь, пересчитать cadence.
    • unsubscribe(asset_id) — убрать, пересчитать.
    • current_rate() → метрика.
  • Queue: round-robin VecDeque<asset_id> (FIFO с гарантированным fairness).

2.2 Rate budget ​

  • Константа: MAX_BUDGET_PER_10S = 1200 // Q1, headroom 300 req/10s от Polymarket лимита 1500/10s для других REST вызовов бота.
  • Константа: NEW_ASSET_RESERVE_PER_10S = 100 // Q2, резерв на burst новых subscriptions (до 10 одновременных новых ассетов c initial snapshot per second).
  • Effective budget = MAX_BUDGET_PER_10S - NEW_ASSET_RESERVE_PER_10S = 1100.
  • Per-asset cadence (ms) = 10_000 * queue.len() / 1100.
  • Примеры:
    • queue=20 → cadence ≈ 181 ms per asset.
    • queue=50 → 454 ms.
    • queue=100 → 909 ms.
    • queue=500 → 4545 ms.
  • При subscribe/unsubscribe — пересчёт immediate (не на следующий тик).

2.3 Poll cycle ​

  • Каждый тик: asset = queue.next(), snapshot = clob.fetch_book(asset) (через существующий fetch_orderbook_rest_with_metadata).
  • Сравнение с in-memory OrderbookStore: per-level diff (полная глубина, decimal-aware, БЕЗ dust threshold).
  • Для каждого diff'а — emit DiffEvent.
  • Обновление store через OrderbookStore::update_from_rest_snapshot (ADR-022 4-branch guard сам решит race с свежим WS через timestamp).

2.4 DiffEvent schema ​

rust
struct DiffEvent {
    asset_id: String,
    side: Side,
    price: Decimal,
    old_size: Option<Decimal>,  // None если уровень был отсутствует
    new_size: Option<Decimal>,  // None если уровень удалён
    source: DiffSource::RestReconcile,
    server_timestamp_ms: i64,
    local_timestamp_ms: i64,
    snapshot_hash: Option<String>,
}

2.5 Event bus integration ​

  • DiffEvent → тот же event channel, что WS deltas.
  • Стратегии не различают источник (обычный orderbook update flow).
  • Strategy engine получает чистые OrderBookEvent-ы, абстрагируется от reconciliation.

2.6 Audit table ​

sql
CREATE TABLE reconciliation_diffs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  asset_id TEXT NOT NULL,
  side TEXT NOT NULL CHECK(side IN ('bid','ask')),
  price TEXT NOT NULL,            -- Decimal as string
  old_size TEXT,                  -- NULL = новый уровень
  new_size TEXT,                  -- NULL = удалённый уровень
  server_timestamp_ms INTEGER NOT NULL,
  local_timestamp_ms INTEGER NOT NULL,
  snapshot_hash TEXT,
  inserted_at INTEGER NOT NULL DEFAULT (strftime('%s','now')*1000)
);
CREATE INDEX idx_recon_asset_ts ON reconciliation_diffs(asset_id, server_timestamp_ms);
  • Локация: bot DB (общая, для JOIN с orders_history).
  • Migration: новая идемпотентная (CREATE TABLE IF NOT EXISTS).
  • Никаких retention policy в этом fix (отдельная задача).

3. Config ​

  • RECONCILIATION_ENABLED (bool, default true).
  • RECONCILIATION_MAX_BUDGET_PER_10S (i32, default 1200).
  • RECONCILIATION_NEW_ASSET_RESERVE_PER_10S (i32, default 100).
  • RECONCILIATION_LRU_AUDIT_BATCH (i32, default 100) — batch INSERT в audit table.

(в config.db через JSON, не env — ADR-015.)

4. Coverage rule-IDs ​

Rule-IDПокрытиеКомментарий
R-CLOB-PURPOSE-RECONCILIATION✓Pool существует.
R-RECON-EXISTS✓
R-RECON-PERIODIC-REST-BOOK✓
R-RECON-FREQUENCY-CONFIGURABLE✓budget + reserve конфигурируемы.
R-RECON-COMPARE-TOP-N✓вся глубина из REST.
R-RECON-ON-MISMATCH✓diff → event + audit row.
R-RECON-DECIMAL-AWARE-COMPARE✓rust_decimal, bit-equal.
R-RECON-MISMATCH-NOT-IGNORED✓все diff'ы эмитятся.
R-RECON-SIZE-DUST-THRESHOLDне применимбот торгует на спреды в центы, dust thresholds недопустимы в финансовой системе (user 2026-05-13).
R-RECON-AUTO-RESYNC✓через ADR-022 update_from_rest_snapshot.
R-RECON-AUDIT-TRAIL✓reconciliation_diffs table.

5. План реализации (для backend-developer) ​

Phase 1 — Foundation ​

  • Crate placement: crates/pm-common/src/reconciliation/ (модуль) или внутри crates/pm-engine/src/ (движок).
  • Структуры: ReconciliationPool, DiffEvent, DiffSource.
  • API pool'а (subscribe/unsubscribe/current_rate).
  • Unit tests: rate-budget математика, queue management.

Phase 2 — Compare logic ​

  • compare_rest_with_store(snapshot, store) -> Vec<DiffEvent>.
  • Decimal-aware, без порогов.
  • Unit tests: новый уровень / удалённый / изменённый size / no-op / симметрия sides.

Phase 3 — Event bus + Store integration ​

  • DiffEvent → существующий orderbook event channel (где-то в scanner.rs).
  • update_from_rest_snapshot вызов после emit'а.
  • Integration test: симуляция drift → REST poll → diff event observed downstream.

Phase 4 — Audit table ​

  • Migration script.
  • Batch writer (избегаем INSERT-spam).
  • Integration test: проверка записи всех diff'ов.

Phase 5 — Wiring + config ​

  • Pool start/stop в pm-engine main.
  • Config через config.db.
  • Subscribe/unsubscribe в моменты WS подписки/отписки.
  • E2E smoke test: read-only probe с реальным REST poll; проверить метрики и отсутствие торговых побочных эффектов.

Тесты: каждая phase покрывается unit + integration перед merge. Никаких stretch / future-work backlog'ов (см. feedback_no_silent_backlog).

6. Метрики (R-OBS-RECON-METRICS если в эталоне есть, или backlog'ом) ​

  • reconciliation_polls_total{asset, result} — counter.
  • reconciliation_diffs_total{asset, side, kind} — counter (new/removed/changed).
  • reconciliation_effective_rate_per_10s — gauge.
  • reconciliation_queue_length — gauge.
  • reconciliation_audit_write_lag_ms — histogram.

7. Acceptance criteria ​

  • Все unit + integration tests зелёные.
  • cargo clippy --workspace --all-targets -- -D warnings зелёный.
  • VERIFIED markers выставлены на: ReconciliationPool struct, compare fn, audit table writer.
  • components-status.md C-08.F/G → проверено для 10 rule-IDs (1 — не применим с обоснованием).
  • BUG-11 → DONE-11 после implementation merge.