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_history in crates/wx-market/src/lib.rs

Public API

PricePoint

pub struct PricePoint { pub t: i64, pub p: f64 }
  • #[derive(Debug, Clone, Copy, Deserialize)] — intentionally Copy
  • t: 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>) -> Self

history — 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 market param must be the YES-token CLOB token ID (clobTokenIds[0] from the Gamma API), NOT the condition ID. Using the wrong ID returns an empty history array silently.

fidelity parameter

fidelity is resolution in minutes. fidelity=1 gives minute-level bars, fidelity=60 gives hourly. For a full-lifetime curve on a 24h market, fidelity=1 with interval=max is 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.rs but implemented inline rather than as an extracted helper — this is a conscious duplication to avoid an unrelated refactor scope-creep

Error Mapping

Failure modeError variant
HTTP send / network errorwx_core::error::Error::Http(String)
JSON deserialisation failurewx_core::error::Error::Parse(String)

Testing

  • Uses wiremock (already a dev-dep in wx-market)
  • TDD: test written first, confirmed failing red, then implementation added
  • Single test parses_history_points covers: mock server setup, history() dispatch, response parsing, field accuracy (t exact, p within 1e-12)

Design Notes

  • ClobPriceHistory is read-only by construction — no write methods, no mutable state beyond the inner reqwest::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
  • HistResp is private — callers only ever see Vec<PricePoint>
  • history field on HistResp has #[serde(default)] so a missing key returns [] rather than a parse error (CLOB API sometimes omits the field for very new markets)