Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bd8418f

Browse files
feat: Added basic finance ratios, NPV sensitivity and Treynor ratio to the financial module (TheAlgorithms#937)
1 parent 83ad4d9 commit bd8418f

File tree

8 files changed

+247
-0
lines changed

8 files changed

+247
-0
lines changed

‎DIRECTORY.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@
105105
* [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs)
106106
* Financial
107107
* [Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/present_value.rs)
108+
* [Net Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv.rs)
109+
* [NPV Sensitivity](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv_sensitivity.rs)
110+
* [Compound Interest](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/compound_interest.rs)
111+
* [Payback Period](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/payback.rs)
112+
* [Finance Ratios](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/finance_ratios.rs)
113+
* [Treynor Ratio](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/treynor_ratio.rs)
108114
* General
109115
* [Convex Hull](https://github.com/TheAlgorithms/Rust/blob/master/src/general/convex_hull.rs)
110116
* [Fisher Yates Shuffle](https://github.com/TheAlgorithms/Rust/blob/master/src/general/fisher_yates_shuffle.rs)

‎src/financial/compound_interest.rs‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// compound interest is given by A = P(1+r/n)^nt
2+
// where: A = Final Amount, P = Principal Amount, r = rate of interest,
3+
// n = number of times interest is compounded per year and t = time (in years)
4+
5+
pub fn compound_interest(principal: f64, rate: f64, comp_per_year: u32, years: f64) -> f64 {
6+
principal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years)
7+
}
8+
9+
#[cfg(test)]
10+
mod tests {
11+
use super::*;
12+
13+
#[test]
14+
fn test_compound_interest() {
15+
let principal = 1000.0;
16+
let rate = 0.05; // 5% annual interest
17+
let times_per_year = 4; // interest compounded quarterly
18+
let years = 2.0; // 2 years tenure
19+
let result = compound_interest(principal, rate, times_per_year, years);
20+
assert!((result - 1104.486).abs() < 0.001); // expected value rounded to 3 decimal
21+
// places
22+
}
23+
}

‎src/financial/finance_ratios.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Calculating simple ratios like Return on Investment (ROI), Debt to Equity, Gross Profit Margin
2+
// and Earnings per Sale (EPS)
3+
pub fn return_on_investment(gain: f64, cost: f64) -> f64 {
4+
(gain - cost) / cost
5+
}
6+
7+
pub fn debt_to_equity(debt: f64, equity: f64) -> f64 {
8+
debt / equity
9+
}
10+
11+
pub fn gross_profit_margin(revenue: f64, cost: f64) -> f64 {
12+
(revenue - cost) / revenue
13+
}
14+
15+
pub fn earnings_per_sale(net_income: f64, pref_dividend: f64, share_avg: f64) -> f64 {
16+
(net_income - pref_dividend) / share_avg
17+
}
18+
19+
#[cfg(test)]
20+
mod tests {
21+
use super::*;
22+
23+
#[test]
24+
fn test_return_on_investment() {
25+
// let gain = 1200, cost = 1000 thus, ROI = (1200 - 1000)/1000 = 0.2
26+
let result = return_on_investment(1200.0, 1000.0);
27+
assert!((result - 0.2).abs() < 0.001);
28+
}
29+
30+
#[test]
31+
fn test_debt_to_equity() {
32+
// let debt = 300, equity = 150 thus, debt to equity ratio = 300/150 = 2
33+
let result = debt_to_equity(300.0, 150.0);
34+
assert!((result - 2.0).abs() < 0.001);
35+
}
36+
37+
#[test]
38+
fn test_gross_profit_margin() {
39+
// let revenue = 1000, cost = 800 thus, gross profit margin = (1000-800)/1000 = 0.2
40+
let result = gross_profit_margin(1000.0, 800.0);
41+
assert!((result - 0.2).abs() < 0.01);
42+
}
43+
44+
#[test]
45+
fn test_earnings_per_sale() {
46+
// let net_income = 350, pref_dividend = 50, share_avg = 25 this EPS = (350-50)/25 = 12
47+
let result = earnings_per_sale(350.0, 50.0, 25.0);
48+
assert!((result - 12.0).abs() < 0.001);
49+
}
50+
}

‎src/financial/mod.rs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,18 @@
1+
mod compound_interest;
2+
mod finance_ratios;
3+
mod npv;
4+
mod npv_sensitivity;
5+
mod payback;
16
mod present_value;
7+
mod treynor_ratio;
8+
pub use compound_interest::compound_interest;
9+
pub use npv::npv;
10+
pub use npv_sensitivity::npv_sensitivity;
11+
pub use payback::payback;
212
pub use present_value::present_value;
13+
pub use treynor_ratio::treynor_ratio;
14+
15+
pub use finance_ratios::debt_to_equity;
16+
pub use finance_ratios::earnings_per_sale;
17+
pub use finance_ratios::gross_profit_margin;
18+
pub use finance_ratios::return_on_investment;

‎src/financial/npv.rs‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/// Calculates Net Present Value given a vector of cash flows and a discount rate.
2+
/// cash_flows: Vector of f64 representing cash flows for each period.
3+
/// rate: Discount rate as an f64 (e.g., 0.05 for 5%)
4+
5+
pub fn npv(cash_flows: &[f64], rate: f64) -> f64 {
6+
cash_flows
7+
.iter()
8+
.enumerate()
9+
.map(|(t, &cf)| cf / (1.00 + rate).powi(t as i32))
10+
.sum()
11+
}
12+
13+
// tests
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use super::*;
18+
19+
#[test]
20+
fn test_npv_basic() {
21+
let cash_flows = vec![-1000.0, 300.0, 400.0, -50.0];
22+
let rate = 0.10;
23+
let result = npv(&cash_flows, rate);
24+
// Calculated value ≈ -434.25
25+
assert!((result - (-434.25)).abs() < 0.05); // Allow small margin of error
26+
}
27+
28+
#[test]
29+
fn test_npv_zero_rate() {
30+
let cash_flows = vec![100.0, 200.0, -50.0];
31+
let rate = 0.0;
32+
let result = npv(&cash_flows, rate);
33+
assert!((result - 250.0).abs() < 0.05);
34+
}
35+
36+
#[test]
37+
fn test_npv_empty() {
38+
// For empty cash flows: NPV should be 0
39+
let cash_flows: Vec<f64> = vec![];
40+
let rate = 0.05;
41+
let result = npv(&cash_flows, rate);
42+
assert_eq!(result, 0.0);
43+
}
44+
}

‎src/financial/npv_sensitivity.rs‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/// Computes the Net Present Value (NPV) of a cash flow series
2+
/// at multiple discount rates to show sensitivity.
3+
///
4+
/// # Inputs:
5+
/// - `cash_flows`: A slice of cash flows, where each entry is a period value
6+
/// e.g., year 0 is initial investment, year 1+ are returns or costs
7+
/// - `discount_rates`: A slice of discount rates, e.g. `[0.05, 0.10, 0.20]`,
8+
/// where each rate is evaluated independently.
9+
///
10+
/// # Output:
11+
/// - Returns a vector of NPV values, each corresponding to a rate in `discount_rates`.
12+
/// For example, output is `[npv_rate1, npv_rate2, ...]`.
13+
14+
pub fn npv_sensitivity(cash_flows: &[f64], discount_rates: &[f64]) -> Vec<f64> {
15+
discount_rates
16+
.iter()
17+
.cloned()
18+
.map(|rate| {
19+
cash_flows
20+
.iter()
21+
.enumerate()
22+
.map(|(t, &cf)| cf / (1.0 + rate).powi(t as i32))
23+
.sum()
24+
})
25+
.collect()
26+
}
27+
28+
#[cfg(test)]
29+
mod tests {
30+
use super::*;
31+
#[test]
32+
fn test_npv_sensitivity() {
33+
let cashflows = [-1000.00, 400.00, 400.00, 400.00];
34+
let rates = [0.05, 0.1, 0.2];
35+
let expected = [89.30, -5.26, -157.41];
36+
let out = npv_sensitivity(&cashflows, &rates);
37+
assert_eq!(out.len(), 3);
38+
// value check
39+
for (o, e) in out.iter().zip(expected.iter()) {
40+
assert!((o - e).abs() < 0.1);
41+
}
42+
}
43+
}

‎src/financial/payback.rs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/// Returns the payback period in years
2+
/// If investment is not paid back, returns None.
3+
4+
pub fn payback(cash_flow: &[f64]) -> Option<usize> {
5+
let mut total = 0.00;
6+
for (year, &cf) in cash_flow.iter().enumerate() {
7+
total += cf;
8+
if total >= 0.00 {
9+
return Some(year);
10+
}
11+
}
12+
None
13+
}
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use super::*;
18+
19+
#[test]
20+
fn test_payback() {
21+
let cash_flows = vec![-1000.0, 300.0, 400.0, 500.0];
22+
assert_eq!(payback(&cash_flows), Some(3)); // paid back in year 3
23+
}
24+
25+
#[test]
26+
fn test_no_payback() {
27+
let cash_flows = vec![-1000.0, 100.0, 100.0, 100.0];
28+
assert_eq!(payback(&cash_flows), None); // never paid back
29+
}
30+
}

‎src/financial/treynor_ratio.rs‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// Calculates the Treynor Ratio for a portfolio.
2+
///
3+
/// # Inputs
4+
/// - `portfolio_return`: Portfolio return
5+
/// - `risk_free_rate`: Risk-free rate
6+
/// - `beta`: Portfolio beta
7+
/// where Beta is a financial metric that measures the systematic risk of a security or portfolio compared to the overall market.
8+
///
9+
/// # Output
10+
/// - Returns excess return per unit of market risk
11+
pub fn treynor_ratio(portfolio_return: f64, risk_free_rate: f64, beta: f64) -> f64 {
12+
if beta == 0.0 {
13+
f64::NAN
14+
} else {
15+
(portfolio_return - risk_free_rate) / beta
16+
}
17+
}
18+
19+
#[cfg(test)]
20+
mod tests {
21+
use super::*;
22+
23+
#[test]
24+
fn test_treynor_ratio() {
25+
// for portfolio_return = 0.10, risk_free_rate = 0.05, beta = 1.5
26+
// expected result: (0.10 - 0.05) / 1.5 = 0.033333...
27+
assert!((treynor_ratio(0.10, 0.05, 1.50) - 0.03333).abs() < 0.01);
28+
}
29+
30+
#[test]
31+
fn test_treynor_ratio_empty_beta() {
32+
// test for zero beta (undefined ratio)
33+
assert!(treynor_ratio(0.10, 0.05, 0.00).is_nan());
34+
}
35+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /