From 9019283341ab77e9a50d2a6f2d04680224efab4a Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 23:46:40 +0530 Subject: [PATCH 1/9] added npv, compound interest and payback period --- src/financial/compound_interest.rs | 24 ++++++++++++++++ src/financial/mod.rs | 6 ++++ src/financial/npv.rs | 44 ++++++++++++++++++++++++++++++ src/financial/payback.rs | 30 ++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 src/financial/compound_interest.rs create mode 100644 src/financial/npv.rs create mode 100644 src/financial/payback.rs diff --git a/src/financial/compound_interest.rs b/src/financial/compound_interest.rs new file mode 100644 index 00000000000..d348078ddcc --- /dev/null +++ b/src/financial/compound_interest.rs @@ -0,0 +1,24 @@ +// 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(princpal: f64, rate: f64, comp_per_year: u32, years: f64) -> f64 { + let amount = princpal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years); + return amount; +} + +#[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 up to 3 decimal + // places + } +} diff --git a/src/financial/mod.rs b/src/financial/mod.rs index 89b36bfa5e0..b257aec4d7a 100644 --- a/src/financial/mod.rs +++ b/src/financial/mod.rs @@ -1,2 +1,8 @@ +mod compound_interest; +mod npv; +mod payback; mod present_value; +pub use compound_interest::compound_interest; +pub use npv::npv; +pub use payback; pub use present_value::present_value; 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/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 + } +} From c202eeeb6975504b08d1f09091daa86bc33ac61b Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 23:53:52 +0530 Subject: [PATCH 2/9] fixed mod.rs errors --- src/financial/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/financial/mod.rs b/src/financial/mod.rs index b257aec4d7a..f1e11883fea 100644 --- a/src/financial/mod.rs +++ b/src/financial/mod.rs @@ -4,5 +4,5 @@ mod payback; mod present_value; pub use compound_interest::compound_interest; pub use npv::npv; -pub use payback; +pub use payback::payback; pub use present_value::present_value; From 8f93f282553689e76b51cac146db93a06bcf5e4b Mon Sep 17 00:00:00 2001 From: omi Date: Mon, 27 Oct 2025 00:00:03 +0530 Subject: [PATCH 3/9] added algos to DIRECTORY.md --- DIRECTORY.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 7a800e41379..5edeaf0decc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -105,6 +105,9 @@ * [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) + * [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) * 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) From d29ce8c3af0fba8f65f2a174137500d3508da8fb Mon Sep 17 00:00:00 2001 From: omi Date: Mon, 27 Oct 2025 07:26:55 +0530 Subject: [PATCH 4/9] fixed typos + removed 'return' keyword --- src/financial/compound_interest.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/financial/compound_interest.rs b/src/financial/compound_interest.rs index d348078ddcc..664ea47805c 100644 --- a/src/financial/compound_interest.rs +++ b/src/financial/compound_interest.rs @@ -2,9 +2,10 @@ // 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(princpal: f64, rate: f64, comp_per_year: u32, years: f64) -> f64 { - let amount = princpal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years); - return amount; +pub fn compound_interest(principal: f64, rate: f64, comp_per_year: u32, years: f64) -> f64 { + let amount = + principal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years); + amount } #[cfg(test)] From ec6f731a35979523b5bc8121e48d8bb6b021987f Mon Sep 17 00:00:00 2001 From: omi Date: Mon, 27 Oct 2025 07:33:17 +0530 Subject: [PATCH 5/9] made the function more idiomatic to resolve clippy warnings --- src/financial/compound_interest.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/financial/compound_interest.rs b/src/financial/compound_interest.rs index 664ea47805c..e8fb7fffc33 100644 --- a/src/financial/compound_interest.rs +++ b/src/financial/compound_interest.rs @@ -3,9 +3,7 @@ // 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 { - let amount = - principal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years); - amount + principal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years) } #[cfg(test)] From 21541d8b16337b86cda2c1fa9f8e9444edf04ae6 Mon Sep 17 00:00:00 2001 From: Andrii Siriak Date: Mon, 27 Oct 2025 09:28:23 +0200 Subject: [PATCH 6/9] Update src/financial/compound_interest.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/financial/compound_interest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/financial/compound_interest.rs b/src/financial/compound_interest.rs index e8fb7fffc33..bc3bfbc23e0 100644 --- a/src/financial/compound_interest.rs +++ b/src/financial/compound_interest.rs @@ -17,7 +17,7 @@ mod tests { 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 up to 3 decimal + assert!((result - 1104.486).abs() < 0.001); // expected value rounded to 3 decimal // places } } From b629e90cfc5a1c9f45f6040527204003d181af2d Mon Sep 17 00:00:00 2001 From: omi Date: Mon, 27 Oct 2025 20:23:05 +0530 Subject: [PATCH 7/9] added some finance ratios, NPV sensitivity and Treynor Ratio to financial module --- DIRECTORY.md | 3 ++ src/financial/finance_ratios.rs | 50 ++++++++++++++++++++++++++++++++ src/financial/mod.rs | 10 +++++++ src/financial/npv_sensitivity.rs | 43 +++++++++++++++++++++++++++ src/financial/treynor_ratio.rs | 35 ++++++++++++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 src/financial/finance_ratios.rs create mode 100644 src/financial/npv_sensitivity.rs create mode 100644 src/financial/treynor_ratio.rs diff --git a/DIRECTORY.md b/DIRECTORY.md index 20d835b41e2..c6ce922d13f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -106,8 +106,11 @@ * 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/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 f1e11883fea..66fb54a3f57 100644 --- a/src/financial/mod.rs +++ b/src/financial/mod.rs @@ -1,8 +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_sensitivity.rs b/src/financial/npv_sensitivity.rs new file mode 100644 index 00000000000..a65f552e89b --- /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 = vec![-1000.00, 400.00, 400.00, 400.00]; + let rates = vec![0.05, 0.1, 0.2]; + let expected = vec![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/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()); + } +} From 4875cb6ef81e9deefd67d345fb08ca637638a8ee Mon Sep 17 00:00:00 2001 From: omi Date: Mon, 27 Oct 2025 20:30:23 +0530 Subject: [PATCH 8/9] fixed clippy warnings in npv_sensitivity.rs --- src/financial/npv_sensitivity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/financial/npv_sensitivity.rs b/src/financial/npv_sensitivity.rs index a65f552e89b..63b4d93fc8b 100644 --- a/src/financial/npv_sensitivity.rs +++ b/src/financial/npv_sensitivity.rs @@ -30,7 +30,7 @@ mod tests { use super::*; #[test] fn test_npv_sensitivity() { - let cashflows = vec![-1000.00, 400.00, 400.00, 400.00]; + let cashflows = [-1000.00, 400.00, 400.00, 400.00]; let rates = vec![0.05, 0.1, 0.2]; let expected = vec![89.30, -5.26, -157.41]; let out = npv_sensitivity(&cashflows, &rates); From dbb9beedd22652b121de3d185637b970c61facce Mon Sep 17 00:00:00 2001 From: omi Date: Mon, 27 Oct 2025 20:33:13 +0530 Subject: [PATCH 9/9] fixed clippy warnings in npv_sensitivity.rs --- src/financial/npv_sensitivity.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/financial/npv_sensitivity.rs b/src/financial/npv_sensitivity.rs index 63b4d93fc8b..24853f29907 100644 --- a/src/financial/npv_sensitivity.rs +++ b/src/financial/npv_sensitivity.rs @@ -31,8 +31,8 @@ mod tests { #[test] fn test_npv_sensitivity() { let cashflows = [-1000.00, 400.00, 400.00, 400.00]; - let rates = vec![0.05, 0.1, 0.2]; - let expected = vec![89.30, -5.26, -157.41]; + 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