Skip to content

Commit 53c0047

Browse files
committed
feat: add macaulay duration algorithm
1 parent 840ca00 commit 53c0047

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

financial/macaulay_duration.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from __future__ import annotations
2+
3+
def macaulay_duration(
4+
face_value: float,
5+
coupon_rate: float,
6+
periods: int,
7+
yield_rate: float
8+
) -> float:
9+
"""
10+
Calculates the Macaulay Duration of a bond.
11+
12+
Reference:
13+
https://www.investopedia.com/terms/m/macaulayduration.asp
14+
15+
Args:
16+
face_value: The final payout amount of the bond.
17+
coupon_rate: The annual interest rate paid by the bond.
18+
periods: The number of years until the bond matures.
19+
yield_rate: The current market interest rate used to discount future cash flows.
20+
21+
Returns:
22+
The Macaulay Duration of the bond in years.
23+
24+
>>> round(macaulay_duration(1000.0, 0.05, 8, 0.04), 2)
25+
6.83
26+
>>> round(macaulay_duration(987435.34, 0.07, 5, 0.038), 2)
27+
4.43
28+
>>> round(macaulay_duration(3564.2, 0.023, 6, 0.071), 2)
29+
5.62
30+
>>> macaulay_duration(-1000.0, 0.05, 8, 0.04)
31+
Traceback (most recent call last):
32+
...
33+
ValueError: face_value must be > 0
34+
>>> macaulay_duration(1000.0, -0.05, 8, 0.04)
35+
Traceback (most recent call last):
36+
...
37+
ValueError: coupon_rate must be >= 0
38+
>>> macaulay_duration(1000.0, 0.05, 0, 0.04)
39+
Traceback (most recent call last):
40+
...
41+
ValueError: periods must be > 0
42+
>>> macaulay_duration(1000.0, 0.05, 8, -0.04)
43+
Traceback (most recent call last):
44+
...
45+
ValueError: yield_rate must be > 0
46+
"""
47+
if face_value <= 0:
48+
raise ValueError("face_value must be > 0")
49+
if coupon_rate < 0:
50+
raise ValueError("coupon_rate must be >= 0")
51+
if periods < 1:
52+
raise ValueError("periods must be > 0")
53+
if yield_rate <= 0:
54+
raise ValueError("yield_rate must be > 0")
55+
56+
total_present_value: float = 0.0
57+
total_time_weighted_value: float = 0.0
58+
59+
for period in range(1, periods + 1):
60+
cash_flow: float = (face_value * coupon_rate) + (face_value if period == periods else 0)
61+
62+
time_weighted_value: float = (period * cash_flow) / pow(1 + yield_rate, period)
63+
total_time_weighted_value += time_weighted_value
64+
65+
present_value: float = cash_flow / pow(1 + yield_rate, period)
66+
total_present_value += present_value
67+
68+
return total_time_weighted_value / total_present_value
69+
70+
if __name__ == "__main__":
71+
import doctest
72+
73+
doctest.testmod()

0 commit comments

Comments
 (0)