use crate::presentation::account::AccountTransaction;
use crate::utils::parsing::{ParsedOptionInfo, parse_instrument_name};
use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, Utc, Weekday};
use pretty_simple_display::{DebugPretty, DisplaySimple};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, PartialEq, Clone, Default)]
pub struct StoreTransaction {
pub deal_date: DateTime<Utc>,
pub underlying: Option<String>,
pub strike: Option<f64>,
pub option_type: Option<String>,
pub expiry: Option<NaiveDate>,
pub transaction_type: String,
pub pnl_eur: f64,
pub reference: String,
pub is_fee: bool,
pub raw_json: String,
}
impl From<AccountTransaction> for StoreTransaction {
fn from(raw: AccountTransaction) -> Self {
fn parse_period(period: &str) -> Option<NaiveDate> {
if let Some((day_str, rest)) = period.split_once('-')
&& let Some((mon_str, year_str)) = rest.split_once('-')
{
if let Ok(day) = day_str.parse::<u32>() {
let month = chrono::Month::from_str(mon_str).ok()?;
let year = 2000 + year_str.parse::<i32>().ok()?;
return NaiveDate::from_ymd_opt(year, month.number_from_month(), day);
}
}
if let Some((mon_str, year_str)) = period.split_once('-') {
let month = chrono::Month::from_str(mon_str).ok()?;
let year = 2000 + year_str.parse::<i32>().ok()?;
let first_of_month = NaiveDate::from_ymd_opt(year, month.number_from_month(), 1)?;
let prev_month = if month.number_from_month() == 1 {
NaiveDate::from_ymd_opt(year - 1, 12, 1)?
} else {
NaiveDate::from_ymd_opt(year, month.number_from_month() - 1, 1)?
};
let last_day_of_prev_month = if prev_month.month() == 12 {
NaiveDate::from_ymd_opt(prev_month.year(), 12, 31)?
} else {
first_of_month - Duration::days(1)
};
let days_back = (last_day_of_prev_month.weekday().num_days_from_monday() + 7
- Weekday::Wed.num_days_from_monday())
% 7;
return Some(last_day_of_prev_month - Duration::days(days_back as i64));
}
None
}
let instrument_info: ParsedOptionInfo = parse_instrument_name(&raw.instrument_name);
let underlying = Some(instrument_info.asset_name);
let strike = match instrument_info {
ParsedOptionInfo {
strike: Some(s), ..
} => Some(s.parse::<f64>().ok()).flatten(),
_ => None,
};
let option_type = instrument_info.option_type;
let deal_date = NaiveDateTime::parse_from_str(&raw.date_utc, "%Y-%m-%dT%H:%M:%S")
.map(|naive| naive.and_utc())
.unwrap_or_else(|_| Utc::now());
let pnl_eur = raw
.profit_and_loss
.trim_start_matches('E')
.replace(',', "") .parse::<f64>()
.unwrap_or(0.0);
let expiry = parse_period(&raw.period);
let is_fee = raw.transaction_type == "WITH" && pnl_eur.abs() < 1.0;
StoreTransaction {
deal_date,
underlying,
strike,
option_type,
expiry,
transaction_type: raw.transaction_type.clone(),
pnl_eur,
reference: raw.reference.clone(),
is_fee,
raw_json: raw.to_string(),
}
}
}
impl From<&AccountTransaction> for StoreTransaction {
fn from(raw: &AccountTransaction) -> Self {
StoreTransaction::from(raw.clone())
}
}
pub struct TransactionList(pub Vec<StoreTransaction>);
impl AsRef<[StoreTransaction]> for TransactionList {
fn as_ref(&self) -> &[StoreTransaction] {
&self.0[..]
}
}
impl From<&Vec<AccountTransaction>> for TransactionList {
fn from(raw: &Vec<AccountTransaction>) -> Self {
TransactionList(
raw.iter() .map(StoreTransaction::from) .collect(),
)
}
}