Read-only Polymarket data-API trades client (TradesClient) added to the wx-market crate for fetching per-token trade history with automatic pagination and retry.

Location

  • File: crates/wx-market/src/trades.rs
  • Module: pub mod trades in crates/wx-market/src/lib.rs
  • Branch: research/intraday-drift

Public API

Side

pub enum Side { Buy, Sell }

#[derive(Debug, Clone, Copy, PartialEq, Eq)]

Deserialized from the "side" field using eq_ignore_ascii_case("SELL") — anything that is not "SELL" (case-insensitive) maps to Buy.

Trade

pub struct Trade {
    pub t:     i64,
    pub price: f64,
    pub size:  f64,
    pub side:  Side,
}

t is a Unix timestamp in seconds. price and size are floating-point; size is quantity in shares, not dollars.

TradesClient

Constructor takes any Into<String> base URL — the same pattern used by ClobPriceHistory, which enables wiremock injection in tests:

pub fn new(base: impl Into<String>) -> Self

page — single page fetch

pub async fn page(
    &self,
    asset: &str,
    limit: u32,
    offset: u32,
) -> Result<Vec<Trade>>

GET {base}/trades?asset=<asset>&limit=<limit>&offset=<offset>

Retries up to 3 times with linear backoff: 200ms * attempt (200ms, 400ms, 600ms).

all_by_asset — full paginated fetch

pub async fn all_by_asset(&self, asset: &str) -> Result<Vec<Trade>>

Paginates automatically with PAGE = 500 items per request. Stops when either:

  • A partial page is returned (n < PAGE), indicating the last page, or
  • The accumulated count reaches MAX_TRADES_PER_TOKEN = 20_000

Endpoint & Response Shape

  • Base URL (production): https://data-api.polymarket.com
  • Path: /trades
  • Key query params: asset = YES-token CLOB token ID, limit, offset
  • Response JSON: a JSON array of trade objects

Different base URL from CLOB

The trades endpoint lives at data-api.polymarket.com, NOT clob.polymarket.com. The price-history endpoint (ClobPriceHistory) uses the CLOB base. Mixing them causes 404s.

asset param

Like ClobPriceHistory, the asset param must be the YES-token CLOB token ID (clobTokenIds[0] from the Gamma API), not the condition ID.

Internal: RawTrade and de_flex_i64

RawTrade is a private deserialization struct that feeds into Trade. The timestamp field uses a custom deserializer de_flex_i64 that accepts the value as either a JSON number or a JSON string:

fn de_flex_i64<'de, D: Deserializer<'de>>(d: D) -> std::result::Result<i64, D::Error>

This handles observed (and potential future) API drift where the t field is emitted as "1234567890" (string) instead of 1234567890 (number). All other RawTrade fields are straightforwardly typed.

Why de_flex_i64?

The Polymarket data-API has returned timestamps as strings in some responses. Rather than crashing at the deserialize step, the flex deserializer future-proofs the client against silent API format changes.

Retry Behaviour

Same pattern as ClobPriceHistory / clob.rs:

  • Up to MAX_RETRIES = 3 attempts
  • Linear backoff: 200ms * attempt (200, 400, 600ms)
  • On HTTP send error → Error::Http(String)
  • On JSON deserialisation failure → Error::Parse(String)

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 MockServer — same dev-dep already present in wx-market
  • Test confirms mock server wiring, response parsing, Side deserialization, de_flex_i64 flexibility, and field-level accuracy
  • All tests passing

Pagination Constants

ConstantValuePurpose
PAGE500Items per request
MAX_TRADES_PER_TOKEN20 000Hard ceiling to prevent runaway fetches

Design Notes

  • TradesClient is read-only — no write methods, no mutable state beyond the inner reqwest::Client
  • Constructor pattern (new(base)) matches ClobPriceHistory exactly — both clients can be constructed for the same wiremock server in integration tests
  • RawTrade is private — callers only ever see Vec<Trade>
  • MAX_TRADES_PER_TOKEN cap prevents a runaway loop on a heavily-traded token; call page() directly if you need more control