What Is This Project?
Polywinner is a toolkit for analyzing, learning from, and potentially replicating the trading strategies of successful traders on Polymarket (a prediction market platform).
The core insight: Some traders consistently make money through a technique called "Gabagool" (hedging arbitrage) — buying BOTH sides of a prediction market (YES and NO) when the combined cost is less than $1.00. Since one side must pay out $1.00 at resolution, this guarantees profit.
Your goal: Build an ML model that learns WHEN these successful traders choose to trade, so you can eventually automate similar decisions.
The High-Level Pipeline
YOUR ML DATA PIPELINE
STEP 1: EXTRACT TRADE DATA
pmx extract --user @wallet --out ./data/wallet
- Pulls historical trades from Polymarket's APIs
- Stores: trades, positions, market metadata (activity dataset is opt-in via --with-activity)
- Prefer windowed runs (
--start/--end) for large wallets; omit dates only when a full-history pull is intentional. - Default trade mode is strict (single-market fetches; most reliable)
- Optional alternative:
--trade-mode packed(volume-target chunking; uses cached per-market trade counts and activity-sliced preseed on cold windows; can be disabled via--no-trade-activity-preseed) - Optional verification:
--verify-tradesto sample-check and auto-promote to strict on gaps - For windowed runs, use
--condition-enum-strategy goldsky-orderbookfor fuller market coverage - Tables are Parquet by default; JSON tables are written only with
--emit-json. - Raw trades are compressed by default (
raw/trades.ndjson.zst); keep uncompressed raw with--keep-raw-uncompressed. - Auto-compact is enabled for >=2h windows; disable with
--no-compact-afteror adjust with--compact-after-hours.
STEP 1B: MERGE WINDOWED EXTRACTS (optional but recommended for ML)
pmx merge --out ./data/merged ./data/window-*
- Merges multiple windowed extracts into a single, ordered dataset (raw trades + recomputed derived tables)
- Writes new output directory; never mutates inputs
- Add
--verifyto validate sortedness, dedupe, and parity - Merge accepts
raw/trades.ndjson,raw/trades.ndjson.zst, orraw/trades.ndjson.gz. - Runbook:
docs/merge/merge-runbook.md
STEP 2: DISCOVER THE BEST TRADERS
pmx discover:gabagool --data ./data --out ./wallets.json
- Analyzes all extracted wallets
- Ranks them by "Gabagool Score" (cost efficiency, volume, profit, timing)
- Outputs top N wallets + their market IDs
STEP 3: FETCH ORDERBOOK HISTORY
pmx dome:backfill --all --data ./data --out ./clob --trade-bounded --resume
- Uses Dome API to get historical orderbook snapshots (1 QPS, slow but resumable)
- Defaults to L1-only (best bid/ask, mid, spread, depth1). Use
--full-bookto persist full depth. - Use
--max-runtime-minutesfor short validation slices that still write checkpoints + reports. - This is the "market context" when traders made decisions
- Decoupled from extraction so you can run it later or in the background
- Coverage check:
node scripts/report-clob-coverage.js --trades ./data --clob ./clob --report ./data/clob-coverage.json
STEP 4: GENERATE ML DATASET
pmx ml:generate-dataset --trades ./data --clob ./clob --out ./dataset
- Aligns trades with orderbook state at trade time
- Extracts 50 total features per decision point (49 numeric + 1 categorical)
- Default labels: EPISODE_ACTIVE, NO_TRADE (optional
--label-mode 3classfor BUY_YES/BUY_NO/NO_TRADE) - Splits into train/val/test (chronological, no data leakage)
- Writes Parquet part files by default (use --output-format ndjson for NDJSON)
STEP 5: TRAIN MODEL (Python)
python3 python/train_baseline.py ./dataset
- Loads the generated dataset
- Trains classifier (RandomForest, etc.)
- Evaluates on held-out test data
Key Concepts
The "Gabagool" Strategy
On Polymarket, you can buy YES or NO shares. If the event resolves YES, YES shares pay $1.00. If NO, NO shares pay $1.00.
The arbitrage: If you can buy YES at $0.48 and NO at $0.48, you pay $0.96 total. When the market resolves, you get $1.00 back no matter what. That's a guaranteed $0.04 profit (4.2% return).
The best practitioners:
- Find markets where combined cost < $1.00
- Trade in the final minutes before resolution (when they're most confident)
- Execute quickly on both sides
The 50 ML Features (49 Numeric + 1 Categorical)
| Category | What It Captures |
|---|---|
| Lifecycle (6) | Time to resolution features + calendar timing |
| Prices (9) | Best bid/ask, midprice, spread for YES and NO |
| Depth L1 (6) | Top-of-book liquidity |
| Depth L_n (10) | Multi-level depth, VWAP, and imbalance |
| Arb Geometry (6) | Combined cost, edge, pressure between YES/NO books |
| Dynamics (5) | Recent trade volume, momentum, time since last trade |
| Inventory (8) | Wallet's current position, P&L, cost basis |
The Labels
- Default (
episodemode): EPISODE_ACTIVE, NO_TRADE - Optional (
3classmode): BUY_YES, BUY_NO, NO_TRADE
ML Model Details (Baseline)
What the model predicts
- Default: a binary classifier for each decision point: EPISODE_ACTIVE (1), NO_TRADE (0)
- Optional: a 3-class action classifier: BUY_YES (0), BUY_NO (1), NO_TRADE (2)
- Each row is a trade-aligned snapshot with 50 total features in TS; the Python baseline loads the 49 numeric features (resolutionBucket is categorical and not used unless you encode it).
How training works
- Data loading:
python/load_dataset.pyuses pandas to load train/val/test splits - Feature prep: numeric columns only, coerced to floats, NaNs filled
- Scaling: StandardScaler (fit on train, applied to val/test)
- Class imbalance: class weights computed from training labels (balanced)
- Metrics: accuracy + macro precision/recall/F1, per-class metrics, confusion matrix
Algorithms used right now
- RandomForestClassifier (scikit-learn) [default]
- n_estimators=200, max_depth=15, min_samples_leaf=10, min_samples_split=20
- class_weight=balanced, n_jobs=-1, random_state=42
- LogisticRegression (scikit-learn) [optional]
- class_weight=balanced, max_iter=500, n_jobs=-1, random_state=42
Artifacts
- Saved to
<dataset_dir>/artifacts/:- model.pkl (model + scaler + model_type)
- feature_list.json
- metrics.json
Libraries in use
- pandas, numpy (data handling)
- scikit-learn (models, scaling, metrics)
- pyarrow (optional: Parquet read/write support)
- apache-arrow, parquet-wasm (TS Parquet output)