Claude-skill-registry bond-benchmarking
Compare bond performance against market benchmarks and indices. Analyzes yield spreads, duration matching, and relative value. Requires numpy>=1.24.0, pandas>=2.0.0. Use when evaluating bonds against peers, measuring portfolio attribution, or identifying value opportunities in fixed income markets.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/bond-benchmarking" ~/.claude/skills/majiayu000-claude-skill-registry-bond-benchmarking && rm -rf "$T"
skills/data/bond-benchmarking/SKILL.mdBond Benchmarking and Relative Performance Analysis Skill Development
Objective
Compare bond yields, durations, and credit quality against market benchmarks and indices to identify relative value opportunities and measure portfolio performance. Master systematic frameworks for peer comparison and excess return attribution.
Skill Classification
Domain: Fixed Income Portfolio Management Level: Advanced Prerequisites: Bond Pricing, Yield Measures, Duration, Credit Risk Estimated Time: 12-15 hours
Focus Areas
1. Benchmark Selection
Treasury Curve as Benchmark:
def treasury_benchmark_yield(maturity, treasury_curve): """ Interpolate Treasury yield for given maturity. Parameters: ----------- maturity : float Bond maturity in years treasury_curve : dict {maturity: yield} mapping for Treasury curve Returns: -------- float : Interpolated benchmark yield """ from scipy.interpolate import interp1d maturities = sorted(treasury_curve.keys()) yields = [treasury_curve[m] for m in maturities] interpolator = interp1d(maturities, yields, kind='cubic', fill_value='extrapolate') return float(interpolator(maturity))
Corporate Bond Index Benchmarks:
- Bloomberg Barclays US Corporate Bond Index
- ICE BofA US Corporate Index
- S&P US Issued Investment Grade Corporate Bond Index
Index Characteristics:
class BondIndexProfile: """Corporate bond index profile.""" def __init__(self, name): self.name = name self.constituents = [] def add_bond(self, bond_info): """Add bond to index.""" self.constituents.append(bond_info) def calculate_metrics(self): """Calculate index-level metrics.""" weights = np.array([b['market_value'] for b in self.constituents]) weights = weights / weights.sum() durations = np.array([b['duration'] for b in self.constituents]) yields = np.array([b['ytm'] for b in self.constituents]) return { 'weighted_duration': np.dot(weights, durations), 'weighted_yield': np.dot(weights, yields), 'total_market_value': weights.sum(), 'num_constituents': len(self.constituents) }
2. Spread to Benchmark
G-Spread (Spread to Government):
G-Spread = YTM_Corporate - YTM_Treasury_Interpolated
I-Spread (Interpolated Spread):
I-Spread = YTM_Corporate - Swap_Rate_Interpolated
Z-Spread (Zero-Volatility Spread):
P = Σ [CF_t / (1 + r_t + Z)^t] Where: P = Bond price r_t = Spot rate at time t Z = Constant spread (Z-spread) CF_t = Cash flow at time t
Python Implementation:
def g_spread(corporate_ytm, treasury_ytm): """Calculate G-spread (in basis points).""" return (corporate_ytm - treasury_ytm) * 10000 def calculate_z_spread(bond_price, cash_flows, spot_rates, times): """ Calculate Z-spread using root-finding. Parameters: ----------- bond_price : float Current bond price cash_flows : array-like Future cash flows spot_rates : array-like Risk-free spot rates for each cash flow times : array-like Time to each cash flow (years) Returns: -------- float : Z-spread (as decimal) """ from scipy.optimize import newton def price_diff(z_spread): pv = sum(cf / (1 + spot + z_spread)**t for cf, spot, t in zip(cash_flows, spot_rates, times)) return pv - bond_price z_spread = newton(price_diff, 0.01, maxiter=100) return z_spread def spread_duration(bond_duration, spread_change): """ Estimate price impact of spread change. ΔP/P ≈ -Duration × ΔSpread """ return -bond_duration * spread_change
3. Relative Value Analysis
Comparing Bonds:
class RelativeValueAnalysis: """Framework for relative value comparison.""" @staticmethod def compare_bonds(bond1, bond2, benchmark_yield): """ Compare two bonds for relative value. Parameters: ----------- bond1, bond2 : dict Bond characteristics: {'ytm', 'duration', 'rating', 'price'} benchmark_yield : float Benchmark Treasury yield Returns: -------- dict : Comparative metrics """ spread1 = (bond1['ytm'] - benchmark_yield) * 10000 # bps spread2 = (bond2['ytm'] - benchmark_yield) * 10000 spread_per_duration1 = spread1 / bond1['duration'] spread_per_duration2 = spread2 / bond2['duration'] return { 'bond1_spread': spread1, 'bond2_spread': spread2, 'bond1_spread_per_duration': spread_per_duration1, 'bond2_spread_per_duration': spread_per_duration2, 'relative_value_winner': 'Bond 1' if spread_per_duration1 > spread_per_duration2 else 'Bond 2' } @staticmethod def cheapness_score(actual_spread, model_spread): """ Calculate cheapness metric. Positive = bond is cheap (offers excess spread) Negative = bond is rich (offers insufficient spread) """ return actual_spread - model_spread
4. Peer Comparison (Sector/Rating)
Sector Classification:
SECTOR_GROUPS = { 'Financial': ['Banks', 'Insurance', 'Asset Managers', 'REITs'], 'Industrial': ['Manufacturing', 'Transportation', 'Capital Goods'], 'Utility': ['Electric', 'Gas', 'Water'], 'Technology': ['Software', 'Hardware', 'Semiconductors'], 'Consumer': ['Retail', 'Food & Beverage', 'Automobiles'], 'Energy': ['Oil & Gas', 'Renewables'], 'Healthcare': ['Pharmaceuticals', 'Biotechnology', 'Hospitals'] } def sector_peer_analysis(bond, peer_bonds): """ Compare bond against sector peers. Parameters: ----------- bond : dict Subject bond characteristics peer_bonds : list of dict Peer bond characteristics Returns: -------- dict : Peer comparison metrics """ peer_spreads = np.array([b['spread'] for b in peer_bonds]) peer_durations = np.array([b['duration'] for b in peer_bonds]) return { 'bond_spread': bond['spread'], 'peer_median_spread': np.median(peer_spreads), 'peer_mean_spread': np.mean(peer_spreads), 'spread_percentile': (peer_spreads < bond['spread']).sum() / len(peer_spreads) * 100, 'peer_duration_range': (peer_durations.min(), peer_durations.max()), 'relative_cheapness': bond['spread'] - np.median(peer_spreads) }
Rating-Based Comparison:
def rating_tier_analysis(bond_rating, bonds_universe): """ Compare bond spread within rating tier. Parameters: ----------- bond_rating : str Subject bond rating bonds_universe : list of dict All bonds with ratings Returns: -------- dict : Rating tier statistics """ same_rating = [b for b in bonds_universe if b['rating'] == bond_rating] if not same_rating: return None spreads = np.array([b['spread'] for b in same_rating]) return { 'rating': bond_rating, 'count': len(same_rating), 'spread_mean': spreads.mean(), 'spread_median': np.median(spreads), 'spread_std': spreads.std(), 'spread_min': spreads.min(), 'spread_max': spreads.max() }
5. Excess Return Measurement
Total Return Components:
Total Return = Coupon Income + Price Change + Reinvestment Income
Excess Return (Alpha):
Excess Return = Portfolio Return - Benchmark Return
Beta vs. Benchmark:
β = Cov(R_portfolio, R_benchmark) / Var(R_benchmark)
Python Framework:
class PerformanceAttribution: """Performance attribution framework.""" @staticmethod def total_return(beginning_value, ending_value, coupon_income): """ Calculate total return. Returns: -------- float : Total return (as decimal) """ return (ending_value + coupon_income - beginning_value) / beginning_value @staticmethod def excess_return(portfolio_return, benchmark_return): """Calculate alpha.""" return portfolio_return - benchmark_return @staticmethod def calculate_beta(portfolio_returns, benchmark_returns): """ Calculate portfolio beta vs. benchmark. Parameters: ----------- portfolio_returns : array-like Historical portfolio returns benchmark_returns : array-like Historical benchmark returns Returns: -------- float : Beta coefficient """ covariance = np.cov(portfolio_returns, benchmark_returns)[0, 1] benchmark_variance = np.var(benchmark_returns) return covariance / benchmark_variance @staticmethod def attribution_decomposition(portfolio_return, benchmark_return, duration_effect, spread_effect, selection_effect): """ Decompose excess return into components. Parameters: ----------- duration_effect : float Return from duration positioning spread_effect : float Return from spread changes selection_effect : float Return from security selection Returns: -------- dict : Attribution breakdown """ total_alpha = portfolio_return - benchmark_return return { 'total_excess_return': total_alpha, 'duration_contribution': duration_effect, 'spread_contribution': spread_effect, 'selection_contribution': selection_effect, 'residual': total_alpha - (duration_effect + spread_effect + selection_effect) }
6. Benchmark Replication and Tracking Error
Tracking Error:
TE = σ(R_portfolio - R_benchmark)
Information Ratio:
IR = Excess Return / Tracking Error
Python Implementation:
def tracking_error(portfolio_returns, benchmark_returns): """ Calculate tracking error. Parameters: ----------- portfolio_returns : array-like Portfolio returns over time benchmark_returns : array-like Benchmark returns over time Returns: -------- float : Annualized tracking error """ excess_returns = np.array(portfolio_returns) - np.array(benchmark_returns) return excess_returns.std() * np.sqrt(12) # Annualize if monthly def information_ratio(portfolio_returns, benchmark_returns): """ Calculate information ratio. Returns: -------- float : IR (higher is better) """ excess_returns = np.array(portfolio_returns) - np.array(benchmark_returns) mean_excess = excess_returns.mean() * 12 # Annualize te = excess_returns.std() * np.sqrt(12) return mean_excess / te if te > 0 else 0 def portfolio_replication(benchmark_holdings, target_budget): """ Replicate benchmark with subset of holdings. Uses optimization to minimize tracking error. """ from scipy.optimize import minimize n = len(benchmark_holdings) # Objective: minimize deviation from benchmark weights def objective(weights): benchmark_weights = np.array([h['weight'] for h in benchmark_holdings]) return np.sum((weights - benchmark_weights)**2) # Constraints constraints = [ {'type': 'eq', 'fun': lambda w: np.sum(w) - 1} # Sum to 1 ] bounds = [(0, 1) for _ in range(n)] initial_weights = np.ones(n) / n result = minimize(objective, initial_weights, method='SLSQP', bounds=bounds, constraints=constraints) return result.x
Expected Competency
Select benchmarks, calculate spreads (G/I/Z), perform peer analysis, measure excess returns and attribution, compute tracking error/IR, identify relative value, replicate indices.
Deliverables
benchmark_comparison.ipynb: Yield curves, spread tools (G/I/Z), peer comparison, excess return attribution, tracking error, relative value screener
bond_benchmark_report.md: Spread visualization, peer tables, attribution charts, relative value ranking, portfolio positioning
Reference Materials
Texts: Fabozzi Handbook of Fixed Income Securities (Performance Attribution), Dynkin Quantitative Management of Bond Portfolios
Standards: Bloomberg indices methodology, ICE BofA index construction, S&P Dow Jones fixed income methodology
Data: Bloomberg Terminal (BVAL curves), FRED (Treasury/spreads), FINRA TRACE (transactions)
Validation Framework
Self-Assessment Checklist
- Calculate G-spread for corporate vs. Treasury
- Compute Z-spread using spot curve
- Perform sector peer comparison
- Calculate portfolio tracking error
- Attribute excess returns to duration/spread/selection
- Identify cheap/rich bonds vs. peers
- Compute information ratio
Practice Problems
- Bond 5% YTM, 7-year, Treasury 3.5% → Calculate G-spread
- Portfolio 6% return, Benchmark 5.2%, TE 1.5% → Calculate IR
- 10 peer bonds → Identify 3 cheapest by spread-per-duration
- 150 bps excess return = duration (50) + spread (75) + selection (?)
Integration with Other Skills
- Yield Measures: YTM serves as basis for spreads
- Duration: Duration-adjusted spread comparison
- Credit Risk: Credit quality peer grouping
- Portfolio Management: Benchmark-aware construction
Standards and Compliance
All benchmarking analysis must document:
- Benchmark selection rationale
- Rebalancing frequency
- Data sources and dates
- Spread calculation methodology
- Performance calculation conventions
Version Control
Version: 1.0.0 | Last Updated: 2025-12-07 | Status: Production Ready
Previous:
credit-risk/ | Next: option-adjusted-spread/