diff --git a/DIRECTORY.md b/DIRECTORY.md index 4024c1c8eb0..c6ce922d13f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -105,6 +105,12 @@ * [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs) * Financial * [Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/present_value.rs) + * [Net Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv.rs) + * [NPV Sensitivity](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv_sensitivity.rs) + * [Compound Interest](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/compound_interest.rs) + * [Payback Period](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/payback.rs) + * [Finance Ratios](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/finance_ratios.rs) + * [Treynor Ratio](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/treynor_ratio.rs) * General * [Convex Hull](https://github.com/TheAlgorithms/Rust/blob/master/src/general/convex_hull.rs) * [Fisher Yates Shuffle](https://github.com/TheAlgorithms/Rust/blob/master/src/general/fisher_yates_shuffle.rs) diff --git a/src/financial/compound_interest.rs b/src/financial/compound_interest.rs new file mode 100644 index 00000000000..bc3bfbc23e0 --- /dev/null +++ b/src/financial/compound_interest.rs @@ -0,0 +1,23 @@ +// compound interest is given by A = P(1+r/n)^nt +// where: A = Final Amount, P = Principal Amount, r = rate of interest, +// n = number of times interest is compounded per year and t = time (in years) + +pub fn compound_interest(principal: f64, rate: f64, comp_per_year: u32, years: f64) -> f64 { + principal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compound_interest() { + let principal = 1000.0; + let rate = 0.05; // 5% annual interest + let times_per_year = 4; // interest compounded quarterly + let years = 2.0; // 2 years tenure + let result = compound_interest(principal, rate, times_per_year, years); + assert!((result - 1104.486).abs() < 0.001); // expected value rounded to 3 decimal + // places + } +} diff --git a/src/financial/finance_ratios.rs b/src/financial/finance_ratios.rs new file mode 100644 index 00000000000..035d3cacf8a --- /dev/null +++ b/src/financial/finance_ratios.rs @@ -0,0 +1,50 @@ +// Calculating simple ratios like Return on Investment (ROI), Debt to Equity, Gross Profit Margin +// and Earnings per Sale (EPS) +pub fn return_on_investment(gain: f64, cost: f64) -> f64 { + (gain - cost) / cost +} + +pub fn debt_to_equity(debt: f64, equity: f64) -> f64 { + debt / equity +} + +pub fn gross_profit_margin(revenue: f64, cost: f64) -> f64 { + (revenue - cost) / revenue +} + +pub fn earnings_per_sale(net_income: f64, pref_dividend: f64, share_avg: f64) -> f64 { + (net_income - pref_dividend) / share_avg +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_return_on_investment() { + // let gain = 1200, cost = 1000 thus, ROI = (1200 - 1000)/1000 = 0.2 + let result = return_on_investment(1200.0, 1000.0); + assert!((result - 0.2).abs() < 0.001); + } + + #[test] + fn test_debt_to_equity() { + // let debt = 300, equity = 150 thus, debt to equity ratio = 300/150 = 2 + let result = debt_to_equity(300.0, 150.0); + assert!((result - 2.0).abs() < 0.001); + } + + #[test] + fn test_gross_profit_margin() { + // let revenue = 1000, cost = 800 thus, gross profit margin = (1000-800)/1000 = 0.2 + let result = gross_profit_margin(1000.0, 800.0); + assert!((result - 0.2).abs() < 0.01); + } + + #[test] + fn test_earnings_per_sale() { + // let net_income = 350, pref_dividend = 50, share_avg = 25 this EPS = (350-50)/25 = 12 + let result = earnings_per_sale(350.0, 50.0, 25.0); + assert!((result - 12.0).abs() < 0.001); + } +} diff --git a/src/financial/mod.rs b/src/financial/mod.rs index 89b36bfa5e0..66fb54a3f57 100644 --- a/src/financial/mod.rs +++ b/src/financial/mod.rs @@ -1,2 +1,18 @@ +mod compound_interest; +mod finance_ratios; +mod npv; +mod npv_sensitivity; +mod payback; mod present_value; +mod treynor_ratio; +pub use compound_interest::compound_interest; +pub use npv::npv; +pub use npv_sensitivity::npv_sensitivity; +pub use payback::payback; pub use present_value::present_value; +pub use treynor_ratio::treynor_ratio; + +pub use finance_ratios::debt_to_equity; +pub use finance_ratios::earnings_per_sale; +pub use finance_ratios::gross_profit_margin; +pub use finance_ratios::return_on_investment; diff --git a/src/financial/npv.rs b/src/financial/npv.rs new file mode 100644 index 00000000000..d194fa302ff --- /dev/null +++ b/src/financial/npv.rs @@ -0,0 +1,44 @@ +/// Calculates Net Present Value given a vector of cash flows and a discount rate. +/// cash_flows: Vector of f64 representing cash flows for each period. +/// rate: Discount rate as an f64 (e.g., 0.05 for 5%) + +pub fn npv(cash_flows: &[f64], rate: f64) -> f64 { + cash_flows + .iter() + .enumerate() + .map(|(t, &cf)| cf / (1.00 + rate).powi(t as i32)) + .sum() +} + +// tests + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_npv_basic() { + let cash_flows = vec![-1000.0, 300.0, 400.0, -50.0]; + let rate = 0.10; + let result = npv(&cash_flows, rate); + // Calculated value ≈ -434.25 + assert!((result - (-434.25)).abs() < 0.05); // Allow small margin of error + } + + #[test] + fn test_npv_zero_rate() { + let cash_flows = vec![100.0, 200.0, -50.0]; + let rate = 0.0; + let result = npv(&cash_flows, rate); + assert!((result - 250.0).abs() < 0.05); + } + + #[test] + fn test_npv_empty() { + // For empty cash flows: NPV should be 0 + let cash_flows: Vec = vec![]; + let rate = 0.05; + let result = npv(&cash_flows, rate); + assert_eq!(result, 0.0); + } +} diff --git a/src/financial/npv_sensitivity.rs b/src/financial/npv_sensitivity.rs new file mode 100644 index 00000000000..24853f29907 --- /dev/null +++ b/src/financial/npv_sensitivity.rs @@ -0,0 +1,43 @@ +/// Computes the Net Present Value (NPV) of a cash flow series +/// at multiple discount rates to show sensitivity. +/// +/// # Inputs: +/// - `cash_flows`: A slice of cash flows, where each entry is a period value +/// e.g., year 0 is initial investment, year 1+ are returns or costs +/// - `discount_rates`: A slice of discount rates, e.g. `[0.05, 0.10, 0.20]`, +/// where each rate is evaluated independently. +/// +/// # Output: +/// - Returns a vector of NPV values, each corresponding to a rate in `discount_rates`. +/// For example, output is `[npv_rate1, npv_rate2, ...]`. + +pub fn npv_sensitivity(cash_flows: &[f64], discount_rates: &[f64]) -> Vec { + discount_rates + .iter() + .cloned() + .map(|rate| { + cash_flows + .iter() + .enumerate() + .map(|(t, &cf)| cf / (1.0 + rate).powi(t as i32)) + .sum() + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_npv_sensitivity() { + let cashflows = [-1000.00, 400.00, 400.00, 400.00]; + let rates = [0.05, 0.1, 0.2]; + let expected = [89.30, -5.26, -157.41]; + let out = npv_sensitivity(&cashflows, &rates); + assert_eq!(out.len(), 3); + // value check + for (o, e) in out.iter().zip(expected.iter()) { + assert!((o - e).abs() < 0.1); + } + } +} diff --git a/src/financial/payback.rs b/src/financial/payback.rs new file mode 100644 index 00000000000..012e50c503a --- /dev/null +++ b/src/financial/payback.rs @@ -0,0 +1,30 @@ +/// Returns the payback period in years +/// If investment is not paid back, returns None. + +pub fn payback(cash_flow: &[f64]) -> Option { + let mut total = 0.00; + for (year, &cf) in cash_flow.iter().enumerate() { + total += cf; + if total >= 0.00 { + return Some(year); + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_payback() { + let cash_flows = vec![-1000.0, 300.0, 400.0, 500.0]; + assert_eq!(payback(&cash_flows), Some(3)); // paid back in year 3 + } + + #[test] + fn test_no_payback() { + let cash_flows = vec![-1000.0, 100.0, 100.0, 100.0]; + assert_eq!(payback(&cash_flows), None); // never paid back + } +} diff --git a/src/financial/treynor_ratio.rs b/src/financial/treynor_ratio.rs new file mode 100644 index 00000000000..d53d7246f72 --- /dev/null +++ b/src/financial/treynor_ratio.rs @@ -0,0 +1,35 @@ +/// Calculates the Treynor Ratio for a portfolio. +/// +/// # Inputs +/// - `portfolio_return`: Portfolio return +/// - `risk_free_rate`: Risk-free rate +/// - `beta`: Portfolio beta +/// where Beta is a financial metric that measures the systematic risk of a security or portfolio compared to the overall market. +/// +/// # Output +/// - Returns excess return per unit of market risk +pub fn treynor_ratio(portfolio_return: f64, risk_free_rate: f64, beta: f64) -> f64 { + if beta == 0.0 { + f64::NAN + } else { + (portfolio_return - risk_free_rate) / beta + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_treynor_ratio() { + // for portfolio_return = 0.10, risk_free_rate = 0.05, beta = 1.5 + // expected result: (0.10 - 0.05) / 1.5 = 0.033333... + assert!((treynor_ratio(0.10, 0.05, 1.50) - 0.03333).abs() < 0.01); + } + + #[test] + fn test_treynor_ratio_empty_beta() { + // test for zero beta (undefined ratio) + assert!(treynor_ratio(0.10, 0.05, 0.00).is_nan()); + } +}