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::DisplaySimple;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt;
10use std::ops::Add;
11
12#[derive(Debug, Clone, Deserialize)]
14pub struct AccountInfo {
15 pub accounts: Vec<Account>,
17}
18
19#[derive(Debug, Clone, Deserialize)]
21pub struct Account {
22 #[serde(rename = "accountId")]
24 pub account_id: String,
25 #[serde(rename = "accountName")]
27 pub account_name: String,
28 #[serde(rename = "accountType")]
30 pub account_type: String,
31 pub balance: AccountBalance,
33 pub currency: String,
35 pub status: String,
37 pub preferred: bool,
39}
40
41#[derive(Debug, Clone, Deserialize)]
43pub struct AccountBalance {
44 pub balance: f64,
46 pub deposit: f64,
48 #[serde(rename = "profitLoss")]
50 pub profit_loss: f64,
51 pub available: f64,
53}
54
55#[derive(Debug, Clone, Deserialize)]
57pub struct ActivityMetadata {
58 pub paging: Option<ActivityPaging>,
60}
61
62#[derive(Debug, Clone, Deserialize)]
64pub struct ActivityPaging {
65 pub size: Option<i32>,
67 pub next: Option<String>,
69}
70
71#[derive(Debug, Copy, Clone, DisplaySimple, Deserialize, Serialize)]
72pub enum ActivityType {
74 #[serde(rename = "EDIT_STOP_AND_LIMIT")]
76 EditStopAndLimit,
77 #[serde(rename = "POSITION")]
79 Position,
80 #[serde(rename = "SYSTEM")]
82 System,
83 #[serde(rename = "WORKING_ORDER")]
85 WorkingOrder,
86}
87
88#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
90pub struct Activity {
91 pub date: String,
93 #[serde(rename = "dealId", default)]
95 pub deal_id: Option<String>,
96 #[serde(default)]
98 pub epic: Option<String>,
99 #[serde(default)]
101 pub period: Option<String>,
102 #[serde(rename = "dealReference", default)]
104 pub deal_reference: Option<String>,
105 #[serde(rename = "type")]
107 pub activity_type: ActivityType,
108 #[serde(default)]
110 pub status: Option<Status>,
111 #[serde(default)]
113 pub description: Option<String>,
114 #[serde(default)]
117 pub details: Option<ActivityDetails>,
118 #[serde(default)]
120 pub channel: Option<String>,
121 #[serde(default)]
123 pub currency: Option<String>,
124 #[serde(default)]
126 pub level: Option<String>,
127}
128
129#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
132pub struct ActivityDetails {
133 #[serde(rename = "dealReference", default)]
135 pub deal_reference: Option<String>,
136 #[serde(default)]
138 pub actions: Vec<ActivityAction>,
139 #[serde(rename = "marketName", default)]
141 pub market_name: Option<String>,
142 #[serde(rename = "goodTillDate", default)]
144 pub good_till_date: Option<String>,
145 #[serde(default)]
147 pub currency: Option<String>,
148 #[serde(default)]
150 pub size: Option<f64>,
151 #[serde(default)]
153 pub direction: Option<Direction>,
154 #[serde(default)]
156 pub level: Option<f64>,
157 #[serde(rename = "stopLevel", default)]
159 pub stop_level: Option<f64>,
160 #[serde(rename = "stopDistance", default)]
162 pub stop_distance: Option<f64>,
163 #[serde(rename = "guaranteedStop", default)]
165 pub guaranteed_stop: Option<bool>,
166 #[serde(rename = "trailingStopDistance", default)]
168 pub trailing_stop_distance: Option<f64>,
169 #[serde(rename = "trailingStep", default)]
171 pub trailing_step: Option<f64>,
172 #[serde(rename = "limitLevel", default)]
174 pub limit_level: Option<f64>,
175 #[serde(rename = "limitDistance", default)]
177 pub limit_distance: Option<f64>,
178}
179
180#[derive(Debug, Copy, Clone, DisplaySimple, Deserialize, Serialize)]
182#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
183pub enum ActionType {
184 LimitOrderDeleted,
186 LimitOrderFilled,
188 LimitOrderOpened,
190 LimitOrderRolled,
192 PositionClosed,
194 PositionDeleted,
196 PositionOpened,
198 PositionPartiallyClosed,
200 PositionRolled,
202 StopLimitAmended,
204 StopOrderAmended,
206 StopOrderDeleted,
208 StopOrderFilled,
210 StopOrderOpened,
212 StopOrderRolled,
214 Unknown,
216 WorkingOrderDeleted,
218}
219
220#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
222#[serde(rename_all = "camelCase")]
223pub struct ActivityAction {
224 pub action_type: ActionType,
226 pub affected_deal_id: Option<String>,
228}
229
230#[derive(Debug, Clone, DisplaySimple, Serialize, Deserialize)]
232pub struct Position {
233 pub position: PositionDetails,
235 pub market: PositionMarket,
237 pub pnl: Option<f64>,
239}
240
241impl Add for Position {
242 type Output = Position;
243
244 fn add(self, other: Position) -> Position {
245 if self.market.epic != other.market.epic {
246 panic!("Cannot add positions from different markets");
247 }
248 Position {
249 position: self.position + other.position,
250 market: self.market,
251 pnl: match (self.pnl, other.pnl) {
252 (Some(a), Some(b)) => Some(a + b),
253 (Some(a), None) => Some(a),
254 (None, Some(b)) => Some(b),
255 (None, None) => None,
256 },
257 }
258 }
259}
260
261#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
263pub struct PositionDetails {
264 #[serde(rename = "contractSize")]
266 pub contract_size: f64,
267 #[serde(rename = "createdDate")]
269 pub created_date: String,
270 #[serde(rename = "createdDateUTC")]
272 pub created_date_utc: String,
273 #[serde(rename = "dealId")]
275 pub deal_id: String,
276 #[serde(rename = "dealReference")]
278 pub deal_reference: String,
279 pub direction: Direction,
281 #[serde(rename = "limitLevel")]
283 pub limit_level: Option<f64>,
284 pub level: f64,
286 pub size: f64,
288 #[serde(rename = "stopLevel")]
290 pub stop_level: Option<f64>,
291 #[serde(rename = "trailingStep")]
293 pub trailing_step: Option<f64>,
294 #[serde(rename = "trailingStopDistance")]
296 pub trailing_stop_distance: Option<f64>,
297 pub currency: String,
299 #[serde(rename = "controlledRisk")]
301 pub controlled_risk: bool,
302 #[serde(rename = "limitedRiskPremium")]
304 pub limited_risk_premium: Option<f64>,
305}
306
307impl Add for PositionDetails {
308 type Output = PositionDetails;
309
310 fn add(self, other: PositionDetails) -> PositionDetails {
311 let (contract_size, size) = if self.direction != other.direction {
312 (
313 (self.contract_size - other.contract_size).abs(),
314 (self.size - other.size).abs(),
315 )
316 } else {
317 (
318 self.contract_size + other.contract_size,
319 self.size + other.size,
320 )
321 };
322
323 PositionDetails {
324 contract_size,
325 created_date: self.created_date,
326 created_date_utc: self.created_date_utc,
327 deal_id: self.deal_id,
328 deal_reference: self.deal_reference,
329 direction: self.direction,
330 limit_level: other.limit_level.or(self.limit_level),
331 level: (self.level + other.level) / 2.0, size,
333 stop_level: other.stop_level.or(self.stop_level),
334 trailing_step: other.trailing_step.or(self.trailing_step),
335 trailing_stop_distance: other.trailing_stop_distance.or(self.trailing_stop_distance),
336 currency: self.currency.clone(),
337 controlled_risk: self.controlled_risk || other.controlled_risk,
338 limited_risk_premium: other.limited_risk_premium.or(self.limited_risk_premium),
339 }
340 }
341}
342
343#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
345pub struct PositionMarket {
346 #[serde(rename = "instrumentName")]
348 pub instrument_name: String,
349 pub expiry: String,
351 pub epic: String,
353 #[serde(rename = "instrumentType")]
355 pub instrument_type: String,
356 #[serde(rename = "lotSize")]
358 pub lot_size: f64,
359 pub high: Option<f64>,
361 pub low: Option<f64>,
363 #[serde(rename = "percentageChange")]
365 pub percentage_change: f64,
366 #[serde(rename = "netChange")]
368 pub net_change: f64,
369 pub bid: Option<f64>,
371 pub offer: Option<f64>,
373 #[serde(rename = "updateTime")]
375 pub update_time: String,
376 #[serde(rename = "updateTimeUTC")]
378 pub update_time_utc: String,
379 #[serde(rename = "delayTime")]
381 pub delay_time: i64,
382 #[serde(rename = "streamingPricesAvailable")]
384 pub streaming_prices_available: bool,
385 #[serde(rename = "marketStatus")]
387 pub market_status: String,
388 #[serde(rename = "scalingFactor")]
390 pub scaling_factor: i64,
391}
392
393#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
395pub struct WorkingOrder {
396 #[serde(rename = "workingOrderData")]
398 pub working_order_data: WorkingOrderData,
399 #[serde(rename = "marketData")]
401 pub market_data: AccountMarketData,
402}
403
404#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
406pub struct WorkingOrderData {
407 #[serde(rename = "dealId")]
409 pub deal_id: String,
410 pub direction: Direction,
412 pub epic: String,
414 #[serde(rename = "orderSize")]
416 pub order_size: f64,
417 #[serde(rename = "orderLevel")]
419 pub order_level: f64,
420 #[serde(rename = "timeInForce")]
422 pub time_in_force: TimeInForce,
423 #[serde(rename = "goodTillDate")]
425 pub good_till_date: Option<String>,
426 #[serde(rename = "goodTillDateISO")]
428 pub good_till_date_iso: Option<String>,
429 #[serde(rename = "createdDate")]
431 pub created_date: String,
432 #[serde(rename = "createdDateUTC")]
434 pub created_date_utc: String,
435 #[serde(rename = "guaranteedStop")]
437 pub guaranteed_stop: bool,
438 #[serde(rename = "orderType")]
440 pub order_type: OrderType,
441 #[serde(rename = "stopDistance")]
443 pub stop_distance: Option<f64>,
444 #[serde(rename = "limitDistance")]
446 pub limit_distance: Option<f64>,
447 #[serde(rename = "currencyCode")]
449 pub currency_code: String,
450 pub dma: bool,
452 #[serde(rename = "limitedRiskPremium")]
454 pub limited_risk_premium: Option<f64>,
455 #[serde(rename = "limitLevel", default)]
457 pub limit_level: Option<f64>,
458 #[serde(rename = "stopLevel", default)]
460 pub stop_level: Option<f64>,
461 #[serde(rename = "dealReference", default)]
463 pub deal_reference: Option<String>,
464}
465
466#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
468pub struct AccountMarketData {
469 #[serde(rename = "instrumentName")]
471 pub instrument_name: String,
472 #[serde(rename = "exchangeId")]
474 pub exchange_id: String,
475 pub expiry: String,
477 #[serde(rename = "marketStatus")]
479 pub market_status: MarketState,
480 pub epic: String,
482 #[serde(rename = "instrumentType")]
484 pub instrument_type: InstrumentType,
485 #[serde(rename = "lotSize")]
487 pub lot_size: f64,
488 pub high: Option<f64>,
490 pub low: Option<f64>,
492 #[serde(rename = "percentageChange")]
494 pub percentage_change: f64,
495 #[serde(rename = "netChange")]
497 pub net_change: f64,
498 pub bid: Option<f64>,
500 pub offer: Option<f64>,
502 #[serde(rename = "updateTime")]
504 pub update_time: String,
505 #[serde(rename = "updateTimeUTC")]
507 pub update_time_utc: String,
508 #[serde(rename = "delayTime")]
510 pub delay_time: i64,
511 #[serde(rename = "streamingPricesAvailable")]
513 pub streaming_prices_available: bool,
514 #[serde(rename = "scalingFactor")]
516 pub scaling_factor: i64,
517}
518
519#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
521pub struct TransactionMetadata {
522 #[serde(rename = "pageData")]
524 pub page_data: PageData,
525 pub size: i32,
527}
528
529#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
531pub struct PageData {
532 #[serde(rename = "pageNumber")]
534 pub page_number: i32,
535 #[serde(rename = "pageSize")]
537 pub page_size: i32,
538 #[serde(rename = "totalPages")]
540 pub total_pages: i32,
541}
542
543#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
545pub struct AccountTransaction {
546 pub date: String,
548 #[serde(rename = "dateUtc")]
550 pub date_utc: String,
551 #[serde(rename = "openDateUtc")]
553 pub open_date_utc: String,
554 #[serde(rename = "instrumentName")]
556 pub instrument_name: String,
557 pub period: String,
559 #[serde(rename = "profitAndLoss")]
561 pub profit_and_loss: String,
562 #[serde(rename = "transactionType")]
564 pub transaction_type: String,
565 pub reference: String,
567 #[serde(rename = "openLevel")]
569 pub open_level: String,
570 #[serde(rename = "closeLevel")]
572 pub close_level: String,
573 pub size: String,
575 pub currency: String,
577 #[serde(rename = "cashTransaction")]
579 pub cash_transaction: bool,
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize, Default)]
584pub struct AccountData {
585 item_name: String,
587 item_pos: i32,
589 fields: AccountFields,
591 changed_fields: AccountFields,
593 is_snapshot: bool,
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize, Default)]
599pub struct AccountFields {
600 #[serde(rename = "PNL")]
601 #[serde(with = "string_as_float_opt")]
602 #[serde(default)]
603 pnl: Option<f64>,
604
605 #[serde(rename = "DEPOSIT")]
606 #[serde(with = "string_as_float_opt")]
607 #[serde(default)]
608 deposit: Option<f64>,
609
610 #[serde(rename = "AVAILABLE_CASH")]
611 #[serde(with = "string_as_float_opt")]
612 #[serde(default)]
613 available_cash: Option<f64>,
614
615 #[serde(rename = "PNL_LR")]
616 #[serde(with = "string_as_float_opt")]
617 #[serde(default)]
618 pnl_lr: Option<f64>,
619
620 #[serde(rename = "PNL_NLR")]
621 #[serde(with = "string_as_float_opt")]
622 #[serde(default)]
623 pnl_nlr: Option<f64>,
624
625 #[serde(rename = "FUNDS")]
626 #[serde(with = "string_as_float_opt")]
627 #[serde(default)]
628 funds: Option<f64>,
629
630 #[serde(rename = "MARGIN")]
631 #[serde(with = "string_as_float_opt")]
632 #[serde(default)]
633 margin: Option<f64>,
634
635 #[serde(rename = "MARGIN_LR")]
636 #[serde(with = "string_as_float_opt")]
637 #[serde(default)]
638 margin_lr: Option<f64>,
639
640 #[serde(rename = "MARGIN_NLR")]
641 #[serde(with = "string_as_float_opt")]
642 #[serde(default)]
643 margin_nlr: Option<f64>,
644
645 #[serde(rename = "AVAILABLE_TO_DEAL")]
646 #[serde(with = "string_as_float_opt")]
647 #[serde(default)]
648 available_to_deal: Option<f64>,
649
650 #[serde(rename = "EQUITY")]
651 #[serde(with = "string_as_float_opt")]
652 #[serde(default)]
653 equity: Option<f64>,
654
655 #[serde(rename = "EQUITY_USED")]
656 #[serde(with = "string_as_float_opt")]
657 #[serde(default)]
658 equity_used: Option<f64>,
659}
660
661impl AccountData {
662 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
670 let item_name = item_update.item_name.clone().unwrap_or_default();
672
673 let item_pos = item_update.item_pos as i32;
675
676 let is_snapshot = item_update.is_snapshot;
678
679 let fields = Self::create_account_fields(&item_update.fields)?;
681
682 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
684 for (key, value) in &item_update.changed_fields {
685 changed_fields_map.insert(key.clone(), Some(value.clone()));
686 }
687 let changed_fields = Self::create_account_fields(&changed_fields_map)?;
688
689 Ok(AccountData {
690 item_name,
691 item_pos,
692 fields,
693 changed_fields,
694 is_snapshot,
695 })
696 }
697
698 fn create_account_fields(
706 fields_map: &HashMap<String, Option<String>>,
707 ) -> Result<AccountFields, String> {
708 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
710
711 let parse_float = |key: &str| -> Result<Option<f64>, String> {
713 match get_field(key) {
714 Some(val) if !val.is_empty() => val
715 .parse::<f64>()
716 .map(Some)
717 .map_err(|_| format!("Failed to parse {key} as float: {val}")),
718 _ => Ok(None),
719 }
720 };
721
722 Ok(AccountFields {
723 pnl: parse_float("PNL")?,
724 deposit: parse_float("DEPOSIT")?,
725 available_cash: parse_float("AVAILABLE_CASH")?,
726 pnl_lr: parse_float("PNL_LR")?,
727 pnl_nlr: parse_float("PNL_NLR")?,
728 funds: parse_float("FUNDS")?,
729 margin: parse_float("MARGIN")?,
730 margin_lr: parse_float("MARGIN_LR")?,
731 margin_nlr: parse_float("MARGIN_NLR")?,
732 available_to_deal: parse_float("AVAILABLE_TO_DEAL")?,
733 equity: parse_float("EQUITY")?,
734 equity_used: parse_float("EQUITY_USED")?,
735 })
736 }
737}
738
739impl fmt::Display for AccountData {
740 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741 let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
742 write!(f, "{json}")
743 }
744}
745
746impl From<&ItemUpdate> for AccountData {
747 fn from(item_update: &ItemUpdate) -> Self {
748 Self::from_item_update(item_update).unwrap_or_else(|_| AccountData::default())
749 }
750}