Розробка системи зберігання tick-даних
Tick-дані — це кожна окрема угода на біржі: час, ціна, обсяг, сторона (maker/taker). На відміну від OHLCV-свічок, tick-дані дозволяють відновити повну картину активності ринку в будь-який момент. Це вимагає значно більше місця для зберігання та спеціалізованої інфраструктури.
Обсяги даних
Щоб розуміти масштаб завдання: Binance на BTC/USDT генерує приблизно 50,000–200,000 трейдів на день. На всіх парах на всіх біржах — сотні мільйонів записів щодня. Рік даних — десятки мільярдів рядків. Звичайний PostgreSQL не справляється без спеціалізованих рішень.
Технології зберігання
ClickHouse — колоночна СУБД від Yandex, оптимізована для аналітичних запитів на великих обсягах. Стискає часові ряди в 5–20 разів краще за PostgreSQL, виконує агрегації по мільярдам рядків за секунди.
CREATE TABLE trades (
exchange LowCardinality(String),
symbol LowCardinality(String),
trade_id String,
timestamp DateTime64(3, 'UTC'),
price Decimal(24, 8),
quantity Decimal(24, 8),
side LowCardinality(String),
is_maker Bool
)
ENGINE = MergeTree()
PARTITION BY (exchange, toYYYYMM(timestamp))
ORDER BY (exchange, symbol, timestamp)
SETTINGS index_granularity = 8192;
LowCardinality для рядків з кількома унікальними значеннями (exchange, symbol, side) — автоматичне dictionary encoding забезпечує значну економію місця.
TimescaleDB — якщо вже використовуєте PostgreSQL та обсяги помірні (< 1 млрд рядків). Підтримує hypertables з автоматичним партиціонуванням за часом, compression policies.
Arctic (Python library, на MongoDB) — спеціалізоване рішення для фінансових часових рядів. Підтримує tick-data, OHLCV, довільні DataFrames з версіюванням.
Схема для ClickHouse
Для максимальної продуктивності запису використовуємо буферну таблицю:
-- Буфер: накопичує дані в пам'яті, скидає кожні 10 сек або 1M рядків
CREATE TABLE trades_buffer AS trades
ENGINE = Buffer(currentDatabase(), 'trades', 16, 10, 100, 10000, 1000000, 10000000, 100000000);
-- Записуємо в буфер, читаємо з основної таблиці
INSERT INTO trades_buffer VALUES (...);
SELECT * FROM trades WHERE ...;
Запити на tick-дані
Агрегування tick-даних у OHLCV на льоту:
SELECT
toStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS candle_time,
argMin(price, timestamp) AS open,
max(price) AS high,
min(price) AS low,
argMax(price, timestamp) AS close,
sum(quantity) AS volume,
count() AS trade_count
FROM trades
WHERE exchange = 'binance'
AND symbol = 'BTC/USDT'
AND timestamp BETWEEN '2024-01-01' AND '2024-01-02'
GROUP BY candle_time
ORDER BY candle_time;
На ClickHouse цей запит по 50M рядків виконується за 1–3 секунди.
Pipeline ingestion
import asyncio
from collections import deque
class TickDataIngester:
BATCH_SIZE = 10000
FLUSH_INTERVAL = 5.0 # секунди
def __init__(self, clickhouse_client):
self.buffer = deque()
self.client = clickhouse_client
async def on_trade(self, trade: NormalizedTrade):
self.buffer.append(trade)
if len(self.buffer) >= self.BATCH_SIZE:
await self.flush()
async def flush(self):
if not self.buffer:
return
batch = [self.buffer.popleft() for _ in range(min(self.BATCH_SIZE, len(self.buffer)))]
await self.client.insert('trades_buffer', batch)
async def flush_loop(self):
while True:
await asyncio.sleep(self.FLUSH_INTERVAL)
await self.flush()
Компресія та утримання
ClickHouse стискає дані автоматично. Крім того можна включити cold storage через TTL:
ALTER TABLE trades
MODIFY TTL timestamp + INTERVAL 1 YEAR TO DISK 'cold_storage';
Дані старше року автоматично переносяться на дешевше сховище (S3-сумісне через ClickHouse object storage).
Бэкфилл історичних даних
Для заповнення історичних даних використовуємо публічні API бірж. Binance, наприклад, надає trade history через /api/v3/aggTrades з пагінацією за fromId. Паралельний бэкфилл за часовими діапазонами з rate limiting дозволяє завантажити роки даних за кілька годин.







