Тема
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-RECONCILIATIONin 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, defaulttrue).RECONCILIATION_MAX_BUDGET_PER_10S(i32, default1200).RECONCILIATION_NEW_ASSET_RESERVE_PER_10S(i32, default100).RECONCILIATION_LRU_AUDIT_BATCH(i32, default100) — 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-enginemain. - 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 выставлены на:
ReconciliationPoolstruct,comparefn, audit table writer. components-status.mdC-08.F/G → проверено для 10 rule-IDs (1 — не применим с обоснованием).- BUG-11 → DONE-11 после implementation merge.