Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.struct.to/llms.txt

Use this file to discover all available pages before exploring further.

Polymarket’s Crypto Up/Down markets resolve over fixed-length windows: the window’s open price is locked when it starts, the close price is locked when it ends, and the market resolves UP if close > open or DOWN otherwise. Struct exposes the resolution data behind these markets through three sources, all keyed off (symbol, variant, start_time):
NeedSource
Past window results (open, close, outcome)client.assets.getAssetHistory
Live in-window asset pricepolymarket_asset_prices (asset_price_tick)
Window open and close eventspolymarket_asset_window_updates (asset_price_window_update)
This is the resolution feed for the markets themselves. It is not OHLC candle data; the only prices on each window are the open at start_time and the close at end_time.

When to use this

  • Up/Down market dashboards: a row per (asset, variant) showing the current window’s open price, the live spot price, and whether spot is currently above or below open.
  • Resolution monitors: react when a window closes and a market resolves.
  • Past-outcome leaderboards: “BTC 1h windows over the last 24h, which closed UP vs DOWN”.

How a window resolves

Every Up/Down market is keyed by (symbol, variant, start_time):
  • start_time and end_time are Unix milliseconds, end_time - start_time matches the variant length.
  • At start_time, an update_type: "open" event fires with open_price locked in.
  • While the window is live, asset_price_tick events for the same symbol give you spot.
  • At end_time, an update_type: "close" event fires with close_price locked in. The market is now resolved: UP if close_price > open_price, DOWN otherwise.
Supported variant values across the price-history endpoint and the window-updates room: 5m, 15m, 1h, 4h, 1d, 24h.

Step 1: backfill past windows

client.assets.getAssetHistory returns past resolution windows for a (symbol, variant) pair. Each row carries the locked open_price and close_price for that window, which is what determines the UP / DOWN outcome.
import { StructClient } from "@structbuild/sdk";

const client = new StructClient({ apiKey: "sk_live_xxx" });

const { data: history } = await client.assets.getAssetHistory({
  asset_symbol: "BTC",
  variant: "1h",
});

const past = history.map((row) => ({
  start_time: row.start_time,
  end_time: row.end_time,
  open_price: row.open_price,
  close_price: row.close_price,
  outcome: row.close_price > row.open_price ? "UP" : "DOWN",
}));

Step 2: subscribe to the live spot price

The asset-prices room pushes sub-second ticks (rate: 0.005 credits per message). Use them to show where the asset is right now relative to the current window’s locked open_price.
import { StructWebSocket } from "@structbuild/sdk";

const ws = new StructWebSocket({ apiKey: "sk_live_xxx" });
await ws.connect();

await ws.subscribe("polymarket_asset_prices", {
  asset_symbols: ["BTC", "ETH"],
});

ws.on("asset_price_tick", (event) => {
  const { symbol, price, timestamp_ms } = event.data;
  updateSpot(symbol, price, timestamp_ms);
});
Each tick carries symbol, price, timestamp_ms, and published_at. Compare price against the open of the current window for the asset you’re watching to render the live UP / DOWN indicator.

Step 3: subscribe to window open and close events

Window updates fire twice per resolution window per (symbol, variant): an open event when the window starts, a close event when it ends. Rate: 0.025 credits per message. At least one of asset_symbols or timeframes is required.
await ws.subscribe("polymarket_asset_window_updates", {
  asset_symbols: ["BTC"],
  timeframes: ["1h"],
});

ws.on("asset_price_window_update", (event) => {
  const { symbol, variant, start_time, end_time, open_price, close_price, update_type } = event.data;
  if (update_type === "open") {
    openWindow(symbol, variant, start_time, end_time, open_price);
  } else {
    const outcome = close_price > open_price ? "UP" : "DOWN";
    resolveWindow(symbol, variant, start_time, close_price, outcome);
  }
});
Merge into a per-window map keyed by (symbol, variant, start_time). The open event tells you a new market just kicked off; the close event resolves it.

Putting it together

type Window = {
  symbol: string;
  variant: string;
  start_time: number;
  end_time: number;
  open_price: number;
  close_price: number | null;
  outcome: "UP" | "DOWN" | null;
};

const windows = new Map<string, Window>();
const spot = new Map<string, number>();

const key = (s: string, v: string, t: number) => `${s}:${v}:${t}`;

const past = await client.assets.getAssetHistory({ asset_symbol: "BTC", variant: "1h" });
for (const row of past.data) {
  windows.set(key("BTC", "1h", row.start_time), {
    symbol: "BTC",
    variant: "1h",
    start_time: row.start_time,
    end_time: row.end_time,
    open_price: row.open_price,
    close_price: row.close_price,
    outcome: row.close_price > row.open_price ? "UP" : "DOWN",
  });
}

ws.on("asset_price_tick", (event) => {
  spot.set(event.data.symbol, event.data.price);
  renderLiveIndicator(event.data.symbol);
});

ws.on("asset_price_window_update", (event) => {
  const { symbol, variant, start_time, end_time, open_price, close_price, update_type } = event.data;
  const k = key(symbol, variant, start_time);
  if (update_type === "open") {
    windows.set(k, { symbol, variant, start_time, end_time, open_price, close_price: null, outcome: null });
  } else {
    const outcome = close_price > open_price ? "UP" : "DOWN";
    windows.set(k, { symbol, variant, start_time, end_time, open_price, close_price, outcome });
  }
  renderWindows();
});
The live indicator for the current window is spot.get(symbol) compared against windows.get(currentKey).open_price.

Common combinations

GoalSubscribe payload
Multi-timeframe Up/Down board for one assetpolymarket_asset_window_updates, asset_symbols=["BTC"], timeframes=["5m","1h","1d"]
All assets on one timeframepolymarket_asset_window_updates, timeframes=["1h"] (no asset_symbols)
Live spot for every tracked assetpolymarket_asset_prices with no asset_symbols
Backfill long-window outcomesgetAssetHistory({ asset_symbol: "ETH", variant: "1d" })

Follow-on

For threshold-based alerts on these resolutions (price crosses, individual window outcomes), see asset-price-tick and asset-price-window-update under the alerts websocket.
Last modified on April 28, 2026