roshi 0.0.1

A Rust client for the Kalshi API
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value; // For custom strike field
use std::collections::HashMap; // For empty object placeholder

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Market {
    pub can_close_early: Option<bool>,
    pub cap_strike: Option<f64>,
    pub category: Option<String>,
    pub close_time: Option<DateTime<Utc>>,
    pub custom_strike: Option<HashMap<String, Value>>,
    pub event_ticker: Option<String>,
    pub expected_expiration_time: Option<DateTime<Utc>>,
    pub expiration_time: Option<DateTime<Utc>>,
    pub expiration_value: Option<String>,
    pub fee_waiver_expiration_time: Option<DateTime<Utc>>,
    pub floor_strike: Option<f64>,
    pub functional_strike: Option<String>,
    pub last_price: Option<f64>,
    pub latest_expiration_time: Option<DateTime<Utc>>,
    pub liquidity: Option<i64>,
    pub no_ask: Option<f64>,
    pub no_bid: Option<f64>,
    pub no_sub_title: Option<String>,
    pub notional_value: Option<f64>,
    pub open_interest: Option<f64>,
    pub open_time: Option<DateTime<Utc>>,
    pub previous_price: Option<f64>,
    pub previous_yes_ask: Option<f64>,
    pub previous_yes_bid: Option<f64>,
    pub response_price_units: Option<String>,
    pub result: Option<String>,
    pub risk_limit_cents: Option<f64>,
    pub rules_primary: Option<String>,
    pub rules_secondary: Option<String>,
    pub settlement_timer_seconds: Option<f64>,
    pub settlement_value: Option<f64>,
    pub strike_type: Option<String>,
    pub subtitle: Option<String>,
    pub tick_size: Option<f64>,
    pub ticker: Option<String>,
    pub title: Option<String>,
    pub volume: Option<f64>,
    pub volume_24h: Option<f64>,
    pub yes_ask: Option<f64>,
    pub yes_bid: Option<f64>,
    pub yes_sub_title: Option<String>,
}

impl Market {
    pub fn liquidity_dollars(&self) -> String {
        self.liquidity
            .map(|cents| format!("${:.2}", cents as f64 / 100.0))
            .unwrap_or_else(|| "Liquidity N/A".to_string())
    }

    pub fn volume_dollars(&self) -> String {
        self.volume
            .map(|volume| format!("${:.2}", volume))
            .unwrap_or_else(|| "Volume N/A".to_string())
    }

    pub fn pretty_print(&self) {
        println!("Market: {}", self.ticker.as_deref().unwrap_or("N/A"));
        println!("Liquidity: {}", self.liquidity_dollars());
        println!(
            "Yes Side Prices - Ask: {}, Bid: {}, Avg: {}",
            self.yes_ask.map_or("N/A".to_string(), |v| v.to_string()),
            self.yes_bid.map_or("N/A".to_string(), |v| v.to_string()),
            self.yes_ask
                .zip(self.yes_bid)
                .map_or("N/A".to_string(), |(ask, bid)| ((ask + bid) / 2.0)
                    .to_string())
        );
        println!(
            "No Side Prices - Ask: {}, Bid: {}, Avg: {}",
            self.no_ask.map_or("N/A".to_string(), |v| v.to_string()),
            self.no_bid.map_or("N/A".to_string(), |v| v.to_string()),
            self.no_ask
                .zip(self.no_bid)
                .map_or("N/A".to_string(), |(ask, bid)| ((ask + bid) / 2.0)
                    .to_string())
        );
        println!("-------------------------")
    }
}

// -------------------- Balance Models --------------------

#[derive(Debug, Serialize, Deserialize)]
pub struct BalanceResponse {
    pub balance: Option<i64>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PortfolioValueResponse {
    pub total_value: Option<i64>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CursorResponse {
    pub cursor: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Event {
    pub category: String,
    pub event_ticker: String,
    pub mutually_exclusive: bool,
    pub series_ticker: String,
    pub strike_date: DateTime<Utc>,
    pub sub_title: Option<String>,
    pub title: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EventsResponse {
    #[serde(flatten)]
    pub cursor: CursorResponse,
    pub events: Vec<Event>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct MarketsResponse {
    #[serde(flatten)]
    pub cursor: CursorResponse,
    pub markets: Vec<Market>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Orderbook {
    pub no: Vec<Vec<u32>>,
    pub no_dollars: Vec<DollarEntry>,
    pub yes: Vec<Vec<u32>>,
    pub yes_dollars: Vec<DollarEntry>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DollarEntry {
    #[serde(rename = "Count")]
    pub count: u32,
    #[serde(rename = "Dollars")]
    pub dollars: Vec<u32>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OrderbookWrapper {
    pub orderbook: Orderbook,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EventResponse {
    pub event: Event,
    pub markets: Vec<Market>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Fill {
    pub action: String,
    pub count: i64,
    pub created_time: DateTime<Utc>,
    pub is_taker: bool,
    pub no_price: i64,
    pub order_id: String,
    pub side: String,
    pub ticker: String,
    pub trade_id: String,
    pub yes_price: i64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct FillsResponse {
    #[serde(flatten)]
    pub cursor: CursorResponse,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub fills: Option<Vec<Fill>>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Action {
    Buy,
    Sell,
}

impl From<String> for Action {
    fn from(action: String) -> Self {
        match action.as_str() {
            "buy" => Action::Buy,
            "sell" => Action::Sell,
            _ => panic!("Invalid action type for conversion to Action"),
        }
    }
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum TimeInForce {
    FillOrKill,
    ImmediateOrCancel,
    GoodTilCanceled,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OrderRequest {
    pub action: Action,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub buy_max_cost: Option<i64>,
    pub client_order_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub count: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expiration_ts: Option<u128>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub no_price: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub no_price_dollars: Option<Vec<i64>>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub order_group_id: Option<String>,
    pub post_only: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub self_trade_prevention_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sell_position_capped: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sell_position_floor: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub side: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ticker: Option<String>,

    pub time_in_force: String,

    #[serde(rename = "type")]
    pub order_type: Option<String>, // always "limit"

    #[serde(skip_serializing_if = "Option::is_none")]
    pub yes_price: Option<i64>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub yes_price_dollars: Option<Vec<i64>>,
}

#[derive(Debug, Deserialize)]
pub struct OrderResponse {
    pub order: Order,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Order {
    pub action: Option<Action>,
    pub client_order_id: Option<String>,

    #[serde(rename = "created_time")]
    pub created_time: serde_json::Value, // can be replaced with `chrono::DateTime<Utc>` if type is known

    #[serde(rename = "expiration_time")]
    pub expiration_time: serde_json::Value, // can also be chrono or u64 if you know it's a timestamp

    pub no_price: u32,
    pub no_price_dollars: Vec<u32>,
    pub order_group_id: String,
    pub order_id: String,
    pub self_trade_prevention_type: String,
    pub side: String,
    pub status: String,
    pub ticker: String,

    #[serde(rename = "type")]
    pub order_type: OrderKind, // "limit", "market", etc.

    pub user_id: String,
    pub yes_price: u32,
    pub yes_price_dollars: Vec<u32>,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum OrderKind {
    Limit,
    Market,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OrdersResponse {
    #[serde(flatten)]
    pub cursor: CursorResponse,
    pub orders: Vec<Order>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EventPosition {
    pub event_exposure: i64,
    pub event_ticker: String,
    pub fees_paid: i64,
    pub realized_pnl: i64,
    pub resting_order_count: i64,
    pub total_cost: i64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct MarketPosition {
    pub fees_paid: i64,
    pub position: i64,
    pub realized_pnl: i64,
    pub resting_orders_count: i64,
    pub ticker: String,
    pub total_traded: i64,
    pub market_exposure: i64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PositionsResponse {
    #[serde(flatten)]
    pub cursor: CursorResponse,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub event_positions: Option<Vec<EventPosition>>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub market_positions: Option<Vec<MarketPosition>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Settlement {
    pub market_result: String,
    pub no_count: i64,
    pub no_total_cost: i64,
    pub revenue: i64,
    pub settled_time: DateTime<Utc>,
    pub ticker: String,
    pub yes_count: i64,
    pub yes_total_cost: i64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SettlementsResponse {
    #[serde(flatten)]
    pub cursor: CursorResponse,
    pub settlements: Vec<Settlement>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Trade {
    pub count: i64,
    pub created_time: DateTime<Utc>,
    pub no_price: i64,
    pub taker_side: String,
    pub ticker: String,
    pub trade_id: String,
    pub yes_price: i64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TradesResponse {
    #[serde(flatten)]
    pub cursor: CursorResponse,
    pub trades: Vec<Trade>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ExchangeStatusResponse {
    pub exchange_active: bool,
    pub trading_active: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Series {
    pub frequency: String,
    pub ticker: String,
    pub title: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SeriesResponse {
    pub series: Series,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum SideKind {
    Neutral,
    Yes,
    No,
}

impl From<String> for SideKind {
    fn from(side: String) -> Self {
        match side.as_str() {
            "Yes" => SideKind::Yes,
            "No" => SideKind::No,
            _ => SideKind::Neutral,
        }
    }
}