1use serde::{Deserialize, Serialize};
4
5use super::macros::string_enum;
6use super::{Currency, DateTime, DecimalNumber, InstrumentName, PriceValue, UnitsAvailable};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[non_exhaustive]
11pub struct Price {
12 #[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
14 pub instrument: Option<InstrumentName>,
15
16 #[serde(rename = "tradeable", skip_serializing_if = "Option::is_none")]
18 pub tradeable: Option<bool>,
19
20 #[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")]
22 pub timestamp: Option<DateTime>,
23
24 #[serde(rename = "baseBid", skip_serializing_if = "Option::is_none")]
26 pub base_bid: Option<PriceValue>,
27
28 #[serde(rename = "baseAsk", skip_serializing_if = "Option::is_none")]
30 pub base_ask: Option<PriceValue>,
31
32 #[serde(rename = "bids", default, skip_serializing_if = "Vec::is_empty")]
36 pub bids: Vec<PriceBucket>,
37
38 #[serde(rename = "asks", default, skip_serializing_if = "Vec::is_empty")]
42 pub asks: Vec<PriceBucket>,
43
44 #[serde(rename = "closeoutBid", skip_serializing_if = "Option::is_none")]
48 pub closeout_bid: Option<PriceValue>,
49
50 #[serde(rename = "closeoutAsk", skip_serializing_if = "Option::is_none")]
54 pub closeout_ask: Option<PriceValue>,
55}
56
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59#[non_exhaustive]
60pub struct ClientPrice {
61 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
63 pub r#type: Option<String>,
64
65 #[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
67 pub instrument: Option<InstrumentName>,
68
69 #[serde(rename = "time", skip_serializing_if = "Option::is_none")]
71 pub time: Option<DateTime>,
72
73 #[serde(rename = "status", skip_serializing_if = "Option::is_none")]
75 pub status: Option<PriceStatus>,
76
77 #[serde(rename = "tradeable", skip_serializing_if = "Option::is_none")]
79 pub tradeable: Option<bool>,
80
81 #[serde(rename = "bids", default, skip_serializing_if = "Vec::is_empty")]
85 pub bids: Vec<PriceBucket>,
86
87 #[serde(rename = "asks", default, skip_serializing_if = "Vec::is_empty")]
91 pub asks: Vec<PriceBucket>,
92
93 #[serde(rename = "closeoutBid", skip_serializing_if = "Option::is_none")]
97 pub closeout_bid: Option<PriceValue>,
98
99 #[serde(rename = "closeoutAsk", skip_serializing_if = "Option::is_none")]
103 pub closeout_ask: Option<PriceValue>,
104
105 #[serde(
107 rename = "quoteHomeConversionFactors",
108 skip_serializing_if = "Option::is_none"
109 )]
110 pub quote_home_conversion_factors: Option<QuoteHomeConversionFactors>,
111
112 #[serde(rename = "unitsAvailable", skip_serializing_if = "Option::is_none")]
114 pub units_available: Option<UnitsAvailable>,
115
116 #[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")]
119 pub timestamp: Option<DateTime>,
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124#[non_exhaustive]
125pub struct PriceBucket {
126 #[serde(rename = "price", skip_serializing_if = "Option::is_none")]
128 pub price: Option<PriceValue>,
129
130 #[serde(rename = "liquidity", skip_serializing_if = "Option::is_none")]
134 pub liquidity: Option<DecimalNumber>,
135}
136
137string_enum! {
138 pub enum PriceStatus {
140 Tradeable => "tradeable",
141 NonTradeable => "non-tradeable",
142 Invalid => "invalid",
143 }
144}
145
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149#[non_exhaustive]
150pub struct PricingHeartbeat {
151 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
153 pub r#type: Option<String>,
154
155 #[serde(rename = "time", skip_serializing_if = "Option::is_none")]
157 pub time: Option<DateTime>,
158}
159
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164#[non_exhaustive]
165pub struct HomeConversions {
166 #[serde(rename = "currency", skip_serializing_if = "Option::is_none")]
168 pub currency: Option<Currency>,
169
170 #[serde(rename = "accountGain", skip_serializing_if = "Option::is_none")]
175 pub account_gain: Option<DecimalNumber>,
176
177 #[serde(rename = "accountLoss", skip_serializing_if = "Option::is_none")]
179 pub account_loss: Option<DecimalNumber>,
180
181 #[serde(rename = "positionValue", skip_serializing_if = "Option::is_none")]
185 pub position_value: Option<DecimalNumber>,
186}
187
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192#[non_exhaustive]
193pub struct HomeConversionFactors {
194 #[serde(rename = "gainQuoteHome", skip_serializing_if = "Option::is_none")]
196 pub gain_quote_home: Option<ConversionFactor>,
197
198 #[serde(rename = "lossQuoteHome", skip_serializing_if = "Option::is_none")]
200 pub loss_quote_home: Option<ConversionFactor>,
201
202 #[serde(rename = "gainBaseHome", skip_serializing_if = "Option::is_none")]
204 pub gain_base_home: Option<ConversionFactor>,
205
206 #[serde(rename = "lossBaseHome", skip_serializing_if = "Option::is_none")]
208 pub loss_base_home: Option<ConversionFactor>,
209}
210
211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215#[non_exhaustive]
216pub struct QuoteHomeConversionFactors {
217 #[serde(rename = "positiveUnits", skip_serializing_if = "Option::is_none")]
222 pub positive_units: Option<DecimalNumber>,
223
224 #[serde(rename = "negativeUnits", skip_serializing_if = "Option::is_none")]
229 pub negative_units: Option<DecimalNumber>,
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
235#[non_exhaustive]
236pub struct ConversionFactor {
237 #[serde(rename = "factor", skip_serializing_if = "Option::is_none")]
240 pub factor: Option<DecimalNumber>,
241}
242
243#[derive(Debug, Clone, PartialEq)]
246#[non_exhaustive]
247#[allow(clippy::large_enum_variant)] pub enum PriceStreamItem {
249 Price(ClientPrice),
251 Heartbeat(PricingHeartbeat),
253}
254
255impl serde::Serialize for PriceStreamItem {
256 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
257 match self {
258 PriceStreamItem::Price(price) => price.serialize(serializer),
259 PriceStreamItem::Heartbeat(heartbeat) => heartbeat.serialize(serializer),
260 }
261 }
262}
263
264impl<'de> serde::Deserialize<'de> for PriceStreamItem {
265 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
266 let value = serde_json::Value::deserialize(deserializer)?;
267 let is_heartbeat =
268 value.get("type").and_then(serde_json::Value::as_str) == Some("HEARTBEAT");
269 if is_heartbeat {
270 serde_json::from_value(value)
271 .map(PriceStreamItem::Heartbeat)
272 .map_err(serde::de::Error::custom)
273 } else {
274 serde_json::from_value(value)
275 .map(PriceStreamItem::Price)
276 .map_err(serde::de::Error::custom)
277 }
278 }
279}