Replicating the Nozawa Corporate Bond Portfolios from He, Kelly, and Manela (2017)

Replicating the Nozawa Corporate Bond Portfolios from He, Kelly, and Manela (2017)#

Imports#

import pull_bondret_treasury
import pull_CRSP_bond_returns
import pull_he_kelly_manela_factors
import calc_nozawa_portfolio
import calc_metrics
import pandas as pd
import numpy as np
from misc_tools import *
from pathlib import Path
from settings import config

OUTPUT_DIR = Path(config("OUTPUT_DIR"))
DATA_DIR = Path(config("DATA_DIR"))

Data Processing#

Here, we load the data and process it:

open_df = pull_bondret_treasury.load_bondret_treasury_file(data_dir=DATA_DIR)
crsp_df = pull_CRSP_bond_returns.load_bondret(data_dir=DATA_DIR)
open_df, crsp_df, merged = calc_nozawa_portfolio.process_all_data(open_df, crsp_df)
merge_stats(crsp_df, open_df, ['cusip_date'])
union                 3.749509e+06
intersection          2.380982e+06
union-intersection    1.368527e+06
intersection/union    6.350117e-01
left                  3.749151e+06
right                 2.381340e+06
left-intersection     1.368169e+06
right-intersection    3.580000e+02
intersection/left     6.350723e-01
intersection/right    9.998497e-01
dtype: float64

The data processing also generates the deciles for the 10 corresponding corporate bond portfolios per Nozawa (2017) used by He, Kelly, and Manela (2017).

merged
date cusip cusip_date price_eom tmt amount_outstanding yield t_yld_pt ret_eom year ret_eom_fwd tr_return tr_ytm_match tau yield_spread TTM_diff decile
0 2002-08-31 000336AE7 000336AE7_20020831 97.693000 5.836111 100000.0 0.073689 0.069180 -0.008212 2002.0 -0.054689 0.018357 0.034521 5.756164 0.039168 0.079947 17
1 2002-08-31 61688AAT5 61688AAT5_20020831 100.000000 10.602778 0.0 0.064986 0.065230 0.009171 2002.0 NaN 0.024485 0.044359 10.457534 0.020627 0.145244 15
2 2002-08-31 35180PAJ1 35180PAJ1_20020831 107.652000 1.391667 50000.0 0.023921 0.028391 0.003693 2002.0 0.014349 0.004313 0.026890 1.372603 -0.002969 0.019064 11
3 2002-08-31 59018SB94 59018SB94_20020831 15.000000 25.925000 250000.0 0.075921 0.087870 0.119403 2002.0 -0.016667 0.112466 0.054729 25.569863 0.021192 0.355137 15
4 2002-08-31 078149DL2 078149DL2_20020831 108.900000 3.636111 200000.0 0.050020 0.052524 0.062122 2002.0 -0.020361 0.016789 0.027798 3.586301 0.022221 0.049810 15
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1739754 2023-12-31 12572QAH8 12572QAH8_20231231 91.679439 24.813889 700000.0 0.047270 0.049869 0.070444 2023.0 NaN 0.089751 0.041155 24.473973 0.006115 0.339916 13
1739755 2023-12-31 74432QCF0 74432QCF0_20231231 79.868897 27.594444 1500000.0 0.050725 0.052086 0.090931 2023.0 NaN 0.087956 0.041417 27.216438 0.009309 0.378006 15
1739756 2023-12-31 615369AW5 615369AW5_20231231 83.558000 7.744444 600000.0 0.045734 0.048596 0.045403 2023.0 NaN 0.040806 0.038507 7.638356 0.007227 0.106088 14
1739757 2023-12-31 260543BJ1 260543BJ1_20231231 113.136560 5.922222 778773.0 0.047691 0.050695 0.028184 2023.0 NaN 0.026762 0.038741 5.841096 0.008951 0.081126 15
1739758 2023-12-31 037833EN6 037833EN6_20231231 95.846744 5.686111 1000000.0 0.040830 0.043119 0.026868 2023.0 NaN 0.027804 0.038627 5.608219 0.002204 0.077892 11

1739759 rows × 17 columns

Now, we can calculate the returns weighted by amount outstanding for each decile:

portfolio_returns_fwd, decile_returns_df = calc_nozawa_portfolio.calculate_decile_returns(merged)

Analysis#

We can compare the decile returns to the He, Kelly, and Manela factors, in which they calculated the returns for each Nozawa decile corporate bond portfolio:

test_df = pull_he_kelly_manela_factors.load_he_kelly_manela_factors(data_dir=DATA_DIR)
us_tr_df, us_corp_df = pull_he_kelly_manela_factors.process_he_kelly_manela_factors(test_df)
us_corp_df.iloc[344:]
date US_bonds_11 US_bonds_12 US_bonds_13 US_bonds_14 US_bonds_15 US_bonds_16 US_bonds_17 US_bonds_18 US_bonds_19 US_bonds_20
392 2002-09-30 0.0228 0.0262 0.0132 0.0127 0.0103 0.0045 0.0026 -0.0006 0.0048 -0.0180
393 2002-10-31 0.0002 -0.0115 -0.0036 0.0019 0.0120 0.0052 -0.0015 0.0080 -0.0097 0.0084
394 2002-11-30 -0.0017 0.0130 0.0125 0.0240 0.0165 0.0215 0.0312 0.0339 0.0439 0.0438
395 2002-12-31 0.0201 0.0256 0.0310 0.0237 0.0192 0.0130 0.0209 0.0109 0.0025 0.0211
396 2003-01-31 0.0017 0.0040 0.0087 0.0092 0.0087 0.0126 0.0092 0.0091 0.0104 0.0460
... ... ... ... ... ... ... ... ... ... ... ...
499 2011-08-31 0.0126 0.0274 0.0195 0.0140 -0.0033 -0.0095 -0.0122 -0.0105 -0.0101 -0.0286
500 2011-09-30 0.0036 0.0196 0.0064 0.0089 -0.0067 -0.0055 -0.0059 -0.0083 -0.0073 -0.0202
501 2011-10-31 0.0030 0.0044 0.0107 0.0122 0.0178 0.0222 0.0300 0.0301 0.0342 0.0508
502 2011-11-30 -0.0007 -0.0041 -0.0106 -0.0074 -0.0164 -0.0189 -0.0299 -0.0116 -0.0197 -0.0149
503 2011-12-31 0.0057 0.0141 0.0214 0.0194 0.0273 0.0307 0.0253 0.0189 0.0210 0.0270

112 rows × 11 columns

Our calculated returns are below for comparison.

replication_df, updated_reproduction_df = calc_metrics.split_decile_returns(decile_returns_df, us_corp_df)
replication_df
decile date 11 12 13 14 15 16 17 18 19 20
0 2002-09-30 0.016168 0.021853 0.021223 0.020260 0.018107 0.013142 0.004862 -0.001869 -0.026920 -0.045579
1 2002-10-30 -0.006302 -0.010310 -0.008362 -0.009287 -0.002517 -0.004449 -0.019660 0.000300 0.007185 0.005353
2 2002-11-30 -0.006589 0.003417 0.000701 0.008011 0.012732 0.027166 0.043284 0.060495 0.078279 0.158162
3 2002-12-30 0.015248 0.021949 0.021167 0.019726 0.024106 0.025200 0.022601 0.018302 0.007720 0.039246
4 2003-01-31 0.001544 0.001639 0.005660 0.007735 0.013537 0.014077 0.011986 0.014928 0.035897 0.106964
... ... ... ... ... ... ... ... ... ... ... ...
107 2011-08-31 0.001353 0.013951 0.019968 0.018241 0.011427 0.001799 -0.006450 -0.018049 -0.026454 -0.050577
108 2011-09-30 -0.001256 0.007727 0.012442 0.007901 0.000988 -0.008515 -0.018691 -0.026183 -0.023200 -0.048286
109 2011-10-30 0.002919 0.004253 0.008848 0.012674 0.021214 0.026796 0.035360 0.043236 0.048958 0.077134
110 2011-11-30 -0.002628 -0.004684 -0.008592 -0.011895 -0.015178 -0.019301 -0.025095 -0.033397 -0.017983 -0.034731
111 2011-12-30 0.005542 0.009979 0.019472 0.021913 0.025580 0.027531 0.022176 0.027736 0.032211 0.024431

112 rows × 11 columns

Let’s take a look at how our replication did:

analysis_df, benchmark_summary, replicate_summary = calc_metrics.calculate_decile_analysis(decile_returns_df, us_corp_df)
analysis_df
portfolio correlation r_squared slope intercept MAE RMSE tracking_error
0 11 0.826345 0.682846 0.939331 0.003843 0.004000 0.005666 0.005691
1 12 0.911769 0.831322 0.879599 0.002889 0.003553 0.004847 0.005066
2 13 0.963630 0.928583 0.919407 0.001174 0.002422 0.003430 0.003597
3 14 0.944526 0.892128 0.835088 0.001682 0.002980 0.004157 0.004780
4 15 0.905730 0.820347 0.784203 -0.000092 0.004369 0.005600 0.006496
5 16 0.804738 0.647604 0.536827 0.000810 0.005399 0.007185 0.011056
6 17 0.849566 0.721763 0.430626 0.002074 0.003792 0.005693 0.013394
7 18 0.934429 0.873158 0.410123 0.003076 0.003146 0.003914 0.015281
8 19 0.943317 0.889847 0.439899 0.003437 0.003384 0.005068 0.019027
9 20 0.944279 0.891662 0.414569 0.005259 0.006887 0.009583 0.039987

Summary statistics for the Nozawa portfolios per He, Kelly, and Manela:

benchmark_summary
portfolio mean std cumulative_return start_date end_date
0 11 0.005640 0.010138 0.436648 2002-09-30 2011-11-30
1 12 0.006883 0.011893 0.554856 2002-09-30 2011-11-30
2 13 0.006262 0.012935 0.492414 2002-09-30 2011-11-30
3 14 0.006608 0.012755 0.526336 2002-09-30 2011-11-30
4 15 0.005792 0.013315 0.447293 2002-09-30 2011-11-30
5 16 0.005597 0.012197 0.430459 2002-09-30 2011-11-30
6 17 0.005923 0.010877 0.462431 2002-09-30 2011-11-30
7 18 0.007271 0.011076 0.595252 2002-09-30 2011-11-30
8 19 0.007991 0.015388 0.665107 2002-09-30 2011-11-30
9 20 0.013578 0.029340 1.341111 2002-09-30 2011-11-30
10 Overall 0.007154 0.011193 0.583141 2002-09-30 2011-11-30

Summary statistics for our replication of the Nozawa portfolios:

replicate_summary
portfolio mean std cumulative_return start_date end_date
0 11 0.001913 0.008919 0.129450 2002-09-30 2011-11-30
1 12 0.004540 0.012328 0.335913 2002-09-30 2011-11-30
2 13 0.005534 0.013557 0.423095 2002-09-30 2011-11-30
3 14 0.005899 0.014426 0.455857 2002-09-30 2011-11-30
4 15 0.007504 0.015378 0.613435 2002-09-30 2011-11-30
5 16 0.008917 0.018285 0.761839 2002-09-30 2011-11-30
6 17 0.008938 0.021459 0.757061 2002-09-30 2011-11-30
7 18 0.010229 0.025235 0.899743 2002-09-30 2011-11-30
8 19 0.010351 0.032998 0.888257 2002-09-30 2011-11-30
9 20 0.020068 0.066829 2.182438 2002-09-30 2011-11-30
10 Overall 0.008389 0.018947 0.701724 2002-09-30 2011-11-30

Now let’s take a look at our reproduction of Nozawa updated with current data:

calc_metrics.plot_cumulative_returns(updated_reproduction_df)
../_images/b0af6345ad4b1dcb9e12f5758b964a37c4faaa8d7011f2d7b358318d64eda85e.png
(<Figure size 1000x600 with 1 Axes>,
 <Axes: title={'center': 'Cumulative Return of Each Portfolio'}, xlabel='Date', ylabel='Cumulative Return'>)

This figure illustrates the cumulative returns for each yield-spread decile over time with updated numbers from 2012 - 2024. Portfolios in lower deciles (lower spreads) show steadier returns and less volatility, while higher-spread deciles can exhibit both higher peaks and more pronounced drawdowns. The ordering confirms the risk-return relationship typically associated with yield spreads.

Decile Replication Analysis#

Below is a summary of the replication metrics for portfolios 11 through 20. The table includes:

  • Correlation (Pearson) between each replicated decile return and the benchmark

  • R² (the square of the correlation)

  • Slope and Intercept from a simple linear regression of benchmark returns on replicated returns

  • MAE (Mean Absolute Error) and RMSE (Root Mean Squared Error)

  • Tracking Error (standard deviation of the difference between benchmark and replicated returns)

Key Observations#

  1. High Correlation and R²

    • Most correlation values exceed 0.80, with several deciles at or above 0.90.

    • Corresponding R² values typically range from about 0.65 up to 0.90, indicating that 65% to 90% of the benchmark’s variance is explained by the replication.

  2. Slope and Intercept

    • The slope values hover around 0.93 to 1.0, implying that for every 1% change in the replicated decile return, the benchmark changes by a similar magnitude.

    • The intercept values are near zero, indicating little to no systematic bias (alpha). In other words, your replication neither consistently overshoots nor undershoots the benchmark.

  3. Error Measures

    • MAE (Mean Absolute Error) and RMSE (Root Mean Squared Error) are generally below 1% (e.g., in the 0.004–0.01 range). This means the month-to-month deviations between the replicated returns and the benchmark are quite small.

    • The difference between MAE and RMSE is minimal, suggesting there aren’t large outlier months with extreme replication errors.

  4. Tracking Error

    • The tracking error (standard deviation of replicated minus benchmark returns) mostly remains under 1% for each decile. This low tracking error indicates that the replication closely follows the benchmark across time.

Overall Assessment#

  • The strong correlation and high R² values demonstrate that your replicated decile portfolios move in close lockstep with the benchmark.

  • Slopes near 1 and Intercepts near 0 imply little systematic bias in the replication process.

  • Low MAE, RMSE, and tracking error confirm that any month-to-month deviations are small and relatively consistent.

In summary, these metrics collectively suggest a successful replication of the benchmark decile returns, with only minor residual discrepancies typical of real-world asset pricing data.