Skip to main content

polyoxide_clob/api/
markets.rs

1use polyoxide_core::QueryBuilder;
2use reqwest::Client;
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7use crate::{
8    request::{AuthMode, Request},
9    types::OrderSide,
10};
11
12/// Markets namespace for market-related operations
13#[derive(Clone)]
14pub struct Markets {
15    pub(crate) client: Client,
16    pub(crate) base_url: Url,
17    pub(crate) chain_id: u64,
18}
19
20impl Markets {
21    /// Get a market by condition ID
22    pub fn get(&self, condition_id: impl Into<String>) -> Request<Market> {
23        Request::get(
24            self.client.clone(),
25            self.base_url.clone(),
26            format!("/markets/{}", urlencoding::encode(&condition_id.into())),
27            AuthMode::None,
28            self.chain_id,
29        )
30    }
31
32    pub fn get_by_token_ids(
33        &self,
34        token_ids: impl Into<Vec<String>>,
35    ) -> Request<ListMarketsResponse> {
36        Request::get(
37            self.client.clone(),
38            self.base_url.clone(),
39            "/markets",
40            AuthMode::None,
41            self.chain_id,
42        )
43        .query_many("clob_token_ids", token_ids.into())
44    }
45
46    /// List all markets
47    pub fn list(&self) -> Request<ListMarketsResponse> {
48        Request::get(
49            self.client.clone(),
50            self.base_url.clone(),
51            "/markets",
52            AuthMode::None,
53            self.chain_id,
54        )
55    }
56
57    /// Get order book for a token
58    pub fn order_book(&self, token_id: impl Into<String>) -> Request<OrderBook> {
59        Request::get(
60            self.client.clone(),
61            self.base_url.clone(),
62            "/book",
63            AuthMode::None,
64            self.chain_id,
65        )
66        .query("token_id", token_id.into())
67    }
68
69    /// Get price for a token and side
70    pub fn price(&self, token_id: impl Into<String>, side: OrderSide) -> Request<PriceResponse> {
71        Request::get(
72            self.client.clone(),
73            self.base_url.clone(),
74            "/price",
75            AuthMode::None,
76            self.chain_id,
77        )
78        .query("token_id", token_id.into())
79        .query("side", side.to_string())
80    }
81
82    /// Get midpoint price for a token
83    pub fn midpoint(&self, token_id: impl Into<String>) -> Request<MidpointResponse> {
84        Request::get(
85            self.client.clone(),
86            self.base_url.clone(),
87            "/midpoint",
88            AuthMode::None,
89            self.chain_id,
90        )
91        .query("token_id", token_id.into())
92    }
93
94    /// Get historical prices for a token
95    pub fn prices_history(&self, token_id: impl Into<String>) -> Request<PricesHistoryResponse> {
96        Request::get(
97            self.client.clone(),
98            self.base_url.clone(),
99            "/prices-history",
100            AuthMode::None,
101            self.chain_id,
102        )
103        .query("market", token_id.into())
104    }
105
106    /// Get neg_risk status for a token
107    pub fn neg_risk(&self, token_id: impl Into<String>) -> Request<NegRiskResponse> {
108        Request::get(
109            self.client.clone(),
110            self.base_url.clone(),
111            "/neg-risk".to_string(),
112            AuthMode::None,
113            self.chain_id,
114        )
115        .query("token_id", token_id.into())
116    }
117
118    /// Get tick size for a token
119    pub fn tick_size(&self, token_id: impl Into<String>) -> Request<TickSizeResponse> {
120        Request::get(
121            self.client.clone(),
122            self.base_url.clone(),
123            "/tick-size".to_string(),
124            AuthMode::None,
125            self.chain_id,
126        )
127        .query("token_id", token_id.into())
128    }
129}
130
131/// Market information
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct Market {
134    pub condition_id: String,
135    pub question_id: String,
136    pub tokens: Vec<MarketToken>,
137    pub rewards: Option<serde_json::Value>,
138    pub minimum_order_size: f64,
139    pub minimum_tick_size: f64,
140    pub description: String,
141    pub category: Option<String>,
142    pub end_date_iso: Option<String>,
143    pub question: String,
144    pub active: bool,
145    pub closed: bool,
146    pub archived: bool,
147    pub neg_risk: Option<bool>,
148    pub neg_risk_market_id: Option<String>,
149    pub enable_order_book: Option<bool>,
150}
151
152/// Markets list response
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ListMarketsResponse {
155    pub data: Vec<Market>,
156    pub next_cursor: Option<String>,
157}
158
159/// Market token (outcome)
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct MarketToken {
162    pub token_id: Option<String>,
163    pub outcome: String,
164    pub price: Option<f64>,
165    pub winner: Option<bool>,
166}
167
168/// Order book level (price and size)
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct OrderLevel {
171    #[serde(with = "rust_decimal::serde::str")]
172    pub price: Decimal,
173    #[serde(with = "rust_decimal::serde::str")]
174    pub size: Decimal,
175}
176
177/// Order book data
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct OrderBook {
180    pub market: String,
181    pub asset_id: String,
182    pub bids: Vec<OrderLevel>,
183    pub asks: Vec<OrderLevel>,
184    pub timestamp: String,
185    pub hash: String,
186}
187
188/// Price response
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct PriceResponse {
191    pub price: String,
192}
193
194/// Midpoint price response
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct MidpointResponse {
197    pub mid: String,
198}
199
200/// A single point in the price history timeseries
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct PriceHistoryPoint {
203    /// Unix timestamp (seconds)
204    #[serde(rename = "t")]
205    pub timestamp: i64,
206    /// Price at this point in time
207    #[serde(rename = "p")]
208    pub price: f64,
209}
210
211/// Response from the prices-history endpoint
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct PricesHistoryResponse {
214    pub history: Vec<PriceHistoryPoint>,
215}
216
217/// Response from the neg-risk endpoint
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct NegRiskResponse {
220    pub neg_risk: bool,
221}
222
223/// Response from the tick-size endpoint
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct TickSizeResponse {
226    #[serde(deserialize_with = "deserialize_tick_size")]
227    pub minimum_tick_size: String,
228}
229
230fn deserialize_tick_size<'de, D>(deserializer: D) -> Result<String, D::Error>
231where
232    D: serde::Deserializer<'de>,
233{
234    use serde::Deserialize;
235    let v = serde_json::Value::deserialize(deserializer)?;
236    match v {
237        serde_json::Value::String(s) => Ok(s),
238        serde_json::Value::Number(n) => Ok(n.to_string()),
239        _ => Err(serde::de::Error::custom(
240            "expected string or number for tick size",
241        )),
242    }
243}