use bigdecimal::{BigDecimal, Context, RoundingMode}; use std::sync::LazyLock; static ROUNDING_MODE: RoundingMode = RoundingMode::HalfEven; /// Will use default precision (either 100 or ${RUST_BIGDECIMAL_DEFAULT_PRECISION}) static DECIMAL_CONTEXT: LazyLock = std::sync::LazyLock::new(|| Context::default().with_rounding_mode(ROUNDING_MODE)); /// Represents an account that holds equity. An account may hold equity with /// multiple equity symbols. pub struct FiduciaryAccount { pub fiduciary_account_id: u8, pub fiduciary_account_number: usize, pub fiduciary_account_name: String, pub fiduciary_account_description: String, pub fiduciary_account_created_timestamp: chrono::DateTime, } /// An atomic enum to describe for what element of equity a symbol tracks /// (stock, fund, etc.) pub enum EquityType { STOCK, ETF, CRYPTO } impl std::fmt::Display for EquityType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", match self { EquityType::STOCK => "STOCK", EquityType::ETF => "ETF", EquityType::CRYPTO => "CRYPTO", }) } } /// Represents a symbol used to ascribe speculative value pub struct EquitySymbol { pub equity_symbol_id: usize, pub equity_symbol_name: String, pub equity_symbol_type: EquityType, pub equity_symbol_managing_company: String, pub equity_symbol_created_timestamp: chrono::DateTime, } /// An atomic enum to describe the basic action of an equity change event pub enum ChangeEventType { BUY, SELL } impl ChangeEventType { /// Relativizes the effect of a change event for numeration pub fn score(&self) -> i8 { match self { ChangeEventType::BUY => 1, ChangeEventType::SELL => -1, } } } impl std::fmt::Display for ChangeEventType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", match self { ChangeEventType::BUY => "BUY", ChangeEventType::SELL => "SELL", }) } } /// An immutable record describing a quantative change for a symbolized /// equity attributed to an account. pub struct EquityChangeEvent { pub equity_change_event_id: usize, pub fiduciary_account_id: u8, pub equity_symbol_id: usize, pub equity_change_quantity: BigDecimal, pub equity_change_cost_basis_usd: BigDecimal, pub equity_change_type: ChangeEventType, pub equity_change_timestamp: chrono::DateTime, } impl EquityChangeEvent { /// Provides an estimate of the numerated change based on the cost basis /// of the equity and the quantity pub fn estimated_price(&self) -> BigDecimal { DECIMAL_CONTEXT.multiply(&self.equity_change_cost_basis_usd, &self.equity_change_quantity) } } mod tests { use super::*; use std::str::FromStr; fn nat(num: BigDecimal) -> BigDecimal { num.with_scale_round(2_i64, ROUNDING_MODE) } #[test] fn event_type_score_buy() { let eventType = ChangeEventType::BUY; assert_eq!(eventType.score(), 1_i8); } #[test] fn event_type_score_sell() { let eventType = ChangeEventType::SELL; assert_eq!(eventType.score(), -1_i8); } #[test] fn change_event_estimated_price() { let event = 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_usd: BigDecimal::from_str("82.80").unwrap(), equity_change_type: ChangeEventType::BUY, equity_change_timestamp: chrono::Local::now().to_utc(), }; assert_eq!(nat(event.estimated_price()).to_plain_string(), "10.35"); } #[test] fn change_event_estimated_price_rounded() { let event = EquityChangeEvent { equity_change_event_id: 0, fiduciary_account_id: 0, equity_symbol_id: 0, equity_change_quantity: BigDecimal::from_str("87.12808").unwrap(), equity_change_cost_basis_usd: BigDecimal::from_str("14.91").unwrap(), equity_change_type: ChangeEventType::BUY, equity_change_timestamp: chrono::Local::now().to_utc(), }; // actual: 1299.0796728 assert_eq!(nat(event.estimated_price()).to_plain_string(), "1299.08"); } }