use serde::{Deserialize, Serialize};
use std::fmt;
pub mod order_action {
pub const BUY: &str = "ORDER_ACTION_BUY";
pub const SELL: &str = "ORDER_ACTION_SELL";
}
pub mod order_type {
pub const LIMIT: &str = "ORDER_TYPE_LIMIT";
}
pub mod tif {
pub const GTC: &str = "TIME_IN_FORCE_GOOD_TILL_CANCEL";
pub const GTD: &str = "TIME_IN_FORCE_GOOD_TILL_DATE";
pub const FAK: &str = "TIME_IN_FORCE_IMMEDIATE_OR_CANCEL";
pub const FOK: &str = "TIME_IN_FORCE_FILL_OR_KILL";
}
pub mod outcome {
pub const LONG: &str = "LONG";
pub const SHORT: &str = "SHORT";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum OrderAction {
#[serde(rename = "ORDER_ACTION_BUY")]
Buy,
#[serde(rename = "ORDER_ACTION_SELL")]
Sell,
}
impl fmt::Display for OrderAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Buy => f.write_str("ORDER_ACTION_BUY"),
Self::Sell => f.write_str("ORDER_ACTION_SELL"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum OrderSide {
#[serde(rename = "LONG")]
Long,
#[serde(rename = "SHORT")]
Short,
}
impl fmt::Display for OrderSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Long => f.write_str("LONG"),
Self::Short => f.write_str("SHORT"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum OrderType {
#[serde(rename = "ORDER_TYPE_LIMIT")]
Limit,
}
impl fmt::Display for OrderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ORDER_TYPE_LIMIT")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TimeInForce {
#[serde(rename = "TIME_IN_FORCE_GOOD_TILL_CANCEL")]
GoodTillCancel,
#[serde(rename = "TIME_IN_FORCE_GOOD_TILL_DATE")]
GoodTillDate,
#[serde(rename = "TIME_IN_FORCE_IMMEDIATE_OR_CANCEL")]
ImmediateOrCancel,
#[serde(rename = "TIME_IN_FORCE_FILL_OR_KILL")]
FillOrKill,
}
impl fmt::Display for TimeInForce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::GoodTillCancel => "TIME_IN_FORCE_GOOD_TILL_CANCEL",
Self::GoodTillDate => "TIME_IN_FORCE_GOOD_TILL_DATE",
Self::ImmediateOrCancel => "TIME_IN_FORCE_IMMEDIATE_OR_CANCEL",
Self::FillOrKill => "TIME_IN_FORCE_FILL_OR_KILL",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum MarketStatus {
Open,
Closed,
Resolved,
#[serde(other)]
Unknown,
}
impl MarketStatus {
pub fn from_api_str(raw: &str) -> Self {
match raw.trim().to_ascii_lowercase().as_str() {
"open" => Self::Open,
"closed" => Self::Closed,
"resolved" => Self::Resolved,
_ => Self::Unknown,
}
}
}
impl std::str::FromStr for MarketStatus {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from_api_str(s))
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct HealthResponse {
#[serde(default)]
pub status: String,
#[serde(default)]
pub timestamp: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MarketsResponse {
#[serde(default)]
pub markets: Vec<UsMarket>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UsMarket {
#[serde(default)]
pub id: String,
#[serde(default)]
pub slug: String,
#[serde(default)]
pub question: String,
#[serde(default)]
pub status: String,
#[serde(default)]
pub category: String,
#[serde(default, rename = "startDate")]
pub start_date: String,
#[serde(default, rename = "endDate")]
pub end_date: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub active: bool,
#[serde(default)]
pub closed: bool,
#[serde(default, rename = "marketType")]
pub market_type: String,
#[serde(default, rename = "marketSides")]
pub market_sides: Vec<MarketSide>,
#[serde(default)]
pub instruments: Vec<serde_json::Value>,
#[serde(default)]
pub outcomes: Vec<serde_json::Value>,
}
impl UsMarket {
pub fn parsed_status(&self) -> MarketStatus {
MarketStatus::from_api_str(&self.status)
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct MarketSide {
#[serde(default)]
pub id: String,
#[serde(default)]
pub identifier: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub price: String,
#[serde(default)]
pub long: bool,
#[serde(default, rename = "marketSideType")]
pub market_side_type: String,
#[serde(default)]
pub team: Option<serde_json::Value>,
#[serde(default)]
pub player: Option<serde_json::Value>,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize)]
pub struct PlaceOrderRequest {
pub symbol: String,
pub action: OrderAction,
#[serde(rename = "outcomeSide")]
pub outcome_side: OrderSide,
#[serde(rename = "type")]
pub order_type: OrderType,
pub price: Money,
pub quantity: u64,
pub tif: TimeInForce,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_order_id: Option<String>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub post_only: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Money {
pub value: String,
pub currency: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PlaceOrderResponse {
pub order_id: String,
#[serde(default)]
pub client_order_id: Option<String>,
#[serde(default)]
pub status: String,
#[serde(default)]
pub filled_quantity: u64,
#[serde(default)]
pub remaining_quantity: u64,
#[serde(default)]
pub created_at: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct BatchedOrderRequest {
pub orders: Vec<PlaceOrderRequest>,
pub atomic: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchedOrderResponse {
#[serde(default)]
pub orders: Vec<PlaceOrderResponse>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelOrderResponse {
pub order_id: String,
#[serde(default)]
pub status: String,
#[serde(default)]
pub cancelled_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PortfolioPositionsResponse {
#[serde(default)]
pub positions: std::collections::HashMap<String, UsPosition>,
#[serde(default)]
pub next_cursor: String,
#[serde(default)]
pub eof: bool,
#[serde(default, rename = "availablePositions")]
pub available_positions: Vec<UsPosition>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UsPosition {
#[serde(default)]
pub symbol: String,
#[serde(default)]
pub quantity: i64,
#[serde(default, rename = "avgEntryPrice")]
pub avg_entry_price: String,
#[serde(default, rename = "unrealizedPnl")]
pub unrealized_pnl: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PortfolioActivitiesResponse {
#[serde(default)]
pub activities: Vec<serde_json::Value>,
#[serde(default)]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AccountBalancesResponse {
#[serde(default)]
pub balances: Vec<UserBalance>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UserBalance {
#[serde(default, rename = "currentBalance")]
pub current_balance: f64,
#[serde(default)]
pub currency: String,
#[serde(default, rename = "lastUpdated")]
pub last_updated: Option<String>,
#[serde(default, rename = "buyingPower")]
pub buying_power: f64,
#[serde(default, rename = "assetNotional")]
pub asset_notional: Option<f64>,
#[serde(default, rename = "assetAvailable")]
pub asset_available: Option<f64>,
#[serde(default, rename = "pendingCredit")]
pub pending_credit: Option<f64>,
#[serde(default, rename = "openOrders")]
pub open_orders: Option<f64>,
#[serde(default, rename = "unsettledFunds")]
pub unsettled_funds: Option<f64>,
#[serde(default, rename = "marginRequirement")]
pub margin_requirement: Option<f64>,
#[serde(default, rename = "balanceReservation")]
pub balance_reservation: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct CancelOrderParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub quantity: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct CancelAllOrdersParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelAllOrdersResponse {
#[serde(default)]
pub cancelled: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ModifyOrderRequest {
pub price: Money,
pub quantity: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct PreviewOrderRequest {
pub symbol: String,
pub action: OrderAction,
#[serde(rename = "outcomeSide")]
pub outcome_side: OrderSide,
#[serde(rename = "type")]
pub order_type: OrderType,
pub price: Money,
pub quantity: u64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PreviewOrderResponse {
#[serde(default)]
pub estimate: serde_json::Value,
}
#[derive(Debug, Clone, Serialize)]
pub struct ClosePositionRequest {
pub symbol: String,
pub quantity: u64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ClosePositionResponse {
#[serde(default)]
pub status: String,
#[serde(default)]
pub order_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct GetOpenOrdersResponse {
#[serde(default)]
pub orders: Vec<PlaceOrderResponse>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct EventsResponse {
#[serde(default)]
pub events: Vec<UsEvent>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UsEvent {
#[serde(default)]
pub id: String,
#[serde(default)]
pub slug: String,
#[serde(default)]
pub title: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub category: String,
#[serde(default)]
pub start_date: Option<String>,
#[serde(default)]
pub end_date: Option<String>,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OrderBook {
#[serde(default)]
pub bids: Vec<PriceLevel>,
#[serde(default)]
pub asks: Vec<PriceLevel>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PriceLevel {
pub price: String,
pub quantity: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BestBidOffer {
#[serde(default)]
pub bid: Option<PriceLevel>,
#[serde(default)]
pub ask: Option<PriceLevel>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SettlementPrice {
#[serde(default)]
pub symbol: String,
#[serde(default)]
pub price: String,
#[serde(default)]
pub timestamp: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SearchResults {
#[serde(default)]
pub markets: Vec<UsMarket>,
#[serde(default)]
pub events: Vec<UsEvent>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn market_status_parses_case_insensitively() {
assert_eq!(MarketStatus::from_api_str("open"), MarketStatus::Open);
assert_eq!(MarketStatus::from_api_str("OPEN"), MarketStatus::Open);
assert_eq!(
MarketStatus::from_api_str(" Closed "),
MarketStatus::Closed
);
assert_eq!(
MarketStatus::from_api_str("RESOLVED"),
MarketStatus::Resolved
);
}
#[test]
fn market_status_falls_back_to_unknown() {
assert_eq!(MarketStatus::from_api_str("halted"), MarketStatus::Unknown);
assert_eq!(MarketStatus::from_api_str(""), MarketStatus::Unknown);
}
#[test]
fn parsed_status_reads_the_raw_field() {
let json = r#"{"id": "m1", "status": "OPEN"}"#;
let market: UsMarket = serde_json::from_str(json).expect("deserialize");
assert_eq!(market.status, "OPEN");
assert_eq!(market.parsed_status(), MarketStatus::Open);
}
#[test]
fn market_sides_deserialize_into_typed_values() {
let json = r#"{
"id": "m1",
"marketSides": [
{"id": "s1", "identifier": "YES", "price": "0.62", "long": true,
"marketSideType": "BINARY", "unmodelledField": 7}
]
}"#;
let market: UsMarket = serde_json::from_str(json).expect("deserialize");
assert_eq!(market.market_sides.len(), 1);
let side = &market.market_sides[0];
assert_eq!(side.identifier, "YES");
assert_eq!(side.price, "0.62");
assert!(side.long);
assert_eq!(
side.extra.get("unmodelledField"),
Some(&serde_json::json!(7))
);
}
#[test]
fn market_sides_tolerate_missing_fields() {
let json = r#"{"id": "m1", "marketSides": [{}]}"#;
let market: UsMarket = serde_json::from_str(json).expect("deserialize");
assert_eq!(market.market_sides.len(), 1);
assert_eq!(market.market_sides[0].identifier, "");
}
}