Interest Rate Risk Measurement using Duration, Convexity -Excel / Python

Interest Rate Risk Measurement using Duration, Convexity -Excel / Python

Outline for this article :

  1. Interest Risk Management - Introduction
  2. Inverse relationship between bond price and Yield
  3. Types of interest risk
  4. Macaulay Duration - Overview, analysis - Calculation in Excel / Python
  5. Modified duration, Effective Duration, Key Rate Duration (Excel /Python)
  6. Convexity - Effective Convexity (Excel / Python)
  7. Analysis of Yield shock in Portfolio Bond value due % change due to Portfolio MD, Portfolio Convexity (Excel /Python)

Interest Rate Risk Management:

 Introduction:

Companies often face two key financial risk exposures, interest rate risk and Foreign exchange risk. Int. rate risk is concerned with variability of profit, cash flows or the valuation of a company due to movements of interest rates. Int. rate risk is the most important of all financial risks, in which changes in int rates affect a business due to the quantum of debts in their balance sheets and their linkage to base rates or any floating rates like LIBOR or SOFR etc. A highly leveraged company with a large amount of debt capital relative to equity capital may suffer financial distress if int rates increase dramatically. Alternatively, if cash surplus companies with huge investments on deposits linked t floating rate investments may suffer if interest rates fall dramatically.

Also, individuals who took floating rate mortgages may be impacted due to int rate changes.

 Financial Institutions and Int rate risks:

Financial institutions and other market participants manage many types of

risks, including interest rate risk, credit risk, foreign exchange risk, liquidity risk, market risk, and operational risk. For a bank, int rate risk is the threat to the capital position and earnings of a bank driven by changes in the interest rates in the market. Changes to interest rates threaten a bank’s earnings by impacting its Net Interest Income (NII) which is the main source of earnings for a bank. The composition of total income and net Interest Incomes contribute more than 60% on average. Changes to interest rates also threaten the underlying value of the bank’s assets, liabilities, and off-balance sheet instruments, given the adverse impact which may arise on the present value of items, in particular their future cash flows.

 Maturity of assets vs Maturity of Liabilities:

For most large banks, the average maturity of the assets is longer than the average maturity of the liabilities, as banks typically lend in the intermediate to long maturity sector and borrow in the short maturity sector. Average maturity, more popularly known as the duration of security, is the most used risk measure for measuring the interest rate risk exposure of the security. The key question is -How do the managers of financial institutions, such as banks, insurance companies, and index bond funds hedge against the effects of int. rate changes? One of the main activities of treasury department in any organization is the management of int rate risk.

 What is Interest Rate Risk?

 Interest rate risk refers to the potential impact of changes in interest rates on the value of financial instruments, such as bonds, loans, and other fixed-income securities. Price Sensitivity: When interest rates change, the prices of fixed-income securities (like bonds) fluctuate. The relationship is inverse: as interest rates rise, bond prices fall, and vice versa

 Types of Interest Rate Risk:

  1. Price Risk (Market Risk):
  2. Reinvestment Risk
  3. Cash Flow Risk

 1/ Price Risk (Market Risk)?

This risk arises from changes in bond prices due to interest rate movements. Longer-term bonds are more sensitive to rate changes. Price Risk (Market Risk), also known as interest rate risk, refers to the impact of changes in interest rates on the value of fixed-income securities (such as bonds).

Inverse Relationship (Convex )between Bond Prices and Interest Rates

Price Movement: Bond prices and interest rates have an inverse relationship. When interest rates rise, bond prices fall, and vice versa. This occurs because the fixed coupon payments of existing bonds become less attractive compared to new bonds issued at higher rates, leading to a decrease in their market value.

Why?

This is because existing bonds with fixed coupon rates become less attractive compared to newly issued bonds with higher coupon rates. How?

 Bonds typically pay fixed coupon rates. If a bond pays a 5% coupon rate, it will continue to do so regardless of changes in market interest rates. When market interest rates rise and when new bonds are issued at a higher coupon rate, say 6%, they offer more attractive returns compared to the existing 5% bonds.

Investors prefer the new bonds because they yield higher returns. Consequently, the demand for existing bonds decreases. The existing bond prices adjust; to make the existing bonds appealing again, their prices must decrease to match the higher yield offered by new bonds. This is why bond prices fall when interest rates rise.


 Python Code for Bond Price vs YTM Inverse Relationship

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

def calculate_bond_price(face_value, coupon_rate, frequency, maturity, ytm):
    periods = maturity * frequency
    coupon = face_value * (coupon_rate / frequency)
    ytm_period = ytm / frequency
    
    cash_flows = [coupon] * int(periods)
    cash_flows[-1] += face_value
    
    discount_factors = [(1 / (1 + ytm_period) ** t) for t in range(1, int(periods) + 1)]
    
    present_values = [cf * df for cf, df in zip(cash_flows, discount_factors)]
    bond_price = sum(present_values)
    
    return bond_price

def main():
    face_value = float(input("Enter the face value of the bond: "))
    coupon_rate = float(input("Enter the coupon rate (in decimal, e.g., 0.08 for 8%): "))
    frequency = int(input("Enter the coupon frequency (e.g., 2 for semi-annual): "))
    maturity = float(input("Enter the maturity of the bond (in years): "))
    ytm_start = float(input("Enter the starting yield to maturity (in decimal, e.g., 0.01 for 1%): "))
    ytm_end = float(input("Enter the ending yield to maturity (in decimal, e.g., 0.15 for 15%): "))
    num_ytms = int(input("Enter the number of YTMs to calculate: "))
    
    ytms = np.linspace(ytm_start, ytm_end, num_ytms)
    bond_prices = [calculate_bond_price(face_value, coupon_rate, frequency, maturity, ytm) for ytm in ytms]
    
    # Display results
    print("\nBond Price vs. Yield to Maturity (YTM):\n")
    for ytm, price in zip(ytms, bond_prices):
        print(f"YTM: {ytm:.4f} | Bond Price: {price:.2f}")
    
    # Plotting Bond Price vs. YTM
    plt.figure(figsize=(10, 6))
    plt.plot(ytms, bond_prices, marker='o', linestyle='-', color='b', markersize=8)
    plt.title('Bond Price vs. Yield to Maturity')
    plt.xlabel('Yield to Maturity (YTM)')
    plt.ylabel('Bond Price')
    plt.grid(True)
    plt.show()

if __name__ == "__main__":
    main()
        



 2/ Reinvestment Risk?

Reinvestment risk refers to the possibility that an investor will be unable to reinvest cash flows received from an investment, such as coupon payments, at a rate comparable to their current rate of return. Suppose when the market int rates go down, a bond investor who is at present is reinvesting his coupon payments, say at 6% , will not able to re-invest at 6% instead only at rates below 6%.

 How do you manage re-investment risk?

Consider investing non-callable bonds, zero- coupon bonds, invest in long term securities to reduce the frequency of re-investment decisions.

 3/ Cash Flow Risk: For institutions (like banks), changes in interest rates affect cash flows from loans and deposits.  Companies with loans or bonds face interest payments. Fluctuating interest rates impact the cash outflows required for these payments.

Large capital investments (e.g., equipment, real estate) can reduce liquidity, affecting immediate and near-future cash flow. Interest rate changes make it harder for businesses to predict cash flows and control margins. Rising rates may render some projects unviable if revenues no longer cover funding costs

Measuring Interest Rate Risk:

Recall, Interest rate risk refers to the potential for financial losses due to changes in interest rates. It can impact a firm’s profitability and the value of its assets and liabilities. When interest rates fluctuate, the value of interest-sensitive assets and liabilities can change, affecting net interest income and overall financial stability

 Measurement and Assessment: Two common methods for measuring interest rate risk are:

Gap Analysis: It compares the repricing of assets and liabilities to identify potential mismatches.

Duration Analysis: This method focuses on the sensitivity of assets and liabilities to interest rate changes. Duration measures the weighted average time to receive cash flows from an investment.

 Macaulay Duration Overview

Macaulay Duration, named after economist Frederick Macaulay, is a measure of a bond's sensitivity to interest rate changes. It calculates the weighted average time it takes to receive the bond's cash flows, factoring in present value.

 The purpose of Macaulay Duration is to provide investors with a precise measure of risk and return trade-off, which is essential for effective portfolio management. Understanding this concept impacts investors as it helps gauge the potential volatility of a bond or bond portfolio.

Excel calculation of Macaulay Duration

Let us take the below example:


Interpretation of Macaulay Duration :

The Macaulay Duration of the bond is approximately 2.72 years. This means that, on average, it will take about 2.72 years to receive the bond's cash flows, weighted by their present value 

Python Code for Macaulay Duration for a bond – features of the bond such as face value, coupon rate, frequency of coupon, Yield to Maturity, and Maturity chosen by the user.

 import numpy as np
import pandas as pd

def calculate_macaulay_duration(face_value, coupon_rate, frequency, maturity, ytm):
    periods = maturity * frequency
    coupon = face_value * (coupon_rate / frequency)
    ytm_period = ytm / frequency
    
    cash_flows = [coupon] * periods
    cash_flows[-1] += face_value
    
    discount_factors = [(1 / (1 + ytm_period) ** t) for t in range(1, periods + 1)]
    
    present_values = [cf * df for cf, df in zip(cash_flows, discount_factors)]
    
    time_weighted_pvs = [(t * pv) for t, pv in zip(range(1, periods + 1), present_values)]
    
    total_present_value = sum(present_values)
    macaulay_duration = sum(time_weighted_pvs) / total_present_value
    
    df = pd.DataFrame({
        "Term": range(1, periods + 1),
        "CFs": cash_flows,
        "PV of CFs": present_values,
        "Time * PV CFs": time_weighted_pvs
    })
    
    return macaulay_duration, total_present_value, sum(time_weighted_pvs), df

def main():
    face_value = float(input("Enter the face value of the bond: "))
    coupon_rate = float(input("Enter the coupon rate (in decimal, e.g., 0.08 for 8%): "))
    frequency = int(input("Enter the coupon frequency (e.g., 2 for semi-annual): "))
    maturity = int(input("Enter the maturity of the bond (in years): "))
    ytm = float(input("Enter the yield to maturity (in decimal, e.g., 0.09 for 9%): "))
    
    macaulay_duration, total_pv, total_time_weighted_pv, df = calculate_macaulay_duration(face_value, coupon_rate, frequency, maturity, ytm)
    macaulay_duration /= frequency
    df.loc['Total'] = df[['PV of CFs', 'Time * PV CFs']].sum()
    df.loc['Macaulay Duration'] = [None, None, None, macaulay_duration]

    print(df)
    print(f"\nMacaulay Duration: {macaulay_duration:.6f} years")

if __name__ == "__main__":
    main()
        

Link Between Macaulay Duration and Interest Rates

 Interest Rate Sensitivity:

The Macaulay Duration serves as an indicator of a bond's sensitivity to changes in interest rates. It provides a linear approximation of how much the price of a bond will change for a given change in interest rates.

 The higher the YTM, the Macaulay Duration will lower

Macaulay Duration as a Measure of Bond's Interest Rate Risk-

Risk Indicator:

The longer the Macaulay Duration, the greater the bond's exposure to interest rate risk. A bond with a high Macaulay Duration will experience larger price fluctuations in response to changes in interest rates compared to a bond with a shorter duration.

Long maturity, low coupon, low yield = high duration

Python Code for Mac Duration analysis

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

def calculate_macaulay_duration(face_value, coupon_rate, frequency, maturity, ytm):
    periods = maturity * frequency
    coupon = face_value * (coupon_rate / frequency)
    ytm_period = ytm / frequency
    
    cash_flows = [coupon] * int(periods)
    cash_flows[-1] += face_value
    
    discount_factors = [(1 / (1 + ytm_period) ** t) for t in range(1, int(periods) + 1)]
    
    present_values = [cf * df for cf, df in zip(cash_flows, discount_factors)]
    
    time_weighted_pvs = [(t * pv) for t, pv in zip(range(1, int(periods) + 1), present_values)]
    
    total_present_value = sum(present_values)
    macaulay_duration = sum(time_weighted_pvs) / total_present_value / frequency
    
    df = pd.DataFrame({
        "Term": range(1, int(periods) + 1),
        "CFs": cash_flows,
        "PV of CFs": present_values,
        "Time * PV CFs": time_weighted_pvs
    })
    
    return macaulay_duration, df

def main():
    face_value = float(input("Enter the face value of the bond: "))
    coupon_rate = float(input("Enter the coupon rate (in decimal, e.g., 0.08 for 8%): "))
    frequency = int(input("Enter the coupon frequency (e.g., 2 for semi-annual): "))
    maturity = float(input("Enter the maturity of the bond (in years): "))
    ytm_start = float(input("Enter the starting yield to maturity (in decimal, e.g., 0.08 for 8%): "))
    ytm_end = float(input("Enter the ending yield to maturity (in decimal, e.g., 0.1 for 10%): "))
    num_ytms = int(input("Enter the number of YTMs to calculate: "))
    
    ytms = np.linspace(ytm_start, ytm_end, num_ytms)
    
    results = []
    for ytm in ytms:
        macaulay_duration, df = calculate_macaulay_duration(face_value, coupon_rate, frequency, maturity, ytm)
        results.append((ytm, macaulay_duration))
    
    # Display results
    print("\nMacaulay Duration vs. Yield to Maturity (YTM):\n")
    for ytm, macaulay_duration in results:
        print(f"YTM: {ytm:.4f} | Macaulay Duration: {macaulay_duration:.6f} years")
    
    # Plotting Macaulay Duration vs. YTM
    ytm_values, duration_values = zip(*results)
    plt.figure(figsize=(10, 6))
    plt.plot(ytm_values, duration_values, marker='o', linestyle='-', color='b', markersize=8)
    plt.title('Macaulay Duration vs. Yield to Maturity')
    plt.xlabel('Yield to Maturity (YTM)')
    plt.ylabel('Macaulay Duration (years)')
    plt.grid(True)
    plt.show()

if __name__ == "__main__":
    main()
        


Note : Macaulay Duration of Zero-coupon bond will be equal to the maturity of the bond

Drawbacks of Macaulay Duration

  • Assumption of Parallel Shifts in Yield Curve: Macaulay duration assumes that the yield curve shifts in a parallel manner, meaning that the yield changes uniformly for all maturities. In reality, yield curves can change in a non-parallel manner, with different maturities experiencing different changes in yield.
  • Sensitivity to Small Changes in Yield: Macaulay duration may not accurately predict the price change of a bond when interest rates change significantly or when interest rates change in a non-parallel manner. It is most accurate for small changes in yield.
  • Limited to Small Yield Changes: Macaulay duration is a linear approximation and assumes that the relationship between bond price and yield is linear. This assumption breaks down for larger changes in yield.
  • Does Not Consider Reinvestment Risk: Macaulay duration assumes that coupon payments are reinvested at the same yield. In reality, reinvestment rates may differ from the yield to maturity.
  • Not Applicable to Bonds with Embedded Options: For bonds with embedded options (e.g., callable bonds, convertible bonds), Macaulay duration may not accurately reflect the interest rate risk because the cash flows are contingent on the issuer's decisions.
  • Does Not Consider Convexity: Macaulay duration does not account for convexity, which is the curvature of the price-yield relationship. Convexity becomes increasingly important for larger changes in yield.
  • Applicability to Only Fixed Rate Bonds: Macaulay duration applies to fixed-rate bonds. For other types of securities (e.g., floating rate bonds, inflation-linked bonds), other measures may be more appropriate. 

Modified Duration

Modified duration is a modified version of the Macaulay model that accounts for changing interest rates. (Yield to Maturity). Modified formula shows how much the duration changes for each percentage change in yield. There is an inverse relationship between modified duration and an approximate 1% change in yield.


Modified Duration = Duration / [1+(Yield / No of coupons per annum)]

Where ‘f’ is the frequency of payment of coupon.


Python Code for Macaulay Duration and Modified Duration based on user-defined inputs for Bond values: Face value, Coupon Rate, Coupon Frequency, Maturity, YTM

import numpy as np
import pandas as pd

def calculate_macaulay_and_modified_duration(face_value, coupon_rate, frequency, maturity, ytm):
    periods = maturity * frequency
    coupon = face_value * (coupon_rate / frequency)
    ytm_period = ytm / frequency
    
    cash_flows = [coupon] * periods
    cash_flows[-1] += face_value
    
    discount_factors = [(1 / (1 + ytm_period) ** t) for t in range(1, periods + 1)]
    
    present_values = [cf / (1 + ytm_period) ** t for t, cf in enumerate(cash_flows, 1)]
    
    total_present_value = sum(present_values)
    macaulay_duration = sum(t * pv for t, pv in enumerate(present_values, 1)) / total_present_value / frequency
    modified_duration = sum(t * pv for t, pv in zip(range(1, periods + 1), present_values)) / total_present_value / (1 + ytm_period)
    
    df = pd.DataFrame({
        "Term": range(1, periods + 1),
        "CFs": cash_flows,
        "PV of CFs": present_values,
        "Time * PV CFs": [t * pv for t, pv in zip(range(1, periods + 1), present_values)]
    })
    
    # Add total row and Macaulay Duration row
    df.loc['Total'] = df[['PV of CFs', 'Time * PV CFs']].sum()
    df.loc['Macaulay Duration'] = [None, None, None, macaulay_duration]
    
    # Calculate Modified Duration
    modified_duration = macaulay_duration / (1 + ytm / frequency)
    df.loc['Modified Duration'] = [None, None, None, modified_duration]
    
    return macaulay_duration, modified_duration, total_present_value, df

def main():
    # Get bond parameters from user
    face_value = float(input("Enter the face value of the bond: "))
    coupon_rate = float(input("Enter the coupon rate (in decimal, e.g., 0.08 for 8%): "))
    frequency = int(input("Enter the coupon frequency (e.g., 2 for semi-annual): "))
    maturity = int(input("Enter the maturity of the bond (in years): "))
    ytm = float(input("Enter the yield to maturity (in decimal, e.g., 0.09 for 9%): "))
    
    macaulay_duration, modified_duration, total_pv, df = calculate_macaulay_and_modified_duration(face_value, coupon_rate, frequency, maturity, ytm)
    
    # Display the DataFrame
    print(df)
    print(f"\nMacaulay Duration: {macaulay_duration:.6f} years")
    print(f"Modified Duration: {modified_duration:.6f} years")

if __name__ == "__main__":
    main()
        


Recall , Modified Duration is a measure of a bond's sensitivity to interest rate changes. It indicates how much the price of a bond is expected to change in response to a change in its yield to maturity (YTM). The relationship between Modified Duration and YTM can be summarized as follows:

  1. Inverse Relationship: Modified Duration and YTM have an inverse relationship. This means that as the YTM increases, the Modified Duration decreases, and vice versa.
  2. Magnitude of Change: The change in the bond's price is approximately equal to the change in the YTM multiplied by the Modified Duration. Mathematically, the percentage change in bond price is given by:

Percentage change in bond price≈−Modified Duration×Change in YTM

This formula shows that the greater the Modified Duration, the greater the change in the bond's price for a given change in YTM.

Example Interpretation:

(I) For example, if a bond has a Modified Duration of 5 years and the YTM increases by 1%, the bond's price would be expected to decrease by approximately 5%.

(ii) Consider a bond with a YTM of 12% and duration is 5 years. If the interest rate increases by 50 basis points , change in the price of the bond will be. Assume annual coupon paying bond.

Modified duration of the bond is 5/1.12 = 4.46 years

Change is price can be calculated as follows:

%ΔPrice = -Dmod ×%Δyield

                  = -4.46 ×0.5 = -2.23%

Hence price will decline by 2.23% for a given change in yield.

Both Macaulay and Modified Duration measure the linear changes in the price of a bond about changes in Yield to maturity. Modified Duration is a better approximation to measure the interest rate sensitivity.

Modified Duration of a coupon paying bond will be less than the Macaulay duration of the same bond.

Key Considerations:

  • Bonds with higher Modified Durations are more sensitive to changes in interest rates.
  • Zero-coupon bonds have durations equal to their maturity, making them highly sensitive to interest rate changes.
  • The relationship assumes a parallel shift in the yield curve (i.e., an equal change in interest rates across all maturities).

Application:

  • Investors use Modified Duration to estimate the impact of interest rate changes on their bond portfolios.
  • It helps in managing interest rate risk by selecting bonds with durations that match the investor's risk tolerance and investment horizon.

Effective Duration

Effective Duration is another measure used to estimate the sensitivity of a bond's price to changes in interest rates. While Modified Duration assumes a parallel shift in the yield curve, Effective Duration accounts for potential changes in the shape of the yield curve and is particularly useful for bonds with embedded options, such as callable bonds or mortgage-backed securities.

Excel Calculation of Effective Duration

Features of Effective Duration

  • Duration: Effective Duration is expressed in years, like Modified Duration.
  • Interest Rate Sensitivity: It provides a more accurate measure of interest rate sensitivity than Modified Duration for bonds with embedded options.
  • Callable Bonds: For callable bonds, Effective Duration accounts for potential changes in cash flows due to early redemption by the issuer when interest rates decline.
  • Mortgage-backed Securities: Effective Duration helps estimate the impact of prepayments (which increase when interest rates decline) on bond prices.

Comparison with Modified Duration:

  • Modified Duration: Assumes a parallel shift in the yield curve.
  • Effective Duration: Accounts for potential changes in the shape of the yield curve and cash flow variations for bonds with embedded options.

Application:

  • Risk Management: Investors use Effective Duration to manage interest rate risk more effectively, particularly when dealing with bonds that have complex cash flows.
  • Portfolio Construction: Helps in constructing portfolios with bonds that have durations that match the investor's risk tolerance and investment objectives.

Key Rate Duration

Key Rate Duration (KRD) is a useful measure for assessing the sensitivity of a bond’s price to changes in specific key interest rates. Key rate duration is a measure of a bond or bond portfolio’s sensitivity to a 100-basis point – 1% – change in yield at a specific maturity point.

  • Key rate duration is considered an improvement over using the effective duration metric, which can only be applied when there are parallel changes in the yield all across the yield curve.
  • Using the metric can help investors or financial analysts predict the probable profitability of investing in bonds with various maturities.

Excel Version of Key Rate Duration

CONVEXITY

Macaulay Duration and Modified Duration do not fully explain the linkage between prices and yield and convexity provides a solution to predicting prices. The actual change depends on the amount of curvature and this is known as convexity.

Convexity measures the curvature of the price-yield relationship and provides a more accurate estimate of how much a bond's price will change in response to interest rate movements.

  • Positive Convexity: Bonds with positive convexity will gain more in price when interest rates fall than they would lose if rates rise.
  • Negative Convexity: Some bonds, like callable bonds, exhibit negative convexity, meaning their prices fall more than they rise when interest rates change.

Understanding convexity is crucial for bond investors and analysts because it helps them better manage interest rate risk and make informed decisions about bond investments based on potential price changes.

Consider the below bond details :

Convexity = (Sum of PV *(t^2+t)) /(1+YTM/freq.)^2) /Price of the bond

In the above example:

Sum of PV * (t^2+t) =53144.38106

The price of the bond =692.771645

(YTM / 2) ^2 = 1.21

Convexity = 63.39892

Convexity using approximation formula:

Convexity = (Phigh + Plow - 2*P0)/(P0*∆r*∆r)


Total % Change in Price of a bond due to Yield change (Yield shock)

% Change due to Modified Duration = -1 MD * Yield change

% Change due to Convexity= 0.5*Convexity * Yield Change ^2

∆P/P = - MD ∆r + 0.5 C * (∆r)^2

Consider a bond portfolio of two bonds equally weighted.

Step 1: Price of the bonds at current yield

Step 2: Calculate Modified Duration, Convexity using approx formula

Step 4: Calculate Portfolio MD, Portfolio Convexity

Step 5: For assumed Yield shock on either side: Say + 40 Basis Points, - 40 Basis points, calculate % Change due to Portfolio MD + %Change due to Portfolio convexity = Total % Change due to Yield in Bond Portfolio Value

Step 6: Finally calculate the price of the bond portfolio value on either side due to yield shock


Python code to calculate Portfolio MD, Portfolio Convexity,

Total % Change in Bond Portfolio value due to Portfolio MD and Portfolio Convexity

Exactly replicating the Excel workout above.

# Function to calculate Present Value of cash flows
def present_value(face_value, coupon_rate, coupon_freq, maturity, ytm):
    coupon_payment = face_value * coupon_rate / coupon_freq
    pv = 0
    for t in range(1, maturity * coupon_freq + 1):
        pv += coupon_payment / (1 + ytm / coupon_freq) ** t
    pv += face_value / (1 + ytm / coupon_freq) ** (maturity * coupon_freq)
    return pv

# Function to calculate Modified Duration
def calc_modified_duration(face_value, coupon_rate, coupon_freq, maturity, ytm, delta_ytm=0.0001):
    pv = present_value(face_value, coupon_rate, coupon_freq, maturity, ytm)
    pv_down = present_value(face_value, coupon_rate, coupon_freq, maturity, ytm - delta_ytm)
    pv_up = present_value(face_value, coupon_rate, coupon_freq, maturity, ytm + delta_ytm)
    
    modified_duration = (pv_down - pv_up) / (2 * pv * delta_ytm)
    
    return modified_duration, pv

# Function to calculate Convexity
def calc_convexity(face_value, coupon_rate, coupon_freq, maturity, ytm, delta_ytm=0.0001):
    pv_current = present_value(face_value, coupon_rate, coupon_freq, maturity, ytm)
    pv_down = present_value(face_value, coupon_rate, coupon_freq, maturity, ytm - delta_ytm)
    pv_up = present_value(face_value, coupon_rate, coupon_freq, maturity, ytm + delta_ytm)
    
    convexity = (pv_down + pv_up - 2 * pv_current) / (pv_current * (delta_ytm ** 2))
    
    return convexity, pv_current

# Main code execution starts here

# Bond A details
face_value_a = 1000
coupon_rate_a = 0.08  # 8% coupon rate
coupon_freq_a = 2  # semi-annual payments
maturity_a = 3
ytm_a = 0.09  # 9%

# Bond B details
face_value_b = 1000
coupon_rate_b = 0.06  # 6% coupon rate
coupon_freq_b = 1  # annual payments
maturity_b = 3
ytm_b = 0.08  # 8%

# Calculate present value, modified duration, and convexity for Bond A
pv_a = present_value(face_value_a, coupon_rate_a, coupon_freq_a, maturity_a, ytm_a)
modified_duration_a, _ = calc_modified_duration(face_value_a, coupon_rate_a, coupon_freq_a, maturity_a, ytm_a)
convexity_a, _ = calc_convexity(face_value_a, coupon_rate_a, coupon_freq_a, maturity_a, ytm_a)

# Calculate present value, modified duration, and convexity for Bond B
pv_b = present_value(face_value_b, coupon_rate_b, coupon_freq_b, maturity_b, ytm_b)
modified_duration_b, _ = calc_modified_duration(face_value_b, coupon_rate_b, coupon_freq_b, maturity_b, ytm_b)
convexity_b, _ = calc_convexity(face_value_b, coupon_rate_b, coupon_freq_b, maturity_b, ytm_b)

# Calculate portfolio values assuming equal weights
weight_a = 0.5
weight_b = 1 - weight_a
portfolio_value = weight_a * pv_a + weight_b * pv_b

# Calculate portfolio modified duration and convexity
portfolio_modified_duration = weight_a * modified_duration_a + weight_b * modified_duration_b
portfolio_convexity = weight_a * convexity_a + weight_b * convexity_b

# Display bond details and portfolio values
print("\nBond A details:")
print(f"Face Value: ₹{face_value_a}")
print(f"Coupon Rate: {coupon_rate_a * 100}%")
print(f"Coupon Frequency: {coupon_freq_a} times per year")
print(f"Maturity: {maturity_a} years")
print(f"Yield to Maturity (YTM): {ytm_a * 100}%")
print(f"Price (Present Value): ₹{pv_a:.2f}")
print(f"Modified Duration: {modified_duration_a:.4f}")
print(f"Convexity: {convexity_a:.4f}")

print("\nBond B details:")
print(f"Face Value: ₹{face_value_b}")
print(f"Coupon Rate: {coupon_rate_b * 100}%")
print(f"Coupon Frequency: {coupon_freq_b} times per year")
print(f"Maturity: {maturity_b} years")
print(f"Yield to Maturity (YTM): {ytm_b * 100}%")
print(f"Price (Present Value): ₹{pv_b:.2f}")
print(f"Modified Duration: {modified_duration_b:.4f}")
print(f"Convexity: {convexity_b:.4f}")

print("\nPortfolio Details:")
print(f"Weight of Bond A: {weight_a * 100}%")
print(f"Weight of Bond B: {weight_b * 100}%")
print(f"Total Portfolio Value: ₹{portfolio_value:.2f}")
print(f"Portfolio Modified Duration: {portfolio_modified_duration:.4f}")
print(f"Portfolio Convexity: {portfolio_convexity:.4f}")

# Input yield change in decimal
yield_change = float(input("\nEnter Yield Change (e.g., 0.004 for 0.4%): "))

# Calculate % change in bond portfolio value due to yield change
percent_change_due_to_md = -1 * portfolio_modified_duration * yield_change * 100  # Convert to percentage
percent_change_due_to_convexity = 0.5 * portfolio_convexity * (yield_change ** 2) * 100  # Convert to percentage

# Total percentage change
total_percent_change = percent_change_due_to_md + percent_change_due_to_convexity

# Calculate new bond portfolio value
new_portfolio_value = portfolio_value * (1 + total_percent_change / 100)

# Display the calculated percentage changes and new portfolio value
print("\nPercentage Change in Bond Portfolio Value due to Yield Change:")
print(f"Due to Modified Duration: {percent_change_due_to_md:.4f}%")
print(f"Due to Convexity: {percent_change_due_to_convexity:.4f}%")
print(f"Total % Change in Bond Portfolio Value: {total_percent_change:.4f}%")

print("\nNew Bond Portfolio Value:")
print(f"Current Portfolio Value: ₹{portfolio_value:.2f}")
print(f"New Portfolio Value: ₹{new_portfolio_value:.2f}")
        

Output

Conclusion

There are several classes of risk associated with investing in bonds apart from changes in interest rates. These include risks from reinvestment risk, changes in the yield curve, prepayment and call risk, credit, liquidity, exchange rates, inflation and exterior factors.

Modified Duration is the key measure for estimating changes in a bond’s value due to interest rate changes; however, measures based solely on duration become increasingly inaccurate.

Since the calculation needs to include the convexity of the price yield relationship, further measures of convexity are needed to predict the changed price more accurately.



Dr. Dileep S

Narsee Monjee Institute of Management Studies (NMIMS)- Bengaluru

5mo

Sir, beautifully covered the topic and nicely explained the concepts of bond valuation.

To view or add a comment, sign in

More articles by Vaidyanathan Ravichandran

Insights from the community

Others also viewed

Explore topics