Classifying credit markets with macro factors #

Get packages and JPMaQS data #

import os
import numpy as np
import pandas as pd

from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import GaussianNB

from sklearn.metrics import make_scorer, balanced_accuracy_score

import macrosynergy.management as msm
import macrosynergy.panel as msp
import macrosynergy.pnl as msn
import macrosynergy.signal as mss
import macrosynergy.learning as msl
import macrosynergy.visuals as msv

from macrosynergy.download import JPMaQSDownload

import warnings

warnings.simplefilter("ignore")
# Cross-sections
cids_dm = ["USD", "EUR"]
cids_ig = ["UIG", "EIG"]
cids_hy = ["UHY", "EHY"]
cids_cr = ["UIG", "UHY", "EIG", "EHY"]

cids = cids_cr + cids_dm
# Indicator categories

rates = [
    "RYLDIRS02Y_NSA",
    "RYLDIRS05Y_NSA",
]

hpi = [
    "HPI_SA_P6M6ML6AR",
    "HPI_SA_P2Q2QL2AR",
    "HPI_SA_P1M1ML12",
    "HPI_SA_P1Q1QL4",
]

bsurv_changes = [
    # Construction confidence
    "CBCSCORE_SA_D6M6ML6",
    "CBCSCORE_SA_D3M3ML3",
    # Manufacturing confidence
    "MBCSCORE_SA_D6M6ML6",
    "MBCSCORE_SA_D3M3ML3",
    # Services confidence
    "SBCSCORE_SA_D6M6ML6",
    "SBCSCORE_SA_D3M3ML3",
]

csurv_changes = [
    "CCSCORE_SA_D6M6ML6",
    "CCSCORE_SA_D3M3ML3",
]

bank_lending = [
    "BLSCSCORE_NSA",
    "BLSCSCORE_NSA_D2Q2QL2",
    "BLSCSCORE_NSA_D1Q1QL4",
]

credit = [
    "PCREDITBN_SJA_P1M1ML12",
    "PCREDITBN_SJA_P1M1ML12_D1M1ML12",
]

main = rates + hpi + bsurv_changes + csurv_changes + bank_lending + credit

econ = [
    "INFTEFF_NSA",
    "INTRGDP_NSA_P1M1ML12_3MMA",
    "RGDP_SA_P1Q1QL4_20QMA",
]
mark = [
    "CRXR_VT10",
    "CRXR_NSA",
    "CRCRY_NSA",
]

xcats = main + econ + mark
# Tickers for download

single_tix = ["USD_GB10YXR_NSA", "USD_EQXR_NSA"]
tickers = (
    [cid + "_" + xcat for cid in cids_dm for xcat in main + econ]
    + [cid + "_" + xcat for cid in cids_cr for xcat in mark]
    + single_tix
)
# Download series from J.P. Morgan DataQuery by tickers

start_date = "2000-01-01"
end_date = None

# Retrieve credentials

oauth_id = os.getenv("DQ_CLIENT_ID")  # Replace with own client ID
oauth_secret = os.getenv("DQ_CLIENT_SECRET")  # Replace with own secret

# Download from DataQuery

downloader = JPMaQSDownload(client_id=oauth_id, client_secret=oauth_secret)
df = downloader.download(
    tickers=tickers,
    start_date=start_date,
    end_date=end_date,
    metrics=["value"],
    suppress_warning=True,
    show_progress=True,
)

dfd = df.copy()
dfd.info()
Downloading data from JPMaQS.
Timestamp UTC:  2025-09-04 11:32:21
Connection successful!
Requesting data: 100%|██████████| 3/3 [00:00<00:00,  4.92it/s]
Downloading data: 100%|██████████| 3/3 [00:16<00:00,  5.34s/it]
Some expressions are missing from the downloaded data. Check logger output for complete list.
4 out of 58 expressions are missing. To download the catalogue of all available expressions and filter the unavailable expressions, set `get_catalogue=True` in the call to `JPMaQSDownload.download()`.
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 346600 entries, 0 to 346599
Data columns (total 4 columns):
 #   Column     Non-Null Count   Dtype         
---  ------     --------------   -----         
 0   real_date  346600 non-null  datetime64[ns]
 1   cid        346600 non-null  object        
 2   xcat       346600 non-null  object        
 3   value      346600 non-null  float64       
dtypes: datetime64[ns](1), float64(1), object(2)
memory usage: 10.6+ MB

Availability #

Renaming #

# Rename quarterly tickers to roughly equivalent monthly tickers

dict_repl = {
    "HPI_SA_P1Q1QL4": "HPI_SA_P1M1ML12",
    "HPI_SA_P2Q2QL2AR": "HPI_SA_P6M6ML6AR",
}

for key, value in dict_repl.items():
    dfd["xcat"] = dfd["xcat"].str.replace(key, value)
# Rename and duplicate economic area tickers in accordance with credit market tickers

dfa = dfd[
    (dfd["cid"].isin(["EUR", "USD"])) & ~(dfd["xcat"].isin(["EQXR_NSA", "GB10YXR_NSA"]))
]

dfa_ig = dfa.replace({"^EUR": "EIG", "^USD": "UIG"}, regex=True)
dfa_hy = dfa.replace({"^EUR": "EHY", "^USD": "UHY"}, regex=True)

dfx = pd.concat([dfd, dfa_ig, dfa_hy])

Check availability #

xcatx = rates + hpi
cidx = cids_cr

msm.check_availability(df=dfx, xcats=xcatx, cids=cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/e0ebce480d5ed89f5cd13ab67a0244f6e1cc40afba9647ee9c3ac2da9cb1ea10.png
xcatx = bsurv_changes + csurv_changes + econ
cidx = cids_cr

msm.check_availability(df=dfx, xcats=xcatx, cids=cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/eba4098b590b5370363153f994b0cfa1840b30253dd28dd8776e777960aba196.png
xcatx = bank_lending + credit
cidx = cids_cr

msm.check_availability(df=dfx, xcats=xcatx, cids=cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/e90737c2d991621e240d30497dfd0f8d0ee5ac17927ed1fb7c0b763787a37492.png
xcatx = mark
cidx = cids_cr

msm.check_availability(df=dfx, xcats=xcatx, cids=cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/0803ef66f9cc06ea4905d5aaf4d3882534b60131e270d46f194a7c217a701e41.png

Factor computation and checks #

Single-concept calculations #

factors = []

Business sentiment dynamics #

# Annualize sentiment score changes

cidx = cids_cr
calcs = []

bss = ["CBCSCORE_SA", "MBCSCORE_SA", "SBCSCORE_SA"]
for bs in bss:
    calcs.append(f"{bs}_D6M6ML6AR = {bs}_D6M6ML6 * 2")
    calcs.append(f"{bs}_D3M3ML3AR = {bs}_D3M3ML3 * 4")

dfa = msp.panel_calculator(df=dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)

bsar = [f"{bs}_D6M6ML6AR" for bs in bss] + [f"{bs}_D3M3ML3AR" for bs in bss]
cidx = ["UIG", "EIG"]
xcatx = bsar

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=1,
    same_y=True,
    aspect=3,
    title="US- and Euro-area business sentiment changes",
    xcat_labels=[
        "Construction survey changes, 6m/6m",
        "Manufacturing survey changes, 6m/6m",
        "Services survey changes, 6m/6m",
        "Construction survey changes, 3m/3m",
        "Manufacturing confidence changes, 3m/3m",
        "Services confidence changes, 3m/3m",
    ],
    cid_labels=["USD", "EUR"],
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/cde7bb0f8bac6603891f2d8c1b51c027d0e55c5cc57be74e918db131d4c0c75f.png
# Single business sentiment change
cidx = cids_cr
xcatx = bsar

dfa = msp.linear_composite(df=dfx, xcats=xcatx, cids=cids_cr, new_xcat="BCONFCHG")
dfx = msm.update_df(dfx, dfa)
cidx = cids_cr
xcatx = ["BCONFCHG"]

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="Business confidence dynamics score",
    ncol=2,
    same_y=True,
    aspect=1.5,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/7568b696114f930bef49d055d16a56dacb83f0696fa5fce506895c66f594a222.png

Real interest rate conditions #

# Real rates versus 3-year averages

cidx = cids_cr
calcs = []

for rate in rates:
    calcs.append(f"X{rate}_NEG = - {rate} + RGDP_SA_P1Q1QL4_20QMA")
    calcs.append(f"XX{rate}_NEG = - {rate} + 2")

dfa = msp.panel_calculator(df=dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)

xratns = [f"{x}{rate}_NEG" for rate in rates for x in ["X", "XX"]]
cidx = ["UIG", "EIG"]
xcatx = xratns

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="US- and Euro-area excess interest rates",
    ncol=1,
    same_y=True,
    aspect=3,
    xcat_labels=[
        "2-year real yields, excess of GDP growth, negative",
        "2-year real yields, excess of 2%, negative,",
        "5-year real yields, excess of GDP growth, negative",
        "5-year real yields, excess of 2%, negative",
    ],
    cid_labels=["USD", "EUR"],
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/eae3d26963b37ec451a2f1ad9f48b6999e83d6007fb23f7e1bb425d00fd39f8c.png
# Composite excess real interest rate measure

xcatx = xratns
cidx = cids_cr

dfa = msp.linear_composite(df=dfx, xcats=xcatx, cids=cidx, new_xcat="XRATES_NEG")
dfx = msm.update_df(dfx, dfa)
cidx = cids_cr
xcatx = ["XRATES_NEG"]

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="Excess interest rates score",
    ncol=2,
    same_y=True,
    aspect=1.5,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/0d6b3eb8ab325bc184beb446341f151a42380f6d8f77e2ac530709ada16c3f82.png

Bank lending surveys #

bls = ["BLSCSCORE_NSA", "BLSCSCORE_NSA_D2Q2QL2"]

cidx = ["UIG", "EIG"]
xcatx = bls

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="US- and Euro-area bank lending survey statistics",
    ncol=1,
    same_y=True,
    aspect=3,
    xcat_labels=["Bank lending survey levels", "Bank lending survey changes"],
    cid_labels=["USD", "EUR"],
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/30c4192667af31873f0ee002be30fe312f29a1df641489f3225e45ddf25b8d6b.png
# Composite bank lending supply score
cidx = cids_cr
xcatx = bls

dfa = msp.linear_composite(df=dfx, xcats=xcatx, cids=cids_cr, new_xcat="BLSCOND")
dfx = msm.update_df(dfx, dfa)
cidx = cids_cr
xcatx = ["BLSCOND"]

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="Bank lending conditions score",
    ncol=2,
    same_y=True,
    aspect=1.5,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/5abd0e52648499dd6b9c2a188411322b52a905ea3e8d715fc0e85c17c0226049.png

Private credit growth #

# Credit acceleration category

cidx = cids_cr
calcs = []

calcs.append("PCG_DOYA = PCREDITBN_SJA_P1M1ML12_D1M1ML12")
calcs.append("XPCG = PCREDITBN_SJA_P1M1ML12 - INFTEFF_NSA - RGDP_SA_P1Q1QL4_20QMA")

dfa = msp.panel_calculator(df=dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)

xpcgd = ["XPCG", "PCG_DOYA"]
cidx = ["UIG", "EIG"]
xcatx = xpcgd

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="US- and Euro-area private credit levels and changes",
    ncol=1,
    same_y=True,
    aspect=3,
    xcat_labels=["Excess private credit growth", "Private credit growth changes"],
    cid_labels=["USD", "EUR"],
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/ca47fe84dc3aff13b6ed83b333bf8c417539720a411f52250f20f3698e60eb05.png
# Single credit expansion category
cidx = cids_cr
xcatx = xpcgd

dfa = msp.linear_composite(df=dfx, xcats=xcatx, cids=cids_cr, new_xcat="XPCREDIT")
dfx = msm.update_df(dfx, dfa)
cidx = cids_cr
xcatx = ["XPCREDIT"]

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="Private credit score",
    ncol=2,
    same_y=True,
    aspect=1.5,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/16bdd9ece7bec1e415cd30c5ad47a7e5cdf4cbf0e01940282268ca8289a44e4c.png

CDS spread widening #

# Credit acceleration category (temp proxy)

cidx = cids_cr
calcs = []

calcs.append("CSPREAD_1MMA = CRCRY_NSA.rolling(21).mean()")
calcs.append("CSPREAD_3MMA = CRCRY_NSA.rolling(21*3).mean()")
calcs.append("CSPREAD_6MMA = CRCRY_NSA.rolling(21*6).mean()")
calcs.append("CSPREAD_3MMA_L1M = CSPREAD_3MMA.shift(21)")
calcs.append("CSPREAD_6MMA_L3M = CSPREAD_6MMA.shift(21*3)")
calcs.append(
    "CSPREAD_P1Mv3M_NEG = - ( CSPREAD_1MMA - CSPREAD_3MMA_L1M ) / CSPREAD_3MMA_L1M"
)
calcs.append(
    "CSPREAD_P3Mv6M_NEG = - ( CSPREAD_3MMA - CSPREAD_6MMA_L3M ) / CSPREAD_6MMA_L3M"
)

dfa = msp.panel_calculator(df=dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)

csp = ["CSPREAD_P1Mv3M_NEG", "CSPREAD_P3Mv6M_NEG"]
cidx = cids_cr
xcatx = csp

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="CDS spread widening, each credit index",
    ncol=2,
    same_y=True,
    aspect=1.5,
    xcat_labels=[
        "CDS spread widening, 1m vs 3m, negative",
        "CDS spread widening, 3m vs 6m, negative",
    ],
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/bce5714d1c3f6b8427a82d074e6141394cb51d28e8f0d46b6a627f6e4445643a.png
# Single credit expansion category
cidx = cids_cr
xcatx = csp

dfa = msp.linear_composite(
    df=dfx, xcats=xcatx, cids=cids_cr, new_xcat="CSPRWIDE_NEG", start="2002-01-01"
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_cr
xcatx = ["CSPRWIDE_NEG"]

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="CDS spread widening score",
    ncol=2,
    same_y=True,
    aspect=1.5,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/6f5c3d1ac8ac09194ec848140700425ea6384f42397a20b2e383356ea93792bf.png

Transformations and visualizations #

factors = ["BCONFCHG", "XHPI", "XRATES_NEG", "BLSCOND", "XPCREDIT", "CSPRWIDE_NEG"]

Normalization #

# Zn-scores

xcatx = factors
cidx = cids_cr

dfa = pd.DataFrame(columns=list(dfx.columns))

for xc in xcatx:
    dfaa = msp.make_zn_scores(
        dfx,
        xcat=xc,
        cids=cidx,
        sequential=True,
        min_obs=261 * 3,
        neutral="zero",
        pan_weight=0,
        thresh=3,
        postfix="_ZN",
        est_freq="m",
    )
    dfa = msm.update_df(dfa, dfaa)

dfx = msm.update_df(dfx, dfa)

# Modified factor dictionary

factorz = [xcat + "_ZN" for xcat in factors]

Visual checks #

xcatx = factorz
cidx = cids_cr

sdate = "2000-01-01"

renaming_dict = {
    "BCONFCHG_ZN": "Business confidence changes",
    "XHPI_ZN": "Excess house price inflation",
    "XRATES_NEG_ZN": "Excess real rates (negative)",
    "BLSCOND_ZN": "Bank lending survey score",
    "XPCREDIT_ZN": "Private credit expansion score",
    "CSPRWIDE_NEG_ZN": "Spread widening (negative)",
}

msp.correl_matrix(
    dfx,
    xcat_labels=renaming_dict,
    xcats=xcatx,
    cids=cidx,
    start=sdate,
    freq="M",
    title="Factor correlation since 2000, Euro and US markets",
    size=(14, 10),
    annot=True,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/f9e1e4d927552e598eaa31f47638c7ab86814bc262a29cc22be721e6c22bb7c1.png
cidx = cids_cr
xcatx = factorz[:3]

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="Business confidence, house prices and interest rates",
    xcat_labels=list(renaming_dict.values())[:3],
    ncol=2,
    same_y=True,
    aspect=1.5,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/0026c99f0b53ccbf5072d6742cb2d7a1b698b2c86ff04aec23710ab44021b442.png
cidx = cids_cr
xcatx = factorz[3:]

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    title="Bank lending surveys, private credit, credit spreads",
    xcat_labels=list(renaming_dict.values())[3:6],
    ncol=2,
    same_y=True,
    aspect=1.5,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/07c33b1d9d7d0003bc2ba615ae560272bda435e822df73606c4e5eec18847a7c.png
crs = []

for xcat in factorz:
    cr = msp.CategoryRelations(
        df=dfx,
        xcats=[xcat, "CRXR_VT10"],
        cids=cids_hy,
        freq="M",
        lag=1,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    crs.append(cr)


msv.multiple_reg_scatter(
    cat_rels=crs,
    ncol=3,
    nrow=2,
    coef_box="lower right",
    prob_est="map",
    title="Conceptual factors and subsequent month cumulative high-yield CDS index returns, 10% vol-target",
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/dc183c6ab6a78fb9163232f7c2d945943a773b1a772a353c5fd5b0e7ce725058.png

Target return checks #

xcatx = ["CRXR_VT10"]
cidx = cids_cr

msp.view_timelines(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    cumsum=True,
    title="Cumulative returns for vol-targeted CDS indices ",
    ncol=2,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/14ee762ef72024186f2409e0cf23caf8f28e113e09bbe6a644f5efce01d90448.png
xcatx = ["CRXR_VT10"]
cidx = cids_cr

msp.correl_matrix(df=dfx, xcats=xcatx, cids=cidx, cluster=True)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/a65737e0f209d265a56f392828d5d23487fd32b398cbfe8f7a4ca2c1e55ce48c.png

Preparations for statistical learning #

Convert data to scikit-learn format (redundant) #

cidx = cids_cr
targ = "CRXR_VT10"
xcatx = factorz + [targ]

# Downsample from daily to monthly frequency (features as last and target as sum)
dfw = msm.categories_df(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
)

# Drop rows with missing values and assign features and target
dfw.dropna(inplace=True)
X_cr = dfw.iloc[:, :-1]
y_cr = dfw.iloc[:, -1]

Define cross-validation dynamics #

Visualize back test dynamics (with a one year forward window for visualization purposes).

  • The first set comprises the first 2 years of the panel.

  • A one month forward forecast is made.

Noting longer training times for random forests, that particular model is retrained every quarter.

# Initial split dynamics
min_cids = 1
min_periods = 12 * 2
test_size = 1

# Visualize back test pipeline
msl.ExpandingIncrementPanelSplit(
    train_intervals=test_size,
    test_size=12,
    min_cids=min_cids,
    min_periods=min_periods,
).visualise_splits(X_cr, y_cr)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/2aaeed6cd5bccaa755dc56345351f9292ca8f420f87ebb1fa8abaadae6193296.png
# Cross-validation dynamics
splitters = {
    "Expanding": msl.ExpandingKFoldPanelSplit(4),
    "Rolling": msl.RollingKFoldPanelSplit(4),
}

split_functions = None
scorers = {"BAC": make_scorer(balanced_accuracy_score)}
cv_summary = "median"
splitters["Expanding"].visualise_splits(X_cr, y_cr)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/4bc820638f473f8881002a4909d8f7208dec9d0efcb7ce3cf27e4a80d46025cc.png
splitters["Rolling"].visualise_splits(X_cr, y_cr)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/9373091a779dc5e0b4bac904647183472296cf48f67bdc07661fad8074135e8b.png

Signal generation #

Pooled panel models #

Naive Bayes #

so_nb = msl.SignalOptimizer(
    df=dfx,
    xcats=factorz + ["CRXR_VT10"],
    cids=cids_cr,
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    generate_labels=lambda x: 1 if x >= 0 else -1,
)

so_nb.calculate_predictions(
    name="NB",
    models={"NB": GaussianNB()},
    hyperparameters={
        "NB": {
            "var_smoothing": [1e-9, 1e-2, 1e-1, 5e-1, 1],
        },
    },
    scorers=scorers,
    inner_splitters=splitters,
    min_cids=min_cids,
    min_periods=min_periods,
    test_size=test_size,
    n_jobs_outer=-1,
    cv_summary=cv_summary,
    split_functions=split_functions,
)

so_nb.models_heatmap(
    "NB", title="Model selection heatmap, Naive Bayes panel classifier"
)

dfa = so_nb.get_optimized_signals("NB")
dfx = msm.update_df(dfx, dfa)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/fb9a5b6e0eb85ce31df98c8feaa58ab2ee152fcef2aa4282e7422bdd718f477b.png
msp.view_timelines(
    df=dfx,
    xcats=["NB"],
    cids=cids_cr,
    title="Out-of-sample Naive Bayes panel classifications",
    same_y=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/4308be3f123164e9d602d725f88c13e28b0c18c117e06f4221b5fb807a20d56c.png

Logistic regression #

so_lr = msl.SignalOptimizer(
    df=dfx,
    xcats=factorz + ["CRXR_VT10"],
    cids=cids_cr,
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    generate_labels=lambda x: 1 if x >= 0 else -1,
)

so_lr.calculate_predictions(
    name="LR",
    models={
        "LR": LogisticRegression(random_state=42),
    },
    hyperparameters={
        "LR": {
            "class_weight": ["balanced", None],
            "C": [1, 1e-2, 1e-4, 1e-6, 1e-8],
            "fit_intercept": [True, False],
        },
    },
    scorers=scorers,
    inner_splitters=splitters,
    min_cids=min_cids,
    min_periods=min_periods,
    cv_summary=cv_summary,
    test_size=test_size,
    n_jobs_outer=-1,
    split_functions=split_functions,
)

so_lr.models_heatmap(
    "LR", title="Model selection heatmap, Logistic Regression panel classifier"
)

dfa = so_lr.get_optimized_signals("LR")
dfx = msm.update_df(dfx, dfa)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/f4534b5920f62f1201741cbb16a3d557fee7cf14ba6b6cded99bf272fe7be7c2.png
msp.view_timelines(
    df=dfx,
    xcats=["LR"],
    cids=cids_cr,
    title="Out-of-sample Logistic Regression panel classifications",
    same_y=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/008c56074ee7b8c249d56184591310cf6a5fee5bcb960c878bc8f491c44c4b76.png

K Nearest Neighbors #

so_knn = msl.SignalOptimizer(
    df=dfx,
    xcats=factorz + ["CRXR_VT10"],
    cids=cids_cr,
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    generate_labels=lambda x: 1 if x >= 0 else -1,
)

so_knn.calculate_predictions(
    name="KNN",
    models={
        "KNN": msl.KNNClassifier(),
    },
    hyperparameters={
        "KNN": {
            "n_neighbors": ["sqrt", 0.1, 0.25],
            "weights": ["uniform", "distance"],
        },
    },
    scorers=scorers,
    inner_splitters=splitters,
    min_cids=min_cids,
    min_periods=min_periods,  # 2 years
    test_size=test_size,
    n_jobs_outer=-1,
    cv_summary=cv_summary,
)

so_knn.models_heatmap(
    "KNN", title="Model selection heatmap, Nearest Neighbors panel classifier"
)

dfa = so_knn.get_optimized_signals("KNN")
dfx = msm.update_df(dfx, dfa)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/a91f6c907ea867938ebc25bf8b4540bfa82e31891e5e2491c353d09d3e12283e.png
msp.view_timelines(
    df=dfx,
    title="Out-of-sample Nearest Neighbors panel classifications",
    xcats=["KNN"],
    cids=cids_cr,
    same_y=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/460d7e703a60186a8fabd229b0f6c2706c699e2fb675d221dba1d59937c1f195.png

Random forest #

so_rf = msl.SignalOptimizer(
    df=dfx,
    xcats=factorz + ["CRXR_VT10"],
    cids=cids_cr,
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    generate_labels=lambda x: 1 if x >= 0 else -1,
)

so_rf.calculate_predictions(
    name="RF",
    models={
        "RF": RandomForestClassifier(
            random_state=42,
            n_estimators=500,
        ),
    },
    hyperparameters={
        "RF": {
            "class_weight": [None, "balanced"],
            "max_features": ["sqrt", None],
            "max_samples": [0.25, 0.1],
            "min_samples_leaf": [1, 5],
        },
    },
    scorers=scorers,
    inner_splitters=splitters,
    cv_summary=cv_summary,
    min_cids=min_cids,
    min_periods=min_periods,  # 2 years
    test_size=3,
    split_functions=split_functions,
    n_jobs_outer=-1,
)

so_rf.models_heatmap(
    "RF", title="Model selection heatmap, Random Forest panel classifier"
)

dfa = so_rf.get_optimized_signals("RF")
dfx = msm.update_df(dfx, dfa)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/5c6bd4223001a72e1c6da3343efadd3be2f4bc1fbc72b83bdff78faa527fb699.png
msp.view_timelines(
    df=dfx,
    title="Out-of-sample Random Forest panel classifications",
    xcats=["RF"],
    cids=cids_cr,
    same_y=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/af474d8f7ab28164ddb573de081bec2bfa7c3aa59bf7fe299ad798d779b1bdfc.png

Global signal #

For each of the signals previously calculated, we use majority voting to determine a single global signal that is applied to all cross-sections.

mods = ["NB", "LR", "KNN", "RF"]

for mod in mods:
    dfa = msp.panel_calculator(
        df=dfx,
        calcs=[f"{mod}_GLB = iEIG_{mod} + iEHY_{mod} + iUIG_{mod} + iUHY_{mod}"],
        cids=cids_cr,
    ).dropna()
    dfa["value"] = dfa["value"].apply(lambda x: 1 if x > 0 else -1 if x < 0 else 0)
    dfx = msm.update_df(dfx, dfa)

cidx = ["UIG"]
xcatx = [mod + "_GLB" for mod in mods]
msp.view_timelines(
    df=dfx,
    cids=cidx,
    xcats=xcatx,
    same_y=False,
    ncol=2,
    xcat_grid=True,
    title="Unified global signals based on various classification methods",
    xcat_labels=["Naive Bayes", "Logistic Regression", "Nearest Neighbors", "Random Forest"]
)
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/059f1f4ffd741ed55388ca5f9b1cf81c0b6bb7f57d9024b09a8abdc90c768720.png

Signal value checks #

Global signal #

Accuracy check #

## Compare optimized signals with simple average z-scores

srr = mss.SignalReturnRelations(
    df=dfx,
    rets=["CRXR_VT10"],
    sigs=["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"],
    cids=cids_cr,
    cosp=True,
    freqs=["M"],
    agg_sigs=["last"],
    start="2002-12-31",
    slip=1,
)

selcols = [
    "accuracy",
    "bal_accuracy",
    "pos_sigr",  # In the regime classification setting, this is less relevant
    "pos_retr",
]

srr.multiple_relations_table().round(3)  # [selcols]
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
CRXR_VT10 KNN_GLB M last 0.637 0.594 0.891 0.628 0.649 0.538 0.028 0.373 -0.001 0.960 0.539
LR_GLB M last 0.579 0.492 0.815 0.633 0.630 0.354 -0.031 0.327 -0.039 0.118 0.495
NB_GLB M last 0.622 0.548 0.886 0.633 0.644 0.452 0.065 0.038 0.026 0.297 0.521
RF_GLB M last 0.644 0.578 0.919 0.644 0.656 0.500 0.052 0.101 0.049 0.053 0.526
xcatx = ["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"]
for xcat in xcatx:
    srr.accuracy_bars(
        ret="CRXR_VT10",
        sigs=xcat,
        title=f"Monthly accuracy ratios for global signals based on {xcat}",
    )
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/d0e60451a80e7ce4f0b76bb218cb8b84d54d0b9146b4d295756fe5517900353d.png https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/6052ab61dadf6b6543cf60cb0ec0b0c2f98373438802d3fb08eeab01819fce66.png https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/db3dbf6afc5d37b9c5b33dbd7f27b586dc05997dc368462a7badd41fee16a441.png https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/7238b94182052790db5c579d14a84fb4367dd08b729ab4adcafeb91481b0d388.png

Naive PnL #

Simple notional positions #

sigx = ["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"]
cidx = cids_ig

pnls = msn.NaivePnL(
    df=dfx,
    ret="CRXR_NSA",
    sigs=sigx,
    cids=cidx,
    start="2004-12-31",
    bms=["USD_GB10YXR_NSA", "USD_EQXR_NSA"],
)
for sig in sigx:
    pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        sig_add=0,
        sig_mult=1,
        rebal_freq="monthly",
        rebal_slip=1,
        # vol_scale=10,
    )

pnls.plot_pnls(
    title="Naive PnLs of long-short investment grade CDS strategy based on global classification",
    xcat_labels=[
        "Naive Bayes",
        "Logistic Regression",
        "Nearest Neighbors",
        "Random Forest",
    ],
    title_fontsize=14,
)
pnls.evaluate_pnls(pnl_cats=["PNL_" + sig for sig in sigx])
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/870442c8a0f8a8a5b1d3e6835b63f25a12eb788cc280f5bf53f639789d0c10bc.png
xcat PNL_NB_GLB PNL_LR_GLB PNL_KNN_GLB PNL_RF_GLB
Return % 1.121552 0.263721 1.048189 1.45029
St. Dev. % 3.884147 3.775952 3.716017 3.586568
Sharpe Ratio 0.288751 0.069842 0.282073 0.404367
Sortino Ratio 0.401674 0.09793 0.399246 0.573129
Max 21-Day Draw % -8.675489 -8.675489 -6.362013 -8.675489
Max 6-Month Draw % -11.285529 -10.279486 -11.152464 -8.985967
Peak to Trough Draw % -18.698017 -22.894696 -24.878217 -11.118991
Top 5% Monthly PnL Share 1.372922 5.666257 1.49553 0.992962
USD_GB10YXR_NSA correl -0.041543 0.007263 -0.048517 -0.090194
USD_EQXR_NSA correl 0.147809 0.101563 0.116544 0.251621
Traded Months 250 250 250 250
sigx = ["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"]
cidx = cids_ig

pnls = msn.NaivePnL(
    df=dfx,
    ret="CRXR_NSA",
    sigs=sigx,
    cids=cidx,
    start="2004-12-31",
    bms=["USD_GB10YXR_NSA", "USD_EQXR_NSA"],
)
for sig in sigx:
    pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        sig_add=0.5,
        sig_mult=1,
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        # vol_scale=10,
    )

pnls.make_long_pnl(vol_scale=None, label="Long only")

pnls.plot_pnls(
    title="Naive PnLs of long-biased investment grade CDS indices based on global classification",
    xcat_labels=[
        "Naive Bayes",
        "Logistic Regression",
        "Nearest Neighbors",
        "Random Forest",
        "Risk Parity",
    ],
    title_fontsize=14,
)
pnls.evaluate_pnls(pnl_cats=["PNL_" + sig for sig in sigx] + ["Long only"])
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/e33f0dbd59474bc03d7e29c0e1a4fef5d0c7e97319c896e39e14c68a18376b0f.png
xcat PNL_NB_GLB PNL_LR_GLB PNL_KNN_GLB PNL_RF_GLB Long only
Return % 2.207441 1.34961 2.134079 2.536179 2.178046
St. Dev. % 4.578577 4.344658 4.37203 4.610705 4.022915
Sharpe Ratio 0.482124 0.310637 0.488121 0.550063 0.54141
Sortino Ratio 0.67543 0.43675 0.700668 0.784647 0.773776
Max 21-Day Draw % -13.013234 -13.013234 -6.403795 -13.013234 -8.675489
Max 6-Month Draw % -13.878189 -10.181213 -13.878189 -10.181212 -11.587299
Peak to Trough Draw % -14.194191 -15.090157 -19.900931 -13.804806 -16.774382
Top 5% Monthly PnL Share 0.823571 1.213535 0.850391 0.72473 0.73676
USD_GB10YXR_NSA correl -0.158427 -0.123505 -0.170242 -0.192487 -0.280894
USD_EQXR_NSA correl 0.416834 0.395402 0.404268 0.485143 0.664574
Traded Months 250 250 250 250 250
sigx = ["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"]
cidx = cids_hy

pnls = msn.NaivePnL(
    df=dfx,
    ret="CRXR_NSA",
    sigs=sigx,
    cids=cidx,
    start="2004-12-31",
    bms=["USD_GB10YXR_NSA", "USD_EQXR_NSA"],
)
for sig in sigx:
    pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        sig_add=0,
        sig_mult=1,
        rebal_freq="monthly",
        rebal_slip=1,
        # vol_scale=10,
    )

pnls.plot_pnls(
    title="Naive PnLs of long-short high-yield CDS strategy based on global classification",
    xcat_labels=[
        "Naive Bayes",
        "Logistic Regression",
        "Nearest Neighbors",
        "Random Forest",
    ],
    title_fontsize=14,
)
pnls.evaluate_pnls(pnl_cats=["PNL_" + sig for sig in sigx])
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/a61b4a150683ef063cee22cf971c6886e9ed860f05c3404833cd975b1c7bc138.png
xcat PNL_NB_GLB PNL_LR_GLB PNL_KNN_GLB PNL_RF_GLB
Return % 6.202681 1.816972 7.018841 8.617254
St. Dev. % 14.09522 13.959032 13.453201 13.500907
Sharpe Ratio 0.440056 0.130165 0.521723 0.638272
Sortino Ratio 0.610696 0.181327 0.745563 0.918494
Max 21-Day Draw % -39.639918 -39.639918 -26.586614 -39.639918
Max 6-Month Draw % -49.101137 -47.66054 -49.617112 -34.852224
Peak to Trough Draw % -62.150898 -87.427533 -75.008819 -40.547029
Top 5% Monthly PnL Share 0.942931 2.93832 0.858206 0.685282
USD_GB10YXR_NSA correl -0.077925 -0.029777 -0.083647 -0.106726
USD_EQXR_NSA correl 0.24702 0.199624 0.219632 0.340683
Traded Months 250 250 250 250
sigx = ["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"]

cidx = cids_hy

pnls = msn.NaivePnL(
    df=dfx,
    ret="CRXR_NSA",
    sigs=sigx,
    cids=cidx,
    start="2004-12-31",
    bms=["USD_GB10YXR_NSA", "USD_EQXR_NSA"],
)
for sig in sigx:
    pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        sig_add=0.5,
        sig_mult=1,
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
    )

pnls.make_long_pnl(vol_scale=None, label="Long only")

pnls.plot_pnls(
    title="Naive PnLs of long-biased high-yield CDS indices based on global classification",
    xcat_labels=[
        "Naive Bayes",
        "Logistic Regression",
        "Nearest Neighbors",
        "Random Forest",
        "Risk Parity",
    ],
    title_fontsize=14,
)
pnls.evaluate_pnls(pnl_cats=["PNL_" + sig for sig in sigx] + ["Long only"])
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/595677fbd8a37569eb85005807b20e4cb73b7fa130c771fa7c35164674fa83fb.png
xcat PNL_NB_GLB PNL_LR_GLB PNL_KNN_GLB PNL_RF_GLB Long only
Return % 11.751624 7.365915 12.567785 14.166197 11.144394
St. Dev. % 17.867512 17.338767 17.420817 18.344456 14.844305
Sharpe Ratio 0.657709 0.424823 0.721423 0.772233 0.750752
Sortino Ratio 0.92362 0.598071 1.04754 1.11513 1.082708
Max 21-Day Draw % -59.459877 -59.459877 -29.327088 -59.459877 -39.639918
Max 6-Month Draw % -57.168804 -52.278336 -57.168804 -52.278336 -46.216449
Peak to Trough Draw % -63.390562 -63.327214 -69.088203 -60.820544 -58.497244
Top 5% Monthly PnL Share 0.648716 0.861276 0.621778 0.554646 0.600956
USD_GB10YXR_NSA correl -0.170895 -0.136731 -0.176824 -0.185124 -0.263877
USD_EQXR_NSA correl 0.486646 0.461388 0.468871 0.534924 0.703621
Traded Months 250 250 250 250 250

Vol-targeted positions #

sigx = ["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"]
cidx = cids_cr

pnls = msn.NaivePnL(
    df=dfx,
    ret="CRXR_VT10",
    sigs=sigx,
    cids=cidx,
    start="2004-12-31",
    bms=["USD_GB10YXR_NSA", "USD_EQXR_NSA"],
)
for sig in sigx:
    pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        sig_add=0,
        sig_mult=1,
        rebal_freq="monthly",
        rebal_slip=1,
        # vol_scale=10,
    )

pnls.plot_pnls(
    title="Naive PnLs of long-short vol-targeted CDS strategy based on global classification",
    xcat_labels=[
        "Naive Bayes",
        "Logistic Regression",
        "Nearest Neighbors",
        "Random Forest",
    ],
    title_fontsize=14,
)
pnls.evaluate_pnls(pnl_cats=["PNL_" + sig for sig in sigx])
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/37716dccdd81231c5fd74e59a96cb64ff10d85644c67b863af9cf04d40f5f422.png
xcat PNL_NB_GLB PNL_LR_GLB PNL_KNN_GLB PNL_RF_GLB
Return % 26.528523 10.47725 23.081439 25.867141
St. Dev. % 42.08219 42.363484 42.300648 42.017065
Sharpe Ratio 0.630398 0.247318 0.545652 0.615634
Sortino Ratio 0.876912 0.339117 0.750101 0.845534
Max 21-Day Draw % -131.548465 -131.548465 -99.790888 -131.548465
Max 6-Month Draw % -112.129873 -97.473066 -136.836688 -126.658224
Peak to Trough Draw % -135.27724 -161.496618 -147.08924 -155.734608
Top 5% Monthly PnL Share 0.587743 1.451403 0.703123 0.606124
USD_GB10YXR_NSA correl -0.112605 -0.066315 -0.110278 -0.132037
USD_EQXR_NSA correl 0.398808 0.333126 0.346691 0.422452
Traded Months 250 250 250 250
sigx = ["NB_GLB", "LR_GLB", "KNN_GLB", "RF_GLB"]
cidx = cids_cr

pnls = msn.NaivePnL(
    df=dfx,
    ret="CRXR_VT10",
    sigs=sigx,
    cids=cidx,
    start="2004-12-31",
    bms=["USD_GB10YXR_NSA", "USD_EQXR_NSA"],
)
for sig in sigx:
    pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        sig_add=0.5,
        sig_mult=1,
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        # vol_scale=10,
    )

pnls.make_long_pnl(vol_scale=None, label="Long only")

pnls.plot_pnls(
    title="Naive PnLs of long-biased vol-targeted CDS strategy based on global classification",
    xcat_labels=[
        "Naive Bayes",
        "Logistic Regression",
        "Nearest Neighbors",
        "Random Forest",
        "Risk Parity",
    ],
    title_fontsize=14,
)
pnls.evaluate_pnls(pnl_cats=["PNL_" + sig for sig in sigx] + ["Long only"])
https://macrosynergy.com/notebooks.build/trading-factors/classifying-credit-markets-with-macro-factors/_images/8a322c6b22e4b54a34e3c8d02f2964b5df18ce57ce1e5fdbbcb96499eab46948.png
['CRXR_VT10', 'KNN_GLB', 'LR_GLB', 'NB_GLB', 'RF_GLB', ..., 'PNL_KNN_GLB', 'PNL_RF_GLB', 'EQXR_NSA', 'GB10YXR_NSA', 'Long only']
Length: 12
Categories (71, object): ['BCONFCHG', 'BCONFCHG_ZN', 'BLSCOND', 'BLSCOND_ZN', ..., 'PNL_LR_GLB', 'PNL_KNN_GLB', 'PNL_RF_GLB', 'Long only']