1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4pub mod order_action {
9 pub const BUY: &str = "ORDER_ACTION_BUY";
10 pub const SELL: &str = "ORDER_ACTION_SELL";
11}
12
13pub mod order_type {
14 pub const LIMIT: &str = "ORDER_TYPE_LIMIT";
15}
16
17pub mod tif {
18 pub const GTC: &str = "TIME_IN_FORCE_GOOD_TILL_CANCEL";
19 pub const GTD: &str = "TIME_IN_FORCE_GOOD_TILL_DATE";
20 pub const FAK: &str = "TIME_IN_FORCE_IMMEDIATE_OR_CANCEL";
21 pub const FOK: &str = "TIME_IN_FORCE_FILL_OR_KILL";
22}
23
24pub mod outcome {
25 pub const LONG: &str = "LONG";
26 pub const SHORT: &str = "SHORT";
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[non_exhaustive]
36pub enum OrderAction {
37 #[serde(rename = "ORDER_ACTION_BUY")]
38 Buy,
39 #[serde(rename = "ORDER_ACTION_SELL")]
40 Sell,
41}
42
43impl fmt::Display for OrderAction {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 Self::Buy => f.write_str("ORDER_ACTION_BUY"),
47 Self::Sell => f.write_str("ORDER_ACTION_SELL"),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
54#[non_exhaustive]
55pub enum OrderSide {
56 #[serde(rename = "LONG")]
57 Long,
58 #[serde(rename = "SHORT")]
59 Short,
60}
61
62impl fmt::Display for OrderSide {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Self::Long => f.write_str("LONG"),
66 Self::Short => f.write_str("SHORT"),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73#[non_exhaustive]
74pub enum OrderType {
75 #[serde(rename = "ORDER_TYPE_LIMIT")]
76 Limit,
77}
78
79impl fmt::Display for OrderType {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.write_str("ORDER_TYPE_LIMIT")
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
87#[non_exhaustive]
88pub enum TimeInForce {
89 #[serde(rename = "TIME_IN_FORCE_GOOD_TILL_CANCEL")]
91 GoodTillCancel,
92 #[serde(rename = "TIME_IN_FORCE_GOOD_TILL_DATE")]
94 GoodTillDate,
95 #[serde(rename = "TIME_IN_FORCE_IMMEDIATE_OR_CANCEL")]
97 ImmediateOrCancel,
98 #[serde(rename = "TIME_IN_FORCE_FILL_OR_KILL")]
100 FillOrKill,
101}
102
103impl fmt::Display for TimeInForce {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 let s = match self {
106 Self::GoodTillCancel => "TIME_IN_FORCE_GOOD_TILL_CANCEL",
107 Self::GoodTillDate => "TIME_IN_FORCE_GOOD_TILL_DATE",
108 Self::ImmediateOrCancel => "TIME_IN_FORCE_IMMEDIATE_OR_CANCEL",
109 Self::FillOrKill => "TIME_IN_FORCE_FILL_OR_KILL",
110 };
111 f.write_str(s)
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
121#[serde(rename_all = "lowercase")]
122#[non_exhaustive]
123pub enum MarketStatus {
124 Open,
125 Closed,
126 Resolved,
127 #[serde(other)]
129 Unknown,
130}
131
132impl MarketStatus {
133 pub fn from_api_str(raw: &str) -> Self {
138 match raw.trim().to_ascii_lowercase().as_str() {
139 "open" => Self::Open,
140 "closed" => Self::Closed,
141 "resolved" => Self::Resolved,
142 _ => Self::Unknown,
143 }
144 }
145}
146
147impl std::str::FromStr for MarketStatus {
148 type Err = std::convert::Infallible;
149
150 fn from_str(s: &str) -> Result<Self, Self::Err> {
151 Ok(Self::from_api_str(s))
152 }
153}
154
155#[derive(Debug, Clone, Deserialize)]
160pub struct HealthResponse {
161 #[serde(default)]
162 pub status: String,
163 #[serde(default)]
164 pub timestamp: String,
165}
166
167#[derive(Debug, Clone, Deserialize)]
168pub struct MarketsResponse {
169 #[serde(default)]
170 pub markets: Vec<UsMarket>,
171}
172
173#[derive(Debug, Clone, Deserialize)]
174pub struct UsMarket {
175 #[serde(default)]
176 pub id: String,
177 #[serde(default)]
178 pub slug: String,
179 #[serde(default)]
180 pub question: String,
181 #[serde(default)]
182 pub status: String,
183 #[serde(default)]
184 pub category: String,
185 #[serde(default, rename = "startDate")]
186 pub start_date: String,
187 #[serde(default, rename = "endDate")]
188 pub end_date: String,
189 #[serde(default)]
190 pub description: String,
191 #[serde(default)]
192 pub active: bool,
193 #[serde(default)]
194 pub closed: bool,
195 #[serde(default, rename = "marketType")]
196 pub market_type: String,
197 #[serde(default, rename = "marketSides")]
198 pub market_sides: Vec<MarketSide>,
199 #[serde(default)]
200 pub instruments: Vec<serde_json::Value>,
201 #[serde(default)]
202 pub outcomes: Vec<serde_json::Value>,
203}
204
205impl UsMarket {
206 pub fn parsed_status(&self) -> MarketStatus {
209 MarketStatus::from_api_str(&self.status)
210 }
211}
212
213#[derive(Debug, Clone, Deserialize)]
214pub struct MarketSide {
215 #[serde(default)]
216 pub id: String,
217 #[serde(default)]
218 pub identifier: String,
219 #[serde(default)]
220 pub description: String,
221 #[serde(default)]
222 pub price: String,
223 #[serde(default)]
224 pub long: bool,
225 #[serde(default, rename = "marketSideType")]
226 pub market_side_type: String,
227 #[serde(default)]
228 pub team: Option<serde_json::Value>,
229 #[serde(default)]
230 pub player: Option<serde_json::Value>,
231 #[serde(flatten)]
232 pub extra: std::collections::HashMap<String, serde_json::Value>,
233}
234
235#[derive(Debug, Clone, Serialize)]
236pub struct PlaceOrderRequest {
237 pub symbol: String,
238 pub action: OrderAction,
239 #[serde(rename = "outcomeSide")]
240 pub outcome_side: OrderSide,
241 #[serde(rename = "type")]
242 pub order_type: OrderType,
243 pub price: Money,
244 pub quantity: u64,
245 pub tif: TimeInForce,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 pub client_order_id: Option<String>,
248 #[serde(skip_serializing_if = "std::ops::Not::not")]
249 pub post_only: bool,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub expires_at: Option<u64>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct Money {
256 pub value: String,
257 pub currency: String,
258}
259
260#[derive(Debug, Clone, Deserialize)]
261pub struct PlaceOrderResponse {
262 pub order_id: String,
263 #[serde(default)]
264 pub client_order_id: Option<String>,
265 #[serde(default)]
266 pub status: String,
267 #[serde(default)]
268 pub filled_quantity: u64,
269 #[serde(default)]
270 pub remaining_quantity: u64,
271 #[serde(default)]
272 pub created_at: String,
273}
274
275#[derive(Debug, Clone, Serialize)]
276pub struct BatchedOrderRequest {
277 pub orders: Vec<PlaceOrderRequest>,
278 pub atomic: bool,
279}
280
281#[derive(Debug, Clone, Deserialize)]
282pub struct BatchedOrderResponse {
283 #[serde(default)]
284 pub orders: Vec<PlaceOrderResponse>,
285}
286
287#[derive(Debug, Clone, Deserialize)]
288pub struct CancelOrderResponse {
289 pub order_id: String,
290 #[serde(default)]
291 pub status: String,
292 #[serde(default)]
293 pub cancelled_at: Option<String>,
294}
295
296#[derive(Debug, Clone, Deserialize)]
297pub struct PortfolioPositionsResponse {
298 #[serde(default)]
299 pub positions: std::collections::HashMap<String, UsPosition>,
300 #[serde(default)]
301 pub next_cursor: String,
302 #[serde(default)]
303 pub eof: bool,
304 #[serde(default, rename = "availablePositions")]
305 pub available_positions: Vec<UsPosition>,
306}
307
308#[derive(Debug, Clone, Deserialize)]
309pub struct UsPosition {
310 #[serde(default)]
311 pub symbol: String,
312 #[serde(default)]
313 pub quantity: i64,
314 #[serde(default, rename = "avgEntryPrice")]
315 pub avg_entry_price: String,
316 #[serde(default, rename = "unrealizedPnl")]
317 pub unrealized_pnl: Option<String>,
318}
319
320#[derive(Debug, Clone, Deserialize)]
321pub struct PortfolioActivitiesResponse {
322 #[serde(default)]
323 pub activities: Vec<serde_json::Value>,
324 #[serde(default)]
325 pub next_cursor: Option<String>,
326}
327
328#[derive(Debug, Clone, Deserialize)]
329pub struct AccountBalancesResponse {
330 #[serde(default)]
331 pub balances: Vec<UserBalance>,
332}
333
334#[derive(Debug, Clone, Deserialize)]
335pub struct UserBalance {
336 #[serde(default, rename = "currentBalance")]
337 pub current_balance: f64,
338 #[serde(default)]
339 pub currency: String,
340 #[serde(default, rename = "lastUpdated")]
341 pub last_updated: Option<String>,
342 #[serde(default, rename = "buyingPower")]
343 pub buying_power: f64,
344 #[serde(default, rename = "assetNotional")]
345 pub asset_notional: Option<f64>,
346 #[serde(default, rename = "assetAvailable")]
347 pub asset_available: Option<f64>,
348 #[serde(default, rename = "pendingCredit")]
349 pub pending_credit: Option<f64>,
350 #[serde(default, rename = "openOrders")]
351 pub open_orders: Option<f64>,
352 #[serde(default, rename = "unsettledFunds")]
353 pub unsettled_funds: Option<f64>,
354 #[serde(default, rename = "marginRequirement")]
355 pub margin_requirement: Option<f64>,
356 #[serde(default, rename = "balanceReservation")]
357 pub balance_reservation: Option<f64>,
358}
359
360#[derive(Debug, Clone, Serialize, Default)]
361pub struct CancelOrderParams {
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub quantity: Option<u64>,
364}
365
366#[derive(Debug, Clone, Serialize, Default)]
367pub struct CancelAllOrdersParams {
368 #[serde(skip_serializing_if = "Option::is_none")]
369 pub symbol: Option<String>,
370}
371
372#[derive(Debug, Clone, Deserialize)]
373pub struct CancelAllOrdersResponse {
374 #[serde(default)]
375 pub cancelled: Vec<String>,
376}
377
378#[derive(Debug, Clone, Serialize)]
379pub struct ModifyOrderRequest {
380 pub price: Money,
381 pub quantity: u64,
382}
383
384#[derive(Debug, Clone, Serialize)]
385pub struct PreviewOrderRequest {
386 pub symbol: String,
387 pub action: OrderAction,
388 #[serde(rename = "outcomeSide")]
389 pub outcome_side: OrderSide,
390 #[serde(rename = "type")]
391 pub order_type: OrderType,
392 pub price: Money,
393 pub quantity: u64,
394}
395
396#[derive(Debug, Clone, Deserialize)]
397pub struct PreviewOrderResponse {
398 #[serde(default)]
399 pub estimate: serde_json::Value,
400}
401
402#[derive(Debug, Clone, Serialize)]
403pub struct ClosePositionRequest {
404 pub symbol: String,
405 pub quantity: u64,
406}
407
408#[derive(Debug, Clone, Deserialize)]
409pub struct ClosePositionResponse {
410 #[serde(default)]
411 pub status: String,
412 #[serde(default)]
413 pub order_id: Option<String>,
414}
415
416#[derive(Debug, Clone, Deserialize)]
417pub struct GetOpenOrdersResponse {
418 #[serde(default)]
419 pub orders: Vec<PlaceOrderResponse>,
420}
421
422#[derive(Debug, Clone, Deserialize)]
424pub struct EventsResponse {
425 #[serde(default)]
426 pub events: Vec<UsEvent>,
427}
428
429#[derive(Debug, Clone, Deserialize)]
430pub struct UsEvent {
431 #[serde(default)]
432 pub id: String,
433 #[serde(default)]
434 pub slug: String,
435 #[serde(default)]
436 pub title: String,
437 #[serde(default)]
438 pub description: Option<String>,
439 #[serde(default)]
440 pub category: String,
441 #[serde(default)]
442 pub start_date: Option<String>,
443 #[serde(default)]
444 pub end_date: Option<String>,
445 #[serde(flatten)]
446 pub extra: std::collections::HashMap<String, serde_json::Value>,
447}
448
449#[derive(Debug, Clone, Deserialize)]
451pub struct OrderBook {
452 #[serde(default)]
453 pub bids: Vec<PriceLevel>,
454 #[serde(default)]
455 pub asks: Vec<PriceLevel>,
456}
457
458#[derive(Debug, Clone, Deserialize, Serialize)]
459pub struct PriceLevel {
460 pub price: String,
461 pub quantity: String,
462}
463
464#[derive(Debug, Clone, Deserialize)]
465pub struct BestBidOffer {
466 #[serde(default)]
467 pub bid: Option<PriceLevel>,
468 #[serde(default)]
469 pub ask: Option<PriceLevel>,
470}
471
472#[derive(Debug, Clone, Deserialize)]
473pub struct SettlementPrice {
474 #[serde(default)]
475 pub symbol: String,
476 #[serde(default)]
477 pub price: String,
478 #[serde(default)]
479 pub timestamp: String,
480}
481
482#[derive(Debug, Clone, Deserialize)]
484pub struct SearchResults {
485 #[serde(default)]
486 pub markets: Vec<UsMarket>,
487 #[serde(default)]
488 pub events: Vec<UsEvent>,
489}
490
491#[cfg(test)]
497mod tests {
498 use super::*;
499
500 #[test]
501 fn market_status_parses_case_insensitively() {
502 assert_eq!(MarketStatus::from_api_str("open"), MarketStatus::Open);
503 assert_eq!(MarketStatus::from_api_str("OPEN"), MarketStatus::Open);
504 assert_eq!(
505 MarketStatus::from_api_str(" Closed "),
506 MarketStatus::Closed
507 );
508 assert_eq!(
509 MarketStatus::from_api_str("RESOLVED"),
510 MarketStatus::Resolved
511 );
512 }
513
514 #[test]
515 fn market_status_falls_back_to_unknown() {
516 assert_eq!(MarketStatus::from_api_str("halted"), MarketStatus::Unknown);
518 assert_eq!(MarketStatus::from_api_str(""), MarketStatus::Unknown);
519 }
520
521 #[test]
522 fn parsed_status_reads_the_raw_field() {
523 let json = r#"{"id": "m1", "status": "OPEN"}"#;
524 let market: UsMarket = serde_json::from_str(json).expect("deserialize");
525 assert_eq!(market.status, "OPEN");
527 assert_eq!(market.parsed_status(), MarketStatus::Open);
528 }
529
530 #[test]
531 fn market_sides_deserialize_into_typed_values() {
532 let json = r#"{
533 "id": "m1",
534 "marketSides": [
535 {"id": "s1", "identifier": "YES", "price": "0.62", "long": true,
536 "marketSideType": "BINARY", "unmodelledField": 7}
537 ]
538 }"#;
539 let market: UsMarket = serde_json::from_str(json).expect("deserialize");
540 assert_eq!(market.market_sides.len(), 1);
541
542 let side = &market.market_sides[0];
543 assert_eq!(side.identifier, "YES");
544 assert_eq!(side.price, "0.62");
545 assert!(side.long);
546 assert_eq!(
548 side.extra.get("unmodelledField"),
549 Some(&serde_json::json!(7))
550 );
551 }
552
553 #[test]
554 fn market_sides_tolerate_missing_fields() {
555 let json = r#"{"id": "m1", "marketSides": [{}]}"#;
557 let market: UsMarket = serde_json::from_str(json).expect("deserialize");
558 assert_eq!(market.market_sides.len(), 1);
559 assert_eq!(market.market_sides[0].identifier, "");
560 }
561}