From 754f2aab52542f0ff9097a9e7abe863d78b34779 Mon Sep 17 00:00:00 2001 From: Kevin Hoerr Date: Fri, 17 Apr 2026 09:18:00 -0400 Subject: Reroll --- src/app/ledger.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++++ src/app/mod.rs | 1 + src/app/store/ledger.rs | 58 ----------------------- src/app/store/mod.rs | 3 +- src/app/store/records.rs | 8 ++-- 5 files changed, 128 insertions(+), 63 deletions(-) create mode 100644 src/app/ledger.rs delete mode 100644 src/app/store/ledger.rs diff --git a/src/app/ledger.rs b/src/app/ledger.rs new file mode 100644 index 0000000..d20a4fd --- /dev/null +++ b/src/app/ledger.rs @@ -0,0 +1,121 @@ +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); + } +} \ No newline at end of file diff --git a/src/app/mod.rs b/src/app/mod.rs index 6003640..b688f60 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,4 +1,5 @@ mod store; +mod ledger; use store::{Context, Store}; use miette::{Diagnostic, ErrReport, Result}; diff --git a/src/app/store/ledger.rs b/src/app/store/ledger.rs deleted file mode 100644 index df65b58..0000000 --- a/src/app/store/ledger.rs +++ /dev/null @@ -1,58 +0,0 @@ -use bigdecimal::{BigDecimal, Zero}; -use crate::app::store::records::{EquityChangeEvent, EquitySymbol, FiduciaryAccount, ROUNDING_MODE}; - -/// 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: chrono::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), - } - } - -} \ No newline at end of file diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index da01fba..2fa87a4 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -1,6 +1,5 @@ mod sqlite; -mod records; -mod ledger; +pub mod records; use sqlite::{SQLiteStore}; use miette::{Diagnostic, Result}; diff --git a/src/app/store/records.rs b/src/app/store/records.rs index 024c06e..682f676 100644 --- a/src/app/store/records.rs +++ b/src/app/store/records.rs @@ -5,6 +5,8 @@ pub static ROUNDING_MODE: RoundingMode = RoundingMode::HalfEven; /// Will use default precision (either 100 or ${RUST_BIGDECIMAL_DEFAULT_PRECISION}) pub static DECIMAL_CONTEXT: LazyLock = std::sync::LazyLock::new(|| Context::default().with_rounding_mode(ROUNDING_MODE)); +type DateTime = chrono::DateTime; + /// Represents an account that holds equity. An account may hold equity with /// multiple equity symbols. pub struct FiduciaryAccount { @@ -12,7 +14,7 @@ pub struct FiduciaryAccount { pub fiduciary_account_number: usize, pub fiduciary_account_name: String, pub fiduciary_account_description: String, - pub fiduciary_account_created_timestamp: chrono::DateTime, + pub fiduciary_account_created_timestamp: DateTime, } /// An atomic enum to describe for what element of equity a symbol tracks @@ -41,7 +43,7 @@ pub struct EquitySymbol { pub equity_symbol_name: String, pub equity_symbol_type: EquityType, pub equity_symbol_managing_company: String, - pub equity_symbol_created_timestamp: chrono::DateTime, + pub equity_symbol_created_timestamp: DateTime, } /// An atomic enum to describe the basic action of an equity change event @@ -82,7 +84,7 @@ pub struct EquityChangeEvent { pub equity_change_quantity: BigDecimal, pub equity_change_cost_basis: BigDecimal, pub equity_change_type: ChangeEventType, - pub equity_change_timestamp: chrono::DateTime, + pub equity_change_timestamp: DateTime, } impl EquityChangeEvent { -- cgit v1.3