@kglozhkin/orderbook-reducer
v0.1.0
Published
Pure in-memory order book reducer with birth_ts tracking. Shared between book-keeper (online) and second-bar-engine (offline) to eliminate train/serve skew.
Readme
@kglozhkin/orderbook-reducer
Чистый in-memory reducer для стакана заявок Polymarket с трекингом birth_ts
каждого уровня. Не знает про NATS, Redis, файлы и эмиссию. Используется
единым между:
book-keeper(онлайн,BOOK_KEEPER_USE_SHARED_REDUCER=true) — для проекции в Redis.execution-simulator(офлайн-бэктест) — обёрнут вVirtualBook.second-bar-engine(офлайн-реконструкция исторических логов) — для генерации second-bars.
Цель — устранить train/serve skew: одна и та же реализация snapshot/delta семантики на обучении и в проде.
Установка (внутри workspace)
// package.json
"dependencies": {
"@kglozhkin/orderbook-reducer": "file:../orderbook-reducer"
}yarn installПакет не публикуется на npm — это внутренняя workspace-зависимость.
Минимальный пример
import { OrderbookReducer, classifyDelta } from '@kglozhkin/orderbook-reducer';
const r = new OrderbookReducer();
// Snapshot — полное состояние книги (административная пересинхронизация).
r.applySnapshot(
[{ price: 0.5, size: 100 }, { price: 0.49, size: 50 }],
[{ price: 0.51, size: 80 }],
/* ts */ 1779494400_000,
);
console.log(r.bestBid); // { price: 0.5, size: 100 }
console.log(r.bestAsk); // { price: 0.51, size: 80 }
console.log(r.bidCount); // 2
// Delta — точечное изменение одного уровня. size = НОВОЕ АБСОЛЮТНОЕ значение.
const outcome = r.applyDelta('bid', 0.5, 70, 1779494400_100);
// side: 'bid', price: 0.5, old_size: 100, new_size: 70,
// delta_size: -30, was_birth: false, was_death: false,
// birth_ts_died: null
const intent = classifyDelta(outcome);
// { kind: 'decrease_partial' } // (или 'place' / 'decrease_full' / 'no_change')
// Удаление уровня:
r.applyDelta('bid', 0.49, 0, 1779494400_200); // was_death=true, birth_ts_died присутствуетКонтракт API
class OrderbookReducer
| Метод / геттер | Возвращает |
|---|---|
| applyDelta(side, price, new_size, ts) | DeltaOutcome |
| applySnapshot(bids, asks, ts) | SnapshotOutcome |
| bestBid / bestAsk (геттеры) | BestLevel \| null |
| bidCount / askCount | number |
| hasLevel(side, price) | boolean |
| getLevel(side, price) | Readonly<Level> \| undefined |
| levels(side) | IterableIterator<{ price, size, birth_ts }> |
Типы
type Side = 'bid' | 'ask';
interface PriceLevel { price: number; size: number; }
interface Level { size: number; birth_ts: number; }
interface BestLevel { price: number; size: number; }
interface DeltaOutcome {
side: Side;
price: number;
old_size: number;
new_size: number;
delta_size: number; // new_size - old_size, со знаком
was_birth: boolean; // 0 → >0 (или новая цена)
was_death: boolean; // >0 → 0
birth_ts_died: number | null; // если was_death, отдаём для lifetime-метрик
}
interface SnapshotOutcome {
displaced_count: number; // цен, которые были в книге, но не пришли в snapshot
preserved_count: number; // цен с сохранённым birth_ts
created_count: number; // новых цен
}
type DeltaIntent =
| { kind: 'place' }
| { kind: 'decrease_full' }
| { kind: 'decrease_partial' }
| { kind: 'no_change' };
function classifyDelta(o: DeltaOutcome): DeltaIntent;Жёсткие инварианты
sizeвapplyDelta— НОВОЕ АБСОЛЮТНОЕ значение (REPLACE-семантика).delta_size = new_size − old_sizeвычисляется до записи. Это обязательное требование (план §3.2).size === 0удаляет уровень. В этой точке выдаётсяwas_death: true, birth_ts_died: <birth_ts уровня>.birth_tsживёт черезapplyDeltaдля уровней сsize > 0. Place долил, partial decrease —birth_tsсохраняется. Только смерть (>0 → 0) обнуляет его; перерождение (0 → >0) ставит новый.applySnapshotсохраняетbirth_tsдля цен, которые были в книге. Новые цены получаютbirth_ts = tsснапшота. Цены, отсутствующие в снапшоте, удаляются молча (без эмиссии closed_level — снапшот это административное событие).bestBid/bestAsk—nullпри пустой книге, не-Infinity/+Infinity. Это критично для gap-арифметики и band-расчётов.
Что reducer НЕ делает
- Не знает про NATS, Redis, файлы.
- Не делает trade-reconciliation (это решается над reducer'ом).
- Не хранит
lastTs,initialized— это забота вызывающего. - Не валидирует
eventIdидемпотентность. - Не фильтрует out-of-order дельты по
ts. - Не нормирует цены к тиковой сетке (
tick_size) — снаружи.
Всё перечисленное — намеренно за пределами пакета (план §3.4): reducer должен быть переиспользуем между онлайн-сервисом и офлайн-инструментами, и каждый из них добавляет нужные обвязки.
Установка для разработки
cd orderbook-reducer
yarn install
yarn build # → dist/
yarn typecheck
yarn test # 35 тестов: reducer + classify
yarn test:covПосле изменений запустите тесты во всех зависимых проектах:
cd ../book-keeper && yarn test
cd ../execution-simulator && yarn test
cd ../second-bar-engine && yarn testСвязанные документы
- Архитектура и контракт:
engine-implementation-plan.md§3 - Workspace overview:
../CLAUDE.md - Потребители:
book-keeper/,execution-simulator/,second-bar-engine/
