Розробка сховища історичних даних ордерів

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка сховища історичних даних ордерів
Складний
~1-2 тижні
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

Розробка сховища історичних даних ордерів

Історія ордерів — це не просто архів. Це первинне джерело даних для аналізу ефективності виконання, бектестингу, розрахунку комісій, податкової звітності та аудиту торговельних стратегій. Правильно спроектоване сховище повинне забезпечувати високу швидкість запису в real-time та аналітичні запити за багатовимірними зрізами за роками даних.

Структура даних ордера

Ордер у торговельній системі — це не просто запис "купив 1 BTC по 50000". Повна модель включає кілька типів подій:

Order Lifecycle Events:

  • ORDER_CREATED — первинне створення з параметрами
  • ORDER_ACCEPTED — біржа підтвердила отримання
  • ORDER_PARTIALLY_FILLED — часткове виконання
  • ORDER_FILLED — повне виконання
  • ORDER_CANCELLED — відмена (з причиною)
  • ORDER_REJECTED — відмова біржі (з кодом помилки)
  • ORDER_EXPIRED — вичерпав таймаут

Зберігання цих подій окремо (event sourcing) забезпечує повну воспроізводимість: ви завжди можете відновити стан будь-якого ордера в будь-який момент часу.

Схема бази даних

Для часових рядів ордерів оптимально використовувати TimescaleDB (розширення PostgreSQL) або ClickHouse.

TimescaleDB — хороший вибір, якщо вже використовуєте PostgreSQL. Автоматично партиціонує таблиці за часом (hypertables), підтримує безперервні агрегації (continuous aggregates) та політики компресії.

CREATE TABLE order_events (
    event_id        UUID DEFAULT gen_random_uuid(),
    event_time      TIMESTAMPTZ NOT NULL,
    order_id        UUID NOT NULL,
    exchange        VARCHAR(32) NOT NULL,
    symbol          VARCHAR(32) NOT NULL,
    event_type      VARCHAR(32) NOT NULL,
    side            VARCHAR(8),
    order_type      VARCHAR(16),
    price           NUMERIC(24, 8),
    quantity        NUMERIC(24, 8),
    filled_qty      NUMERIC(24, 8),
    avg_fill_price  NUMERIC(24, 8),
    commission      NUMERIC(24, 8),
    commission_asset VARCHAR(16),
    client_order_id VARCHAR(64),
    strategy_id     VARCHAR(64),
    metadata        JSONB
);

SELECT create_hypertable('order_events', 'event_time',
    chunk_time_interval => INTERVAL '1 day');

Індекси для типових паттернів запитів:

CREATE INDEX ON order_events (order_id, event_time DESC);
CREATE INDEX ON order_events (exchange, symbol, event_time DESC);
CREATE INDEX ON order_events (strategy_id, event_time DESC);
CREATE INDEX ON order_events USING GIN (metadata);

Паттерни запитів та оптимізація

Відновлення стану ордера — часта операція. Замість повторного обчислення з подій щоразу, підтримуємо матеріалізовану таблицю orders з поточним станом:

CREATE TABLE orders (
    order_id        UUID PRIMARY KEY,
    exchange        VARCHAR(32) NOT NULL,
    symbol          VARCHAR(32) NOT NULL,
    status          VARCHAR(32) NOT NULL,
    side            VARCHAR(8) NOT NULL,
    order_type      VARCHAR(16) NOT NULL,
    price           NUMERIC(24, 8),
    quantity        NUMERIC(24, 8),
    filled_qty      NUMERIC(24, 8) DEFAULT 0,
    avg_fill_price  NUMERIC(24, 8),
    total_commission NUMERIC(24, 8) DEFAULT 0,
    strategy_id     VARCHAR(64),
    created_at      TIMESTAMPTZ NOT NULL,
    updated_at      TIMESTAMPTZ NOT NULL
);

Оновлення цієї таблиці відбувається при кожній новій події через тригер або логіку на стороні додатку.

Аналітичні запити — зазвичай це агрегації за стратегією, інструментом, періодом:

-- P&L за стратегіями за період
SELECT
    strategy_id,
    symbol,
    SUM(CASE WHEN side = 'BUY' THEN -filled_qty * avg_fill_price ELSE filled_qty * avg_fill_price END) as realized_pnl,
    SUM(total_commission) as total_fees,
    COUNT(*) as order_count
FROM orders
WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'
  AND status = 'FILLED'
GROUP BY strategy_id, symbol
ORDER BY realized_pnl DESC;

TimescaleDB continuous aggregates дозволяють предвичислити ці агрегації та оновлювати їх інкрементально:

CREATE MATERIALIZED VIEW orders_daily_summary
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 day', created_at) AS day,
    exchange,
    symbol,
    strategy_id,
    COUNT(*) AS order_count,
    SUM(filled_qty * avg_fill_price) AS volume,
    SUM(total_commission) AS fees
FROM orders
WHERE status = 'FILLED'
GROUP BY 1, 2, 3, 4;

Зберігання fills окремо

Для детального аналізу якості виконання критично зберігати individual fills (часткові виконання) окремо від ордерів:

CREATE TABLE order_fills (
    fill_id         UUID DEFAULT gen_random_uuid(),
    fill_time       TIMESTAMPTZ NOT NULL,
    order_id        UUID NOT NULL REFERENCES orders(order_id),
    exchange        VARCHAR(32) NOT NULL,
    symbol          VARCHAR(32) NOT NULL,
    price           NUMERIC(24, 8) NOT NULL,
    quantity        NUMERIC(24, 8) NOT NULL,
    commission      NUMERIC(24, 8),
    commission_asset VARCHAR(16),
    is_maker        BOOLEAN,
    trade_id        VARCHAR(64)
);

SELECT create_hypertable('order_fills', 'fill_time',
    chunk_time_interval => INTERVAL '1 day');

Це дозволяє розраховувати VWAP виконання, порівнювати з mid-price на момент виконання (market impact), аналізувати maker/taker ratio за стратегією.

Політики утримання та архівування

Гарячі дані (останні 30 днів) зберігаються без компресії для максимальної швидкості запису та читання. Старіші дані компресуються:

ALTER TABLE order_events SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'exchange, symbol',
    timescaledb.compress_orderby = 'event_time DESC'
);

SELECT add_compression_policy('order_events',
    INTERVAL '30 days');

Компресія TimescaleDB забезпечує 10–20x зменшення розміру для часових рядів з повторюваними значеннями. На практиці таблиця order_events об'ємом 100GB стискається до 5–10GB.

Дані старше 2 років можна експортувати в Parquet-файли на S3 за допомогою pg_parquet або власного ETL, збереживши можливість історичного аналізу через Athena або ClickHouse.

Pipeline ingestion

Високочастотний запис вимагає batching. Замість INSERT на кожну подію:

COPY для bulk inserts — у 10–50x швидше ніж окремі INSERTs. Накопичуємо події в пам'яті (100ms або 1000 подій, що станеться раніше) і записуємо одним COPY.

Unlogged tables для проміжного буфера — WAL не пишеться, швидкість запису значно вища. Після агрегації дані переносяться в основну logged таблицю.

Connection pooling через PgBouncer — торговельні системи відкривають багато коротких з'єднань. PgBouncer у transaction mode дозволяє обслуговувати тисячі клієнтів через невеликий пул реальних з'єднань PostgreSQL.

Моніторинг та алерти

Ключові метрики для моніторингу сховища:

Метрика Норма Алерт
Write latency (p99) < 10ms > 50ms
Query latency (p99) < 100ms > 500ms
Replication lag < 1s > 10s
Disk usage growth Передбачувано Аномальний ріст
Failed inserts 0 Будь-які

Втрата ордерів — це завжди критичний інцидент. Система повинна мати механізм reconciliation: періодично порівнювати локальну історію з даними біржі через REST API та заповнювати прогалини.

Репліація та відмовостійкість

Production-сховище працює в режимі streaming replication PostgreSQL: primary для записів, replica для аналітичних запитів. При падінні primary — failover через Patroni або PgBouncer з автоматичним переключенням. RPO (Recovery Point Objective) при правильній настройці synchronous_commit — нульовий.