02 Mô hình hóa dữ liệu
Tạo các bảng market, tick, trade và aggregate một phút có kiểu rõ ràng trong ClickHouse Cloud.
Điểm khởi đầu
.env.polymarket đã được source và bạn đã hiểu vì sao condition ID khác với token ID.
Vì sao lại là các bảng này
Năm truy vấn ở phần sau đọc các cửa sổ thời gian gần đây trên mọi thị trường được theo dõi. Do đó
key của các bảng sự kiện bắt đầu bằng một key giờ/thời gian, tiếp theo là token hoặc condition dùng
để nhóm. Các trường đã biết dùng kiểu native: UInt256 cho token ID,
DateTime64 cho thời điểm sự kiện, decimal chính xác cho giá và khối lượng, và enum cho các giá trị
sự kiện có tập hữu hạn. Payload nguồn dạng hộp đen vẫn là string vì không truy vấn nào đọc các trường của nó.
Không có partition key. Workshop ngắn ngày này không có ranh giới lưu trữ nào đã được chứng minh; thêm partition trước khi có yêu cầu về vòng đời dữ liệu sẽ tạo ra các part nhỏ mà không mang lại lợi ích gì.
Bước 1 — Tạo database và các bảng thô
Sao chép cả khối vào SQL console của ClickHouse Cloud và chạy nó:
CREATE DATABASE IF NOT EXISTS polymarket;
CREATE TABLE IF NOT EXISTS polymarket.markets
(
market_id UInt64,
condition_id FixedString(66),
token_id UInt256,
outcome LowCardinality(String),
question String,
slug String,
active Bool,
accepting_orders Bool,
volume_24h Decimal128(8),
observed_at DateTime64(3, 'UTC')
)
ENGINE = ReplacingMergeTree(observed_at)
ORDER BY (condition_id, token_id);
CREATE TABLE IF NOT EXISTS polymarket.price_ticks
(
event_id FixedString(64),
condition_id FixedString(66),
token_id UInt256,
event_at DateTime64(3, 'UTC'),
observed_at DateTime64(3, 'UTC'),
event_kind Enum8(
'book_snapshot' = 1,
'price_change' = 2,
'last_trade_price' = 3,
'best_bid_ask' = 4,
'rest_book' = 5
),
source Enum8('WEBSOCKET' = 1, 'CLOB_REST' = 2, 'FIXTURE' = 3),
price Decimal64(12),
size Decimal128(8),
side Enum8('UNKNOWN' = 0, 'BUY' = 1, 'SELL' = 2),
best_bid Decimal64(12),
best_ask Decimal64(12),
midpoint Decimal64(12),
source_hash String,
raw_payload String
)
ENGINE = MergeTree
ORDER BY (toStartOfHour(event_at), token_id, event_at, event_id);
CREATE TABLE IF NOT EXISTS polymarket.trades
(
trade_id FixedString(64),
condition_id FixedString(66),
token_id UInt256,
event_at DateTime64(3, 'UTC'),
observed_at DateTime64(3, 'UTC'),
proxy_wallet FixedString(42),
side Enum8('UNKNOWN' = 0, 'BUY' = 1, 'SELL' = 2),
price Decimal64(12),
size Decimal128(8),
outcome LowCardinality(String),
transaction_hash FixedString(66),
title String
)
ENGINE = ReplacingMergeTree(observed_at)
ORDER BY (toStartOfHour(event_at), condition_id, event_at, trade_id);
CREATE OR REPLACE VIEW polymarket.trades_clean AS
SELECT *
FROM polymarket.trades FINAL;Collector ngăn dữ liệu trùng lặp từ trước khi insert. ReplacingMergeTree là lớp bảo vệ thứ hai.
View trades_clean giúp các truy vấn nhỏ trong workshop cho kết quả tất định trong khi việc merge
vẫn đang diễn ra.
Bước 2 — Tạo aggregate midpoint một phút
CREATE TABLE IF NOT EXISTS polymarket.market_midpoints_1m
(
token_id UInt256,
minute DateTime('UTC'),
open AggregateFunction(argMin, Decimal64(12), Tuple(DateTime64(3, 'UTC'), FixedString(64))),
high AggregateFunction(max, Decimal64(12)),
low AggregateFunction(min, Decimal64(12)),
close AggregateFunction(argMax, Decimal64(12), Tuple(DateTime64(3, 'UTC'), FixedString(64))),
updates AggregateFunction(count)
)
ENGINE = AggregatingMergeTree
ORDER BY (minute, token_id);
CREATE MATERIALIZED VIEW IF NOT EXISTS polymarket.market_midpoints_1m_mv
TO polymarket.market_midpoints_1m
AS
SELECT
token_id,
toStartOfMinute(event_at) AS minute,
argMinState(midpoint, tuple(event_at, event_id)) AS open,
maxState(midpoint) AS high,
minState(midpoint) AS low,
argMaxState(midpoint, tuple(event_at, event_id)) AS close,
countState() AS updates
FROM polymarket.price_ticks
WHERE midpoint > 0
AND event_kind IN ('book_snapshot', 'price_change', 'best_bid_ask', 'rest_book')
GROUP BY token_id, minute;Materialized view này chỉ tổng hợp midpoint của quote. Nó cố ý loại bỏ giá đã thay đổi ở mức lệnh và giá giao dịch gần nhất, nhờ đó chuỗi OHLC chỉ có một ý nghĩa duy nhất.
Bước 3 — Kiểm chứng từng đối tượng
clickhouse client \
--host "$CLICKHOUSE_HOST" \
--port "$CLICKHOUSE_PORT" \
--user "$CLICKHOUSE_USER" \
--password "$CLICKHOUSE_PASSWORD" \
--secure \
--query "SHOW TABLES FROM polymarket"Các tên mong đợi bao gồm:
market_midpoints_1m
market_midpoints_1m_mv
markets
price_ticks
trades
trades_cleanHoàn thành khi
SHOW TABLES trả về cả sáu đối tượng mà không có ClickHouse server nào chạy cục bộ.
Tiếp theo: khởi động collector trực tiếp.