Bond Risk Measures and Immunization › Portfolio Duration and Convexity

Michael BrenndoerferNovember 8, 20255 min read
Quantitative FinanceAll chapters
Part 2 of 15Equity and Fixed Income Markets
Chapter 13Bond Risk Measures and Immunization › Portfolio Duration and Convexity
Reading section 7 of 13 · Portfolio Duration and Convexity · 5 minSection 7 of 13 · 5 min

Book contents

Section 7 of 13 · 5 min read

Bond Risk Measures and Immunization › Portfolio Duration and ConvexityOne section · Sections in this chapter7 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Individual bond risk measures become even more powerful when extended to portfolios. A portfolio manager holding dozens or hundreds of bonds needs a way to summarize the aggregate interest rate exposure. Fortunately, duration and convexity combine in a straightforward way: they are computed as weighted averages, where the weights are each bond's market value as a proportion of total portfolio value.

For a portfolio with nn bonds, each with market value ViV_i and modified duration DiD_i:

DPortfolio=∑i=1nwi⋅DiD_{Portfolio} = \sum_{i=1}^{n} w_i \cdot D_i

where:

  • DPortfolioD_{Portfolio}: the portfolio's modified duration
  • wiw_i: the weight of bond ii in the portfolio, calculated as wi=Vi/∑jVjw_i = V_i / \sum_j V_j
  • ViV_i: the market value of bond ii
  • DiD_i: the modified duration of bond ii
  • nn: the number of bonds in the portfolio

This formula follows from the linearity of the price change approximation. If each bond's percentage price change is given by −Di⋅Δy-D_i \cdot \Delta y, then the portfolio's percentage change is the value-weighted average of the individual changes. The same weighting applies to convexity:

CPortfolio=∑i=1nwi⋅CiC_{Portfolio} = \sum_{i=1}^{n} w_i \cdot C_i

where CiC_i is the convexity of bond ii.

This weighted-average approach allows portfolio managers to adjust interest rate exposure by changing the mix of bonds. With positive portfolio value and a positive-value addition, holding the other positions fixed, a bond whose modified duration exceeds the current portfolio duration raises it; one below the current portfolio duration lowers it. These comparisons use the same yield-change coordinate.

In[19]:
Code
def portfolio_duration_convexity(bonds_data, yield_rate):
    """
    Calculate portfolio duration and convexity.

    bonds_data: list of dicts with 'face_value', 'coupon_rate', 'maturity', 'quantity'
    """
    results = []
    total_value = 0

    for bond in bonds_data:
        price_i = bond_price(
            bond["face_value"],
            bond["coupon_rate"],
            bond["maturity"],
            yield_rate,
        )
        value_i = price_i * bond["quantity"]
        dur_i = modified_duration(
            bond["face_value"],
            bond["coupon_rate"],
            bond["maturity"],
            yield_rate,
        )
        conv_i = convexity(
            bond["face_value"],
            bond["coupon_rate"],
            bond["maturity"],
            yield_rate,
        )

        results.append(
            {
                "price": price_i,
                "value": value_i,
                "duration": dur_i,
                "convexity": conv_i,
            }
        )
        total_value += value_i

    # Calculate weighted averages
    port_duration = sum(r["value"] * r["duration"] for r in results) / total_value
    port_convexity = sum(r["value"] * r["convexity"] for r in results) / total_value

    return port_duration, port_convexity, total_value, results
In[20]:
Code
# Create a sample portfolio
portfolio = [
    {
        "face_value": 1000,
        "coupon_rate": 0.03,
        "maturity": 2,
        "quantity": 100,
        "name": "2-year, 3%",
    },
    {
        "face_value": 1000,
        "coupon_rate": 0.04,
        "maturity": 5,
        "quantity": 150,
        "name": "5-year, 4%",
    },
    {
        "face_value": 1000,
        "coupon_rate": 0.05,
        "maturity": 10,
        "quantity": 100,
        "name": "10-year, 5%",
    },
    {
        "face_value": 1000,
        "coupon_rate": 0.055,
        "maturity": 30,
        "quantity": 50,
        "name": "30-year, 5.5%",
    },
]

yield_rate = 0.05

port_dur, port_conv, total_val, details = portfolio_duration_convexity(
    portfolio, yield_rate
)

print("Portfolio Composition")
print("=" * 75)
print(
    f"{'Bond':<15} {'Price':>10} {'Quantity':>10} {'Value':>12} {'Duration':>10} {'Weight':>10}"
)
print("-" * 75)

for bond, det in zip(portfolio, details):
    weight = det["value"] / total_val * 100
    print(
        f"{bond['name']:<15} ${det['price']:>9.2f} {bond['quantity']:>10} ${det['value']:>11,.2f} {det['duration']:>10.2f} {weight:>9.1f}%"
    )

print("-" * 75)
print(f"{'Total':<15} {'':<10} {'':<10} ${total_val:>11,.2f}")
print(f"\nPortfolio Duration: {port_dur:.2f}")
print(f"Portfolio Convexity: {port_conv:.2f}")
Out[20]:
Console
Portfolio Composition
===========================================================================
Bond                 Price   Quantity        Value   Duration     Weight
---------------------------------------------------------------------------
2-year, 3%      $   962.38        100 $  96,238.03       1.91      24.5%
5-year, 4%      $   956.24        150 $ 143,435.95       4.46      36.4%
10-year, 5%     $  1000.00        100 $ 100,000.00       7.79      25.4%
30-year, 5.5%   $  1077.27         50 $  53,863.58      15.16      13.7%
---------------------------------------------------------------------------
Total                                 $ 393,537.56

Portfolio Duration: 6.15
Portfolio Convexity: 74.98

The portfolio's modified duration is approximately 6.15 years, the market-value-weighted average of the individual modified durations. The thirty-year bond has the longest modified duration, about 15.16 years, but represents only 13.69% of portfolio value. Portfolio convexity is about 74.98. For a small common yield change, these measures give the portfolio's first- and second-order price approximation; positive convexity does not prevent a loss when yields rise.

Out[21]:
Visualization
Bar chart showing individual bond durations for a 2-year, 5-year, 10-year, and 30-year bond.
Individual bond durations for a sample four-bond portfolio, showing durations from about 1.9 years (2-year bond) to 15.2 years (30-year bond), with the 5-year bond at approximately 4.5 years and the 10-year bond near 7.8 years.
Bar chart showing each bond's contribution to portfolio duration, with a horizontal line marking the total portfolio duration.
Duration contribution of each bond to the overall 6.1-year portfolio duration, calculated as individual duration multiplied by portfolio weight. The 30-year bond contributes the most at about 2.1 years, closely followed by the 10-year bond at 2.0 years, while the 5-year and 2-year bonds contribute about 1.6 and 0.5 years respectively.