Guides
Markets
As of v1.15 the markets layer is a set of thin delegators over the venue-free core dataset(): markets.kalshi.dataset(ticker) and markets.polymarket.dataset(event_id) own all venue knowledge — station resolution, label-source routing, strike parsing, and the outcome=True binary settlement target — and forward the join to core with zero duplicated join logic. Plus discover (which station settles this contract?) and trades (per-contract timeseries). Both SDKs ship parity.
Discover — city to station
Section titled “Discover — city to station”Kalshi and Polymarket use different identifiers and different stations for the same city. Mostlyright reconciles them.
from mostlyright import discover
df = discover("new york")print(df)# city station settles_for# 0 new york NYC ["kalshi:NYC"]# 1 new york LGA [] (denylist backstop)# 2 new york JFK ["polymarket:nyc"]import { discover } from "mostlyright";
const rows = await discover("new york");console.log(rows);settles_for is a typed list of <issuer>:<id> strings:
"kalshi:NYC"— Kalshi settles NYC contracts usingstation=NYC(KNYC)."polymarket:nyc"— Polymarket settles nyc contracts using a different station (typically KLGA per Polymarket’s published methodology).[]— Denylist backstop. The station exists but no issuer settles against it.
Contract datasets — the thin delegators
Section titled “Contract datasets — the thin delegators”Point a delegator at a full market identifier and it resolves the settlement station, routes to the venue-correct label recipe, and (optionally) binarizes the day’s settlement value against the contract’s strike. There is zero duplicated join logic — each delegator resolves venue knowledge, then forwards to core dataset().
from mostlyright.markets import kalshi, polymarket
# Kalshi NHIGH/NLOW settle on the NWS CLI product → routes to label="cli".k = kalshi.dataset("KXHIGHNY-25MAY26-T79", "2025-05-26", "2025-05-26", outcome=True)print(k[["date", "cli_high_f", "label_outcome"]])# label_outcome: 1 (YES) when cli_high_f >= 79, else 0; NaN if no CLI report.
# Polymarket resolves on WU/NOAA-WRH extremes → routes to label="daily_extremes".p = polymarket.dataset("<event_id>", "2025-05-26", "2025-05-26", outcome=True)print(p[["date", "daily_extremes_tmax_c", "label_outcome"]])import { kalshi, polymarket } from "mostlyright/markets-dataset";
const k = await kalshi.dataset("KXHIGHNY-25MAY26-T79", "2025-05-26", "2025-05-26", { outcome: true,});
// Polymarket station resolution is server-side — supply the resolved// station (and the strike phrase when outcome=true).const p = await polymarket.dataset("<event_id>", "2025-05-26", "2025-05-26", { station: "KNYC", strikeText: "above 79F", outcome: true,});outcome=True — contract-shaped 0/1 targets
Section titled “outcome=True — contract-shaped 0/1 targets”outcome=True appends a single binary label_outcome column derived from the contract’s own label recipe and its parsed strike — never the other venue’s. Settlement comparison is inclusive, matching the “N° or above” contract wording:
- Kalshi
-T<strike>thresholds settle YES whenvalue >= strike— the strike degree itself is YES.-B<lo>-<hi>ranges are inclusive on both endpoints (Kalshi’s integer-degree buckets tile the degree space contiguously). Malformed tickers raiseKalshiTickerError. - Polymarket
above(YES whenobserved >= strike),below(observed <= strike), andbetween(lo <= observed <= hi, both inclusive) mirror the same rule in °C. Malformed strikes raisePolymarketStrikeError.
A day whose label value is missing (e.g. no CLI report) gets a NaN/NA outcome — never a silent 0 or 1.
The delegators forward extra core kwargs verbatim (granularity=, source=, tz_override=, qc=, features=), so everything in the Research API guide composes on top.
Negative-temperature strikes — the sign rule
Section titled “Negative-temperature strikes — the sign rule”Both strike parsers accept negative-temperature strikes, and both handle the hyphen ambiguity the same way. In a slug the hyphen is both a word-separator (between-70-...) and a numeric minus sign (above -5c). A greedy -?\d+ cannot tell them apart — it would silently turn above -5c into +5 and flip every cold contract. So a leading minus binds to the strike integer:
- Kalshi —
T-5(threshold at −5°F),B-5--3(range −5..−3°F). The parser reads-?\d+for each strike field. - Polymarket — the sign is decided from each value’s separator run: a run ending in
--(slug separator + minus) or whitespace-then--(proseabove -5c) is negative; a bare trailing hyphen after a word (between-70-...) is a slug separator and stays positive. Eachbetweenendpoint is signed independently (between -10 and -5c→ both negative).
Get the sign wrong and every sub-freezing settlement inverts, so the delegators parse it explicitly rather than trust a greedy regex.
Polymarket
Section titled “Polymarket”Polymarket’s city-to-station mapping is published in schemas/polymarket-city-stations.json (mirrored to both SDKs). Each city may use a different station per measure — paris-low settles on LFPB (Le Bourget), not the default LFPG (Charles de Gaulle):
from mostlyright.markets import POLYMARKET_CITY_STATIONS
print(POLYMARKET_CITY_STATIONS["paris"])# {# "default": "LFPG",# "low": "LFPB", # different station for the low measure# }Measure-specific routing is preserved end-to-end — collapsing to default only would silently mis-route settlement.
import { POLYMARKET_CITY_STATIONS } from "@mostlyrightmd/markets";
console.log(POLYMARKET_CITY_STATIONS.paris);Trades — per-contract timeseries
Section titled “Trades — per-contract timeseries”Pull historical trade timeseries (price + size + side + timestamp) for a single contract:
from mostlyright.markets import kalshi_trades
df = kalshi_trades("<contract-ticker>")print(df.head())# Columns: trade_id, executed_at, price_cents, size, sidefrom mostlyright.markets import polymarket_trades
df = polymarket_trades("0x123...condition-id")print(df.head())import { kalshiTrades, polymarketTrades } from "@mostlyrightmd/markets";
const k = await kalshiTrades("<contract-ticker>");const p = await polymarketTrades("0x123...");Shape parity (Phase 21 21-06)
Section titled “Shape parity (Phase 21 21-06)”Three potential divergences between Python and TS trades surfaces are locked in via parity tests:
| Shape detail | Python | TypeScript |
|---|---|---|
| Candle bucket label | end_period_ts | start (Phase 21 docs note: both label by start; legacy doc-only divergence resolved) |
| Polymarket pagination | limit=100 | limit=100 (was 500, fixed) |
| Equal-timestamp ordering | HTTP-order preserved | HTTP-order preserved (no in-SDK sort) |
Both SDKs preserve the issuer’s HTTP order — no implicit re-sorting on the trade-ID dimension. If you need a specific sort, sort downstream.
Catalog — register your own market
Section titled “Catalog — register your own market”The catalog is registry-based. To add a new issuer:
from mostlyright.markets.catalog import register_adapter, MarketAdapter
class MyExchangeAdapter(MarketAdapter): SUPPORTED_SOURCES = ["myexchange.trades"]
def fetch_trades(self, contract_id: str) -> list[dict]: ...
register_adapter("myexchange.trades", MyExchangeAdapter)The same pattern lives in TS (registerAdapter from @mostlyrightmd/markets).
Errors
Section titled “Errors”| Error | When |
|---|---|
KalshiTickerError | A Kalshi ticker doesn’t parse (bad series/date/strike) or its city isn’t in the settlement whitelist |
PolymarketStrikeError | polymarket.dataset(outcome=True) can’t parse an above/below/between <n><C|F> strike |
DataAvailabilityError | Unknown adapter / contract / source |
DeferredMarketError | Contract not yet open for settlement |
PolymarketEventError | Polymarket event not found at the resolver endpoint |
All inherit from TradewindsError — except TradewindsError catches them all.
See also
Section titled “See also”- Choosing the label — why Kalshi routes to
label="cli"and Polymarket tolabel="daily_extremes" - Settlement windows — LST + how the daily bucket bounds work
- Research API — the venue-free core the delegators forward to
- → v1.15 migration — the markets delegators + the
contract=/include_trades=deprecation - API reference:
mostlyright.markets·@mostlyrightmd/markets