use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicTrade {
pub proxy_wallet: String,
pub side: String,
pub asset: String,
pub condition_id: String,
pub size: f64,
pub price: f64,
pub timestamp: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub slug: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub event_slug: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome_index: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pseudonym: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bio: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_image: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_image_optimized: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transaction_hash: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct MarketTrade {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub price: f64,
pub size: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub side: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aggressor_side: Option<String>,
pub timestamp: DateTime<Utc>,
pub source_channel: std::borrow::Cow<'static, str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tx_hash: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub yes_price: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub no_price: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub taker_address: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricePoint {
pub timestamp: DateTime<Utc>,
pub price: f64,
#[serde(default)]
pub raw: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Candlestick {
pub timestamp: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub open_interest: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum PriceHistoryInterval {
#[serde(rename = "1m")]
OneMinute,
#[serde(rename = "1h")]
OneHour,
#[serde(rename = "6h")]
SixHours,
#[serde(rename = "1d")]
OneDay,
#[serde(rename = "1w")]
OneWeek,
#[serde(rename = "max")]
Max,
}
impl PriceHistoryInterval {
pub fn as_str(&self) -> &'static str {
match self {
Self::OneMinute => "1m",
Self::OneHour => "1h",
Self::SixHours => "6h",
Self::OneDay => "1d",
Self::OneWeek => "1w",
Self::Max => "max",
}
}
pub fn seconds(&self) -> i64 {
match self {
Self::OneMinute => 60,
Self::OneHour => 3600,
Self::SixHours => 21600,
Self::OneDay => 86400,
Self::OneWeek => 604_800,
Self::Max => 86400,
}
}
}
impl std::str::FromStr for PriceHistoryInterval {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"1m" => Ok(Self::OneMinute),
"1h" => Ok(Self::OneHour),
"6h" => Ok(Self::SixHours),
"1d" => Ok(Self::OneDay),
"1w" => Ok(Self::OneWeek),
"max" => Ok(Self::Max),
_ => Err(format!("unknown interval: {s}")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn candlestick_omits_open_interest_when_none() {
let c = Candlestick {
timestamp: chrono::Utc::now(),
open: 0.5,
high: 0.6,
low: 0.4,
close: 0.55,
volume: 100.0,
open_interest: None,
};
let json = serde_json::to_value(&c).unwrap();
assert!(json.get("open_interest").is_none());
}
#[test]
fn candlestick_includes_open_interest_when_some() {
let c = Candlestick {
timestamp: chrono::Utc::now(),
open: 0.5,
high: 0.6,
low: 0.4,
close: 0.55,
volume: 100.0,
open_interest: Some(42000.0),
};
let json = serde_json::to_value(&c).unwrap();
assert_eq!(json["open_interest"], 42000.0);
}
#[test]
fn candlestick_roundtrip_with_open_interest() {
let c = Candlestick {
timestamp: chrono::Utc::now(),
open: 0.5,
high: 0.6,
low: 0.4,
close: 0.55,
volume: 100.0,
open_interest: Some(1234.0),
};
let serialized = serde_json::to_string(&c).unwrap();
let deserialized: Candlestick = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.open_interest, Some(1234.0));
}
#[test]
fn candlestick_deserialize_without_open_interest_defaults_none() {
let json = r#"{"timestamp":"2026-01-01T00:00:00Z","open":0.5,"high":0.6,"low":0.4,"close":0.55,"volume":100.0}"#;
let c: Candlestick = serde_json::from_str(json).unwrap();
assert_eq!(c.open_interest, None);
}
}