Overview
The Market struct represents a prediction market. It contains all the information about a market including its question, outcomes, prices, trading metadata, and exchange-specific data.
Structure
pub struct Market {
pub id: String,
pub question: String,
pub outcomes: Vec<String>,
pub close_time: Option<DateTime<Utc>>,
pub volume: f64,
pub liquidity: f64,
pub prices: HashMap<String, f64>,
pub metadata: serde_json::Value,
pub tick_size: f64,
pub description: String,
pub slug: Option<String>,
pub event_id: Option<String>,
pub condition_id: Option<String>,
pub token_ids: Option<Vec<String>>,
pub best_bid: Option<f64>,
pub best_ask: Option<f64>,
pub last_trade_price: Option<f64>,
pub spread: Option<f64>,
pub volume_24h: Option<f64>,
pub daily_avg_volume: Option<f64>,
pub open_interest: Option<f64>,
pub category: Option<String>,
pub status: Option<String>,
pub created_at: Option<DateTime<Utc>>,
pub exchange: Option<String>,
}
Fields
| Field | Type | Description |
|---|
id | String | Unique market identifier |
question | String | The prediction question |
outcomes | Vec<String> | Possible outcomes (e.g., ["Yes", "No"]) |
close_time | Option<DateTime<Utc>> | When the market closes |
volume | f64 | Total lifetime trading volume in USD |
liquidity | f64 | Current market liquidity |
prices | HashMap<String, f64> | Current price for each outcome |
metadata | serde_json::Value | Exchange-specific metadata |
tick_size | f64 | Minimum price increment |
description | String | Detailed market description |
slug | Option<String> | Market slug (Polymarket, Limitless) |
event_id | Option<String> | Event identifier (Polymarket conditionId, Kalshi event_ticker) |
condition_id | Option<String> | On-chain condition or contract ID |
token_ids | Option<Vec<String>> | CLOB token IDs for each outcome |
best_bid | Option<f64> | Best bid price |
best_ask | Option<f64> | Best ask price |
last_trade_price | Option<f64> | Last trade price |
spread | Option<f64> | Pre-computed bid-ask spread |
volume_24h | Option<f64> | 24-hour trading volume |
daily_avg_volume | Option<f64> | Daily average volume |
open_interest | Option<f64> | Open interest (outstanding contracts/positions) |
category | Option<String> | Market category or tag |
status | Option<String> | Market status string |
created_at | Option<DateTime<Utc>> | Creation timestamp |
exchange | Option<String> | Exchange identifier |
Methods
is_binary()
Check if the market is a binary market (exactly 2 outcomes):
let market = exchange.fetch_market("market-id").await?;
if market.is_binary() {
println!("This is a Yes/No market");
} else {
println!("This market has {} outcomes", market.outcomes.len());
}
is_open()
Check if the market is currently open for trading:
if market.is_open() {
println!("Market is open for trading");
} else {
println!("Market is closed");
}
The method checks:
- The
closed field in metadata (if present)
- Whether current time is before
close_time
spread()
Calculate the market spread for binary markets (computed fallback):
if let Some(spread) = market.spread() {
println!("Market spread: {:.2}%", spread * 100.0);
} else {
println!("Spread not available (non-binary market)");
}
The spread field is pre-computed by each exchange from bid/ask data when available. The spread() method computes it from outcome prices as a fallback. Returns None for non-binary markets.
get_token_ids()
Get the CLOB token IDs from market metadata (primarily for Polymarket):
let token_ids = market.get_token_ids();
// ["token-id-for-yes", "token-id-for-no"]
for (i, token_id) in token_ids.iter().enumerate() {
println!("Outcome {}: {}", market.outcomes[i], token_id);
}
get_outcome_tokens()
Get outcome-token pairs for easier handling:
let outcome_tokens = market.get_outcome_tokens();
for ot in outcome_tokens {
println!("{} -> {}", ot.outcome, ot.token_id);
}
// Yes -> 21742633143463906290569050155826241533067272736897614950488156847949938836455
// No -> 48331043336612883890938759509493159234755048973500640148014422747788308965732
Returns a Vec<OutcomeToken>:
pub struct OutcomeToken {
pub outcome: String,
pub token_id: String,
}
Examples
Fetch and Display Market
use pc_core::{Exchange, FetchMarketsParams};
use pc_exchange_polymarket::{Polymarket, PolymarketConfig};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let exchange = Polymarket::new(PolymarketConfig::new())?;
let markets = exchange
.fetch_markets(Some(FetchMarketsParams {
limit: Some(5),
active_only: true,
..Default::default()
}))
.await?;
for market in markets {
println!("ID: {}", market.id);
println!("Question: {}", market.question);
println!("Exchange: {:?}", market.exchange);
println!("Category: {:?}", market.category);
println!("Status: {:?}", market.status);
// Display outcomes and prices
for outcome in &market.outcomes {
if let Some(price) = market.prices.get(outcome) {
println!(" {}: {:.1}%", outcome, price * 100.0);
}
}
// Display spread
if let Some(spread) = market.spread {
println!("Spread: {:.2}%", spread * 100.0);
}
// Display volume metrics
println!("Volume: ${:,.0}", market.volume);
if let Some(v24) = market.volume_24h {
println!("24h Vol: ${:,.0}", v24);
}
if let Some(avg) = market.daily_avg_volume {
println!("Avg Daily: ${:,.0}", avg);
}
if let Some(oi) = market.open_interest {
println!("Open Int: ${:,.0}", oi);
}
}
Ok(())
}
Fetch Markets with Category Filter
use pc_core::{Exchange, FetchMarketsParams};
let markets = exchange
.fetch_markets(Some(FetchMarketsParams {
limit: Some(10),
active_only: true,
category: Some("crypto".to_string()),
keyword: Some("crypto".to_string()),
}))
.await?;
Working with Token IDs
// Fetch a specific market
let market = exchange.fetch_market("condition-id").await?;
// Token IDs are now directly available
if let Some(ref ids) = market.token_ids {
for (i, token_id) in ids.iter().enumerate() {
println!("{}: {}", market.outcomes[i], token_id);
}
}
// Or use the legacy method via metadata
let tokens = market.get_outcome_tokens();
let yes_token = tokens.iter()
.find(|t| t.outcome == "Yes")
.expect("Yes outcome not found");
println!("Yes token ID: {}", yes_token.token_id);
Filter Markets by Criteria
let markets = exchange.fetch_markets(None).await?;
// Filter for binary markets with high volume
let high_volume_binary: Vec<_> = markets
.iter()
.filter(|m| m.is_binary() && m.volume > 100_000.0)
.collect();
// Filter for tight spreads
let tight_spread: Vec<_> = markets
.iter()
.filter(|m| m.spread.map_or(false, |s| s < 0.03))
.collect();
// Filter for open markets closing soon
let closing_soon: Vec<_> = markets
.iter()
.filter(|m| {
if let Some(close_time) = m.close_time {
let hours_until_close = (close_time - Utc::now()).num_hours();
m.is_open() && hours_until_close < 24 && hours_until_close > 0
} else {
false
}
})
.collect();
The metadata field contains exchange-specific data as JSON. Common fields include:
Polymarket
// Access Polymarket-specific metadata
if let Some(obj) = market.metadata.as_object() {
// CLOB token IDs
if let Some(tokens) = obj.get("clobTokenIds") {
println!("Token IDs: {:?}", tokens);
}
// Condition ID
if let Some(condition_id) = obj.get("conditionId") {
println!("Condition ID: {}", condition_id);
}
// Market slug
if let Some(slug) = obj.get("slug") {
println!("Slug: {}", slug);
}
}
Kalshi
// Access Kalshi-specific metadata
if let Some(obj) = market.metadata.as_object() {
// Ticker
if let Some(ticker) = obj.get("ticker") {
println!("Ticker: {}", ticker);
}
// Event ID
if let Some(event_id) = obj.get("eventId") {
println!("Event ID: {}", event_id);
}
}
Serialization
Markets can be serialized/deserialized using serde. Optional fields with None values are omitted from JSON output:
use serde_json;
// Serialize to JSON
let json = serde_json::to_string_pretty(&market)?;
println!("{}", json);
// Deserialize from JSON
let market: Market = serde_json::from_str(&json)?;
Next Steps