1use crate::presentation::instrument::InstrumentType;
2use crate::presentation::market::MarketState;
3use crate::presentation::order::{Direction, OrderType, Status, TimeInForce};
4use crate::presentation::serialization::string_as_float_opt;
5use lightstreamer_rs::subscription::ItemUpdate;
6use pretty_simple_display::{DebugPretty, DisplaySimple};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::ops::Add;
10
11#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
13pub struct AccountInfo {
14 pub accounts: Vec<Account>,
16}
17
18#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
20pub struct Account {
21 #[serde(rename = "accountId")]
23 pub account_id: String,
24 #[serde(rename = "accountName")]
26 pub account_name: String,
27 #[serde(rename = "accountType")]
29 pub account_type: String,
30 pub balance: AccountBalance,
32 pub currency: String,
34 pub status: String,
36 pub preferred: bool,
38}
39
40#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
42pub struct AccountBalance {
43 pub balance: f64,
45 pub deposit: f64,
47 #[serde(rename = "profitLoss")]
49 pub profit_loss: f64,
50 pub available: f64,
52}
53
54#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
56pub struct ActivityMetadata {
57 pub paging: Option<ActivityPaging>,
59}
60
61#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
63pub struct ActivityPaging {
64 pub size: Option<i32>,
66 pub next: Option<String>,
68}
69
70#[derive(Debug, Copy, Clone, DisplaySimple, Deserialize, Serialize)]
71#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
73pub enum ActivityType {
74 EditStopAndLimit,
76 Position,
78 System,
80 WorkingOrder,
82}
83
84#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
86pub struct Activity {
87 pub date: String,
89 #[serde(rename = "dealId", default)]
91 pub deal_id: Option<String>,
92 #[serde(default)]
94 pub epic: Option<String>,
95 #[serde(default)]
97 pub period: Option<String>,
98 #[serde(rename = "dealReference", default)]
100 pub deal_reference: Option<String>,
101 #[serde(rename = "type")]
103 pub activity_type: ActivityType,
104 #[serde(default)]
106 pub status: Option<Status>,
107 #[serde(default)]
109 pub description: Option<String>,
110 #[serde(default)]
113 pub details: Option<ActivityDetails>,
114 #[serde(default)]
116 pub channel: Option<String>,
117 #[serde(default)]
119 pub currency: Option<String>,
120 #[serde(default)]
122 pub level: Option<String>,
123}
124
125#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
128pub struct ActivityDetails {
129 #[serde(rename = "dealReference", default)]
131 pub deal_reference: Option<String>,
132 #[serde(default)]
134 pub actions: Vec<ActivityAction>,
135 #[serde(rename = "marketName", default)]
137 pub market_name: Option<String>,
138 #[serde(rename = "goodTillDate", default)]
140 pub good_till_date: Option<String>,
141 #[serde(default)]
143 pub currency: Option<String>,
144 #[serde(default)]
146 pub size: Option<f64>,
147 #[serde(default)]
149 pub direction: Option<Direction>,
150 #[serde(default)]
152 pub level: Option<f64>,
153 #[serde(rename = "stopLevel", default)]
155 pub stop_level: Option<f64>,
156 #[serde(rename = "stopDistance", default)]
158 pub stop_distance: Option<f64>,
159 #[serde(rename = "guaranteedStop", default)]
161 pub guaranteed_stop: Option<bool>,
162 #[serde(rename = "trailingStopDistance", default)]
164 pub trailing_stop_distance: Option<f64>,
165 #[serde(rename = "trailingStep", default)]
167 pub trailing_step: Option<f64>,
168 #[serde(rename = "limitLevel", default)]
170 pub limit_level: Option<f64>,
171 #[serde(rename = "limitDistance", default)]
173 pub limit_distance: Option<f64>,
174}
175
176#[derive(Debug, Copy, Clone, DisplaySimple, Deserialize, Serialize)]
178#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
179pub enum ActionType {
180 LimitOrderDeleted,
182 LimitOrderFilled,
184 LimitOrderOpened,
186 LimitOrderRolled,
188 PositionClosed,
190 PositionDeleted,
192 PositionOpened,
194 PositionPartiallyClosed,
196 PositionRolled,
198 StopLimitAmended,
200 StopOrderAmended,
202 StopOrderDeleted,
204 StopOrderFilled,
206 StopOrderOpened,
208 StopOrderRolled,
210 Unknown,
212 WorkingOrderDeleted,
214}
215
216#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
218#[serde(rename_all = "camelCase")]
219pub struct ActivityAction {
220 pub action_type: ActionType,
222 pub affected_deal_id: Option<String>,
224}
225
226#[derive(DebugPretty, Clone, DisplaySimple, Serialize, Deserialize)]
228pub struct Position {
229 pub position: PositionDetails,
231 pub market: PositionMarket,
233 pub pnl: Option<f64>,
235}
236
237impl Add for Position {
238 type Output = Position;
239
240 fn add(self, other: Position) -> Position {
241 if self.market.epic != other.market.epic {
242 panic!("Cannot add positions from different markets");
243 }
244 Position {
245 position: self.position + other.position,
246 market: self.market,
247 pnl: match (self.pnl, other.pnl) {
248 (Some(a), Some(b)) => Some(a + b),
249 (Some(a), None) => Some(a),
250 (None, Some(b)) => Some(b),
251 (None, None) => None,
252 },
253 }
254 }
255}
256
257#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
259pub struct PositionDetails {
260 #[serde(rename = "contractSize")]
262 pub contract_size: f64,
263 #[serde(rename = "createdDate")]
265 pub created_date: String,
266 #[serde(rename = "createdDateUTC")]
268 pub created_date_utc: String,
269 #[serde(rename = "dealId")]
271 pub deal_id: String,
272 #[serde(rename = "dealReference")]
274 pub deal_reference: String,
275 pub direction: Direction,
277 #[serde(rename = "limitLevel")]
279 pub limit_level: Option<f64>,
280 pub level: f64,
282 pub size: f64,
284 #[serde(rename = "stopLevel")]
286 pub stop_level: Option<f64>,
287 #[serde(rename = "trailingStep")]
289 pub trailing_step: Option<f64>,
290 #[serde(rename = "trailingStopDistance")]
292 pub trailing_stop_distance: Option<f64>,
293 pub currency: String,
295 #[serde(rename = "controlledRisk")]
297 pub controlled_risk: bool,
298 #[serde(rename = "limitedRiskPremium")]
300 pub limited_risk_premium: Option<f64>,
301}
302
303impl Add for PositionDetails {
304 type Output = PositionDetails;
305
306 fn add(self, other: PositionDetails) -> PositionDetails {
307 let (contract_size, size) = if self.direction != other.direction {
308 (
309 (self.contract_size - other.contract_size).abs(),
310 (self.size - other.size).abs(),
311 )
312 } else {
313 (
314 self.contract_size + other.contract_size,
315 self.size + other.size,
316 )
317 };
318
319 PositionDetails {
320 contract_size,
321 created_date: self.created_date,
322 created_date_utc: self.created_date_utc,
323 deal_id: self.deal_id,
324 deal_reference: self.deal_reference,
325 direction: self.direction,
326 limit_level: other.limit_level.or(self.limit_level),
327 level: (self.level + other.level) / 2.0, size,
329 stop_level: other.stop_level.or(self.stop_level),
330 trailing_step: other.trailing_step.or(self.trailing_step),
331 trailing_stop_distance: other.trailing_stop_distance.or(self.trailing_stop_distance),
332 currency: self.currency.clone(),
333 controlled_risk: self.controlled_risk || other.controlled_risk,
334 limited_risk_premium: other.limited_risk_premium.or(self.limited_risk_premium),
335 }
336 }
337}
338
339#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
341pub struct PositionMarket {
342 #[serde(rename = "instrumentName")]
344 pub instrument_name: String,
345 pub expiry: String,
347 pub epic: String,
349 #[serde(rename = "instrumentType")]
351 pub instrument_type: String,
352 #[serde(rename = "lotSize")]
354 pub lot_size: f64,
355 pub high: Option<f64>,
357 pub low: Option<f64>,
359 #[serde(rename = "percentageChange")]
361 pub percentage_change: f64,
362 #[serde(rename = "netChange")]
364 pub net_change: f64,
365 pub bid: Option<f64>,
367 pub offer: Option<f64>,
369 #[serde(rename = "updateTime")]
371 pub update_time: String,
372 #[serde(rename = "updateTimeUTC")]
374 pub update_time_utc: String,
375 #[serde(rename = "delayTime")]
377 pub delay_time: i64,
378 #[serde(rename = "streamingPricesAvailable")]
380 pub streaming_prices_available: bool,
381 #[serde(rename = "marketStatus")]
383 pub market_status: String,
384 #[serde(rename = "scalingFactor")]
386 pub scaling_factor: i64,
387}
388
389#[derive(DebugPretty, Clone, DisplaySimple, Deserialize, Serialize)]
391pub struct WorkingOrder {
392 #[serde(rename = "workingOrderData")]
394 pub working_order_data: WorkingOrderData,
395 #[serde(rename = "marketData")]
397 pub market_data: AccountMarketData,
398}
399
400#[derive(DebugPretty, Clone, DisplaySimple, Deserialize, Serialize)]
402pub struct WorkingOrderData {
403 #[serde(rename = "dealId")]
405 pub deal_id: String,
406 pub direction: Direction,
408 pub epic: String,
410 #[serde(rename = "orderSize")]
412 pub order_size: f64,
413 #[serde(rename = "orderLevel")]
415 pub order_level: f64,
416 #[serde(rename = "timeInForce")]
418 pub time_in_force: TimeInForce,
419 #[serde(rename = "goodTillDate")]
421 pub good_till_date: Option<String>,
422 #[serde(rename = "goodTillDateISO")]
424 pub good_till_date_iso: Option<String>,
425 #[serde(rename = "createdDate")]
427 pub created_date: String,
428 #[serde(rename = "createdDateUTC")]
430 pub created_date_utc: String,
431 #[serde(rename = "guaranteedStop")]
433 pub guaranteed_stop: bool,
434 #[serde(rename = "orderType")]
436 pub order_type: OrderType,
437 #[serde(rename = "stopDistance")]
439 pub stop_distance: Option<f64>,
440 #[serde(rename = "limitDistance")]
442 pub limit_distance: Option<f64>,
443 #[serde(rename = "currencyCode")]
445 pub currency_code: String,
446 pub dma: bool,
448 #[serde(rename = "limitedRiskPremium")]
450 pub limited_risk_premium: Option<f64>,
451 #[serde(rename = "limitLevel", default)]
453 pub limit_level: Option<f64>,
454 #[serde(rename = "stopLevel", default)]
456 pub stop_level: Option<f64>,
457 #[serde(rename = "dealReference", default)]
459 pub deal_reference: Option<String>,
460}
461
462#[derive(DebugPretty, Clone, DisplaySimple, Deserialize, Serialize)]
464pub struct AccountMarketData {
465 #[serde(rename = "instrumentName")]
467 pub instrument_name: String,
468 #[serde(rename = "exchangeId")]
470 pub exchange_id: String,
471 pub expiry: String,
473 #[serde(rename = "marketStatus")]
475 pub market_status: MarketState,
476 pub epic: String,
478 #[serde(rename = "instrumentType")]
480 pub instrument_type: InstrumentType,
481 #[serde(rename = "lotSize")]
483 pub lot_size: f64,
484 pub high: Option<f64>,
486 pub low: Option<f64>,
488 #[serde(rename = "percentageChange")]
490 pub percentage_change: f64,
491 #[serde(rename = "netChange")]
493 pub net_change: f64,
494 pub bid: Option<f64>,
496 pub offer: Option<f64>,
498 #[serde(rename = "updateTime")]
500 pub update_time: String,
501 #[serde(rename = "updateTimeUTC")]
503 pub update_time_utc: String,
504 #[serde(rename = "delayTime")]
506 pub delay_time: i64,
507 #[serde(rename = "streamingPricesAvailable")]
509 pub streaming_prices_available: bool,
510 #[serde(rename = "scalingFactor")]
512 pub scaling_factor: i64,
513}
514
515#[derive(DebugPretty, Clone, DisplaySimple, Deserialize, Serialize)]
517pub struct TransactionMetadata {
518 #[serde(rename = "pageData")]
520 pub page_data: PageData,
521 pub size: i32,
523}
524
525#[derive(DebugPretty, Clone, DisplaySimple, Deserialize, Serialize)]
527pub struct PageData {
528 #[serde(rename = "pageNumber")]
530 pub page_number: i32,
531 #[serde(rename = "pageSize")]
533 pub page_size: i32,
534 #[serde(rename = "totalPages")]
536 pub total_pages: i32,
537}
538
539#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
541pub struct AccountTransaction {
542 pub date: String,
544 #[serde(rename = "dateUtc")]
546 pub date_utc: String,
547 #[serde(rename = "openDateUtc")]
549 pub open_date_utc: String,
550 #[serde(rename = "instrumentName")]
552 pub instrument_name: String,
553 pub period: String,
555 #[serde(rename = "profitAndLoss")]
557 pub profit_and_loss: String,
558 #[serde(rename = "transactionType")]
560 pub transaction_type: String,
561 pub reference: String,
563 #[serde(rename = "openLevel")]
565 pub open_level: String,
566 #[serde(rename = "closeLevel")]
568 pub close_level: String,
569 pub size: String,
571 pub currency: String,
573 #[serde(rename = "cashTransaction")]
575 pub cash_transaction: bool,
576}
577
578#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
580pub struct AccountData {
581 pub item_name: String,
583 pub item_pos: i32,
585 pub fields: AccountFields,
587 pub changed_fields: AccountFields,
589 pub is_snapshot: bool,
591}
592
593#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
595pub struct AccountFields {
596 #[serde(rename = "PNL")]
597 #[serde(with = "string_as_float_opt")]
598 #[serde(skip_serializing_if = "Option::is_none")]
599 pnl: Option<f64>,
600
601 #[serde(rename = "DEPOSIT")]
602 #[serde(with = "string_as_float_opt")]
603 #[serde(skip_serializing_if = "Option::is_none")]
604 deposit: Option<f64>,
605
606 #[serde(rename = "AVAILABLE_CASH")]
607 #[serde(with = "string_as_float_opt")]
608 #[serde(skip_serializing_if = "Option::is_none")]
609 available_cash: Option<f64>,
610
611 #[serde(rename = "PNL_LR")]
612 #[serde(with = "string_as_float_opt")]
613 #[serde(skip_serializing_if = "Option::is_none")]
614 pnl_lr: Option<f64>,
615
616 #[serde(rename = "PNL_NLR")]
617 #[serde(with = "string_as_float_opt")]
618 #[serde(skip_serializing_if = "Option::is_none")]
619 pnl_nlr: Option<f64>,
620
621 #[serde(rename = "FUNDS")]
622 #[serde(with = "string_as_float_opt")]
623 #[serde(skip_serializing_if = "Option::is_none")]
624 funds: Option<f64>,
625
626 #[serde(rename = "MARGIN")]
627 #[serde(with = "string_as_float_opt")]
628 #[serde(skip_serializing_if = "Option::is_none")]
629 margin: Option<f64>,
630
631 #[serde(rename = "MARGIN_LR")]
632 #[serde(with = "string_as_float_opt")]
633 #[serde(skip_serializing_if = "Option::is_none")]
634 margin_lr: Option<f64>,
635
636 #[serde(rename = "MARGIN_NLR")]
637 #[serde(with = "string_as_float_opt")]
638 #[serde(skip_serializing_if = "Option::is_none")]
639 margin_nlr: Option<f64>,
640
641 #[serde(rename = "AVAILABLE_TO_DEAL")]
642 #[serde(with = "string_as_float_opt")]
643 #[serde(skip_serializing_if = "Option::is_none")]
644 available_to_deal: Option<f64>,
645
646 #[serde(rename = "EQUITY")]
647 #[serde(with = "string_as_float_opt")]
648 #[serde(skip_serializing_if = "Option::is_none")]
649 equity: Option<f64>,
650
651 #[serde(rename = "EQUITY_USED")]
652 #[serde(with = "string_as_float_opt")]
653 #[serde(skip_serializing_if = "Option::is_none")]
654 equity_used: Option<f64>,
655}
656
657impl AccountData {
658 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
666 let item_name = item_update.item_name.clone().unwrap_or_default();
668
669 let item_pos = item_update.item_pos as i32;
671
672 let is_snapshot = item_update.is_snapshot;
674
675 let fields = Self::create_account_fields(&item_update.fields)?;
677
678 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
680 for (key, value) in &item_update.changed_fields {
681 changed_fields_map.insert(key.clone(), Some(value.clone()));
682 }
683 let changed_fields = Self::create_account_fields(&changed_fields_map)?;
684
685 Ok(AccountData {
686 item_name,
687 item_pos,
688 fields,
689 changed_fields,
690 is_snapshot,
691 })
692 }
693
694 fn create_account_fields(
702 fields_map: &HashMap<String, Option<String>>,
703 ) -> Result<AccountFields, String> {
704 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
706
707 let parse_float = |key: &str| -> Result<Option<f64>, String> {
709 match get_field(key) {
710 Some(val) if !val.is_empty() => val
711 .parse::<f64>()
712 .map(Some)
713 .map_err(|_| format!("Failed to parse {key} as float: {val}")),
714 _ => Ok(None),
715 }
716 };
717
718 Ok(AccountFields {
719 pnl: parse_float("PNL")?,
720 deposit: parse_float("DEPOSIT")?,
721 available_cash: parse_float("AVAILABLE_CASH")?,
722 pnl_lr: parse_float("PNL_LR")?,
723 pnl_nlr: parse_float("PNL_NLR")?,
724 funds: parse_float("FUNDS")?,
725 margin: parse_float("MARGIN")?,
726 margin_lr: parse_float("MARGIN_LR")?,
727 margin_nlr: parse_float("MARGIN_NLR")?,
728 available_to_deal: parse_float("AVAILABLE_TO_DEAL")?,
729 equity: parse_float("EQUITY")?,
730 equity_used: parse_float("EQUITY_USED")?,
731 })
732 }
733}
734
735impl From<&ItemUpdate> for AccountData {
736 fn from(item_update: &ItemUpdate) -> Self {
737 Self::from_item_update(item_update).unwrap_or_else(|_| AccountData::default())
738 }
739}