aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Hoerr <kjhoerr@submelon.dev>2026-04-17 09:18:00 -0400
committerKevin Hoerr <kjhoerr@submelon.dev>2026-04-17 09:18:00 -0400
commit754f2aab52542f0ff9097a9e7abe863d78b34779 (patch)
treed1040b9b89f408b9869e718b2379abc47f4b0916
parentfb43ec916a6ebb4142717eca5a09829636f926bf (diff)
downloadequity-tracker-754f2aab52542f0ff9097a9e7abe863d78b34779.tar.gz
equity-tracker-754f2aab52542f0ff9097a9e7abe863d78b34779.tar.bz2
equity-tracker-754f2aab52542f0ff9097a9e7abe863d78b34779.zip
Reroll
-rw-r--r--src/app/ledger.rs121
-rw-r--r--src/app/mod.rs1
-rw-r--r--src/app/store/ledger.rs58
-rw-r--r--src/app/store/mod.rs3
-rw-r--r--src/app/store/records.rs8
5 files changed, 128 insertions, 63 deletions
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<chrono::Utc>;
+
+/// 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::<chrono::Utc>::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<chrono::Utc>,
-}
-
-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::<chrono::Utc>::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<Context> = std::sync::LazyLock::new(|| Context::default().with_rounding_mode(ROUNDING_MODE));
+type DateTime = chrono::DateTime<chrono::Utc>;
+
/// 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<chrono::Utc>,
+ 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<chrono::Utc>,
+ 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<chrono::Utc>,
+ pub equity_change_timestamp: DateTime,
}
impl EquityChangeEvent {