Skip to content
6 changes: 6 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 23 additions & 0 deletions src/financial/compound_interest.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
50 changes: 50 additions & 0 deletions src/financial/finance_ratios.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
16 changes: 16 additions & 0 deletions src/financial/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
44 changes: 44 additions & 0 deletions src/financial/npv.rs
Original file line number Diff line number Diff line change
@@ -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<f64> = vec![];
let rate = 0.05;
let result = npv(&cash_flows, rate);
assert_eq!(result, 0.0);
}
}
43 changes: 43 additions & 0 deletions src/financial/npv_sensitivity.rs
Original file line number Diff line number Diff line change
@@ -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<f64> {
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);
}
}
}
30 changes: 30 additions & 0 deletions src/financial/payback.rs
Original file line number Diff line number Diff line change
@@ -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<usize> {
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
}
}
35 changes: 35 additions & 0 deletions src/financial/treynor_ratio.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}