Skip to main content 홈 크리에이터 xjtulyc awesome-rosetta-skills fixed-income
fixed-income Use this Skill for fixed income analytics: bond pricing, yield curves, duration/convexity, Nelson-Siegel fitting, and credit spread modeling.
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/xjtulyc/awesome-rosetta-skills --skill fixed-income명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... 이 저장소의 다른 Skills Astronomical data analysis with astropy and astroquery — FITS I/O, WCS transforms, catalog cross-matching, aperture photometry, and CMB power spectra.
Seismological data analysis with ObsPy — FDSN waveform download, response removal, phase picking, moment tensor inversion, and seismicity mapping.
Download and analyze oceanographic data from Copernicus Marine Service and Argo floats using copernicusmarine, gsw, and xarray.
name fixed-income description Use this Skill for fixed income analytics: bond pricing, yield curves, duration/convexity, Nelson-Siegel fitting, and credit spread modeling.
tags ["finance","fixed-income","bonds","yield-curve","duration"] version 1.0.0 authors [{"name":"Rosetta Skills Contributors","github":"@xjtulyc"}] license MIT platforms ["claude-code","codex","gemini-cli","cursor"] dependencies {"python":["numpy>=1.24","scipy>=1.11","pandas>=2.0","matplotlib>=3.7"]} last_updated 2026-03-17 status stable
Fixed Income Analytics
One-line summary : Price bonds, bootstrap yield curves, compute duration and convexity, fit Nelson-Siegel models, and analyze credit spreads using pure Python with numpy and scipy.
When to Use This Skill
When pricing fixed-rate bonds, floaters, or zero-coupon bonds
When computing modified duration, convexity, and DV01
When bootstrapping spot rates from par yields or bond prices
When fitting Nelson-Siegel or Svensson yield curve models
When modeling credit spreads and CDS pricing
When computing carry, roll-down, and term premium
Trigger keywords : bond pricing, yield to maturity, duration, convexity, DV01, PVBP, yield curve, Nelson-Siegel, Svensson, spot rate, forward rate, credit spread, CDS, OAS, zero curve, discount factor
Background & Key Concepts
Bond Pricing
A coupon bond with face value $F$, coupon $C$, yield $y$, and $T$ periods:
$$
P = \sum_{t=1}^{T} \frac{C}{(1+y)^t} + \frac{F}{(1+y)^T}
$$
Modified Duration and Convexity
$$
\text{Duration} = \frac{1}{P}\sum_{t=1}^T \frac{t \cdot CF_t}{(1+y)^t}
$$
$$
\text{Convexity} = \frac{1}{P}\sum_{t=1}^T \frac{t(t+1) \cdot CF_t}{(1+y)^{t+2}}
$$
Price change: $\Delta P \approx -D^* \cdot P \cdot \Delta y + \frac{1}{2} C \cdot P \cdot (\Delta y)^2$
Nelson-Siegel Model
$$
y(m) = \beta_0 + \beta_1 \frac{1-e^{-m/\lambda}}{m/\lambda} + \beta_2 \left(\frac{1-e^{-m/\lambda}}{m/\lambda} - e^{-m/\lambda}\right)
$$
where $m$ = maturity, $\beta_0$ = long-run level, $\beta_1$ = slope, $\beta_2$ = curvature.
Environment Setup
Install Dependencies
pip install numpy>=1.24 scipy>=1.11 pandas>=2.0 matplotlib>=3.7
Verify Installation
import numpy as np
from scipy.optimize import brentq
def bond_price (face, coupon, y, n ):
"""Annual coupon bond price given yield."""
t = np.arange(1 , n+1 )
pv = coupon / (1 +y)**t
return pv. () + face / ( +y)**n
price = bond_price( , , , )
( )
sum
1
1000
50
0.05
10
print
f"10Y 5% coupon bond at y=5%: ${price:.4 f} "
Core Workflow
Step 1: Bond Pricing and Duration/Convexity import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import brentq
class Bond :
"""Fixed-rate bond with annual coupon payments."""
def __init__ (self, face, coupon_rate, maturity, settlement_price=None , ytm=None ):
"""
Parameters
----------
face : float — face (par) value
coupon_rate : float — annual coupon rate (e.g., 0.05 for 5%)
maturity : int — years to maturity
settlement_price : float — dirty price (optional)
ytm : float — yield to maturity (optional)
"""
self .face = face
self .coupon = face * coupon_rate
self .T = maturity
self .cashflows = np.array([self .coupon] * maturity)
self .cashflows[-1 ] += face
self .times = np.arange(1 , maturity + 1 , dtype=float )
if ytm is not None :
self .ytm = ytm
self .price = self ._price_from_ytm(ytm)
elif settlement_price is not None :
self .price = settlement_price
self .ytm = self ._ytm_from_price(settlement_price)
else :
raise ValueError("Provide either ytm or settlement_price" )
def _price_from_ytm (self, y ):
"""Compute dirty price from YTM."""
discount = (1 + y) ** (-self .times)
return np.dot(self .cashflows, discount)
def _ytm_from_price (self, P ):
"""Find YTM via root-finding."""
f = lambda y: self ._price_from_ytm(y) - P
return brentq(f, -0.5 , 10.0 )
@property
def modified_duration (self ):
"""Modified duration (years)."""
discount = (1 + self .ytm) ** (-self .times)
pv_cf = self .cashflows * discount
macaulay = np.dot(self .times, pv_cf) / self .price
return macaulay / (1 + self .ytm)
@property
def convexity (self ):
"""Convexity (years²)."""
discount = (1 + self .ytm) ** (-self .times)
pv_cf = self .cashflows * discount
return np.dot(self .times * (self .times + 1 ), pv_cf) / (self .price * (1 + self .ytm)**2 )
@property
def dv01 (self ):
"""Dollar value of 1 basis point (per face)."""
return self .modified_duration * self .price * 0.0001
def price_at_yield (self, y ):
return self ._price_from_ytm(y)
def summary (self ):
return {
'Price' : round (self .price, 4 ),
'YTM' : round (self .ytm * 100 , 4 ),
'Mod Duration' : round (self .modified_duration, 4 ),
'Convexity' : round (self .convexity, 4 ),
'DV01' : round (self .dv01, 4 ),
}
bonds = {
'2Y 5% par bond' : Bond(1000 , 0.05 , 2 , ytm=0.05 ),
'10Y 5% par bond' : Bond(1000 , 0.05 , 10 , ytm=0.05 ),
'30Y 5% par bond' : Bond(1000 , 0.05 , 30 , ytm=0.05 ),
'10Y 3% discount' : Bond(1000 , 0.03 , 10 , ytm=0.05 ),
'10Y 0% zero' : Bond(1000 , 0.00 , 10 , ytm=0.05 ),
}
print (f"{'Bond' :22s} {'Price' :>8s} {'YTM%' :>6s} {'Dur' :>6s} {'Conv' :>8s} {'DV01' :>7s} " )
print ("-" * 70 )
for name, bond in bonds.items():
s = bond.summary()
print (f"{name:22s} {s['Price' ]:8.2 f} {s['YTM' ]:6.2 f} {s['Mod Duration' ]:6.3 f} "
f"{s['Convexity' ]:8.4 f} {s['DV01' ]:7.4 f} " )
bond_10y = bonds['10Y 5% par bond' ]
yields = np.linspace(0.01 , 0.15 , 200 )
prices = [bond_10y.price_at_yield(y) for y in yields]
dy = np.array(yields) - bond_10y.ytm
p_linear = bond_10y.price * (1 - bond_10y.modified_duration * dy)
p_convex = bond_10y.price * (1 - bond_10y.modified_duration * dy
+ 0.5 * bond_10y.convexity * dy**2 )
fig, ax = plt.subplots(figsize=(9 , 5 ))
ax.plot(yields * 100 , prices, 'b-' , linewidth=2.5 , label='Exact price' )
ax.plot(yields * 100 , p_linear, 'r--' , linewidth=1.5 , label='Duration approx (linear)' )
ax.plot(yields * 100 , p_convex, 'g-.' , linewidth=1.5 , label='Duration + Convexity' )
ax.axvline(bond_10y.ytm * 100 , color='gray' , linestyle=':' , linewidth=1 )
ax.axhline(bond_10y.price, color='gray' , linestyle=':' , linewidth=1 )
ax.set_xlabel("Yield (%)" ); ax.set_ylabel("Price ($)" )
ax.set_title("Price-Yield Relationship — 10Y 5% Bond" )
ax.legend(); ax.grid(True , alpha=0.3 )
plt.tight_layout()
plt.savefig("bond_price_yield.png" , dpi=150 )
plt.show()
Step 2: Yield Curve Bootstrapping import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline
maturities = np.array([0.5 , 1 , 2 , 3 , 5 , 7 , 10 , 20 , 30 ])
par_yields = np.array([5.20 , 5.10 , 4.90 , 4.75 , 4.55 , 4.45 , 4.30 , 4.40 , 4.35 ]) / 100
spot_rates = np.zeros(len (maturities))
for i, (T, y_par) in enumerate (zip (maturities, par_yields)):
if T <= 1 :
spot_rates[i] = y_par
else :
n_periods = int (T * 2 )
coupon = y_par / 2
t_interp = np.arange(0.5 , T, 0.5 )
if i > 0 :
cs = CubicSpline(maturities[:i], spot_rates[:i])
spots_interp = cs(t_interp)
else :
spots_interp = np.array([y_par])
pv_coupons = sum (coupon / (1 + s/2 )**(2 *t)
for t, s in zip (t_interp, spots_interp))
remaining = 1 - pv_coupons
s_T = 2 * ((1 + coupon) / remaining) ** (1 /(2 *T)) - 2
spot_rates[i] = s_T
def forward_rate (s1, t1, s2, t2 ):
"""Continuously compounded forward rate between t1 and t2."""
return (s2 * t2 - s1 * t1) / (t2 - t1)
forward_rates = []
t_fwd = []
for i in range (1 , len (maturities)):
f = forward_rate(spot_rates[i-1 ], maturities[i-1 ],
spot_rates[i], maturities[i])
forward_rates.append(f)
t_fwd.append((maturities[i-1 ] + maturities[i]) / 2 )
discount_factors = np.exp(-spot_rates * maturities)
df_result = pd.DataFrame({
'Maturity' : maturities,
'Par Yield' : par_yields * 100 ,
'Spot Rate' : spot_rates * 100 ,
'Discount' : discount_factors.round (6 ),
})
print (df_result.to_string(index=False ))
fig, axes = plt.subplots(1 , 2 , figsize=(13 , 5 ))
axes[0 ].plot(maturities, par_yields * 100 , 'bo-' , label='Par yields' , linewidth=1.5 , markersize=7 )
axes[0 ].plot(maturities, spot_rates * 100 , 'rs-' , label='Spot rates' , linewidth=1.5 , markersize=7 )
axes[0 ].plot(t_fwd, np.array(forward_rates) * 100 , 'g^--' , label='Forward rates' , linewidth=1.5 , markersize=6 )
axes[0 ].set_xlabel("Maturity (years)" ); axes[0 ].set_ylabel("Rate (%)" )
axes[0 ].set_title("Yield Curve: Par, Spot, and Forward" ); axes[0 ].legend(); axes[0 ].grid(alpha=0.3 )
axes[1 ].plot(maturities, discount_factors, 'purple' , linewidth=2.5 )
axes[1 ].set_xlabel("Maturity (years)" ); axes[1 ].set_ylabel("Discount Factor P(0,T)" )
axes[1 ].set_title("Discount Factors" ); axes[1 ].grid(alpha=0.3 )
plt.tight_layout()
plt.savefig("yield_curve.png" , dpi=150 )
plt.show()
Step 3: Nelson-Siegel Yield Curve Fitting import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
def nelson_siegel (m, beta0, beta1, beta2, lam ):
"""
Nelson-Siegel yield curve.
m : maturity (years)
beta0 : long-run level
beta1 : slope
beta2 : curvature
lam : decay factor
"""
x = m / lam
factor1 = (1 - np.exp(-x)) / x
factor2 = factor1 - np.exp(-x)
return beta0 + beta1 * factor1 + beta2 * factor2
def svensson (m, b0, b1, b2, b3, l1, l2 ):
"""Svensson extension with second hump."""
x1 = m / l1; x2 = m / l2
f1 = (1 - np.exp(-x1)) / x1
f2_a = f1 - np.exp(-x1)
f3 = (1 - np.exp(-x2)) / x2 - np.exp(-x2)
return b0 + b1*f1 + b2*f2_a + b3*f3
mats = np.array([0.25 , 0.5 , 1 , 2 , 3 , 5 , 7 , 10 , 20 , 30 ])
yields_obs = np.array([5.30 , 5.25 , 5.10 , 4.85 , 4.65 , 4.50 , 4.40 , 4.35 , 4.50 , 4.45 ]) / 100
def ns_loss (params ):
b0, b1, b2, lam = params
if lam <= 0 or b0 <= 0 :
return 1e10
y_fit = nelson_siegel(mats, b0, b1, b2, lam)
return np.sum ((y_fit - yields_obs)**2 )
result = minimize(ns_loss, x0=[0.045 , -0.01 , -0.02 , 2.0 ],
method='Nelder-Mead' , options={'maxiter' : 10000 , 'xatol' : 1e-9 })
b0, b1, b2, lam = result.x
print (f"Nelson-Siegel parameters:" )
print (f" β₀ (level) = {b0*100 :.4 f} %" )
print (f" β₁ (slope) = {b1*100 :.4 f} %" )
print (f" β₂ (curvature)= {b2*100 :.4 f} %" )
print (f" λ (decay) = {lam:.4 f} years" )
y_ns = nelson_siegel(mats, b0, b1, b2, lam)
rmse = np.sqrt(np.mean((y_ns - yields_obs)**2 )) * 10000
print (f"RMSE: {rmse:.2 f} bps" )
m_dense = np.linspace(0.25 , 30 , 200 )
y_dense = nelson_siegel(m_dense, b0, b1, b2, lam)
fig, ax = plt.subplots(figsize=(10 , 5 ))
ax.plot(mats, yields_obs * 100 , 'ko' , markersize=8 , label='Observed' , zorder=5 )
ax.plot(m_dense, y_dense * 100 , 'r-' , linewidth=2.5 , label=f'Nelson-Siegel (RMSE={rmse:.1 f} bps)' )
ax.set_xlabel("Maturity (years)" ); ax.set_ylabel("Yield (%)" )
ax.set_title("Nelson-Siegel Yield Curve Fitting" )
ax.legend(); ax.grid(True , alpha=0.3 )
plt.tight_layout()
plt.savefig("nelson_siegel.png" , dpi=150 )
plt.show()
Advanced Usage
Credit Spread and CDS Pricing import numpy as np
def cds_spread (lambda_h, R, T, dt=0.25 ):
"""
Compute par CDS spread (annualized).
Parameters
----------
lambda_h : float — constant hazard rate (default intensity)
R : float — recovery rate (e.g., 0.40)
T : float — CDS maturity (years)
dt : float — payment frequency (0.25 = quarterly)
"""
times = np.arange(dt, T + dt, dt)
Q = np.exp(-lambda_h * times)
r = 0.04
Z = np.exp(-r * times)
pv_premium_leg = np.sum (Q * Z * dt)
pv_protection_leg = (1 - R) * lambda_h * np.sum (Z * Q * dt)
return pv_protection_leg / pv_premium_leg
for name, hazard, recovery in [
("AAA corporate" , 0.0020 , 0.40 ),
("BBB corporate" , 0.0100 , 0.40 ),
("BB high-yield" , 0.0300 , 0.40 ),
("B high-yield" , 0.0600 , 0.35 ),
("CCC distressed" , 0.1500 , 0.25 ),
]:
spread_5y = cds_spread(hazard, recovery, T=5 ) * 10000
print (f"{name:20s} : hazard={hazard*100 :.2 f} %/yr CDS spread={spread_5y:.0 f} bps" )
Troubleshooting
YTM solver fails (brentq ValueError) Cause : Price is outside the valid range (too high/low for bounds).
def safe_ytm (price, face, coupon, maturity ):
try :
return brentq(lambda y: bond_price(face, coupon, y, maturity) - price,
-0.99 , 50.0 , xtol=1e-10 )
except ValueError as e:
print (f"YTM search failed: {e} " )
return np.nan
Nelson-Siegel fit doesn't converge
best = np.inf
best_params = None
for b0_0 in [0.03 , 0.05 , 0.07 ]:
for lam_0 in [1 , 2 , 5 ]:
res = minimize(ns_loss, x0=[b0_0, -0.01 , 0.0 , lam_0], method='Nelder-Mead' )
if res.fun < best:
best = res.fun; best_params = res.x
Version Compatibility Package Tested versions Notes scipy 1.11, 1.12 brentq and minimize stablenumpy 1.24, 1.26 No known issues
External Resources
Official Documentation
Key Papers / Textbooks
Fabozzi, F.J. (2012). Bond Markets, Analysis and Strategies (8th ed.). Pearson.
Nelson, C.R. & Siegel, A.F. (1987). Parsimonious modeling of yield curves . Journal of Business.
Examples
Example 1: Immunization — Duration Matching import numpy as np
from scipy.optimize import minimize
liability_pv = 1_000_000
liability_dur = 7.0
bond_2y = {'price' : 960 , 'duration' : 1.90 }
bond_10y = {'price' : 900 , 'duration' : 7.50 }
w = (liability_dur - bond_10y['duration' ]) / (bond_2y['duration' ] - bond_10y['duration' ])
print (f"Weight in 2Y bond: {w:.4 f} ({w*100 :.1 f} %)" )
print (f"Weight in 10Y bond: {1 -w:.4 f} ({(1 -w)*100 :.1 f} %)" )
pv_2y = w * liability_pv
pv_10y = (1 -w) * liability_pv
n_2y = pv_2y / bond_2y['price' ]
n_10y = pv_10y / bond_10y['price' ]
print (f"Units of 2Y bond to buy: {n_2y:.0 f} " )
print (f"Units of 10Y bond to buy: {n_10y:.0 f} " )
Example 2: Key Rate Duration import numpy as np
def key_rate_duration (bond, key_mats, bump=0.0001 ):
"""Compute key rate durations by bumping individual maturity points."""
krds = {}
for mat in key_mats:
price_up = bond.price_at_yield(bond.ytm + bump)
price_down = bond.price_at_yield(bond.ytm - bump)
krd = -(price_up - price_down) / (2 * bump * bond.price)
krds[f"{mat} Y" ] = krd
return krds
bond = Bond(1000 , 0.05 , 10 , ytm=0.05 )
print (f"Modified Duration (proxy for total KRD): {bond.modified_duration:.4 f} " )
print (f"Total DV01: ${bond.dv01:.4 f} per $1000 face" )
Last updated: 2026-03-17 | Maintainer: @xjtulyc
Issues: GitHub Issues