1use crate::presentation::instrument::InstrumentType;
2use crate::presentation::serialization::{string_as_bool_opt, string_as_float_opt};
3use lightstreamer_rs::subscription::ItemUpdate;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt;
7use std::fmt::Display;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct Instrument {
12 pub epic: String,
14 pub name: String,
16 pub expiry: String,
18 #[serde(rename = "contractSize")]
20 pub contract_size: String,
21 #[serde(rename = "lotSize")]
23 pub lot_size: Option<f64>,
24 #[serde(rename = "highLimitPrice")]
26 pub high_limit_price: Option<f64>,
27 #[serde(rename = "lowLimitPrice")]
29 pub low_limit_price: Option<f64>,
30 #[serde(rename = "marginFactor")]
32 pub margin_factor: Option<f64>,
33 #[serde(rename = "marginFactorUnit")]
35 pub margin_factor_unit: Option<String>,
36 pub currencies: Option<Vec<Currency>>,
38 #[serde(rename = "valueOfOnePip")]
39 pub value_of_one_pip: String,
41 #[serde(rename = "instrumentType")]
43 pub instrument_type: Option<InstrumentType>,
44 #[serde(rename = "expiryDetails")]
46 pub expiry_details: Option<ExpiryDetails>,
47 #[serde(rename = "slippageFactor")]
48 pub slippage_factor: Option<StepDistance>,
50 #[serde(rename = "limitedRiskPremium")]
51 pub limited_risk_premium: Option<StepDistance>,
53 #[serde(rename = "newsCode")]
54 pub news_code: Option<String>,
56 #[serde(rename = "chartCode")]
57 pub chart_code: Option<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
63pub struct Currency {
64 pub code: String,
66 pub symbol: Option<String>,
68 #[serde(rename = "baseExchangeRate")]
70 pub base_exchange_rate: Option<f64>,
71 #[serde(rename = "exchangeRate")]
73 pub exchange_rate: Option<f64>,
74 #[serde(rename = "isDefault")]
76 pub is_default: Option<bool>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct MarketDetails {
82 pub instrument: Instrument,
84 pub snapshot: MarketSnapshot,
86 #[serde(rename = "dealingRules")]
88 pub dealing_rules: DealingRules,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct DealingRules {
94 #[serde(rename = "minStepDistance")]
96 pub min_step_distance: StepDistance,
97
98 #[serde(rename = "minDealSize")]
100 pub min_deal_size: StepDistance,
101
102 #[serde(rename = "minControlledRiskStopDistance")]
104 pub min_controlled_risk_stop_distance: StepDistance,
105
106 #[serde(rename = "minNormalStopOrLimitDistance")]
108 pub min_normal_stop_or_limit_distance: StepDistance,
109
110 #[serde(rename = "maxStopOrLimitDistance")]
112 pub max_stop_or_limit_distance: StepDistance,
113
114 #[serde(rename = "controlledRiskSpacing")]
116 pub controlled_risk_spacing: StepDistance,
117
118 #[serde(rename = "marketOrderPreference")]
120 pub market_order_preference: String,
121
122 #[serde(rename = "trailingStopsPreference")]
124 pub trailing_stops_preference: String,
125
126 #[serde(rename = "maxDealSize")]
127 pub max_deal_size: Option<f64>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct MarketSnapshot {
134 #[serde(rename = "marketStatus")]
136 pub market_status: String,
137
138 #[serde(rename = "netChange")]
140 pub net_change: Option<f64>,
141
142 #[serde(rename = "percentageChange")]
144 pub percentage_change: Option<f64>,
145
146 #[serde(rename = "updateTime")]
148 pub update_time: Option<String>,
149
150 #[serde(rename = "delayTime")]
152 pub delay_time: Option<i64>,
153
154 pub bid: Option<f64>,
156
157 pub offer: Option<f64>,
159
160 pub high: Option<f64>,
162
163 pub low: Option<f64>,
165
166 #[serde(rename = "binaryOdds")]
168 pub binary_odds: Option<f64>,
169
170 #[serde(rename = "decimalPlacesFactor")]
172 pub decimal_places_factor: Option<i64>,
173
174 #[serde(rename = "scalingFactor")]
176 pub scaling_factor: Option<i64>,
177
178 #[serde(rename = "controlledRiskExtraSpread")]
180 pub controlled_risk_extra_spread: Option<f64>,
181}
182
183#[derive(Debug, Clone, Deserialize, Serialize)]
185pub struct MarketData {
186 pub epic: String,
188 #[serde(rename = "instrumentName")]
190 pub instrument_name: String,
191 #[serde(rename = "instrumentType")]
193 pub instrument_type: InstrumentType,
194 pub expiry: String,
196 #[serde(rename = "highLimitPrice")]
198 pub high_limit_price: Option<f64>,
199 #[serde(rename = "lowLimitPrice")]
201 pub low_limit_price: Option<f64>,
202 #[serde(rename = "marketStatus")]
204 pub market_status: String,
205 #[serde(rename = "netChange")]
207 pub net_change: Option<f64>,
208 #[serde(rename = "percentageChange")]
210 pub percentage_change: Option<f64>,
211 #[serde(rename = "updateTime")]
213 pub update_time: Option<String>,
214 #[serde(rename = "updateTimeUTC")]
216 pub update_time_utc: Option<String>,
217 pub bid: Option<f64>,
219 pub offer: Option<f64>,
221}
222
223impl Display for MarketData {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 let json = serde_json::to_string(self).unwrap_or_else(|_| "Invalid JSON".to_string());
226 write!(f, "{json}")
227 }
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct HistoricalPrice {
233 #[serde(rename = "snapshotTime")]
235 pub snapshot_time: String,
236 #[serde(rename = "openPrice")]
238 pub open_price: PricePoint,
239 #[serde(rename = "highPrice")]
241 pub high_price: PricePoint,
242 #[serde(rename = "lowPrice")]
244 pub low_price: PricePoint,
245 #[serde(rename = "closePrice")]
247 pub close_price: PricePoint,
248 #[serde(rename = "lastTradedVolume")]
250 pub last_traded_volume: Option<i64>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct PricePoint {
256 pub bid: Option<f64>,
258 pub ask: Option<f64>,
260 #[serde(rename = "lastTraded")]
262 pub last_traded: Option<f64>,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct PriceAllowance {
268 #[serde(rename = "remainingAllowance")]
270 pub remaining_allowance: i64,
271 #[serde(rename = "totalAllowance")]
273 pub total_allowance: i64,
274 #[serde(rename = "allowanceExpiry")]
276 pub allowance_expiry: i64,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
281pub struct ExpiryDetails {
282 #[serde(rename = "lastDealingDate")]
284 pub last_dealing_date: String,
285
286 #[serde(rename = "settlementInfo")]
288 pub settlement_info: Option<String>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
292pub enum StepUnit {
294 #[serde(rename = "POINTS")]
295 Points,
297 #[serde(rename = "PERCENTAGE")]
298 Percentage,
300 #[serde(rename = "pct")]
301 Pct,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
307pub struct StepDistance {
308 pub unit: Option<StepUnit>,
310 pub value: Option<f64>,
312}
313
314#[derive(Debug, Clone, Deserialize, Serialize)]
316pub struct MarketNavigationNode {
317 pub id: String,
319 pub name: String,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct MarketNode {
326 pub id: String,
328 pub name: String,
330 #[serde(skip_serializing_if = "Vec::is_empty", default)]
332 pub children: Vec<MarketNode>,
333 #[serde(skip_serializing_if = "Vec::is_empty", default)]
335 pub markets: Vec<MarketData>,
336}
337
338#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
340#[serde(rename_all = "UPPERCASE")]
341pub enum MarketState {
342 Closed,
344 #[default]
346 Offline,
347 Tradeable,
349 Edit,
351 Auction,
353 AuctionNoEdit,
355 Suspended,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize, Default)]
361pub struct PresentationMarketData {
362 pub item_name: String,
364 pub item_pos: i32,
366 pub fields: MarketFields,
368 pub changed_fields: MarketFields,
370 pub is_snapshot: bool,
372}
373
374impl PresentationMarketData {
375 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
383 let item_name = item_update.item_name.clone().unwrap_or_default();
385
386 let item_pos = item_update.item_pos as i32;
388
389 let is_snapshot = item_update.is_snapshot;
391
392 let fields = Self::create_market_fields(&item_update.fields)?;
394
395 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
397 for (key, value) in &item_update.changed_fields {
398 changed_fields_map.insert(key.clone(), Some(value.clone()));
399 }
400 let changed_fields = Self::create_market_fields(&changed_fields_map)?;
401
402 Ok(PresentationMarketData {
403 item_name,
404 item_pos,
405 fields,
406 changed_fields,
407 is_snapshot,
408 })
409 }
410
411 fn create_market_fields(
419 fields_map: &HashMap<String, Option<String>>,
420 ) -> Result<MarketFields, String> {
421 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
423
424 let market_state = match get_field("MARKET_STATE").as_deref() {
426 Some("closed") => Some(MarketState::Closed),
427 Some("offline") => Some(MarketState::Offline),
428 Some("tradeable") => Some(MarketState::Tradeable),
429 Some("edit") => Some(MarketState::Edit),
430 Some("auction") => Some(MarketState::Auction),
431 Some("auction_no_edit") => Some(MarketState::AuctionNoEdit),
432 Some("suspended") => Some(MarketState::Suspended),
433 Some(unknown) => return Err(format!("Unknown market state: {unknown}")),
434 None => None,
435 };
436
437 let market_delay = match get_field("MARKET_DELAY").as_deref() {
439 Some("0") => Some(false),
440 Some("1") => Some(true),
441 Some(val) => return Err(format!("Invalid MARKET_DELAY value: {val}")),
442 None => None,
443 };
444
445 let parse_float = |key: &str| -> Result<Option<f64>, String> {
447 match get_field(key) {
448 Some(val) if !val.is_empty() => val
449 .parse::<f64>()
450 .map(Some)
451 .map_err(|_| format!("Failed to parse {key} as float: {val}")),
452 _ => Ok(None),
453 }
454 };
455
456 Ok(MarketFields {
457 mid_open: parse_float("MID_OPEN")?,
458 high: parse_float("HIGH")?,
459 offer: parse_float("OFFER")?,
460 change: parse_float("CHANGE")?,
461 market_delay,
462 low: parse_float("LOW")?,
463 bid: parse_float("BID")?,
464 change_pct: parse_float("CHANGE_PCT")?,
465 market_state,
466 update_time: get_field("UPDATE_TIME"),
467 })
468 }
469}
470
471impl fmt::Display for PresentationMarketData {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
474 write!(f, "{json}")
475 }
476}
477
478impl From<&ItemUpdate> for PresentationMarketData {
479 fn from(item_update: &ItemUpdate) -> Self {
480 Self::from_item_update(item_update).unwrap_or_else(|_| PresentationMarketData {
481 item_name: String::new(),
482 item_pos: 0,
483 fields: MarketFields::default(),
484 changed_fields: MarketFields::default(),
485 is_snapshot: false,
486 })
487 }
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
492pub struct MarketFields {
493 #[serde(rename = "MID_OPEN")]
495 #[serde(with = "string_as_float_opt")]
496 #[serde(default)]
497 pub mid_open: Option<f64>,
498
499 #[serde(rename = "HIGH")]
501 #[serde(with = "string_as_float_opt")]
502 #[serde(default)]
503 pub high: Option<f64>,
504
505 #[serde(rename = "OFFER")]
507 #[serde(with = "string_as_float_opt")]
508 #[serde(default)]
509 pub offer: Option<f64>,
510
511 #[serde(rename = "CHANGE")]
513 #[serde(with = "string_as_float_opt")]
514 #[serde(default)]
515 pub change: Option<f64>,
516
517 #[serde(rename = "MARKET_DELAY")]
519 #[serde(with = "string_as_bool_opt")]
520 #[serde(default)]
521 pub market_delay: Option<bool>,
522
523 #[serde(rename = "LOW")]
525 #[serde(with = "string_as_float_opt")]
526 #[serde(default)]
527 pub low: Option<f64>,
528
529 #[serde(rename = "BID")]
531 #[serde(with = "string_as_float_opt")]
532 #[serde(default)]
533 pub bid: Option<f64>,
534
535 #[serde(rename = "CHANGE_PCT")]
537 #[serde(with = "string_as_float_opt")]
538 #[serde(default)]
539 pub change_pct: Option<f64>,
540
541 #[serde(rename = "MARKET_STATE")]
543 #[serde(default)]
544 pub market_state: Option<MarketState>,
545
546 #[serde(rename = "UPDATE_TIME")]
548 #[serde(default)]
549 pub update_time: Option<String>,
550}