use serde::{Deserialize, Serialize};
use super::macros::string_enum;
use super::{Currency, DateTime, DecimalNumber, InstrumentName, PriceValue, UnitsAvailable};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Price {
#[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
pub instrument: Option<InstrumentName>,
#[serde(rename = "tradeable", skip_serializing_if = "Option::is_none")]
pub tradeable: Option<bool>,
#[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")]
pub timestamp: Option<DateTime>,
#[serde(rename = "baseBid", skip_serializing_if = "Option::is_none")]
pub base_bid: Option<PriceValue>,
#[serde(rename = "baseAsk", skip_serializing_if = "Option::is_none")]
pub base_ask: Option<PriceValue>,
#[serde(rename = "bids", default, skip_serializing_if = "Vec::is_empty")]
pub bids: Vec<PriceBucket>,
#[serde(rename = "asks", default, skip_serializing_if = "Vec::is_empty")]
pub asks: Vec<PriceBucket>,
#[serde(rename = "closeoutBid", skip_serializing_if = "Option::is_none")]
pub closeout_bid: Option<PriceValue>,
#[serde(rename = "closeoutAsk", skip_serializing_if = "Option::is_none")]
pub closeout_ask: Option<PriceValue>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClientPrice {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
pub instrument: Option<InstrumentName>,
#[serde(rename = "time", skip_serializing_if = "Option::is_none")]
pub time: Option<DateTime>,
#[serde(rename = "status", skip_serializing_if = "Option::is_none")]
pub status: Option<PriceStatus>,
#[serde(rename = "tradeable", skip_serializing_if = "Option::is_none")]
pub tradeable: Option<bool>,
#[serde(rename = "bids", default, skip_serializing_if = "Vec::is_empty")]
pub bids: Vec<PriceBucket>,
#[serde(rename = "asks", default, skip_serializing_if = "Vec::is_empty")]
pub asks: Vec<PriceBucket>,
#[serde(rename = "closeoutBid", skip_serializing_if = "Option::is_none")]
pub closeout_bid: Option<PriceValue>,
#[serde(rename = "closeoutAsk", skip_serializing_if = "Option::is_none")]
pub closeout_ask: Option<PriceValue>,
#[serde(
rename = "quoteHomeConversionFactors",
skip_serializing_if = "Option::is_none"
)]
pub quote_home_conversion_factors: Option<QuoteHomeConversionFactors>,
#[serde(rename = "unitsAvailable", skip_serializing_if = "Option::is_none")]
pub units_available: Option<UnitsAvailable>,
#[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")]
pub timestamp: Option<DateTime>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PriceBucket {
#[serde(rename = "price", skip_serializing_if = "Option::is_none")]
pub price: Option<PriceValue>,
#[serde(rename = "liquidity", skip_serializing_if = "Option::is_none")]
pub liquidity: Option<DecimalNumber>,
}
string_enum! {
pub enum PriceStatus {
Tradeable => "tradeable",
NonTradeable => "non-tradeable",
Invalid => "invalid",
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PricingHeartbeat {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(rename = "time", skip_serializing_if = "Option::is_none")]
pub time: Option<DateTime>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HomeConversions {
#[serde(rename = "currency", skip_serializing_if = "Option::is_none")]
pub currency: Option<Currency>,
#[serde(rename = "accountGain", skip_serializing_if = "Option::is_none")]
pub account_gain: Option<DecimalNumber>,
#[serde(rename = "accountLoss", skip_serializing_if = "Option::is_none")]
pub account_loss: Option<DecimalNumber>,
#[serde(rename = "positionValue", skip_serializing_if = "Option::is_none")]
pub position_value: Option<DecimalNumber>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HomeConversionFactors {
#[serde(rename = "gainQuoteHome", skip_serializing_if = "Option::is_none")]
pub gain_quote_home: Option<ConversionFactor>,
#[serde(rename = "lossQuoteHome", skip_serializing_if = "Option::is_none")]
pub loss_quote_home: Option<ConversionFactor>,
#[serde(rename = "gainBaseHome", skip_serializing_if = "Option::is_none")]
pub gain_base_home: Option<ConversionFactor>,
#[serde(rename = "lossBaseHome", skip_serializing_if = "Option::is_none")]
pub loss_base_home: Option<ConversionFactor>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct QuoteHomeConversionFactors {
#[serde(rename = "positiveUnits", skip_serializing_if = "Option::is_none")]
pub positive_units: Option<DecimalNumber>,
#[serde(rename = "negativeUnits", skip_serializing_if = "Option::is_none")]
pub negative_units: Option<DecimalNumber>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ConversionFactor {
#[serde(rename = "factor", skip_serializing_if = "Option::is_none")]
pub factor: Option<DecimalNumber>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
#[allow(clippy::large_enum_variant)] pub enum PriceStreamItem {
Price(ClientPrice),
Heartbeat(PricingHeartbeat),
}
impl serde::Serialize for PriceStreamItem {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
PriceStreamItem::Price(price) => price.serialize(serializer),
PriceStreamItem::Heartbeat(heartbeat) => heartbeat.serialize(serializer),
}
}
}
impl<'de> serde::Deserialize<'de> for PriceStreamItem {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(deserializer)?;
let is_heartbeat =
value.get("type").and_then(serde_json::Value::as_str) == Some("HEARTBEAT");
if is_heartbeat {
serde_json::from_value(value)
.map(PriceStreamItem::Heartbeat)
.map_err(serde::de::Error::custom)
} else {
serde_json::from_value(value)
.map(PriceStreamItem::Price)
.map_err(serde::de::Error::custom)
}
}
}