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::fmt;
10use std::ops::Add;
11
12#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
14pub struct AccountInfo {
15 pub accounts: Vec<Account>,
17}
18
19#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
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(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
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(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
57pub struct ActivityMetadata {
58 pub paging: Option<ActivityPaging>,
60}
61
62#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize)]
64pub struct ActivityPaging {
65 pub size: Option<i32>,
67 pub next: Option<String>,
69}
70
71#[derive(Debug, Copy, Clone, DisplaySimple, Deserialize, Serialize)]
72#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
74pub enum ActivityType {
75 EditStopAndLimit,
77 Position,
79 System,
81 WorkingOrder,
83}
84
85#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
87pub struct Activity {
88 pub date: String,
90 #[serde(rename = "dealId", default)]
92 pub deal_id: Option<String>,
93 #[serde(default)]
95 pub epic: Option<String>,
96 #[serde(default)]
98 pub period: Option<String>,
99 #[serde(rename = "dealReference", default)]
101 pub deal_reference: Option<String>,
102 #[serde(rename = "type")]
104 pub activity_type: ActivityType,
105 #[serde(default)]
107 pub status: Option<Status>,
108 #[serde(default)]
110 pub description: Option<String>,
111 #[serde(default)]
114 pub details: Option<ActivityDetails>,
115 #[serde(default)]
117 pub channel: Option<String>,
118 #[serde(default)]
120 pub currency: Option<String>,
121 #[serde(default)]
123 pub level: Option<String>,
124}
125
126#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
129pub struct ActivityDetails {
130 #[serde(rename = "dealReference", default)]
132 pub deal_reference: Option<String>,
133 #[serde(default)]
135 pub actions: Vec<ActivityAction>,
136 #[serde(rename = "marketName", default)]
138 pub market_name: Option<String>,
139 #[serde(rename = "goodTillDate", default)]
141 pub good_till_date: Option<String>,
142 #[serde(default)]
144 pub currency: Option<String>,
145 #[serde(default)]
147 pub size: Option<f64>,
148 #[serde(default)]
150 pub direction: Option<Direction>,
151 #[serde(default)]
153 pub level: Option<f64>,
154 #[serde(rename = "stopLevel", default)]
156 pub stop_level: Option<f64>,
157 #[serde(rename = "stopDistance", default)]
159 pub stop_distance: Option<f64>,
160 #[serde(rename = "guaranteedStop", default)]
162 pub guaranteed_stop: Option<bool>,
163 #[serde(rename = "trailingStopDistance", default)]
165 pub trailing_stop_distance: Option<f64>,
166 #[serde(rename = "trailingStep", default)]
168 pub trailing_step: Option<f64>,
169 #[serde(rename = "limitLevel", default)]
171 pub limit_level: Option<f64>,
172 #[serde(rename = "limitDistance", default)]
174 pub limit_distance: Option<f64>,
175}
176
177#[derive(Debug, Copy, Clone, DisplaySimple, Deserialize, Serialize)]
179#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
180pub enum ActionType {
181 LimitOrderDeleted,
183 LimitOrderFilled,
185 LimitOrderOpened,
187 LimitOrderRolled,
189 PositionClosed,
191 PositionDeleted,
193 PositionOpened,
195 PositionPartiallyClosed,
197 PositionRolled,
199 StopLimitAmended,
201 StopOrderAmended,
203 StopOrderDeleted,
205 StopOrderFilled,
207 StopOrderOpened,
209 StopOrderRolled,
211 Unknown,
213 WorkingOrderDeleted,
215}
216
217#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
219#[serde(rename_all = "camelCase")]
220pub struct ActivityAction {
221 pub action_type: ActionType,
223 pub affected_deal_id: Option<String>,
225}
226
227#[derive(Debug, Clone, DisplaySimple, Serialize, Deserialize)]
229pub struct Position {
230 pub position: PositionDetails,
232 pub market: PositionMarket,
234 pub pnl: Option<f64>,
236}
237
238impl Add for Position {
239 type Output = Position;
240
241 fn add(self, other: Position) -> Position {
242 if self.market.epic != other.market.epic {
243 panic!("Cannot add positions from different markets");
244 }
245 Position {
246 position: self.position + other.position,
247 market: self.market,
248 pnl: match (self.pnl, other.pnl) {
249 (Some(a), Some(b)) => Some(a + b),
250 (Some(a), None) => Some(a),
251 (None, Some(b)) => Some(b),
252 (None, None) => None,
253 },
254 }
255 }
256}
257
258#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
260pub struct PositionDetails {
261 #[serde(rename = "contractSize")]
263 pub contract_size: f64,
264 #[serde(rename = "createdDate")]
266 pub created_date: String,
267 #[serde(rename = "createdDateUTC")]
269 pub created_date_utc: String,
270 #[serde(rename = "dealId")]
272 pub deal_id: String,
273 #[serde(rename = "dealReference")]
275 pub deal_reference: String,
276 pub direction: Direction,
278 #[serde(rename = "limitLevel")]
280 pub limit_level: Option<f64>,
281 pub level: f64,
283 pub size: f64,
285 #[serde(rename = "stopLevel")]
287 pub stop_level: Option<f64>,
288 #[serde(rename = "trailingStep")]
290 pub trailing_step: Option<f64>,
291 #[serde(rename = "trailingStopDistance")]
293 pub trailing_stop_distance: Option<f64>,
294 pub currency: String,
296 #[serde(rename = "controlledRisk")]
298 pub controlled_risk: bool,
299 #[serde(rename = "limitedRiskPremium")]
301 pub limited_risk_premium: Option<f64>,
302}
303
304impl Add for PositionDetails {
305 type Output = PositionDetails;
306
307 fn add(self, other: PositionDetails) -> PositionDetails {
308 let (contract_size, size) = if self.direction != other.direction {
309 (
310 (self.contract_size - other.contract_size).abs(),
311 (self.size - other.size).abs(),
312 )
313 } else {
314 (
315 self.contract_size + other.contract_size,
316 self.size + other.size,
317 )
318 };
319
320 PositionDetails {
321 contract_size,
322 created_date: self.created_date,
323 created_date_utc: self.created_date_utc,
324 deal_id: self.deal_id,
325 deal_reference: self.deal_reference,
326 direction: self.direction,
327 limit_level: other.limit_level.or(self.limit_level),
328 level: (self.level + other.level) / 2.0, size,
330 stop_level: other.stop_level.or(self.stop_level),
331 trailing_step: other.trailing_step.or(self.trailing_step),
332 trailing_stop_distance: other.trailing_stop_distance.or(self.trailing_stop_distance),
333 currency: self.currency.clone(),
334 controlled_risk: self.controlled_risk || other.controlled_risk,
335 limited_risk_premium: other.limited_risk_premium.or(self.limited_risk_premium),
336 }
337 }
338}
339
340#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
342pub struct PositionMarket {
343 #[serde(rename = "instrumentName")]
345 pub instrument_name: String,
346 pub expiry: String,
348 pub epic: String,
350 #[serde(rename = "instrumentType")]
352 pub instrument_type: String,
353 #[serde(rename = "lotSize")]
355 pub lot_size: f64,
356 pub high: Option<f64>,
358 pub low: Option<f64>,
360 #[serde(rename = "percentageChange")]
362 pub percentage_change: f64,
363 #[serde(rename = "netChange")]
365 pub net_change: f64,
366 pub bid: Option<f64>,
368 pub offer: Option<f64>,
370 #[serde(rename = "updateTime")]
372 pub update_time: String,
373 #[serde(rename = "updateTimeUTC")]
375 pub update_time_utc: String,
376 #[serde(rename = "delayTime")]
378 pub delay_time: i64,
379 #[serde(rename = "streamingPricesAvailable")]
381 pub streaming_prices_available: bool,
382 #[serde(rename = "marketStatus")]
384 pub market_status: String,
385 #[serde(rename = "scalingFactor")]
387 pub scaling_factor: i64,
388}
389
390#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
392pub struct WorkingOrder {
393 #[serde(rename = "workingOrderData")]
395 pub working_order_data: WorkingOrderData,
396 #[serde(rename = "marketData")]
398 pub market_data: AccountMarketData,
399}
400
401#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
403pub struct WorkingOrderData {
404 #[serde(rename = "dealId")]
406 pub deal_id: String,
407 pub direction: Direction,
409 pub epic: String,
411 #[serde(rename = "orderSize")]
413 pub order_size: f64,
414 #[serde(rename = "orderLevel")]
416 pub order_level: f64,
417 #[serde(rename = "timeInForce")]
419 pub time_in_force: TimeInForce,
420 #[serde(rename = "goodTillDate")]
422 pub good_till_date: Option<String>,
423 #[serde(rename = "goodTillDateISO")]
425 pub good_till_date_iso: Option<String>,
426 #[serde(rename = "createdDate")]
428 pub created_date: String,
429 #[serde(rename = "createdDateUTC")]
431 pub created_date_utc: String,
432 #[serde(rename = "guaranteedStop")]
434 pub guaranteed_stop: bool,
435 #[serde(rename = "orderType")]
437 pub order_type: OrderType,
438 #[serde(rename = "stopDistance")]
440 pub stop_distance: Option<f64>,
441 #[serde(rename = "limitDistance")]
443 pub limit_distance: Option<f64>,
444 #[serde(rename = "currencyCode")]
446 pub currency_code: String,
447 pub dma: bool,
449 #[serde(rename = "limitedRiskPremium")]
451 pub limited_risk_premium: Option<f64>,
452 #[serde(rename = "limitLevel", default)]
454 pub limit_level: Option<f64>,
455 #[serde(rename = "stopLevel", default)]
457 pub stop_level: Option<f64>,
458 #[serde(rename = "dealReference", default)]
460 pub deal_reference: Option<String>,
461}
462
463#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
465pub struct AccountMarketData {
466 #[serde(rename = "instrumentName")]
468 pub instrument_name: String,
469 #[serde(rename = "exchangeId")]
471 pub exchange_id: String,
472 pub expiry: String,
474 #[serde(rename = "marketStatus")]
476 pub market_status: MarketState,
477 pub epic: String,
479 #[serde(rename = "instrumentType")]
481 pub instrument_type: InstrumentType,
482 #[serde(rename = "lotSize")]
484 pub lot_size: f64,
485 pub high: Option<f64>,
487 pub low: Option<f64>,
489 #[serde(rename = "percentageChange")]
491 pub percentage_change: f64,
492 #[serde(rename = "netChange")]
494 pub net_change: f64,
495 pub bid: Option<f64>,
497 pub offer: Option<f64>,
499 #[serde(rename = "updateTime")]
501 pub update_time: String,
502 #[serde(rename = "updateTimeUTC")]
504 pub update_time_utc: String,
505 #[serde(rename = "delayTime")]
507 pub delay_time: i64,
508 #[serde(rename = "streamingPricesAvailable")]
510 pub streaming_prices_available: bool,
511 #[serde(rename = "scalingFactor")]
513 pub scaling_factor: i64,
514}
515
516#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
518pub struct TransactionMetadata {
519 #[serde(rename = "pageData")]
521 pub page_data: PageData,
522 pub size: i32,
524}
525
526#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
528pub struct PageData {
529 #[serde(rename = "pageNumber")]
531 pub page_number: i32,
532 #[serde(rename = "pageSize")]
534 pub page_size: i32,
535 #[serde(rename = "totalPages")]
537 pub total_pages: i32,
538}
539
540#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize)]
542pub struct AccountTransaction {
543 pub date: String,
545 #[serde(rename = "dateUtc")]
547 pub date_utc: String,
548 #[serde(rename = "openDateUtc")]
550 pub open_date_utc: String,
551 #[serde(rename = "instrumentName")]
553 pub instrument_name: String,
554 pub period: String,
556 #[serde(rename = "profitAndLoss")]
558 pub profit_and_loss: String,
559 #[serde(rename = "transactionType")]
561 pub transaction_type: String,
562 pub reference: String,
564 #[serde(rename = "openLevel")]
566 pub open_level: String,
567 #[serde(rename = "closeLevel")]
569 pub close_level: String,
570 pub size: String,
572 pub currency: String,
574 #[serde(rename = "cashTransaction")]
576 pub cash_transaction: bool,
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize, Default)]
581pub struct AccountData {
582 item_name: String,
584 item_pos: i32,
586 fields: AccountFields,
588 changed_fields: AccountFields,
590 is_snapshot: bool,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize, Default)]
596pub struct AccountFields {
597 #[serde(rename = "PNL")]
598 #[serde(with = "string_as_float_opt")]
599 #[serde(default)]
600 pnl: Option<f64>,
601
602 #[serde(rename = "DEPOSIT")]
603 #[serde(with = "string_as_float_opt")]
604 #[serde(default)]
605 deposit: Option<f64>,
606
607 #[serde(rename = "AVAILABLE_CASH")]
608 #[serde(with = "string_as_float_opt")]
609 #[serde(default)]
610 available_cash: Option<f64>,
611
612 #[serde(rename = "PNL_LR")]
613 #[serde(with = "string_as_float_opt")]
614 #[serde(default)]
615 pnl_lr: Option<f64>,
616
617 #[serde(rename = "PNL_NLR")]
618 #[serde(with = "string_as_float_opt")]
619 #[serde(default)]
620 pnl_nlr: Option<f64>,
621
622 #[serde(rename = "FUNDS")]
623 #[serde(with = "string_as_float_opt")]
624 #[serde(default)]
625 funds: Option<f64>,
626
627 #[serde(rename = "MARGIN")]
628 #[serde(with = "string_as_float_opt")]
629 #[serde(default)]
630 margin: Option<f64>,
631
632 #[serde(rename = "MARGIN_LR")]
633 #[serde(with = "string_as_float_opt")]
634 #[serde(default)]
635 margin_lr: Option<f64>,
636
637 #[serde(rename = "MARGIN_NLR")]
638 #[serde(with = "string_as_float_opt")]
639 #[serde(default)]
640 margin_nlr: Option<f64>,
641
642 #[serde(rename = "AVAILABLE_TO_DEAL")]
643 #[serde(with = "string_as_float_opt")]
644 #[serde(default)]
645 available_to_deal: Option<f64>,
646
647 #[serde(rename = "EQUITY")]
648 #[serde(with = "string_as_float_opt")]
649 #[serde(default)]
650 equity: Option<f64>,
651
652 #[serde(rename = "EQUITY_USED")]
653 #[serde(with = "string_as_float_opt")]
654 #[serde(default)]
655 equity_used: Option<f64>,
656}
657
658impl AccountData {
659 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
667 let item_name = item_update.item_name.clone().unwrap_or_default();
669
670 let item_pos = item_update.item_pos as i32;
672
673 let is_snapshot = item_update.is_snapshot;
675
676 let fields = Self::create_account_fields(&item_update.fields)?;
678
679 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
681 for (key, value) in &item_update.changed_fields {
682 changed_fields_map.insert(key.clone(), Some(value.clone()));
683 }
684 let changed_fields = Self::create_account_fields(&changed_fields_map)?;
685
686 Ok(AccountData {
687 item_name,
688 item_pos,
689 fields,
690 changed_fields,
691 is_snapshot,
692 })
693 }
694
695 fn create_account_fields(
703 fields_map: &HashMap<String, Option<String>>,
704 ) -> Result<AccountFields, String> {
705 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
707
708 let parse_float = |key: &str| -> Result<Option<f64>, String> {
710 match get_field(key) {
711 Some(val) if !val.is_empty() => val
712 .parse::<f64>()
713 .map(Some)
714 .map_err(|_| format!("Failed to parse {key} as float: {val}")),
715 _ => Ok(None),
716 }
717 };
718
719 Ok(AccountFields {
720 pnl: parse_float("PNL")?,
721 deposit: parse_float("DEPOSIT")?,
722 available_cash: parse_float("AVAILABLE_CASH")?,
723 pnl_lr: parse_float("PNL_LR")?,
724 pnl_nlr: parse_float("PNL_NLR")?,
725 funds: parse_float("FUNDS")?,
726 margin: parse_float("MARGIN")?,
727 margin_lr: parse_float("MARGIN_LR")?,
728 margin_nlr: parse_float("MARGIN_NLR")?,
729 available_to_deal: parse_float("AVAILABLE_TO_DEAL")?,
730 equity: parse_float("EQUITY")?,
731 equity_used: parse_float("EQUITY_USED")?,
732 })
733 }
734}
735
736impl fmt::Display for AccountData {
737 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738 let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
739 write!(f, "{json}")
740 }
741}
742
743impl From<&ItemUpdate> for AccountData {
744 fn from(item_update: &ItemUpdate) -> Self {
745 Self::from_item_update(item_update).unwrap_or_else(|_| AccountData::default())
746 }
747}