Estimating EM sovereign risk premia #
Get packages and JPMaQS data #
Setup and imports #
# !pip install macrosynergy --upgrade
DQ_CLIENT_ID: str = "your_client_id"
DQ_CLIENT_SECRET: str = "your_client_secret"
# Constants and credentials
import os
REQUIRED_VERSION: str = "1.2.2"
DQ_CLIENT_ID: str = os.getenv("DQ_CLIENT_ID")
DQ_CLIENT_SECRET: str = os.getenv("DQ_CLIENT_SECRET")
PROXY = {} # Configure if behind corporate firewall
START_DATE: str = "1998-01-01"
import macrosynergy as msy
msy.check_package_version(required_version=REQUIRED_VERSION)
# If version check fails: pip install macrosynergy --upgrade
if not DQ_CLIENT_ID or not DQ_CLIENT_SECRET:
raise ValueError(
"Missing DataQuery credentials." \
"Please set DQ_CLIENT_ID and DQ_CLIENT_SECRET as environment variables or insert them directly in the notebook."
)
import numpy as np
import pandas as pd
from pandas import Timestamp
import matplotlib.pyplot as plt
import os
import io
from datetime import datetime
import macrosynergy.management as msm
import macrosynergy.panel as msp
import macrosynergy.signal as mss
import macrosynergy.pnl as msn
import macrosynergy.visuals as msv
from macrosynergy.download import JPMaQSDownload
from macrosynergy.panel.adjust_weights import adjust_weights
import warnings
pd.set_option('display.width', 400)
warnings.simplefilter("ignore")
Data selection and download #
cids_fc_latam = [ # Latam foreign currency debt countries
"BRL",
"CLP",
"COP",
"DOP",
"MXN",
"PEN",
"UYU",
]
cids_fc_emeu = [ # EM Europe foreign currency debt countries
"HUF",
"PLN",
"RON",
"RUB",
"RSD",
"TRY",
]
cids_fc_meaf = [ # Middle-East and Africa foreign currency debt countries
"AED",
"EGP",
"NGN",
"OMR",
"QAR",
"ZAR",
"SAR",
]
cids_fc_asia = [ # Asia foreign currency debt countries
"CNY",
"IDR",
"INR",
"PHP",
]
cids_fc = sorted(
list(
set(cids_fc_latam + cids_fc_emeu + cids_fc_meaf + cids_fc_asia)
)
)
cids_emxfc = ["CZK", "ILS", "KRW", "MYR", "SGD", "THB", "TWD"]
cids_em = sorted(cids_fc + cids_emxfc)
# Category tickers
# Features
govfin = [
"GGOBGDPRATIO_NSA",
"GGOBGDPRATIONY_NSA",
"GGDGDPRATIO_NSA",
]
xbal = [
"CABGDPRATIO_NSA_12MMA",
"MTBGDPRATIO_NSA_12MMA",
]
xliab = [
"NIIPGDP_NSA_D1Mv2YMA",
"NIIPGDP_NSA_D1Mv5YMA",
"IIPLIABGDP_NSA_D1Mv2YMA",
"IIPLIABGDP_NSA_D1Mv5YMA",
]
xdebt = [
"ALLIFCDSGDP_NSA",
"GGIFCDSGDP_NSA",
]
GOVRISK = [
"ACCOUNTABILITY_NSA",
"POLSTAB_NSA",
"CORRCONTROL_NSA",
]
growth = [
'RGDP_SA_P1Q1QL4_20QMA',
"RGDP_SA_P1Q1QL4"
]
infl = [
"CPIC_SA_P1M1ML12",
"CPIH_SA_P1M1ML12",
]
risk_metrics = [
"LTFCRATING_NSA",
"LTLCRATING_NSA",
"FCBICRY_NSA",
"FCBICRY_VT10",
"CDS05YSPRD_NSA",
"CDS05YXRxEASD_NSA",
]
# Targets
rets = ["FCBIR_NSA", "FCBIXR_NSA", "FCBIXR_VT10"]
# Benchmark returns
bms = ["USD_EQXR_NSA", "UHY_CRXR_NSA", "UIG_CRXR_NSA"]
# Create ticker list
xcats = govfin + xbal + xliab + xdebt + GOVRISK + growth + infl + risk_metrics + rets
tickers = [cid + "_" + xcat for cid in cids_em for xcat in xcats] + bms
print(f"Maximum number of tickers is {len(tickers)}")
Maximum number of tickers is 840
# Download macro-quantamental indicators from JPMaQS via the DataQuery API
with JPMaQSDownload(
client_id=DQ_CLIENT_ID,
client_secret=DQ_CLIENT_SECRET,
proxy=PROXY
) as downloader:
df: pd.DataFrame = downloader.download(
tickers=tickers,
start_date=START_DATE,
metrics=["value",],
suppress_warning=True,
show_progress=True,
report_time_taken=True,
get_catalogue=True,
)
Downloading the JPMAQS catalogue from DataQuery...
Downloaded JPMAQS catalogue with 23224 tickers.
Removed 82/840 expressions that are not in the JPMaQS catalogue.
Downloading data from JPMaQS.
Timestamp UTC: 2025-07-11 11:05:55
Connection successful!
Requesting data: 100%|██████████| 38/38 [00:07<00:00, 4.92it/s]
Downloading data: 100%|██████████| 38/38 [00:32<00:00, 1.15it/s]
Time taken to download data: 42.53 seconds.
Some expressions are missing from the downloaded data. Check logger output for complete list.
1 out of 758 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()`.
# Preserve original downloaded data for debugging and comparison
dfx = df.copy()
dfx.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4680163 entries, 0 to 4680162
Data columns (total 4 columns):
# Column Dtype
--- ------ -----
0 real_date datetime64[ns]
1 cid object
2 xcat object
3 value float64
dtypes: datetime64[ns](1), float64(1), object(2)
memory usage: 142.8+ MB
Availability checks and blacklisting #
Availability #
xcatx = govfin
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

xcatx = xbal
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

xcatx = xliab
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

xcatx = xdebt
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

xcatx = risk_metrics
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

xcatx = rets
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

xcatx = growth
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

xcatx = infl
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_em, missing_recent=False)

Blacklist #
black_fc = {'RUB': [Timestamp('2022-02-01 00:00:00'), Timestamp('2035-02-26 00:00:00')]}
Macro risk score construction and checks #
Preparatory transformations #
# Non-linear inflation effect theory
x = np.linspace(-5, 100, 100)
y = np.power(abs(x - 2), 1/2)
plt.figure(figsize=(12, 4))
plt.plot(x, y)
plt.title("Inflation and presumed effect on sovereign default risk")
plt.xlabel("Inflation")
plt.ylabel("Presumed impact on default risk")
plt.grid(True)
plt.show()

# Inflation effects
cidx = cids_fc
xcx = ["CPIH", "CPIC"]
calcs = [
f"{xc}_IE = {xc}_SA_P1M1ML12.applymap(lambda x: np.power(abs(x - 2), 1/2))"
for xc in xcx
]
dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)
xcatx = ["CPIH_IE", "CPIH_SA_P1M1ML12"]
msp.view_timelines(
dfx,
xcats=xcatx,
cids=cidx,
ncol=4,
start="2000-01-01",
title='CPI comparison - square function vs standard inflation change',
aspect=2,
same_y=False,
)

Macro risk scores #
Conceptual factor scores #
# Placeholder dictionaries
dict_factz = {}
dict_labels = {}
# Governing dictionary of quantamental indicators, neutral levels, and weightings
dict_macros = {
"GFINRISK":{
"GGOBGDPRATIO_NSA": ["median", "NEG", 1/3],
"GGOBGDPRATIONY_NSA": ["median", "NEG", 1/3],
"GGDGDPRATIO_NSA": ["median", "", 1/3],
},
"XBALRISK":{
"CABGDPRATIO_NSA_12MMA": ["median", "NEG", 0.5],
"MTBGDPRATIO_NSA_12MMA": ["median", "NEG", 0.5],
},
"XLIABRISK":{
"NIIPGDP_NSA_D1Mv2YMA": ["median", "NEG", 0.25],
"NIIPGDP_NSA_D1Mv5YMA": ["median", "NEG", 0.25],
"IIPLIABGDP_NSA_D1Mv2YMA": ["median", "", 0.25],
"IIPLIABGDP_NSA_D1Mv5YMA": ["median", "", 0.25],
},
"XDEBTRISK":{
"ALLIFCDSGDP_NSA": ["median", "", 0.5],
"GGIFCDSGDP_NSA": ["median", "", 0.5],
},
"GOVRISK":{
"ACCOUNTABILITY_NSA": ["median", "NEG", 1/3],
"POLSTAB_NSA": ["median", "NEG", 1/3],
"CORRCONTROL_NSA": ["median", "NEG", 1/3],
},
"GROWTHRISK":{
"RGDP_SA_P1Q1QL4_20QMA": ["median", "NEG", 0.5],
"RGDP_SA_P1Q1QL4": ["median", "NEG", 0.5],
},
"INFLRISK":{
"CPIH_IE": ["median", "", 0.5],
"CPIC_IE": ["median", "", 0.5],
}
}
# Normalize all macro-quantamental categories based on the broad EM set
cidx = cids_em
for fact in dict_macros.keys():
dict_fact = dict_macros[fact]
xcatx = list(dict_fact.keys())
dfa = pd.DataFrame(columns=list(dfx.columns))
for xc in xcatx:
postfix = dict_fact[xc][1]
neutral = dict_fact[xc][0]
dfaa = msp.make_zn_scores(
dfx,
xcat=xc,
cids=cidx,
sequential=True,
min_obs=261 * 3,
neutral=neutral,
pan_weight=1,
thresh=3,
postfix="_" + postfix + "ZN",
est_freq="m",
)
dfaa["value"] = dfaa["value"] * (1 if postfix == "" else -1)
dfa = msm.update_df(dfa, dfaa)
dict_factz[fact] = dfa["xcat"].unique()
dfx = msm.update_df(dfx, dfa)
# Combine quantamental scores to conceptual scores
cidx = cids_em
for key, value in dict_factz.items():
weights = [w[2] for w in list(dict_macros[key].values())]
dfa = msp.linear_composite(
dfx,
xcats=value,
cids=cidx,
weights=weights,
normalize_weights= True,
complete_xcats=False,
new_xcat=key,
)
dfx = msm.update_df(dfx, dfa)
for key in dict_factz.keys():
dfa = msp.make_zn_scores(
dfx,
xcat=key,
cids=cidx,
sequential=True,
min_obs=261 * 3,
neutral="zero",
pan_weight=1,
thresh=3,
postfix="_ZN",
est_freq="m",
)
dfx = msm.update_df(dfx, dfa)
macroz = [fact + "_ZN" for fact in dict_factz.keys()]
dict_labels["GGOBGDPRATIO_NSA_NEGZN"] = "Excess government balance, % of GDP, current year, negative"
dict_labels["GGOBGDPRATIONY_NSA_NEGZN"] = "Excess government balance, % of GDP, next year, negative"
dict_labels["GGDGDPRATIO_NSA_ZN"] = "Excess general government debt ratio, % of GDP"
dict_labels["GFINRISK_ZN"] = "Government finances risk score"
dict_labels["CABGDPRATIO_NSA_12MMA_NEGZN"] = "Current account balance, 12mma, % of GDP, negative"
dict_labels["MTBGDPRATIO_NSA_12MMA_NEGZN"] = "Merchandise trade balance, 12mma, % of GDP, negative"
dict_labels["XBALRISK_ZN"] = "External balances risk score"
dict_labels["IIPLIABGDP_NSA_D1Mv2YMA_ZN"] = "International liabilities, vs. 2yma, % of GDP"
dict_labels["IIPLIABGDP_NSA_D1Mv5YMA_ZN"] = "International liabilities, vs. 5yma, % of GDP"
dict_labels["NIIPGDP_NSA_D1Mv2YMA_NEGZN"] = "Net international investment position, vs. 2yma, % of GDP, negative"
dict_labels["NIIPGDP_NSA_D1Mv5YMA_NEGZN"] = "Net international investment position, vs. 5yma, % of GDP, negative"
dict_labels["XLIABRISK_ZN"] = "International position risk score"
dict_labels["ALLIFCDSGDP_NSA_NEGZN"] = "Excess foreign-currency debt securities, all, % of GDP, negative"
dict_labels["GGIFCDSGDP_NSA_NEGZN"] = "Excess foreign-currency debt securities, government, % of GDP, negative"
dict_labels["XDEBTRISK_ZN"] = "Foreign-currency debt risk score"
dict_labels["ACCOUNTABILITY_NSA_NEGZN"] = "Voice and accountability index, z-score"
dict_labels["POLSTAB_NSA_NEGZN"] = "Political stability and absence of violence/terrorism index, z-score"
dict_labels["CORRCONTROL_NSA_NEGZN"] = "Corruption control index, z-score"
dict_labels['GOVRISK_ZN'] = 'Governance risk score'
dict_labels["RGDP_SA_P1Q1QL4_20QMA_NEGZN"] = "Real GDP growth, percentage over a year ago, z-score, negative"
dict_labels["RGDP_SA_P1Q1QL4_NEGZN"] = "Real GDP growth, percentage over a year ago, z-score"
dict_labels['GROWTHRISK_ZN'] = "Growth risk score"
dict_labels["CPIH_IE_NEGZN"] = "Headline CPI inflation effect, z-score"
dict_labels["CPIC_IE_NEGZN"] = "Core CPI inflation effect, z-score"
dict_labels["INFLRISK_ZN"] = "Inflation risk score"
dict_countries = {
'AED': 'United Arab Emirates',
'BRL': 'Brazil',
'CLP': 'Chile',
'CNY': 'China',
'COP': 'Colombia',
'DOP': 'Dominican Republic',
'EGP': 'Egypt',
'HUF': 'Hungary',
'IDR': 'Indonesia',
'INR': 'India',
'MXN': 'Mexico',
'NGN': 'Nigeria',
'OMR': 'Oman',
'PEN': 'Peru',
'PHP': 'Philippines',
'PLN': 'Poland',
'QAR': 'Qatar',
'RON': 'Romania',
'RSD': 'Serbia',
'RUB': 'Russia',
'SAR': 'Saudi Arabia',
'TRY': 'Turkey',
'UYU': 'Uruguay',
'ZAR': 'South Africa'
}
# Box for quantamental score review
factor = "XBALRISK" # "GFINRISK" "XBALRISK" "XLIABRISK" "XDEBTRISK" "GOVRISK" "GROWTHRISK" "INFLRISK"
xcatx = list(dict_factz[factor])
cidx = cids_fc
sdate = "2000-01-01"
msp.view_ranges(
dfx,
xcats=xcatx,
kind="bar",
sort_cids_by="mean", # countries sorted by mean of the first category
start=sdate,
xcat_labels=dict_labels,
)
msp.view_timelines(
dfx,
xcats=xcatx,
cids=cidx,
ncol=4,
start=sdate,
same_y=True,
aspect=2,
xcat_labels=dict_labels,
cid_labels=dict_countries,
title=None,
title_fontsize=22,
legend_fontsize=16,
height=2,
)


xcatx = macroz
cidx = cids_fc
sdate = "2000-01-01"
msp.view_ranges(
dfx,
xcats=xcatx,
kind="bar",
sort_cids_by=None,
start=sdate,
xcat_labels=dict_labels,
)
msp.view_timelines(
dfx,
xcats=xcatx,
cids=cidx,
ncol=4,
start=sdate,
same_y=True,
aspect=2,
xcat_labels=dict_labels,
cid_labels=dict_countries,
title="Macro scores related to sovereign debt default risk (higher score means higher risk)",
title_fontsize=25,
legend_fontsize=16,
height=2,
)


Composite macro risk scores #
# Custom weights
dict_custom_weights = {
'GFINRISK_ZN': 1,
'XBALRISK_ZN': 1,
'XLIABRISK_ZN': 1,
'XDEBTRISK_ZN': 0.001,
'GOVRISK_ZN': 1,
'GROWTHRISK_ZN': 0.001,
'INFLRISK_ZN': 0.001,
}
reduced_dict_custom_weights = {
'GFINRISK_ZN': 1,
'XBALRISK_ZN': 1,
'XLIABRISK_ZN': 1,
'GOVRISK_ZN': 1
}
# Weighted composite macro risk scores
cidx = cids_fc
xcatx = macroz
reduced_macroz = ['GFINRISK_ZN', 'XBALRISK_ZN', 'XLIABRISK_ZN', 'GOVRISK_ZN']
equal_weights = [1/len(macroz)] * len(macroz)
reduced_custom_weights = [reduced_dict_custom_weights[m] for m in reduced_macroz]
weights = {
"EWS": (macroz, equal_weights),
"CWS": (reduced_macroz, reduced_custom_weights),
}
# Step 4: Loop through
for k, (xcat_list, weight_list) in weights.items():
dfa = msp.linear_composite(
dfx,
xcats=xcat_list,
cids=cids_fc,
weights=weight_list,
normalize_weights=True,
complete_xcats=False,
new_xcat="MACRORISK_" + k,
)
dfx = msm.update_df(dfx, dfa)
# Re-scoring the composites
for m in ['MACRORISK_EWS', 'MACRORISK_CWS']:
dfa = msp.make_zn_scores(
dfx,
xcat=m,
cids=cidx,
sequential=True,
min_obs=261 * 3,
neutral="zero",
pan_weight=1,
thresh=3,
postfix="_ZN",
est_freq="m",
)
dfx = msm.update_df(dfx, dfa)
dict_labels['MACRORISK_EWS_ZN'] = 'Composite macro risk score'
dict_labels['MACRORISK_CWS_ZN'] = 'Custom weighted macro risk score'
xcatx = ['MACRORISK_EWS_ZN'] # ['MACRORISK_EWS_ZN', 'MACRORISK_CWS_ZN']
cidx = cids_fc
sdate = "2000-01-01"
msp.view_ranges(
dfx,
xcats=xcatx,
kind="bar",
sort_cids_by=None,
start=sdate,
xcat_labels=dict_labels,
)
msp.view_timelines(
dfx,
xcats=xcatx,
cids=cidx,
ncol=4,
start=sdate,
same_y=True,
aspect=2,
xcat_labels=dict_labels,
cid_labels=dict_countries,
title="Composite macro risk related to sovereign risk, equally-weighted score of 7 constituents",
title_fontsize=25,
legend_fontsize=16,
height=2,
)


Market-implied risk scores #
# Use index carry where CDS spreads not available ("priced risk" score)
msm.missing_in_df(df, xcats=["CDS05YSPRD_NSA"], cids=cids_fc) # countries without CDS
cidx = ['AED', 'DOP', 'EGP', 'INR', 'NGN', 'OMR', 'QAR', 'RSD', 'SAR', 'UYU']
calcs = ["CDS05YSPRD_NSA = FCBICRY_NSA"]
dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)
# Use inverse rating score ("rated risk" score)
calcs = [
"LTFCRATING_ADJ = LTFCRATING_NSA + 1", # temporary fix for VEF/RUB problem
"LTFCRATING_INV = 1 / LTFCRATING_ADJ",
"CDS05YSPRD_LOG = np.log( CDS05YSPRD_NSA )" # accoount for non-linearity of spread changes
]
cidx = cids_fc
dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)
No missing XCATs across DataFrame.
Missing cids for CDS05YSPRD_NSA: ['AED', 'DOP', 'EGP', 'INR', 'NGN', 'OMR', 'QAR', 'RSD', 'SAR', 'UYU']
xcatx = ["LTFCRATING_INV", "MACRORISK_EWS_ZN"]
cidx = cids_fc
catregs = {}
for xc in xcatx:
catregs[xc] = msp.CategoryRelations(
dfx,
xcats=[xc, "CDS05YSPRD_NSA"],
cids=cidx,
years=1,
lag=0,
xcat_aggs=["mean", "mean"],
blacklist=black_fc,
start="2000-01-01",
)
msv.multiple_reg_scatter(
cat_rels=[v for k, v in catregs.items()],
reg_order=2,
share_axes=False,
ncol=2,
nrow=1,
figsize=(14, 7),
title="Risk indices and sovereign credit spreads, annual averages, 24 countries, since 2000",
title_fontsize=18,
xlab="Risk index",
ylab="Sovereign credit spread, %",
subplot_titles=["Ratings-related risk index", "Macro risk score"],
)

# Re-score the composite
for xc in ["CDS05YSPRD_LOG", "LTFCRATING_INV"]:
dfa = msp.make_zn_scores(
dfx,
xcat=xc,
cids=cidx,
sequential=True,
min_obs=261 * 3,
neutral="median",
pan_weight=1,
thresh=3,
postfix="_ZN",
est_freq="m",
)
dfx = msm.update_df(dfx, dfa)
dict_labels["CDS05YSPRD_LOG_ZN"] = "Log spread-based market risk score"
dict_labels["LTFCRATING_INV_ZN"] = "Ratings-based market risk score"
# Composite market risk scores
cidx = cids_fc
xcatx = ["CDS05YSPRD_LOG_ZN", "LTFCRATING_INV_ZN"]
dfa = msp.linear_composite(
dfx,
xcats=xcatx,
cids=cidx,
complete_xcats=False,
new_xcat="MARKETRISK",
)
dfx = msm.update_df(dfx, dfa)
# Re-score the composite
dfa = msp.make_zn_scores(
dfx,
xcat="MARKETRISK",
cids=cidx,
sequential=True,
min_obs=261 * 3,
neutral="zero",
pan_weight=1,
thresh=3,
postfix="_ZN",
est_freq="m",
)
dfx = msm.update_df(dfx, dfa)
dict_labels["MARKETRISK_ZN"] = "Composite market risk score"
xcatx = ["MARKETRISK_ZN", "CDS05YSPRD_LOG_ZN", "LTFCRATING_INV_ZN"]
cidx = cids_fc
sdate = "2000-01-01"
msp.view_ranges(
dfx,
xcats=xcatx,
kind="bar",
sort_cids_by="mean",
start=sdate,
xcat_labels=dict_labels,
)
msp.view_timelines(
dfx,
xcats=xcatx,
cids=cidx,
ncol=4,
start=sdate,
same_y=True,
xcat_labels=dict_labels,
title='Market risk scores for foreign-currency sovereign debt of major EMBI countries',
title_fontsize=25,
legend_fontsize=16,
cid_labels=dict_countries,
aspect=2,
height=2,
blacklist=black_fc
)


Basic relationship visualizations #
Long-term relations #
# Long-term ratings - spread relations
xcatx = ["LTFCRATING_INV_ZN", "CDS05YSPRD_LOG_ZN"]
cidx = cids_fc
cr = msp.CategoryRelations(
dfx,
xcats=xcatx,
cids=cidx,
years=5,
lag=0,
xcat_aggs=["mean", "mean"],
blacklist=black_fc,
start="2000-01-01",
)
cr.reg_scatter(
labels=True,
label_fontsize=12,
title="Long-term relations between credit spread and rated risk scores, by half-decades, since 2000",
title_fontsize=16,
xlab="Rated risk score, half-decade average",
ylab="Credit spread score, half-decade average",
)

# Long-term macro risk - spread relations
xcatx = ["MACRORISK_EWS_ZN", "CDS05YSPRD_LOG_ZN"]
cidx = cids_fc
cr = msp.CategoryRelations(
dfx,
xcats=xcatx,
cids=cidx,
years=5,
lag=0,
xcat_aggs=["mean", "mean"],
blacklist=black_fc,
start="2000-01-01",
)
cr.reg_scatter(
labels=True,
label_fontsize=12,
title="Long-term relations between composite sovereign risk and credit spread scores, by half-decades, since 2000",
title_fontsize=16,
xlab="Sovereign credit-related macro risk score, half-decade average (as far as available)",
ylab=dict_labels['CDS05YSPRD_LOG_ZN'],
)

# Long-term macro risk - ratings relations
xcatx = ["MACRORISK_EWS_ZN", "LTFCRATING_INV_ZN"]
cidx = cids_fc
cr = msp.CategoryRelations(
dfx,
xcats=xcatx,
cids=cidx,
years=5,
lag=0,
xcat_aggs=["mean", "mean"],
blacklist=black_fc,
start="2000-01-01",
)
cr.reg_scatter(
labels=True,
label_fontsize=12,
title="Long-term relations between composite sovereign risk and rated risk scores, by half-decades, since 2000",
title_fontsize=16,
xlab="Sovereign credit-related macro risk score, half-decade average (as far as available)",
ylab=dict_labels['LTFCRATING_INV_ZN'],
)

# Long-term macro risk - ratings relations
xcatx = ["MACRORISK_EWS_ZN", "MARKETRISK_ZN"]
cidx = cids_fc
cr = msp.CategoryRelations(
dfx,
xcats=xcatx,
cids=cidx,
years=5,
lag=0,
xcat_aggs=["mean", "mean"],
blacklist=black_fc,
start="2000-01-01",
)
cr.reg_scatter(
labels=True,
label_fontsize=12,
title="EM credit risk: macro risk scores and market risk scores, by half-decades, 24 sovereigns, since 2000",
title_fontsize=16,
xlab="Sovereign credit-related macro risk score, half-decade average (as far as available)",
ylab="Market risk score, half-decade average (as far as available)",
)

cidx = cids_fc
risks = ["MARKETRISK_ZN", "MACRORISK_EWS_ZN"]
returns = ["FCBIXR_NSA", "FCBIXR_VT10"]
dict_labels["FCBIXR_NSA"] = "Foreign-currency sovereign bond index return, %"
dict_labels["FCBIXR_VT10"] = "Vol-targeted foreign-currency sovereign bond index return, %"
all_relations = []
all_titles = []
for risk in risks:
for ret in returns:
cr = msp.CategoryRelations(
dfx,
xcats=[risk, ret],
cids=cidx,
freq="A",
# years=10,
lag=1,
xcat_aggs=["mean", "sum"],
blacklist=black_fc,
start="2000-01-01",
)
all_relations.append(cr)
risk_label = dict_labels[risk]
ret_label = dict_labels[ret].lower()
all_titles.append(risk_label + " vs. " + ret_label)
msv.multiple_reg_scatter(
cat_rels=all_relations,
title="Annual risk scores and subsequent returns for foreign-currency sovereign debt, 24 countries, since 2000",
xlab="Risk score (annual average)",
ylab="Returns, %, next year",
ncol=2,
nrow=2,
figsize=(14, 12),
prob_est="map",
coef_box="lower left",
subplot_titles=all_titles
)

cidx = cids_fc
risks = ["CDS05YSPRD_LOG_ZN", "LTFCRATING_INV_ZN"]
returns = ["FCBIXR_NSA", "FCBIXR_VT10"]
all_relations = []
all_titles = []
for risk in risks:
for ret in returns:
cr = msp.CategoryRelations(
dfx,
xcats=[risk, ret],
cids=cidx,
freq="A",
# years=10,
lag=1,
xcat_aggs=["mean", "sum"],
blacklist=black_fc,
start="2000-01-01",
)
all_relations.append(cr)
risk_label = dict_labels[risk]
ret_label = dict_labels[ret].lower()
all_titles.append(risk_label + " vs. " + ret_label)
msv.multiple_reg_scatter(
cat_rels=all_relations,
title=None,
xlab=None,
ylab=None,
ncol=2,
nrow=2,
figsize=(16, 10),
prob_est="map",
coef_box="lower left",
subplot_titles=all_titles
)

Value checks #
Composite directional signals #
Specs and panel test #
dict_dir = {
"sigs": ['MACROSPREAD_RPS_ZN','MACRORATING_RPS_ZN', 'MACROALL_RPS_ZN', 'MARKETRISK_ZN'],
"targs": ["FCBIXR_VT10", "FCBIXR_NSA"],
"cidx": cids_fc,
"start": "2000-01-01",
"black": black_fc,
}
dix = dict_dir
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
catregs = {}
for sig in sigs:
catregs[sig] = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
lag=1,
xcat_aggs=["last", "sum"],
start=start,
blacklist=black,
)
msv.multiple_reg_scatter(
cat_rels=[v for k, v in catregs.items()],
ncol=2,
nrow=2,
figsize=(14, 12),
title="Risk scores and subsequent foreign currency bond index excess returns, 24 countries since 2000",
title_fontsize=20,
xlab="End-of-quarter score",
ylab="Foreign currency bond index excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
single_chart=True,
subplot_titles=[dict_labels[key] for key in sigs]
)

dix = dict_dir
sig = dix["sigs"][2]
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
cr = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
years=None,
lag=1,
xcat_aggs=["last", "sum"], # period mean adds stability
start=start,
blacklist=black,
)
cr.reg_scatter(
title="Macro risk premia and subsequent foreign currency bond index excess returns, 24 countries since 2000",
labels=False,
xlab="Sovereign credit macro risk premium score, end of quarter",
ylab="Foreign currency bond index excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
separator=2013,
size=(10, 8),
)
cr.reg_scatter(
title="Macro risk premia and subsequent foreign currency bond index excess returns, 24 countries since 2000",
labels=False,
xlab="Macro risk premium",
ylab="Vol-adjusted returns",
coef_box="lower right",
prob_est="pool",
separator="cids",
)


Accuracy and correlation check #
dix = dict_dir
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
srr = mss.SignalReturnRelations(
dfx,
rets=ret,
cids=cidx,
sigs=sigs,
# cosp=True,
freqs="M",
agg_sigs=["last"], # for stability
start=start,
)
dix["srr"] = srr
dix = dict_dir
srr = dix["srr"]
tbl=srr.multiple_relations_table(signal_name_dict=dict_labels).round(3)
display(tbl.transpose())
Return | Vol-targeted foreign-currency sovereign bond index return, % | |||
---|---|---|---|---|
Signal | Composite market risk score | Spread-based macro risk premium score | Ratings-based macro risk premium score | Macro risk premium score |
Frequency | M | M | M | M |
Aggregation | last | last | last | last |
accuracy | 0.501 | 0.451 | 0.474 | 0.464 |
bal_accuracy | 0.510 | 0.511 | 0.510 | 0.515 |
pos_sigr | 0.462 | 0.239 | 0.336 | 0.282 |
pos_retr | 0.610 | 0.608 | 0.608 | 0.610 |
pos_prec | 0.620 | 0.625 | 0.622 | 0.631 |
neg_prec | 0.399 | 0.397 | 0.399 | 0.399 |
pearson | 0.020 | 0.040 | 0.035 | 0.042 |
pearson_pval | 0.122 | 0.003 | 0.009 | 0.001 |
kendall | 0.025 | 0.045 | 0.028 | 0.038 |
kendall_pval | 0.004 | 0.000 | 0.002 | 0.000 |
auc | 0.510 | 0.508 | 0.510 | 0.513 |
dix = dict_dir
srr = dix["srr"]
srr.accuracy_bars(type='cross_section', sigs="MACROALL_RPS_ZN", size=(16, 4))
srr.accuracy_bars(type='signals', size=(16, 4))


dix = dict_dir
srr = dix["srr"]
srr.correlation_bars(type='cross_section', sigs="MACROALL_RPS_ZN", size=(16, 4))
srr.correlation_bars(type='signals', size=(16, 4), x_labels=dict_labels)


Naive PnLs #
dix = dict_dir
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
rt = ret.split('_')[-1] # 'NSA' or 'VT10'
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
for bias in [0, 1]:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add = bias,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL" + rt + str(bias),
rebal_slip=1,
vol_scale=10,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")
dix["pnls_"+rt.lower()] = pnls
dix = dict_dir
rt = "VT10"
bias = 1
pnls = dix["pnls_"+rt.lower()]
sigs = ["MACROALL_RPS_ZN", "MARKETRISK_ZN"]
strats = [sig + "_PNL" + rt + str(bias) for sig in sigs]
pnl_labels = {
"MACROALL_RPS_ZN_PNL" + rt + str(bias): "Macro risk premium score with long bias",
"MARKETRISK_ZN_PNL" + rt + str(bias): "Market risk score with long bias",
"Long only": "Long only risk parity",
}
pnls.plot_pnls(
title="Naive PnL for risk-premia and benchmark strategies, 24 EM sovereigns, vol-targeted positions",
pnl_cats=strats + ["Long only"],
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats + ["Long only"]).round(3))
pnls.signal_heatmap(pnl_name=f"MACROALL_RPS_ZN_PNL" + rt + str(bias), figsize=(20, 10))

xcat | MACROALL_RPS_ZN_PNLVT101 | MARKETRISK_ZN_PNLVT101 | Long only |
---|---|---|---|
Return % | 11.983004 | 8.314899 | 7.025648 |
St. Dev. % | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 1.1983 | 0.83149 | 0.702565 |
Sortino Ratio | 1.672573 | 1.112942 | 0.925355 |
Max 21-Day Draw % | -39.285473 | -41.390234 | -42.691999 |
Max 6-Month Draw % | -57.286435 | -41.88914 | -37.994371 |
Peak to Trough Draw % | -67.5839 | -48.768828 | -62.285155 |
Top 5% Monthly PnL Share | 0.489217 | 0.622572 | 0.760984 |
USD_EQXR_NSA correl | 0.216862 | 0.293824 | 0.214687 |
UHY_CRXR_NSA correl | 0.2623 | 0.350987 | 0.273763 |
UIG_CRXR_NSA correl | 0.257732 | 0.332196 | 0.253393 |
Traded Months | 307 | 307 | 307 |

dix = dict_dir
sigs = dix['sigs']
ret = dix["targs"][1]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
rt = ret.split('_')[-1] # 'NSA' or 'VT10'
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
for bias in [0, 1]:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add = bias,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL" + rt + str(bias),
rebal_slip=1,
vol_scale=10,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")
dix["pnls_"+rt.lower()] = pnls
dix = dict_dir
rt = "NSA"
bias = 1
pnls = dix["pnls_"+rt.lower()]
sigs = ["MACROALL_RPS_ZN", "MARKETRISK_ZN"]
strats = [sig + "_PNL" + rt + str(bias) for sig in sigs]
pnl_labels = {
"MACROALL_RPS_ZN_PNL" + rt + str(bias): "Macro risk premium score with long bias",
"MARKETRISK_ZN_PNL" + rt + str(bias): "Market risk score with long bias",
"Long only": "Long only risk parity",
}
pnls.plot_pnls(
title="Naive PnL for risk-premia and benchmark strategies, 24 EM sovereigns, nominal positions",
pnl_cats=strats + ["Long only"],
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats + ["Long only"], label_dict=pnl_labels).round(3))
pnls.signal_heatmap(pnl_name=f"MACROALL_RPS_ZN_PNL" + rt + str(bias), figsize=(20, 10))

xcat | Macro risk premium score with long bias | Market risk score with long bias | Long only risk parity |
---|---|---|---|
Return % | 11.273791 | 9.300272 | 8.004047 |
St. Dev. % | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 1.127379 | 0.930027 | 0.800405 |
Sortino Ratio | 1.609415 | 1.291543 | 1.079887 |
Max 21-Day Draw % | -50.839917 | -37.453612 | -40.898679 |
Max 6-Month Draw % | -58.26404 | -42.623356 | -44.785424 |
Peak to Trough Draw % | -58.850524 | -45.725342 | -55.021649 |
Top 5% Monthly PnL Share | 0.569486 | 0.619495 | 0.641037 |
USD_EQXR_NSA correl | 0.247441 | 0.308603 | 0.270744 |
UHY_CRXR_NSA correl | 0.304675 | 0.379014 | 0.343803 |
UIG_CRXR_NSA correl | 0.332395 | 0.376879 | 0.330702 |
Traded Months | 307 | 307 | 307 |

Conceptual directional signals #
Specs and panel test #
dict_cds = {
"sigs": crpz + ['MARKETRISK_ZN'],
"targs": ["FCBIXR_VT10", "FCBIXR_NSA"],
"cidx": cids_fc,
"start": "2000-01-01",
"black": black_fc,
}
dix = dict_cds
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
catregs = {}
for sig in sigs:
catregs[sig] = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
lag=1,
xcat_aggs=["mean", "sum"],
start=start,
blacklist=black,
)
msv.multiple_reg_scatter(
cat_rels=[v for k, v in catregs.items()],
ncol=2,
nrow=4,
figsize=(14, 24),
title="Spread-based premium scores and sovereign bond index returns, 24 EMBI countries, since 2000 or inception",
title_fontsize=20,
xlab="End-of-quarter score",
ylab="Foreign currency bond index excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
single_chart=True,
subplot_titles=[dict_labels[key] for key in sigs]
)
XLIABRISK_RPS_ZN misses: ['AED', 'OMR', 'QAR', 'SAR'].
XDEBTRISK_RPS_ZN misses: ['QAR'].

Accuracy and correlation check #
dix = dict_cds
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
srr = mss.SignalReturnRelations(
dfx,
rets=ret,
cids=cidx,
sigs=sigs,
# cosp=True,
freqs="M",
agg_sigs=["mean"], # for stability
start=start,
)
dix["srr"] = srr
dix = dict_cds
srr = dix["srr"]
tbl=srr.multiple_relations_table(signal_name_dict=dict_labels).round(3)
display(tbl.transpose())
Return | Vol-targeted foreign-currency sovereign bond index return, % | |||||||
---|---|---|---|---|---|---|---|---|
Signal | Composite market risk score | Government finance conceptual macro risk premium score | External balances conceptual macro risk premium score | International position conceptual macro risk premium score | Foreign-currency debt conceptual macro risk premium score | Governance conceptual macro risk premium score | Growth risk conceptual macro risk premium score | Inflation risk conceptual macro risk premium score |
Frequency | M | M | M | M | M | M | M | M |
Aggregation | mean | mean | mean | mean | mean | mean | mean | mean |
accuracy | 0.500 | 0.482 | 0.482 | 0.513 | 0.444 | 0.470 | 0.485 | 0.509 |
bal_accuracy | 0.509 | 0.505 | 0.510 | 0.520 | 0.501 | 0.491 | 0.508 | 0.514 |
pos_sigr | 0.459 | 0.393 | 0.371 | 0.468 | 0.244 | 0.403 | 0.399 | 0.481 |
pos_retr | 0.610 | 0.609 | 0.609 | 0.604 | 0.610 | 0.610 | 0.609 | 0.610 |
pos_prec | 0.620 | 0.616 | 0.622 | 0.625 | 0.611 | 0.599 | 0.619 | 0.624 |
neg_prec | 0.399 | 0.395 | 0.399 | 0.414 | 0.390 | 0.383 | 0.397 | 0.403 |
pearson | 0.022 | 0.025 | 0.011 | 0.033 | 0.019 | 0.019 | 0.010 | 0.052 |
pearson_pval | 0.086 | 0.051 | 0.400 | 0.023 | 0.144 | 0.135 | 0.448 | 0.000 |
kendall | 0.026 | 0.027 | 0.024 | 0.028 | 0.020 | 0.011 | 0.010 | 0.030 |
kendall_pval | 0.003 | 0.002 | 0.007 | 0.004 | 0.024 | 0.201 | 0.240 | 0.001 |
auc | 0.510 | 0.505 | 0.510 | 0.520 | 0.501 | 0.491 | 0.508 | 0.514 |
dix = dict_cds
srr = dix["srr"]
sig = dix['sigs'][0]
srr.accuracy_bars(type='cross_section', sigs=sig, size=(16, 4))
srr.accuracy_bars(type='signals', size=(16, 4), x_labels=dict_labels, x_labels_rotate= 60)


dix = dict_cds
srr = dix["srr"]
sig = dix['sigs'][0]
srr.correlation_bars(type='cross_section', sigs=sig, size=(16, 4))
srr.correlation_bars(type='signals', size=(16, 4), x_labels=dict_labels,x_labels_rotate=70 )


Naive PnLs #
dix = dict_cds
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
rt = ret.split('_')[-1] # 'NSA' or 'VT10'
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
for bias in [0, 1]:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add = bias,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL" + rt + str(bias),
rebal_slip=1,
vol_scale=10,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")
dix["pnls_"+rt.lower()] = pnls
dix = dict_cds
rt = "VT10"
bias = 1
pnls = dix["pnls_"+rt.lower()]
sigs = dix["sigs"]
strats = [sig + "_PNL" + rt + str(bias) for sig in sigs]
pnl_labels = {
'GFINRISK_RPS_ZN_PNLVT101': "Government finance conceptual macro risk premium score",
'XBALRISK_RPS_ZN_PNLVT101': "External balances conceptual macro risk premium score",
'XLIABRISK_RPS_ZN_PNLVT101': "International position conceptual macro risk premium score",
'XDEBTRISK_RPS_ZN_PNLVT101': "Foreign-currency debt conceptual macro risk premium score",
'GOVRISK_RPS_ZN_PNLVT101': "Governance conceptual macro risk premium score",
'GROWTHRISK_RPS_ZN_PNLVT101': "Growth risk conceptual macro risk premium score",
'INFLRISK_RPS_ZN_PNLVT101': "Inflation risk conceptual macro risk premium score",
'MARKETRISK_ZN_PNLVT101': "Market risk score",
'Long only': 'Long only'
}
pnls.plot_pnls(
title=None,
pnl_cats=strats + ["Long only"],
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats + ["Long only"], label_dict=pnl_labels).round(3))

xcat | Government finance conceptual macro risk premium score | External balances conceptual macro risk premium score | International position conceptual macro risk premium score | Foreign-currency debt conceptual macro risk premium score | Governance conceptual macro risk premium score | Growth risk conceptual macro risk premium score | Inflation risk conceptual macro risk premium score | Market risk score | Long only |
---|---|---|---|---|---|---|---|---|---|
Return % | 9.102722 | 7.888097 | 7.871175 | 9.385148 | 7.544993 | 7.249389 | 9.423029 | 8.314899 | 7.025648 |
St. Dev. % | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 0.910272 | 0.78881 | 0.787117 | 0.938515 | 0.754499 | 0.724939 | 0.942303 | 0.83149 | 0.702565 |
Sortino Ratio | 1.228459 | 1.059816 | 1.088246 | 1.257191 | 1.014222 | 0.951667 | 1.259327 | 1.112942 | 0.925355 |
Max 21-Day Draw % | -40.261492 | -45.127431 | -42.388371 | -44.931992 | -41.720205 | -48.349838 | -46.330392 | -41.390234 | -42.691999 |
Max 6-Month Draw % | -62.213966 | -31.774385 | -30.870158 | -64.954393 | -33.972294 | -75.896825 | -29.852892 | -41.88914 | -37.994371 |
Peak to Trough Draw % | -70.445424 | -54.782295 | -52.09107 | -76.673907 | -52.115205 | -80.591062 | -47.747054 | -48.768828 | -62.285155 |
Top 5% Monthly PnL Share | 0.567847 | 0.606462 | 0.720914 | 0.603385 | 0.72878 | 0.676996 | 0.633494 | 0.622572 | 0.760984 |
USD_EQXR_NSA correl | 0.265838 | 0.27471 | 0.268902 | 0.236804 | 0.233093 | 0.216432 | 0.224113 | 0.293824 | 0.214687 |
UHY_CRXR_NSA correl | 0.326448 | 0.328438 | 0.318467 | 0.289352 | 0.286193 | 0.260598 | 0.295552 | 0.350987 | 0.273763 |
UIG_CRXR_NSA correl | 0.327828 | 0.307758 | 0.297411 | 0.308355 | 0.250892 | 0.261908 | 0.267713 | 0.332196 | 0.253393 |
Traded Months | 307 | 307 | 307 | 307 | 307 | 307 | 307 | 307 | 307 |
Composite relative signals #
Specs and panel test #
dict_rel = {
"sigs": ['MACROSPREAD_RPS_ZNvEM','MACRORATING_RPS_ZNvEM', 'MACROALL_RPS_ZNvEM', 'MARKETRISK_ZNvEM'],
"targs": ["FCBIXR_VT10vEM", "FCBIXR_NSAvEM"],
"cidx": cids_fc,
"start": "2000-01-01",
"black": black_fc,
}
dix = dict_rel
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
catregs = {}
for sig in sigs:
catregs[sig] = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
lag=1,
xcat_aggs=["mean", "sum"],
start=start,
blacklist=black,
)
msv.multiple_reg_scatter(
cat_rels=[v for k, v in catregs.items()],
ncol=2,
nrow=2,
figsize=(14, 12),
title="Relative risk scores and subsequent relative excess returns, 24 EM sovereigns since 2000",
title_fontsize=20,
xlab="End-of-quarter score",
ylab="Foreign currency bond index relative excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
single_chart=True,
subplot_titles=[dict_labels[key] for key in sigs]
)

dix = dict_rel
sig = dix["sigs"][2]
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
cr = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
years=None,
lag=1,
xcat_aggs=["mean", "sum"], # period mean adds stability
start=start,
blacklist=black,
)
cr.reg_scatter(
title="Relative macro risk premia and subsequent foreign currency bond index excess returns, 24 countries since 2000",
labels=False,
xlab="Relative sovereign credit macro risk premium score, end of quarter",
ylab="Foreign currency bond index excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
separator=2012,
size=(12, 8),
)
cr.reg_scatter(
title="Relative macro risk premia and subsequent foreign currency bond index excess returns, 24 countries since 2000",
labels=False,
xlab="Relative macro risk premium",
ylab="Returns",
coef_box="lower right",
prob_est="pool",
separator="cids",
)


Accuracy and correlation check #
dix = dict_rel
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
srr = mss.SignalReturnRelations(
dfx,
rets=ret,
cids=cidx,
sigs=sigs,
# cosp=True,
freqs="M",
agg_sigs=["mean"], # for stability
start=start,
)
dix["srr"] = srr
dix = dict_rel
srr = dix["srr"]
tbl=srr.multiple_relations_table(signal_name_dict=dict_labels, return_name_dict={'FCBIXR_VT10vEM': 'Relative foreign currency bond index returns, volatility adjusted'}).round(3)
display(tbl.transpose())
Return | Relative foreign currency bond index returns, volatility adjusted | |||
---|---|---|---|---|
Signal | Relative macro-spread risk premium score | Relative macro-rating risk premium score | Relative macro risk premium score | Relative market risk score |
Frequency | M | M | M | M |
Aggregation | mean | mean | mean | mean |
accuracy | 0.525 | 0.521 | 0.527 | 0.532 |
bal_accuracy | 0.525 | 0.521 | 0.527 | 0.532 |
pos_sigr | 0.501 | 0.497 | 0.514 | 0.491 |
pos_retr | 0.501 | 0.500 | 0.500 | 0.500 |
pos_prec | 0.526 | 0.521 | 0.526 | 0.533 |
neg_prec | 0.524 | 0.522 | 0.527 | 0.531 |
pearson | 0.015 | 0.046 | 0.032 | 0.031 |
pearson_pval | 0.273 | 0.001 | 0.013 | 0.018 |
kendall | 0.026 | 0.038 | 0.036 | 0.042 |
kendall_pval | 0.003 | 0.000 | 0.000 | 0.000 |
auc | 0.525 | 0.521 | 0.527 | 0.532 |
dix = dict_rel
srr = dix["srr"]
srr.accuracy_bars(type='cross_section', sigs="MACROALL_RPS_ZNvEM", size=(16, 4))
srr.accuracy_bars(type='signals', size=(16, 4), x_labels=dict_labels, x_labels_rotate= 60)


dix = dict_rel
srr = dix["srr"]
srr.correlation_bars(type='cross_section', sigs="MACROALL_RPS_ZNvEM", size=(16, 4))
srr.correlation_bars(type='signals', size=(16, 4),x_labels=dict_labels, x_labels_rotate= 60)


Naive PnLs #
dix = dict_rel
sigs = dix["sigs"]
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add=0,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL",
rebal_slip=1,
vol_scale=10,
)
dix["pnls"] = pnls
dix = dict_rel
bias = 0
pnls = dix["pnls"]
sigs = dix["sigs"]
sigs = ["MACROSPREAD_RPS_ZNvEM", "MACRORATING_RPS_ZNvEM", "MACROALL_RPS_ZNvEM"]
strats = [sig + "_PNL" for sig in sigs]
pnl_labels = {
"MACROSPREAD_RPS_ZNvEM_PNL": "Spread-based relative macro risk premium score",
"MACRORATING_RPS_ZNvEM_PNL": "Ratings-based relative macro risk premium score",
"MACROALL_RPS_ZNvEM_PNL": "Composite relative macro risk premium score",
}
pnls.plot_pnls(
title="Naive PnL for relative risk-premia strategies, 24 EM sovereigns, 7-factor macro risk",
pnl_cats=strats,
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats).round(3))
pnls.signal_heatmap(
pnl_name=f"MACROALL_RPS_ZNvEM_PNL",
figsize=(16, 6),
title="Signal heatmap for relative macro risk premium score, 24 EM sovereigns, 7-factor macro risk",
tick_fontsize=10,
)

xcat | MACROSPREAD_RPS_ZNvEM_PNL | MACRORATING_RPS_ZNvEM_PNL | MACROALL_RPS_ZNvEM_PNL |
---|---|---|---|
Return % | 3.292463 | 4.938308 | 4.331788 |
St. Dev. % | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 0.329246 | 0.493831 | 0.433179 |
Sortino Ratio | 0.463184 | 0.701141 | 0.616736 |
Max 21-Day Draw % | -14.293858 | -21.644961 | -17.736543 |
Max 6-Month Draw % | -19.453042 | -29.289245 | -24.167777 |
Peak to Trough Draw % | -42.26745 | -50.514495 | -46.696056 |
Top 5% Monthly PnL Share | 1.22197 | 0.852256 | 1.023519 |
USD_EQXR_NSA correl | 0.014102 | 0.09413 | 0.093127 |
UHY_CRXR_NSA correl | 0.010985 | 0.092003 | 0.092805 |
UIG_CRXR_NSA correl | -0.011349 | 0.093237 | 0.084996 |
Traded Months | 307 | 307 | 307 |

Conceptual relative signals #
Specs and panel test #
dict_crs = {
"sigs": crpzr + ["MARKETRISK_ZNvEM"],
"targs": ["FCBIXR_VT10vEM", "FCBIXR_NSAvEM"],
"cidx": cids_fc,
"start": "2000-01-01",
"black": black_fc,
}
dix = dict_crs
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
catregs = {}
for sig in sigs:
catregs[sig] = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
lag=1,
xcat_aggs=["mean", "sum"],
start=start,
blacklist=black,
)
msv.multiple_reg_scatter(
cat_rels=[v for k, v in catregs.items()],
ncol=2,
nrow=4,
figsize=(14, 24),
title=None,
title_fontsize=20,
xlab="End-of-quarter score",
ylab="Foreign currency bond index excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
single_chart=True,
subplot_titles=['Relative ' + dict_labels[key[:-3]] for key in sigs]
)
XLIABRISK_RPS_ZNvEM misses: ['AED', 'OMR', 'QAR', 'SAR'].
XDEBTRISK_RPS_ZNvEM misses: ['QAR'].

Accuracy and correlation check #
dix = dict_crs
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
srr = mss.SignalReturnRelations(
dfx,
rets=ret,
cids=cidx,
sigs=sigs,
# cosp=True,
freqs="M",
agg_sigs=["mean"], # for stability
start=start,
)
dix["srr"] = srr
dix = dict_crs
srr = dix["srr"]
tbl=srr.multiple_relations_table(signal_name_dict=dict_labels,return_name_dict={'FCBIXR_VT10vEM': 'Relative foreign currency bond index returns, volatility adjusted'}).round(3)
display(tbl.transpose())
Return | Relative foreign currency bond index returns, volatility adjusted | |||||||
---|---|---|---|---|---|---|---|---|
Signal | Relative market risk score | Relative government finance conceptual macro risk premium score | Relative governance conceptual macro risk premium score | Relative growth risk conceptual macro risk premium score | Relative inflation risk conceptual macro risk premium score | Relative external balances conceptual macro risk premium score | Relative foreign-currency debt conceptual macro risk premium score | Relative international position conceptual macro risk premium score |
Frequency | M | M | M | M | M | M | M | M |
Aggregation | mean | mean | mean | mean | mean | mean | mean | mean |
accuracy | 0.532 | 0.519 | 0.512 | 0.525 | 0.512 | 0.526 | 0.516 | 0.538 |
bal_accuracy | 0.532 | 0.519 | 0.512 | 0.525 | 0.512 | 0.526 | 0.517 | 0.538 |
pos_sigr | 0.491 | 0.500 | 0.525 | 0.467 | 0.499 | 0.479 | 0.442 | 0.503 |
pos_retr | 0.500 | 0.500 | 0.500 | 0.499 | 0.501 | 0.501 | 0.500 | 0.499 |
pos_prec | 0.533 | 0.519 | 0.512 | 0.526 | 0.513 | 0.528 | 0.519 | 0.537 |
neg_prec | 0.531 | 0.519 | 0.513 | 0.524 | 0.511 | 0.525 | 0.514 | 0.540 |
pearson | 0.031 | 0.021 | 0.016 | 0.031 | 0.008 | 0.026 | 0.012 | 0.051 |
pearson_pval | 0.018 | 0.106 | 0.208 | 0.021 | 0.557 | 0.047 | 0.368 | 0.001 |
kendall | 0.042 | 0.024 | 0.016 | 0.031 | 0.011 | 0.039 | 0.022 | 0.054 |
kendall_pval | 0.000 | 0.006 | 0.069 | 0.000 | 0.206 | 0.000 | 0.013 | 0.000 |
auc | 0.532 | 0.519 | 0.512 | 0.525 | 0.512 | 0.526 | 0.516 | 0.538 |
Naive PnLs #
dix = dict_crs
sigs = dix["sigs"]
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add=0,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL",
rebal_slip=1,
vol_scale=10,
)
dix["pnls"] = pnls
dix = dict_crs
bias = 0
pnls = dix["pnls"]
sigs = dix["sigs"]
strats = [sig + "_PNL" for sig in sigs]
pnl_labels = {}
for sig in sigs:
pnl_labels[sig+'_PNL'] = dict_labels[sig]
pnls.plot_pnls(
title=None,
pnl_cats=strats,
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats, label_dict=pnl_labels).round(3))

xcat | Relative government finance conceptual macro risk premium score | Relative external balances conceptual macro risk premium score | Relative international position conceptual macro risk premium score | Relative foreign-currency debt conceptual macro risk premium score | Relative governance conceptual macro risk premium score | Relative growth risk conceptual macro risk premium score | Relative inflation risk conceptual macro risk premium score | Relative market risk score |
---|---|---|---|---|---|---|---|---|
Return % | 2.475957 | 3.457089 | 4.129918 | 1.370682 | 2.083336 | 3.867337 | 0.6258 | 3.01279 |
St. Dev. % | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 0.247596 | 0.345709 | 0.412992 | 0.137068 | 0.208334 | 0.386734 | 0.06258 | 0.301279 |
Sortino Ratio | 0.34623 | 0.4799 | 0.582635 | 0.191157 | 0.287121 | 0.548374 | 0.085569 | 0.414655 |
Max 21-Day Draw % | -19.312203 | -24.724539 | -19.28101 | -14.348855 | -20.310091 | -15.013302 | -28.955906 | -26.064021 |
Max 6-Month Draw % | -28.775048 | -29.931264 | -40.067388 | -26.604977 | -30.363695 | -26.347951 | -32.913732 | -35.632727 |
Peak to Trough Draw % | -49.086153 | -49.167892 | -67.021808 | -87.677165 | -57.302893 | -56.970072 | -51.151695 | -68.512847 |
Top 5% Monthly PnL Share | 1.54643 | 1.097971 | 1.007643 | 4.000611 | 2.074613 | 1.26767 | 6.750153 | 1.523972 |
USD_EQXR_NSA correl | 0.197908 | 0.220893 | 0.185761 | 0.085947 | 0.107351 | 0.053354 | 0.182118 | 0.252821 |
UHY_CRXR_NSA correl | 0.222083 | 0.233174 | 0.184688 | 0.072803 | 0.108581 | 0.030994 | 0.203018 | 0.262586 |
UIG_CRXR_NSA correl | 0.231844 | 0.229121 | 0.184093 | 0.087584 | 0.049048 | 0.018669 | 0.207435 | 0.248194 |
Traded Months | 307 | 307 | 307 | 307 | 307 | 307 | 307 | 307 |
Customized directional signals #
Specs and panel test #
dict_dirc = {
"sigs": ['MACROSPREAD_RPS_CWS_ZN','MACRORATING_RPS_CWS_ZN', 'MACROALL_RPS_CWS_ZN', 'MARKETRISK_ZN'],
"targs": ["FCBIXR_VT10", "FCBIXR_NSA"],
"cidx": cids_fc,
"start": "2000-01-01",
"black": black_fc,
}
dix = dict_dirc
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
catregs = {}
for sig in sigs:
catregs[sig] = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
lag=1,
xcat_aggs=["last", "sum"],
start=start,
blacklist=black,
)
msv.multiple_reg_scatter(
cat_rels=[v for k, v in catregs.items()],
ncol=2,
nrow=2,
figsize=(14, 12),
title=None,
title_fontsize=20,
xlab="End-of-quarter score",
ylab="Foreign currency bond index excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
single_chart=True,
subplot_titles=[dict_labels[key] for key in sigs]
)

Accuracy and correlation check #
dix = dict_dirc
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
srr = mss.SignalReturnRelations(
dfx,
rets=ret,
cids=cidx,
sigs=sigs,
# cosp=True,
freqs="M",
agg_sigs=["last"], # for stability
start=start,
)
dix["srr"] = srr
dix = dict_dirc
srr = dix["srr"]
tbl=srr.multiple_relations_table(signal_name_dict=dict_labels,return_name_dict={'FCBIXR_VT10vEM': 'Relative foreign currency bond index returns, volatility adjusted'}).round(3)
display(tbl.transpose())
Return | Vol-targeted foreign-currency sovereign bond index return, % | |||
---|---|---|---|---|
Signal | Composite market risk score | Spread-based macro risk premium score, custom weights | Ratings-based macro risk premium score, custom weights | Macro risk premium score, custom weights |
Frequency | M | M | M | M |
Aggregation | last | last | last | last |
accuracy | 0.501 | 0.475 | 0.499 | 0.485 |
bal_accuracy | 0.510 | 0.515 | 0.516 | 0.513 |
pos_sigr | 0.462 | 0.322 | 0.423 | 0.375 |
pos_retr | 0.610 | 0.609 | 0.608 | 0.610 |
pos_prec | 0.620 | 0.629 | 0.626 | 0.626 |
neg_prec | 0.399 | 0.401 | 0.406 | 0.400 |
pearson | 0.020 | 0.029 | 0.024 | 0.031 |
pearson_pval | 0.122 | 0.030 | 0.077 | 0.016 |
kendall | 0.025 | 0.041 | 0.022 | 0.034 |
kendall_pval | 0.004 | 0.000 | 0.013 | 0.000 |
auc | 0.510 | 0.514 | 0.516 | 0.513 |
Naive PnLs #
dix = dict_dirc
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
rt = ret.split('_')[-1] # 'NSA' or 'VT10'
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
for bias in [0, 1]:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add = bias,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL" + rt + str(bias),
rebal_slip=1,
vol_scale=10,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")
dix["pnls_"+rt.lower()] = pnls
dix = dict_dirc
rt = "VT10"
bias = 1
pnls = dix["pnls_"+rt.lower()]
sigs = ["MACROALL_RPS_CWS_ZN", "MARKETRISK_ZN"]
strats = [sig + "_PNL" + rt + str(bias) for sig in sigs]
pnl_labels = {
'MACROALL_RPS_CWS_ZN_PNLVT101': 'Macro risk premium customised directional score, vol targeted returns',
'MARKETRISK_ZN_PNLVT101': 'Market risk score, directional, vol targeted returns',
'Long only': 'Long only'
}
pnls.plot_pnls(
title=None,
pnl_cats=strats + ["Long only"],
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats + ["Long only"], label_dict=pnl_labels).round(3))
pnls.signal_heatmap(pnl_name=f"MACROALL_RPS_CWS_ZN_PNL" + rt + str(bias), figsize=(20, 10))

xcat | Macro risk premium customised directional score, vol targeted returns | Market risk score, directional, vol targeted returns | Long only |
---|---|---|---|
Return % | 9.765168 | 8.314899 | 7.025648 |
St. Dev. % | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 0.976517 | 0.83149 | 0.702565 |
Sortino Ratio | 1.336394 | 1.112942 | 0.925355 |
Max 21-Day Draw % | -36.565285 | -41.390234 | -42.691999 |
Max 6-Month Draw % | -45.77502 | -41.88914 | -37.994371 |
Peak to Trough Draw % | -54.041833 | -48.768828 | -62.285155 |
Top 5% Monthly PnL Share | 0.556433 | 0.622572 | 0.760984 |
USD_EQXR_NSA correl | 0.264455 | 0.293824 | 0.214687 |
UHY_CRXR_NSA correl | 0.31816 | 0.350987 | 0.273763 |
UIG_CRXR_NSA correl | 0.29464 | 0.332196 | 0.253393 |
Traded Months | 307 | 307 | 307 |

dix = dict_dirc
sigs = dix['sigs']
ret = dix["targs"][1]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
rt = ret.split('_')[-1] # 'NSA' or 'VT10'
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
for bias in [0, 1]:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add = bias,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL" + rt + str(bias),
rebal_slip=1,
vol_scale=10,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")
dix["pnls_"+rt.lower()] = pnls
dix = dict_dirc
rt = "NSA"
bias = 1
pnls = dix["pnls_"+rt.lower()]
sigs = ["MACROALL_RPS_CWS_ZN", "MARKETRISK_ZN"]
strats = [sig + "_PNL" + rt + str(bias) for sig in sigs]
pnl_labels = {
'MACROALL_RPS_CWS_ZN_PNLNSA1': 'Macro risk premium customised directional score',
'MARKETRISK_ZN_PNLNSA1': 'Market risk score, directional',
'Long only': 'Long only'
}
pnls.plot_pnls(
title=None,
pnl_cats=strats + ["Long only"],
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats + ["Long only"], label_dict=pnl_labels).round(3))
pnls.signal_heatmap(pnl_name=f"MACROALL_RPS_CWS_ZN_PNL" + rt + str(bias), figsize=(20, 10))

xcat | Macro risk premium customised directional score | Market risk score, directional | Long only |
---|---|---|---|
Return % | 10.08657 | 9.300272 | 8.004047 |
St. Dev. % | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 1.008657 | 0.930027 | 0.800405 |
Sortino Ratio | 1.418757 | 1.291543 | 1.079887 |
Max 21-Day Draw % | -40.415401 | -37.453612 | -40.898679 |
Max 6-Month Draw % | -46.012824 | -42.623356 | -44.785424 |
Peak to Trough Draw % | -46.605959 | -45.725342 | -55.021649 |
Top 5% Monthly PnL Share | 0.595555 | 0.619495 | 0.641037 |
USD_EQXR_NSA correl | 0.281896 | 0.308603 | 0.270744 |
UHY_CRXR_NSA correl | 0.349204 | 0.379014 | 0.343803 |
UIG_CRXR_NSA correl | 0.349488 | 0.376879 | 0.330702 |
Traded Months | 307 | 307 | 307 |

Customized relative signals #
Specs and panel test #
dict_relc = {
"sigs": ['MACROSPREAD_RPS_CWS_ZNvEM','MACRORATING_RPS_CWS_ZNvEM', 'MACROALL_RPS_CWS_ZNvEM', 'MARKETRISK_ZNvEM'],
"targs": ["FCBIXR_VT10vEM", "FCBIXR_NSAvEM"],
"cidx": cids_fc,
"start": "2000-01-01",
"black": black_fc,
}
dix = dict_relc
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
catregs = {}
for sig in sigs:
catregs[sig] = msp.CategoryRelations(
dfx,
xcats=[sig, ret],
cids=cidx,
freq="Q",
lag=1,
xcat_aggs=["mean", "sum"],
start=start,
blacklist=black,
)
msv.multiple_reg_scatter(
cat_rels=[v for k, v in catregs.items()],
ncol=2,
nrow=2,
figsize=(14, 12),
title=None,
title_fontsize=20,
xlab="End-of-quarter score",
ylab="Foreign currency bond index excess returns, vol targeted, %, next quarter",
coef_box="lower right",
prob_est="map",
single_chart=True,
subplot_titles=[dict_labels[key] for key in sigs]
)

Accuracy and correlation check #
dix = dict_relc
sigs = dix['sigs']
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
srr = mss.SignalReturnRelations(
dfx,
rets=ret,
cids=cidx,
sigs=sigs,
# cosp=True,
freqs="M",
agg_sigs=["mean"], # for stability
start=start,
)
dix["srr"] = srr
dix = dict_relc
srr = dix["srr"]
tbl=srr.multiple_relations_table(signal_name_dict=dict_labels,return_name_dict={'FCBIXR_VT10vEM': 'Relative foreign currency bond index returns, volatility adjusted'}).round(3)
display(tbl.transpose())
Return | Relative foreign currency bond index returns, volatility adjusted | |||
---|---|---|---|---|
Signal | Relative market risk score | Relative macro-spread risk premium score, custom weights | Relative macro-rating risk premium score, custom weights | Relative macro risk premium score, custom weights |
Frequency | M | M | M | M |
Aggregation | mean | mean | mean | mean |
accuracy | 0.532 | 0.528 | 0.525 | 0.534 |
bal_accuracy | 0.532 | 0.528 | 0.525 | 0.534 |
pos_sigr | 0.491 | 0.489 | 0.522 | 0.498 |
pos_retr | 0.500 | 0.501 | 0.500 | 0.500 |
pos_prec | 0.533 | 0.530 | 0.523 | 0.534 |
neg_prec | 0.531 | 0.527 | 0.526 | 0.533 |
pearson | 0.031 | 0.024 | 0.050 | 0.040 |
pearson_pval | 0.018 | 0.073 | 0.000 | 0.002 |
kendall | 0.042 | 0.030 | 0.041 | 0.039 |
kendall_pval | 0.000 | 0.001 | 0.000 | 0.000 |
auc | 0.532 | 0.528 | 0.525 | 0.534 |
Naive PnLs #
dix = dict_relc
sigs = dix["sigs"]
ret = dix["targs"][0]
cidx = dix["cidx"]
start = dix["start"]
black = dix["black"]
pnls = msn.NaivePnL(
df=dfx,
ret=ret,
sigs=sigs,
cids=cidx,
start=start,
blacklist=black,
bms=bms,
)
for sig in sigs:
pnls.make_pnl(
sig=sig,
sig_op="zn_score_pan",
thresh=2,
sig_add=0,
rebal_freq="monthly",
neutral="zero",
pnl_name=sig + "_PNL",
rebal_slip=1,
vol_scale=10,
)
dix["pnls"] = pnls
dix = dict_relc
bias = 0
pnls = dix["pnls"]
sigs = dix["sigs"]
sigs = ["MACROSPREAD_RPS_CWS_ZNvEM", "MACRORATING_RPS_CWS_ZNvEM", "MACROALL_RPS_CWS_ZNvEM"]
strats = [sig + "_PNL" for sig in sigs]
pnl_labels = {
"MACROSPREAD_RPS_CWS_ZNvEM_PNL": "Spread-based relative macro risk premium score",
"MACRORATING_RPS_CWS_ZNvEM_PNL": "Ratings-based relative macro risk premium score",
"MACROALL_RPS_CWS_ZNvEM_PNL": "Composite relative macro risk premium score",
}
pnls.plot_pnls(
title="Naive PnL for relative risk-premia strategies, 24 EM sovereigns, 4-factor macro risk",
pnl_cats=strats,
xcat_labels=pnl_labels,
title_fontsize=14,
)
display(pnls.evaluate_pnls(pnl_cats=strats,label_dict=pnl_labels).round(3))
pnls.signal_heatmap(pnl_name=f"MACROALL_RPS_CWS_ZNvEM_PNL", figsize=(20, 10))

xcat | Spread-based relative macro risk premium score | Ratings-based relative macro risk premium score | Composite relative macro risk premium score |
---|---|---|---|
Return % | 4.252493 | 5.308314 | 4.99858 |
St. Dev. % | 10.0 | 10.0 | 10.0 |
Sharpe Ratio | 0.425249 | 0.530831 | 0.499858 |
Sortino Ratio | 0.598804 | 0.75004 | 0.704273 |
Max 21-Day Draw % | -16.47829 | -20.289363 | -19.03669 |
Max 6-Month Draw % | -24.380391 | -28.480566 | -27.770239 |
Peak to Trough Draw % | -46.541167 | -49.70223 | -49.975228 |
Top 5% Monthly PnL Share | 0.992853 | 0.784579 | 0.837347 |
USD_EQXR_NSA correl | 0.118336 | 0.177049 | 0.171281 |
UHY_CRXR_NSA correl | 0.14116 | 0.189587 | 0.190359 |
UIG_CRXR_NSA correl | 0.120715 | 0.173418 | 0.173881 |
Traded Months | 307 | 307 | 307 |
