Introduction
In our previous lesson, we established the theoretical foundation for a more accurate bond price approximation by incorporating convexity. We derived the second-order Taylor expansion for the percentage price change, which combines the linear sensitivity of Modified Duration with a quadratic adjustment for the curvature of the price-yield relationship:
Today, we will transition from theory to practice. Your goal is to implement this formula in Python. You will first write a function to calculate a bond's convexity and then use it to build a powerful comparison tool. This tool will allow you to quantify the error of a duration-only estimate and see precisely how much accuracy is gained by including the convexity adjustment, especially for large interest rate shocks.
1. Implementing Convexity in Python
Before we can use the full approximation formula, we need a function to calculate convexity. Recall that convexity is the normalized second derivative of the price-yield function: .
For a bond with cash flows, coupon frequency , and yield-to-maturity , the second derivative of the price function is:
Here, represents the period number, so the time to cash flow is .
Therefore, the formula for annualized convexity is:
Notice the components of this formula. It involves discounting cash flows, similar to the price and duration calculations you've already implemented. The weighting factor, , gives more weight to later cash flows, reflecting their greater contribution to the price-yield curve's curvature.
Your Task: Python Implementation
Your first task is to implement this formula in a Python function. You can build upon the functions you created in previous lessons for bond pricing and duration.
Create a function calculate_convexity with the following signature:
def calculate_convexity(bond_price, cash_flows, times, ytm, freq):
"""
Calculates the annualized convexity of a bond.
Args:
bond_price (float): The current clean price of the bond.
cash_flows (list): List of bond's cash flows (coupons and principal).
times (list): List of times (in years) to each cash flow.
ytm (float): The yield-to-maturity of the bond.
freq (int): The number of coupon payments per year.
Returns:
float: The annualized convexity.
"""
# Your implementation here
convexity_sum = 0.0
y_freq = ytm / freq
for i in range(len(cash_flows)):
t_period = times[i] * freq # Convert time in years to period number
discount_factor = (1 + y_freq) ** (t_period + 2)
term = (cash_flows[i] * t_period * (t_period + 1)) / discount_factor
convexity_sum += term
convexity = (1 / (bond_price * freq**2)) * convexity_sum
return convexity
You will need this function for the next step.
2. Comparing Price Approximations
Now we can put all the pieces together. The main goal of this lesson is to compare the accuracy of the different price change estimates. For a given change in yield, , you will calculate:
- Actual Price Change: The true change found by re-pricing the bond at the new yield, .
\text{Actual % Change} = \frac{P(y + \Delta y) - P(y)}{P(y)} - Duration-Only Estimate: The linear approximation using Modified Duration.
\text{Duration-only % Change} = -D_{mod} \cdot \Delta y - Duration + Convexity Estimate: The second-order approximation using both Modified Duration and Convexity.
\text{Duration+Convexity % Change} = -D_{mod} \cdot \Delta y + \frac{1}{2} C \cdot (\Delta y)^2
Your Task: Create the Comparison Script
Write a Python script that performs this comparison for a sample bond. Assume you have access to the following functions from your previous work and the function you just wrote:
generate_bond_cash_flows(maturity, principal, coupon_rate, freq)bond_price(ytm, cash_flows, times, freq)macaulay_duration(bond_price, cash_flows, times, ytm, freq)modified_duration(mac_dur, ytm, freq)calculate_convexity(...)
Example Scenario:
- Bond: 10-year maturity, $1000 principal, 5% annual coupon rate.
- Market: Current YTM is 4%.
- Shock: Analyze the effect of a 100 basis point (1%) increase in yield ().
Your script should:
- Define the bond and market parameters.
- Calculate the initial price, modified duration, and convexity at the 4% YTM.
- Calculate the three percentage price changes defined above for the +1% yield shock.
- Print the results in a clear, comparative format.
3. Analysis and Verification
Once you have your Python script running, you can analyze the results. You should find that the duration-plus-convexity estimate is significantly closer to the actual price change than the duration-only estimate.
To confirm your understanding and implementation, let's review a resource that performs a very similar analysis, albeit in R.
Percent Change of Bond Price using Duration and Convexity in R
This article from the blog R-bloggers provides a concise mathematical derivation and a practical implementation of the exact comparison we are performing.
As you read, focus on these parts: Derivation under "% change in bond price": Confirm that the final formula presented matches the one we are using. This reinforces that our model is standard. R code: Skim the code to see the logical flow. Notice how it first calculates the initial price, duration, and convexity. Then, it computes the price change using the three distinct methods: full re-pricing, duration approximation, and duration-convexity approximation. Output: Examine the final printed output. The results show that the "full" calculation and the "with duration and convexity" calculation are almost identical, while the "with duration" estimate has a noticeable error. Your Python results should show the same pattern.
The discrepancy between the actual price change and the linear approximation is what we call convexity error. The graph below visualizes this error directly.

The key insight is that the convexity term, , provides a correction that is always positive for a standard bond. This explains why the duration line in the graph above is always below the actual price change curve. The duration-only model systematically underestimates the price increase when yields fall and overestimates the price decrease when yields rise.
Conclusion
In this lesson, you have moved from theory to a practical, quantitative tool. By implementing the convexity calculation and comparing it against both the true price change and the linear duration estimate, you have demonstrated the superior accuracy of the second-order price approximation.
Key Takeaways:
- The formula for convexity can be implemented by extending the discounted cash flow logic used for bond pricing and duration.
- A Python script can be used to compare three key metrics: the actual price change, the duration-only estimate, and the duration-plus-convexity estimate.
- For non-infinitesimal yield changes, the duration-plus-convexity model provides a significantly more accurate estimate of bond price sensitivity than the duration-only model.
- This increased accuracy is crucial for effective risk management, hedging, and portfolio analysis.
In our next lesson, we will extend this analysis visually. You will use your Python skills to plot the actual price-yield curve against its linear (duration) and quadratic (duration + convexity) approximations. This will provide a powerful visual representation of the concepts we've explored and give you a final, intuitive understanding of these essential risk measures.