Read-only CLOB price-history client (ClobPriceHistory) added to the wx-market crate as a permanent keeper component used by the research pipeline and future live trader to pull per-market price curves.
Location
- File:
crates/wx-market/src/prices_history.rs - Module:
pub mod prices_historyincrates/wx-market/src/lib.rs
Public API
PricePoint
pub struct PricePoint { pub t: i64, pub p: f64 }#[derive(Debug, Clone, Copy, Deserialize)]— intentionallyCopyt: unix seconds,p: price in[0, 1]
ClobPriceHistory
Constructor takes any Into<String> base URL (allows wiremock in tests):
pub fn new(base: impl Into<String>) -> Selfhistory — interval-capped query
pub async fn history(
&self,
token_id: &str,
interval: &str,
fidelity: u32,
) -> Result<Vec<PricePoint>>Builds: GET <base>/prices-history?market=<token_id>&interval=<interval>&fidelity=<fidelity>
interval=max is the canonical value; the Polymarket CLOB caps this at roughly 410 data points.
history_window — unix-timestamp windowed query
pub async fn history_window(
&self,
token_id: &str,
start_ts: i64,
end_ts: i64,
fidelity: u32,
) -> Result<Vec<PricePoint>>Builds: GET <base>/prices-history?market=<token_id>&startTs=<start_ts>&endTs=<end_ts>&fidelity=<fidelity>
Use this when you need a specific window (e.g., the open-to-close span of a single market day).
Endpoint & Response Shape
- Base URL (production):
https://clob.polymarket.com - Path:
/prices-history - Key query params:
market= YES-token CLOB token ID (not condition ID),fidelity= resolution in minutes - Response JSON:
{"history":[{"t":<unix_secs>,"p":<price_0_to_1>}]}
Token ID vs Condition ID
The
marketparam must be the YES-token CLOB token ID (clobTokenIds[0]from the Gamma API), NOT the condition ID. Using the wrong ID returns an emptyhistoryarray silently.
fidelityparameter
fidelityis resolution in minutes.fidelity=1gives minute-level bars,fidelity=60gives hourly. For a full-lifetime curve on a 24h market,fidelity=1withinterval=maxis typical.
Retry Behaviour
Internal private method get(&self, url) -> Result<Vec<PricePoint>>:
- 3 retries (
MAX_RETRIES = 3) - Exponential-ish backoff:
(attempt + 1) * 200ms(200ms, 400ms, 600ms) - Mirrors the retry pattern in
clob.rsbut implemented inline rather than as an extracted helper — this is a conscious duplication to avoid an unrelated refactor scope-creep
Error Mapping
| Failure mode | Error variant |
|---|---|
| HTTP send / network error | wx_core::error::Error::Http(String) |
| JSON deserialisation failure | wx_core::error::Error::Parse(String) |
Testing
- Uses
wiremock(already a dev-dep inwx-market) - TDD: test written first, confirmed failing red, then implementation added
- Single test
parses_history_pointscovers: mock server setup,history()dispatch, response parsing, field accuracy (texact,pwithin1e-12)
Data Flow
graph LR Caller["Research pipeline\nor live trader"] -->|"history(token_id, interval, fidelity)"| CPH["ClobPriceHistory"] Caller -->|"history_window(token_id, start, end, fidelity)"| CPH CPH -->|"GET /prices-history?market=..."| CLOB["clob.polymarket.com"] CLOB -->|"{\"history\":[{t,p}]}"| CPH CPH -->|"Vec<PricePoint>"| Caller CPH -->|"retry 3x / 200ms base"| CLOB style CPH fill:#264653,stroke:#2a9d8f,color:#fff style CLOB fill:#2d2d2d,stroke:#888,color:#fff
Design Notes
ClobPriceHistoryis read-only by construction — no write methods, no mutable state beyond the innerreqwest::Client- The struct holds its own
reqwest::Client(not shared) — acceptable for a background research process; if hot-path reuse is needed later, switch to a shared client passed in at construction HistRespis private — callers only ever seeVec<PricePoint>historyfield onHistResphas#[serde(default)]so a missing key returns[]rather than a parse error (CLOB API sometimes omits the field for very new markets)
Related
- weather-bet — project overview
- polymarket-weather-market-structure — CLOB token IDs, market structure facts
- weather-bet-supabase-migration — storage layer that will persist pulled price curves