ig_client/application/models/
transaction.rs1use crate::application::models::account::AccountTransaction;
2use crate::impl_json_display;
3use crate::utils::parsing::{InstrumentInfo, parse_instrument_name};
4use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, Utc, Weekday};
5use serde::{Deserialize, Serialize};
6use std::str::FromStr;
7
8#[derive(Debug, Serialize, Deserialize)]
10pub struct StoreTransaction {
11 pub deal_date: DateTime<Utc>,
13 pub underlying: Option<String>,
15 pub strike: Option<f64>,
17 pub option_type: Option<String>,
19 pub expiry: Option<NaiveDate>,
21 pub transaction_type: String,
23 pub pnl_eur: f64,
25 pub reference: String,
27 pub is_fee: bool,
29 pub raw_json: String,
31}
32
33impl_json_display!(StoreTransaction);
34
35impl From<AccountTransaction> for StoreTransaction {
36 fn from(raw: AccountTransaction) -> Self {
37 fn parse_period(period: &str) -> Option<NaiveDate> {
38 if let Some((day_str, rest)) = period.split_once('-') {
40 if let Some((mon_str, year_str)) = rest.split_once('-') {
41 if let Ok(day) = day_str.parse::<u32>() {
43 let month = chrono::Month::from_str(mon_str).ok()?;
44 let year = 2000 + year_str.parse::<i32>().ok()?;
45
46 return NaiveDate::from_ymd_opt(year, month.number_from_month(), day);
48 }
49 }
50 }
51
52 if let Some((mon_str, year_str)) = period.split_once('-') {
54 let month = chrono::Month::from_str(mon_str).ok()?;
55 let year = 2000 + year_str.parse::<i32>().ok()?;
56
57 let first_of_month = NaiveDate::from_ymd_opt(year, month.number_from_month(), 1)?;
59
60 let prev_month = if month.number_from_month() == 1 {
62 NaiveDate::from_ymd_opt(year - 1, 12, 1)?
64 } else {
65 NaiveDate::from_ymd_opt(year, month.number_from_month() - 1, 1)?
67 };
68
69 let last_day_of_prev_month = if prev_month.month() == 12 {
71 NaiveDate::from_ymd_opt(prev_month.year(), 12, 31)?
73 } else {
74 first_of_month - Duration::days(1)
76 };
77
78 let days_back = (last_day_of_prev_month.weekday().num_days_from_monday() + 7
80 - Weekday::Wed.num_days_from_monday())
81 % 7;
82
83 return Some(last_day_of_prev_month - Duration::days(days_back as i64));
85 }
86
87 None
88 }
89
90 let instrument_info: InstrumentInfo = parse_instrument_name(&raw.instrument_name).unwrap();
91 let underlying = instrument_info.underlying;
92 let strike = instrument_info.strike;
93 let option_type = instrument_info.option_type;
94 let deal_date = NaiveDateTime::parse_from_str(&raw.date_utc, "%Y-%m-%dT%H:%M:%S")
95 .map(|naive| naive.and_utc())
96 .unwrap_or_else(|_| Utc::now());
97 let pnl_eur = raw
98 .profit_and_loss
99 .trim_start_matches('E')
100 .parse::<f64>()
101 .unwrap_or(0.0);
102
103 let expiry = parse_period(&raw.period);
104
105 let is_fee = raw.transaction_type == "WITH" && pnl_eur.abs() < 1.0;
106
107 StoreTransaction {
108 deal_date,
109 underlying,
110 strike,
111 option_type,
112 expiry,
113 transaction_type: raw.transaction_type.clone(),
114 pnl_eur,
115 reference: raw.reference.clone(),
116 is_fee,
117 raw_json: raw.to_string(),
118 }
119 }
120}
121
122impl From<&AccountTransaction> for StoreTransaction {
123 fn from(raw: &AccountTransaction) -> Self {
124 StoreTransaction::from(raw.clone())
125 }
126}
127
128pub struct TransactionList(pub Vec<StoreTransaction>);
133
134impl AsRef<[StoreTransaction]> for TransactionList {
135 fn as_ref(&self) -> &[StoreTransaction] {
136 &self.0[..]
137 }
138}
139
140impl From<&Vec<AccountTransaction>> for TransactionList {
141 fn from(raw: &Vec<AccountTransaction>) -> Self {
142 TransactionList(
143 raw.iter() .map(StoreTransaction::from) .collect(),
146 )
147 }
148}