use bigdecimal::{BigDecimal, Zero}; use crate::app::store::records::{EquityChangeEvent, EquitySymbol, FiduciaryAccount, ROUNDING_MODE}; type DateTime = chrono::DateTime; /// An estimated ledger is the effective sum of all the equity change events /// derived from record for a particular fiduciary account and equity symbol. pub struct EstimatedLedger<'a> { pub fiduciary_account: &'a FiduciaryAccount, pub equity_symbol: &'a EquitySymbol, pub equity_change_events: Vec<&'a EquityChangeEvent>, pub account_symbol_quantity: BigDecimal, pub account_symbol_cost_basis_price: BigDecimal, pub account_symbol_cost_basis_total: BigDecimal, pub account_symbol_last_effective_price: BigDecimal, pub account_symbol_last_update_timestamp: DateTime, } impl EstimatedLedger<'_> { /// Takes a list of all change events, a fiduciary account, and an equity /// symbol for which to create a ledger. /// /// All values are taken by reference, so they need to be maintained elsewhere pub fn compile_change_events<'a>(fiduciary_account: &'a FiduciaryAccount, equity_symbol: &'a EquitySymbol, events: Vec<&'a EquityChangeEvent>) -> EstimatedLedger<'a> { let mut acsym_events = vec![]; let mut quantity = BigDecimal::zero(); let mut cost_basis_total = BigDecimal::zero(); let mut last_event: Option<&EquityChangeEvent> = None; for event in events { if event.fiduciary_account_id != fiduciary_account.fiduciary_account_id || event.equity_symbol_id != equity_symbol.equity_symbol_id { continue; } if last_event.is_none() || last_event.unwrap().equity_change_timestamp < event.equity_change_timestamp { last_event = Some(event); } quantity += event.scored_quantity(); cost_basis_total += event.scored_cost_basis(); acsym_events.push(event); } let cost_basis_price = (cost_basis_total.clone() / quantity.clone()).with_scale_round(2, ROUNDING_MODE); EstimatedLedger { fiduciary_account, equity_symbol, equity_change_events: acsym_events, account_symbol_quantity: quantity, account_symbol_cost_basis_price: cost_basis_price, account_symbol_cost_basis_total: cost_basis_total.with_scale_round(2, ROUNDING_MODE), account_symbol_last_effective_price: last_event.map_or_else(|| BigDecimal::zero().with_scale(2), |ev| ev.estimated_price()), account_symbol_last_update_timestamp: last_event.map_or_else(|| chrono::DateTime::::MIN_UTC, |ev| ev.equity_change_timestamp), } } } mod test { use std::str::FromStr; use crate::app::store::records::{ChangeEventType, EquityType}; use super::*; #[test] fn compile_change_events() { let account = FiduciaryAccount { fiduciary_account_id: 0, fiduciary_account_number: 12345678, fiduciary_account_name: "account".into(), fiduciary_account_description: "Brokerage Acct".into(), fiduciary_account_created_timestamp: DateTime::from_str("2025-10-25 13:41:00.000Z").unwrap() }; let symbol = EquitySymbol { equity_symbol_id: 0, equity_symbol_name: "Business Company".into(), equity_symbol_managing_company: "Business Company".into(), equity_symbol_type: EquityType::STOCK, equity_symbol_created_timestamp: DateTime::from_str("2026-01-02 09:43:10.000Z").unwrap(), }; let events = vec![ EquityChangeEvent { equity_change_event_id: 0, fiduciary_account_id: 0, equity_symbol_id: 0, equity_change_quantity: BigDecimal::from_str("0.125").unwrap(), equity_change_cost_basis: BigDecimal::from_str("82.80").unwrap(), equity_change_type: ChangeEventType::BUY, equity_change_timestamp: DateTime::from_str("2026-01-02 09:43:10.000Z").unwrap(), }, EquityChangeEvent { equity_change_event_id: 1, fiduciary_account_id: 0, equity_symbol_id: 1, equity_change_quantity: BigDecimal::from_str("100.01").unwrap(), equity_change_cost_basis: BigDecimal::from_str("5.45").unwrap(), equity_change_type: ChangeEventType::BUY, equity_change_timestamp: DateTime::from_str("2026-01-02 10:43:10.000Z").unwrap(), }, EquityChangeEvent { equity_change_event_id: 2, fiduciary_account_id: 0, equity_symbol_id: 0, equity_change_quantity: BigDecimal::from_str("0.015").unwrap(), equity_change_cost_basis: BigDecimal::from_str("82.50").unwrap(), equity_change_type: ChangeEventType::SELL, equity_change_timestamp: DateTime::from_str("2026-01-02 11:43:10.000Z").unwrap(), } ]; let event_refs = vec![&events[0], &events[1], &events[2]]; let ledger = EstimatedLedger::compile_change_events(&account, &symbol, event_refs); assert_eq!(ledger.equity_change_events.len(), 2); } }