ig_client/presentation/
account.rs1use crate::presentation::serialization::string_as_float_opt;
2use lightstreamer_rs::subscription::ItemUpdate;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9pub struct AccountData {
10 item_name: String,
12 item_pos: i32,
14 fields: AccountFields,
16 changed_fields: AccountFields,
18 is_snapshot: bool,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, Default)]
24pub struct AccountFields {
25 #[serde(rename = "PNL")]
26 #[serde(with = "string_as_float_opt")]
27 #[serde(default)]
28 pnl: Option<f64>,
29
30 #[serde(rename = "DEPOSIT")]
31 #[serde(with = "string_as_float_opt")]
32 #[serde(default)]
33 deposit: Option<f64>,
34
35 #[serde(rename = "AVAILABLE_CASH")]
36 #[serde(with = "string_as_float_opt")]
37 #[serde(default)]
38 available_cash: Option<f64>,
39
40 #[serde(rename = "PNL_LR")]
41 #[serde(with = "string_as_float_opt")]
42 #[serde(default)]
43 pnl_lr: Option<f64>,
44
45 #[serde(rename = "PNL_NLR")]
46 #[serde(with = "string_as_float_opt")]
47 #[serde(default)]
48 pnl_nlr: Option<f64>,
49
50 #[serde(rename = "FUNDS")]
51 #[serde(with = "string_as_float_opt")]
52 #[serde(default)]
53 funds: Option<f64>,
54
55 #[serde(rename = "MARGIN")]
56 #[serde(with = "string_as_float_opt")]
57 #[serde(default)]
58 margin: Option<f64>,
59
60 #[serde(rename = "MARGIN_LR")]
61 #[serde(with = "string_as_float_opt")]
62 #[serde(default)]
63 margin_lr: Option<f64>,
64
65 #[serde(rename = "MARGIN_NLR")]
66 #[serde(with = "string_as_float_opt")]
67 #[serde(default)]
68 margin_nlr: Option<f64>,
69
70 #[serde(rename = "AVAILABLE_TO_DEAL")]
71 #[serde(with = "string_as_float_opt")]
72 #[serde(default)]
73 available_to_deal: Option<f64>,
74
75 #[serde(rename = "EQUITY")]
76 #[serde(with = "string_as_float_opt")]
77 #[serde(default)]
78 equity: Option<f64>,
79
80 #[serde(rename = "EQUITY_USED")]
81 #[serde(with = "string_as_float_opt")]
82 #[serde(default)]
83 equity_used: Option<f64>,
84}
85
86impl AccountData {
87 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
95 let item_name = item_update.item_name.clone().unwrap_or_default();
97
98 let item_pos = item_update.item_pos as i32;
100
101 let is_snapshot = item_update.is_snapshot;
103
104 let fields = Self::create_account_fields(&item_update.fields)?;
106
107 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
109 for (key, value) in &item_update.changed_fields {
110 changed_fields_map.insert(key.clone(), Some(value.clone()));
111 }
112 let changed_fields = Self::create_account_fields(&changed_fields_map)?;
113
114 Ok(AccountData {
115 item_name,
116 item_pos,
117 fields,
118 changed_fields,
119 is_snapshot,
120 })
121 }
122
123 fn create_account_fields(
131 fields_map: &HashMap<String, Option<String>>,
132 ) -> Result<AccountFields, String> {
133 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
135
136 let parse_float = |key: &str| -> Result<Option<f64>, String> {
138 match get_field(key) {
139 Some(val) if !val.is_empty() => val
140 .parse::<f64>()
141 .map(Some)
142 .map_err(|_| format!("Failed to parse {} as float: {}", key, val)),
143 _ => Ok(None),
144 }
145 };
146
147 Ok(AccountFields {
148 pnl: parse_float("PNL")?,
149 deposit: parse_float("DEPOSIT")?,
150 available_cash: parse_float("AVAILABLE_CASH")?,
151 pnl_lr: parse_float("PNL_LR")?,
152 pnl_nlr: parse_float("PNL_NLR")?,
153 funds: parse_float("FUNDS")?,
154 margin: parse_float("MARGIN")?,
155 margin_lr: parse_float("MARGIN_LR")?,
156 margin_nlr: parse_float("MARGIN_NLR")?,
157 available_to_deal: parse_float("AVAILABLE_TO_DEAL")?,
158 equity: parse_float("EQUITY")?,
159 equity_used: parse_float("EQUITY_USED")?,
160 })
161 }
162}
163
164impl fmt::Display for AccountData {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
167 write!(f, "{}", json)
168 }
169}
170
171impl From<&ItemUpdate> for AccountData {
172 fn from(item_update: &ItemUpdate) -> Self {
173 Self::from_item_update(item_update).unwrap_or_else(|_| AccountData::default())
174 }
175}