1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
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> {
/// The reference to the fiduciary account that owns the events of the ledger
pub fiduciary_account: &'a FiduciaryAccount,
/// The reference to the equity symbol that all events describe
pub equity_symbol: &'a EquitySymbol,
/// The list of references to events that compose the ledger
pub equity_change_events: Vec<&'a EquityChangeEvent>,
/// The quantity of shares derived from all events for the account and symbol
pub account_symbol_quantity: BigDecimal,
/// The estimated price of all events in the ledger derived from the cost basis and quantity
pub account_symbol_cost_basis_price: BigDecimal,
/// The basis of all purchases and sales not derived from quantity
pub account_symbol_cost_basis_total: BigDecimal,
/// The estimated price of the equity from the latest event in the ledger
pub account_symbol_last_effective_price: BigDecimal,
/// The event timestmap of the latest event in the ledger
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 = if quantity.is_zero() { BigDecimal::zero() } else { cost_basis_total.clone() / quantity.clone() };
EstimatedLedger {
fiduciary_account,
equity_symbol,
equity_change_events: acsym_events,
account_symbol_quantity: quantity,
account_symbol_cost_basis_price: cost_basis_price.with_scale_round(2, ROUNDING_MODE),
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(|| DateTime::MIN_UTC, |ev| ev.equity_change_timestamp),
}
}
}
mod test {
use crate::app::store::records::*;
use super::*;
/// simple generic to provide auto-incrementing value for the ID type that's needed
fn next<T : std::ops::AddAssign + From::<bool> + Copy>(id: &mut T) -> T {
let tmp = *id;
*id += T::from(true);
tmp
}
fn dec(n: u32, scale: i64) -> BigDecimal {
BigDecimal::from_biguint(bigdecimal::num_bigint::BigUint::from(n), scale)
}
#[test]
fn compile_change_events_small() {
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_timestamp_secs(1761399660).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_timestamp_secs(1767346990).unwrap(),
};
let evid = &mut 0_usize;
let events = vec![
EquityChangeEvent {
equity_change_event_id: next(evid),
fiduciary_account_id: account.fiduciary_account_id,
equity_symbol_id: symbol.equity_symbol_id,
equity_change_quantity: dec(125_u32, 3),
equity_change_cost_basis: dec(8280_u32, 2),
equity_change_type: ChangeEventType::BUY,
equity_change_timestamp: DateTime::from_timestamp_secs(1767346990).unwrap(),
},
EquityChangeEvent { // different symbol, same account
equity_change_event_id: next(evid),
fiduciary_account_id: account.fiduciary_account_id,
equity_symbol_id: symbol.equity_symbol_id + 1,
equity_change_quantity: dec(10001_u32, 2),
equity_change_cost_basis: dec(545_u32, 2),
equity_change_type: ChangeEventType::BUY,
equity_change_timestamp: DateTime::from_timestamp_secs(1767350590).unwrap(),
},
EquityChangeEvent {
equity_change_event_id: next(evid),
fiduciary_account_id: account.fiduciary_account_id,
equity_symbol_id: symbol.equity_symbol_id,
equity_change_quantity: dec(15_u32, 3),
equity_change_cost_basis: dec(998_u32, 2),
equity_change_type: ChangeEventType::SELL,
equity_change_timestamp: DateTime::from_timestamp_secs(1767354190).unwrap(),
}
];
let event_refs = vec![&events[0], &events[1], &events[2]];
let ledger = EstimatedLedger::compile_change_events(&account, &symbol, event_refs);
// Check account, symbol, events references of ledger are what expected
assert_eq!(ledger.fiduciary_account.fiduciary_account_id, account.fiduciary_account_id);
assert_eq!(ledger.equity_symbol.equity_symbol_id, symbol.equity_symbol_id);
assert_eq!(ledger.equity_change_events.len(), 2);
assert_eq!(ledger.equity_change_events[0].equity_change_event_id, events[0].equity_change_event_id);
assert_eq!(ledger.equity_change_events[1].equity_change_event_id, events[2].equity_change_event_id);
// Check computed values
assert_eq!(ledger.account_symbol_quantity.to_plain_string(), "0.110");
assert_eq!(ledger.account_symbol_cost_basis_total.to_plain_string(), "72.82");
assert_eq!(ledger.account_symbol_cost_basis_price.to_plain_string(), "662.00");
// "665.33", repeating, of course
assert_eq!(ledger.account_symbol_last_effective_price, events[2].estimated_price());
assert_eq!(ledger.account_symbol_last_update_timestamp, events[2].equity_change_timestamp);
}
#[test]
fn compile_change_events_empty() {
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_timestamp_secs(1761399660).unwrap(),
};
let symbol = EquitySymbol {
equity_symbol_id: 0,
equity_symbol_name: "US Equity ETF".into(),
equity_symbol_managing_company: "Business Company".into(),
equity_symbol_type: EquityType::ETF,
equity_symbol_created_timestamp: DateTime::from_timestamp_secs(1767346990).unwrap(),
};
let events: Vec<&EquityChangeEvent> = vec![];
let ledger = EstimatedLedger::compile_change_events(&account, &symbol, events);
// Check account, symbol, events references of ledger are what expected
assert_eq!(ledger.fiduciary_account.fiduciary_account_id, account.fiduciary_account_id);
assert_eq!(ledger.equity_symbol.equity_symbol_id, symbol.equity_symbol_id);
assert!(ledger.equity_change_events.is_empty());
// Check computed values
assert_eq!(ledger.account_symbol_quantity.to_plain_string(), "0");
assert_eq!(ledger.account_symbol_cost_basis_total.to_plain_string(), "0.00");
assert_eq!(ledger.account_symbol_cost_basis_price.to_plain_string(), "0.00");
assert_eq!(ledger.account_symbol_last_effective_price.to_plain_string(), "0.00");
assert_eq!(ledger.account_symbol_last_update_timestamp, DateTime::MIN_UTC);
}
}
|