-
-
Notifications
You must be signed in to change notification settings - Fork 368
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce the Fixed Income Class (WIP)
- Loading branch information
Showing
10 changed files
with
1,687 additions
and
456 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,355 @@ | ||
"""Bond Model Module""" | ||
import numpy as np | ||
|
||
|
||
def get_bond_price( | ||
par_value: float, | ||
coupon_rate: float, | ||
years_to_maturity: float, | ||
yield_to_maturity: float, | ||
frequency: int = 1, | ||
): | ||
""" | ||
Calculate the price of a bond. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (int): The number of years until the bond matures. | ||
yield_to_maturity (float): The yield to maturity of the bond (in decimal). | ||
Returns: | ||
float: The price of the bond. | ||
""" | ||
coupon_payment = (par_value * coupon_rate) / frequency | ||
total_periods = int(years_to_maturity * frequency) | ||
present_value: int | float = 0 | ||
|
||
# Calculate the present value of coupon payments | ||
for t in range(1, total_periods + 1): | ||
present_value += coupon_payment / ((1 + yield_to_maturity / frequency) ** t) | ||
|
||
# Add the present value of the face value (at maturity) | ||
present_value += par_value / ((1 + yield_to_maturity / frequency) ** total_periods) | ||
|
||
return present_value | ||
|
||
|
||
def get_current_yield(par_value: float, coupon_rate: float, bond_price: float): | ||
""" | ||
Calculate the current yield of a bond. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
bond_price (float): The current market price of the bond. | ||
Returns: | ||
float: The current yield of the bond. | ||
""" | ||
current_yield = (coupon_rate * par_value) / bond_price | ||
|
||
return current_yield | ||
|
||
|
||
def get_yield_to_maturity( | ||
par_value: float, | ||
coupon_rate: float, | ||
years_to_maturity: float, | ||
bond_price: float, | ||
frequency: int, | ||
guess: float = 0.05, | ||
tolerance: float = 0.0001, | ||
max_iterations: int = 100, | ||
): | ||
""" | ||
Calculate the yield to maturity of a bond using the secant method. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (int): The number of years until the bond matures. | ||
bond_price (float): The current market price of the bond. | ||
frequency (int): The number of coupon payments per year. | ||
guess (float): Initial guess for the yield to maturity. | ||
tolerance (float): The desired level of accuracy. | ||
max_iterations (int): Maximum number of iterations to perform. | ||
Returns: | ||
float: The yield to maturity of the bond. | ||
""" | ||
|
||
# Define the function to solve | ||
def bond_value(ytm): | ||
value = 0 | ||
total_periods = int(years_to_maturity * frequency) | ||
for t in range(1, total_periods + 1): | ||
value += coupon_rate * par_value / frequency / ((1 + ytm / frequency) ** t) | ||
value += par_value / ((1 + ytm / frequency) ** total_periods) | ||
return value - bond_price | ||
|
||
# Initial values | ||
ytm0 = guess | ||
ytm1 = guess * 1.1 # Slightly higher guess for the secant method | ||
|
||
# Iterative process using the secant method | ||
for _ in range(max_iterations): | ||
ytm_next = ytm1 - bond_value(ytm1) * (ytm1 - ytm0) / ( | ||
bond_value(ytm1) - bond_value(ytm0) | ||
) | ||
if abs(ytm_next - ytm1) < tolerance: | ||
return ytm_next | ||
ytm0 = ytm1 | ||
ytm1 = ytm_next | ||
|
||
# If the method fails to converge | ||
return np.nan | ||
|
||
|
||
def get_macaulays_duration( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
): | ||
""" | ||
Calculate Macaulay's duration of a bond. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (float): The number of years until the bond matures. | ||
yield_to_maturity (float): The yield to maturity of the bond (in decimal). | ||
frequency (int): The number of coupon payments per year. | ||
Returns: | ||
float: The Macaulay's duration of the bond. | ||
""" | ||
total_periods = int(years_to_maturity * frequency) | ||
present_value_sum = 0 | ||
cash_flow_weighted_sum = 0 | ||
|
||
# Calculate present value of each cash flow and the sum of present values | ||
for t in range(1, total_periods + 1): | ||
coupon_payment = (par_value * coupon_rate) / frequency | ||
present_value = coupon_payment / ( | ||
(1 + yield_to_maturity / frequency) ** (t / frequency) | ||
) | ||
present_value_sum += present_value | ||
cash_flow_weighted_sum += (t / frequency) * present_value | ||
|
||
# Add the present value of the face value (at maturity) | ||
present_value_sum += par_value / ( | ||
(1 + yield_to_maturity / frequency) ** years_to_maturity | ||
) | ||
cash_flow_weighted_sum += years_to_maturity * ( | ||
par_value / ((1 + yield_to_maturity / frequency) ** years_to_maturity) | ||
) | ||
|
||
# Calculate Macaulay's duration | ||
macaulays_duration = cash_flow_weighted_sum / present_value_sum | ||
|
||
return macaulays_duration | ||
|
||
|
||
def get_modified_duration( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
): | ||
""" | ||
Calculate modified duration of a bond. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (float): The number of years until the bond matures. | ||
yield_to_maturity (float): The yield to maturity of the bond (in decimal). | ||
frequency (int): The number of coupon payments per year. | ||
Returns: | ||
float: The modified duration of the bond. | ||
""" | ||
macaulays_duration = get_macaulays_duration( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
) | ||
|
||
# Calculate modified duration | ||
modified_duration = macaulays_duration / (1 + yield_to_maturity / frequency) | ||
|
||
return modified_duration | ||
|
||
|
||
def get_effective_duration( | ||
par_value, | ||
coupon_rate, | ||
years_to_maturity, | ||
yield_to_maturity, | ||
frequency, | ||
yield_change=0.01, | ||
): | ||
""" | ||
Calculate the effective duration of a bond. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (float): The number of years until the bond matures. | ||
yield_to_maturity (float): The initial yield to maturity of the bond (in decimal). | ||
yield_change (float): The change in yield (in decimal). | ||
frequency (int): The number of coupon payments per year. | ||
Returns: | ||
float: The effective duration of the bond. | ||
""" | ||
# Calculate bond price at initial yield | ||
initial_price = get_bond_price( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
) | ||
|
||
# Calculate bond price at yield change | ||
new_yield = yield_to_maturity + yield_change | ||
new_price = get_bond_price( | ||
par_value, coupon_rate, years_to_maturity, new_yield, frequency | ||
) | ||
|
||
# Calculate effective duration | ||
effective_duration = -((new_price - initial_price) / (initial_price * yield_change)) | ||
|
||
return effective_duration | ||
|
||
|
||
def get_dollar_duration( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
): | ||
""" | ||
Calculate the bond's dollar duration. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (float): The number of years until the bond matures. | ||
yield_to_maturity (float): The yield to maturity of the bond (in decimal). | ||
frequency (int): The number of coupon payments per year. | ||
Returns: | ||
float: The dollar duration of the bond. | ||
""" | ||
# Calculate modified duration | ||
modified_duration = get_modified_duration( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
) | ||
|
||
# Calculate bond price | ||
bond_price = get_bond_price( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
) | ||
|
||
# Calculate dollar duration | ||
dollar_duration = modified_duration * bond_price / 100 | ||
|
||
return dollar_duration | ||
|
||
|
||
def get_dv01(par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency): | ||
""" | ||
Calculate DV01 (Dollar Value of 01) of a bond. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (float): The number of years until the bond matures. | ||
yield_to_maturity (float): The yield to maturity of the bond (in decimal). | ||
frequency (int): The number of coupon payments per year. | ||
Returns: | ||
float: The DV01 of the bond. | ||
""" | ||
# Calculate present value of each cash flow | ||
total_periods = int(years_to_maturity * frequency) | ||
present_value_sum = 0 | ||
|
||
for t in range(1, total_periods + 1): | ||
coupon_payment = (par_value * coupon_rate) / frequency | ||
present_value = coupon_payment / ( | ||
(1 + yield_to_maturity / frequency) ** (t / frequency) | ||
) | ||
present_value_sum += present_value | ||
|
||
# Add the present value of the face value (at maturity) | ||
present_value_sum += par_value / ( | ||
(1 + yield_to_maturity / frequency) ** years_to_maturity | ||
) | ||
|
||
# Calculate bond price when yield decreases by 1 basis point | ||
yield_decreased = yield_to_maturity - 0.0001 # 1 basis point decrease | ||
present_value_sum_decreased = 0 | ||
|
||
for t in range(1, total_periods + 1): | ||
coupon_payment = (par_value * coupon_rate) / frequency | ||
present_value = coupon_payment / ( | ||
(1 + yield_decreased / frequency) ** (t / frequency) | ||
) | ||
present_value_sum_decreased += present_value | ||
|
||
# Add the present value of the face value (at maturity) | ||
present_value_sum_decreased += par_value / ( | ||
(1 + yield_decreased / frequency) ** years_to_maturity | ||
) | ||
|
||
# Calculate bond price when yield increases by 1 basis point | ||
yield_increased = yield_to_maturity + 0.0001 # 1 basis point increase | ||
present_value_sum_increased = 0 | ||
|
||
for t in range(1, total_periods + 1): | ||
coupon_payment = (par_value * coupon_rate) / frequency | ||
present_value = coupon_payment / ( | ||
(1 + yield_increased / frequency) ** (t / frequency) | ||
) | ||
present_value_sum_increased += present_value | ||
|
||
# Add the present value of the face value (at maturity) | ||
present_value_sum_increased += par_value / ( | ||
(1 + yield_increased / frequency) ** years_to_maturity | ||
) | ||
|
||
# Calculate the change in bond price for a 1 basis point change in yield | ||
dv01 = -0.01 * (present_value_sum_increased - present_value_sum_decreased) / 2 | ||
|
||
return dv01 | ||
|
||
|
||
def get_convexity( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
): | ||
""" | ||
Calculate the convexity of a bond. | ||
Args: | ||
par_value (float): The face value of the bond. | ||
coupon_rate (float): The annual coupon rate (in decimal). | ||
years_to_maturity (float): The number of years until the bond matures. | ||
yield_to_maturity (float): The yield to maturity of the bond (in decimal). | ||
frequency (int): The number of coupon payments per year. | ||
Returns: | ||
float: The convexity of the bond. | ||
""" | ||
# Calculate bond price at current yield | ||
bond_price = get_bond_price( | ||
par_value, coupon_rate, years_to_maturity, yield_to_maturity, frequency | ||
) | ||
|
||
# Calculate bond price when yield decreases by 1 basis point | ||
yield_decreased = yield_to_maturity - 0.0001 # 1 basis point decrease | ||
bond_price_down = get_bond_price( | ||
par_value, coupon_rate, years_to_maturity, yield_decreased, frequency | ||
) | ||
|
||
# Calculate bond price when yield increases by 1 basis point | ||
yield_increased = yield_to_maturity + 0.0001 # 1 basis point increase | ||
bond_price_up = get_bond_price( | ||
par_value, coupon_rate, years_to_maturity, yield_increased, frequency | ||
) | ||
|
||
# Calculate convexity | ||
convexity = (bond_price_up + bond_price_down - 2 * bond_price) / ( | ||
bond_price * 0.0001**2 | ||
) | ||
|
||
return convexity |
Oops, something went wrong.