PolymarketClickHouse Workshops

02 Modéliser les données

Créez des tables typées pour marchés, ticks, transactions et agrégats à la minute dans ClickHouse Cloud.

Votre ordinateur
Terminal macOS : Exécutez les commandes de l’atelier dans le Terminal avec zsh ou bash.

Point de départ

.env.polymarket est chargé et vous savez pourquoi conditions et tokens ont des identifiants distincts.

Pourquoi ces tables

Les cinq requêtes suivantes lisent des fenêtres récentes sur tous les marchés observés. Les clés des tables d'événements commencent donc par l'heure, suivie du token ou de la condition servant au regroupement. Les champs connus emploient des types natifs : UInt256 pour les tokens, DateTime64 pour l'heure des événements, des décimaux exacts pour les prix et tailles, et des enums pour les valeurs bornées. Le payload source opaque reste une chaîne, car aucune requête ne le lit.

Il n'existe aucune clé de partition. Cet atelier court n'a pas de limite de rétention démontrée ; partitionner avant d'avoir une exigence de cycle de vie créerait de petites parts sans bénéfice.

Étape 1 — Créer la base et les tables brutes

Copiez le bloc complet dans SQL Console de ClickHouse Cloud et exécutez-le :

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;

Le collecteur évite les doublons avant insertion. ReplacingMergeTree constitue une seconde protection. La vue trades_clean rend les petites requêtes déterministes pendant les merges.

Étape 2 — Créer l'agrégat du point médian à la minute

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;

Cette vue matérialisée agrège uniquement les points médians des cotations. Elle exclut le prix modifié au niveau de l'ordre et celui de la dernière transaction afin que la série OHLC garde un sens unique.

Étape 3 — Vérifier chaque objet

clickhouse client \
  --host "$CLICKHOUSE_HOST" \
  --port "$CLICKHOUSE_PORT" \
  --user "$CLICKHOUSE_USER" \
  --password "$CLICKHOUSE_PASSWORD" \
  --secure \
  --query "SHOW TABLES FROM polymarket"

Les noms attendus comprennent :

market_midpoints_1m
market_midpoints_1m_mv
markets
price_ticks
trades
trades_clean

Terminé lorsque

SHOW TABLES renvoie les six objets sans serveur ClickHouse local en cours d'exécution.

Suite : démarrer le collecteur en direct.

Sur cette page

Suivre votre progression ?

Facultatif. Nous envoyons un lien par e-mail pour confirmer votre adresse ; la progression est enregistrée après son ouverture.

Utilisez votre adresse e-mail professionnelle, et non une adresse personnelle.

Le suivi de la progression exige aussi d’accepter les Conditions d’utilisation actuelles dans les Paramètres de confidentialité.

FR