1use crate::presentation::instrument::InstrumentType;
2use crate::presentation::serialization::{string_as_bool_opt, string_as_float_opt};
3use lightstreamer_rs::subscription::ItemUpdate;
4use pretty_simple_display::{DebugPretty, DisplaySimple};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, PartialEq)]
10pub struct Instrument {
11 pub epic: String,
13 pub name: String,
15 pub expiry: String,
17 #[serde(rename = "contractSize")]
19 pub contract_size: String,
20 #[serde(rename = "lotSize")]
22 pub lot_size: Option<f64>,
23 #[serde(rename = "highLimitPrice")]
25 pub high_limit_price: Option<f64>,
26 #[serde(rename = "lowLimitPrice")]
28 pub low_limit_price: Option<f64>,
29 #[serde(rename = "marginFactor")]
31 pub margin_factor: Option<f64>,
32 #[serde(rename = "marginFactorUnit")]
34 pub margin_factor_unit: Option<String>,
35 pub currencies: Option<Vec<Currency>>,
37 #[serde(rename = "valueOfOnePip")]
38 pub value_of_one_pip: String,
40 #[serde(rename = "instrumentType")]
42 pub instrument_type: Option<InstrumentType>,
43 #[serde(rename = "expiryDetails")]
45 pub expiry_details: Option<ExpiryDetails>,
46 #[serde(rename = "slippageFactor")]
47 pub slippage_factor: Option<StepDistance>,
49 #[serde(rename = "limitedRiskPremium")]
50 pub limited_risk_premium: Option<StepDistance>,
52 #[serde(rename = "newsCode")]
53 pub news_code: Option<String>,
55 #[serde(rename = "chartCode")]
56 pub chart_code: Option<String>,
58}
59
60#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, PartialEq)]
62pub struct Currency {
63 pub code: String,
65 pub symbol: Option<String>,
67 #[serde(rename = "baseExchangeRate")]
69 pub base_exchange_rate: Option<f64>,
70 #[serde(rename = "exchangeRate")]
72 pub exchange_rate: Option<f64>,
73 #[serde(rename = "isDefault")]
75 pub is_default: Option<bool>,
76}
77
78#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
80pub struct MarketDetails {
81 pub instrument: Instrument,
83 pub snapshot: MarketSnapshot,
85 #[serde(rename = "dealingRules")]
87 pub dealing_rules: DealingRules,
88}
89
90#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
92pub struct DealingRules {
93 #[serde(rename = "minStepDistance")]
95 pub min_step_distance: StepDistance,
96
97 #[serde(rename = "minDealSize")]
99 pub min_deal_size: StepDistance,
100
101 #[serde(rename = "minControlledRiskStopDistance")]
103 pub min_controlled_risk_stop_distance: StepDistance,
104
105 #[serde(rename = "minNormalStopOrLimitDistance")]
107 pub min_normal_stop_or_limit_distance: StepDistance,
108
109 #[serde(rename = "maxStopOrLimitDistance")]
111 pub max_stop_or_limit_distance: StepDistance,
112
113 #[serde(rename = "controlledRiskSpacing")]
115 pub controlled_risk_spacing: StepDistance,
116
117 #[serde(rename = "marketOrderPreference")]
119 pub market_order_preference: String,
120
121 #[serde(rename = "trailingStopsPreference")]
123 pub trailing_stops_preference: String,
124
125 #[serde(rename = "maxDealSize")]
126 pub max_deal_size: Option<f64>,
128}
129
130#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
132pub struct MarketSnapshot {
133 #[serde(rename = "marketStatus")]
135 pub market_status: String,
136
137 #[serde(rename = "netChange")]
139 pub net_change: Option<f64>,
140
141 #[serde(rename = "percentageChange")]
143 pub percentage_change: Option<f64>,
144
145 #[serde(rename = "updateTime")]
147 pub update_time: Option<String>,
148
149 #[serde(rename = "delayTime")]
151 pub delay_time: Option<i64>,
152
153 pub bid: Option<f64>,
155
156 pub offer: Option<f64>,
158
159 pub high: Option<f64>,
161
162 pub low: Option<f64>,
164
165 #[serde(rename = "binaryOdds")]
167 pub binary_odds: Option<f64>,
168
169 #[serde(rename = "decimalPlacesFactor")]
171 pub decimal_places_factor: Option<i64>,
172
173 #[serde(rename = "scalingFactor")]
175 pub scaling_factor: Option<i64>,
176
177 #[serde(rename = "controlledRiskExtraSpread")]
179 pub controlled_risk_extra_spread: Option<f64>,
180}
181
182#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
184pub struct MarketData {
185 pub epic: String,
187 #[serde(rename = "instrumentName")]
189 pub instrument_name: String,
190 #[serde(rename = "instrumentType")]
192 pub instrument_type: InstrumentType,
193 pub expiry: String,
195 #[serde(rename = "highLimitPrice")]
197 pub high_limit_price: Option<f64>,
198 #[serde(rename = "lowLimitPrice")]
200 pub low_limit_price: Option<f64>,
201 #[serde(rename = "marketStatus")]
203 pub market_status: String,
204 #[serde(rename = "netChange")]
206 pub net_change: Option<f64>,
207 #[serde(rename = "percentageChange")]
209 pub percentage_change: Option<f64>,
210 #[serde(rename = "updateTime")]
212 pub update_time: Option<String>,
213 #[serde(rename = "updateTimeUTC")]
215 pub update_time_utc: Option<String>,
216 pub bid: Option<f64>,
218 pub offer: Option<f64>,
220}
221
222#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
224pub struct HistoricalPrice {
225 #[serde(rename = "snapshotTime")]
227 pub snapshot_time: String,
228 #[serde(rename = "openPrice")]
230 pub open_price: PricePoint,
231 #[serde(rename = "highPrice")]
233 pub high_price: PricePoint,
234 #[serde(rename = "lowPrice")]
236 pub low_price: PricePoint,
237 #[serde(rename = "closePrice")]
239 pub close_price: PricePoint,
240 #[serde(rename = "lastTradedVolume")]
242 pub last_traded_volume: Option<i64>,
243}
244
245#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
247pub struct PricePoint {
248 pub bid: Option<f64>,
250 pub ask: Option<f64>,
252 #[serde(rename = "lastTraded")]
254 pub last_traded: Option<f64>,
255}
256
257#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
259pub struct PriceAllowance {
260 #[serde(rename = "remainingAllowance")]
262 pub remaining_allowance: i64,
263 #[serde(rename = "totalAllowance")]
265 pub total_allowance: i64,
266 #[serde(rename = "allowanceExpiry")]
268 pub allowance_expiry: i64,
269}
270
271#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, PartialEq)]
273pub struct ExpiryDetails {
274 #[serde(rename = "lastDealingDate")]
276 pub last_dealing_date: String,
277
278 #[serde(rename = "settlementInfo")]
280 pub settlement_info: Option<String>,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
284pub enum StepUnit {
286 #[serde(rename = "POINTS")]
287 Points,
289 #[serde(rename = "PERCENTAGE")]
290 Percentage,
292 #[serde(rename = "pct")]
293 Pct,
295}
296
297#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, PartialEq)]
299pub struct StepDistance {
300 pub unit: Option<StepUnit>,
302 pub value: Option<f64>,
304}
305
306#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
308pub struct MarketNavigationNode {
309 pub id: String,
311 pub name: String,
313}
314
315#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
317pub struct MarketNode {
318 pub id: String,
320 pub name: String,
322 #[serde(skip_serializing_if = "Vec::is_empty", default)]
324 pub children: Vec<MarketNode>,
325 #[serde(skip_serializing_if = "Vec::is_empty", default)]
327 pub markets: Vec<MarketData>,
328}
329
330#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone, PartialEq, Default)]
332#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
333pub enum MarketState {
334 Closed,
336 #[default]
338 Offline,
339 Tradeable,
341 Edit,
343 EditsOnly,
345 Auction,
347 AuctionNoEdit,
349 Suspended,
351 OnAuction,
353 OnAuctionNoEdits,
355}
356
357#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
359pub struct PresentationMarketData {
360 pub item_name: String,
362 pub item_pos: i32,
364 pub fields: MarketFields,
366 pub changed_fields: MarketFields,
368 pub is_snapshot: bool,
370}
371
372impl PresentationMarketData {
373 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
381 let item_name = item_update.item_name.clone().unwrap_or_default();
383
384 let item_pos = item_update.item_pos as i32;
386
387 let is_snapshot = item_update.is_snapshot;
389
390 let fields = Self::create_market_fields(&item_update.fields)?;
392
393 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
395 for (key, value) in &item_update.changed_fields {
396 changed_fields_map.insert(key.clone(), Some(value.clone()));
397 }
398 let changed_fields = Self::create_market_fields(&changed_fields_map)?;
399
400 Ok(PresentationMarketData {
401 item_name,
402 item_pos,
403 fields,
404 changed_fields,
405 is_snapshot,
406 })
407 }
408
409 fn create_market_fields(
417 fields_map: &HashMap<String, Option<String>>,
418 ) -> Result<MarketFields, String> {
419 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
421
422 let market_state = match get_field("MARKET_STATE").as_deref() {
424 Some("closed") => Some(MarketState::Closed),
425 Some("offline") => Some(MarketState::Offline),
426 Some("tradeable") => Some(MarketState::Tradeable),
427 Some("edit") => Some(MarketState::Edit),
428 Some("auction") => Some(MarketState::Auction),
429 Some("auction_no_edit") => Some(MarketState::AuctionNoEdit),
430 Some("suspended") => Some(MarketState::Suspended),
431 Some("on_auction") => Some(MarketState::OnAuction),
432 Some("on_auction_no_edit") => Some(MarketState::OnAuctionNoEdits),
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 From<&ItemUpdate> for PresentationMarketData {
472 fn from(item_update: &ItemUpdate) -> Self {
473 Self::from_item_update(item_update).unwrap_or_else(|_| PresentationMarketData {
474 item_name: String::new(),
475 item_pos: 0,
476 fields: MarketFields::default(),
477 changed_fields: MarketFields::default(),
478 is_snapshot: false,
479 })
480 }
481}
482
483#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default, PartialEq)]
485pub struct MarketFields {
486 #[serde(rename = "MID_OPEN")]
488 #[serde(with = "string_as_float_opt")]
489 #[serde(default)]
490 pub mid_open: Option<f64>,
491
492 #[serde(rename = "HIGH")]
494 #[serde(with = "string_as_float_opt")]
495 #[serde(default)]
496 pub high: Option<f64>,
497
498 #[serde(rename = "OFFER")]
500 #[serde(with = "string_as_float_opt")]
501 #[serde(default)]
502 pub offer: Option<f64>,
503
504 #[serde(rename = "CHANGE")]
506 #[serde(with = "string_as_float_opt")]
507 #[serde(default)]
508 pub change: Option<f64>,
509
510 #[serde(rename = "MARKET_DELAY")]
512 #[serde(with = "string_as_bool_opt")]
513 #[serde(default)]
514 pub market_delay: Option<bool>,
515
516 #[serde(rename = "LOW")]
518 #[serde(with = "string_as_float_opt")]
519 #[serde(default)]
520 pub low: Option<f64>,
521
522 #[serde(rename = "BID")]
524 #[serde(with = "string_as_float_opt")]
525 #[serde(default)]
526 pub bid: Option<f64>,
527
528 #[serde(rename = "CHANGE_PCT")]
530 #[serde(with = "string_as_float_opt")]
531 #[serde(default)]
532 pub change_pct: Option<f64>,
533
534 #[serde(rename = "MARKET_STATE")]
536 #[serde(default)]
537 pub market_state: Option<MarketState>,
538
539 #[serde(rename = "UPDATE_TIME")]
541 #[serde(default)]
542 pub update_time: Option<String>,
543}