EM local bond markets scorecard #

Get packages and JPMaQS data #

Set up parameters and import packages #

# Constants and credentials
import os
import pandas as pd

REQUIRED_VERSION: str = "1.4.0"
DQ_CLIENT_ID: str = os.getenv("DQ_CLIENT_ID")  # Use your DataQuery ID if it is not an environment variable
DQ_CLIENT_SECRET: str = os.getenv("DQ_CLIENT_SECRET") # Use your DataQuery secret if it is not an environment variable
PROXY = {}  # Configure if behind corporate firewall
START_DATE: str = "2000-01-01"  # Start date for data download
END_DATE: str = (pd.Timestamp.today() - pd.offsets.BDay(1)).strftime('%Y-%m-%d')
import macrosynergy as msy

msy.check_package_version(required_version=REQUIRED_VERSION)
# If version check fails: pip install macrosynergy --upgrade
from datetime import date, datetime
import os

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

from macrosynergy.download import JPMaQSDownload
from macrosynergy.visuals import ScoreVisualisers

pd.set_option('display.width', 400)
import warnings

warnings.simplefilter("ignore")

Download quantamental indicators #

# Cross-sections of interest

cids_latm = ["BRL", "COP", "CLP", "MXN", "PEN"]  # Latam countries
cids_emea = ["CZK", "HUF", "PLN", "RON", "RUB", "TRY", "ZAR"]  # EMEA countries, "ILS" not included
cids_emas = ["IDR", "MYR", "PHP", "THB"]  # EM Asia countries "CNY", "INR", "KRW", "SGD", "TWD"

cids_em = sorted(cids_latm + cids_emea + cids_emas)

cids = sorted(cids_em + ["USD"]) 
# Quantamental category groups

# External ratio trends
xbch = [
    "MTBGDPRATIO_NSA_12MMA_D1M1ML3",
    "MTBGDPRATIO_SA_6MMA_D1M1ML6",
    "MTBGDPRATIO_SA_3MMAv60MMA",
    "CABGDPRATIO_SA_3MMAv60MMA",
    "CABGDPRATIO_SA_1QMAv20QMA",
]

# Intuitive GDP growth estimates
igdp = [
    "INTRGDP_NSA_P1M1ML12",
    "INTRGDP_NSA_P1M1ML12_3MMA",
    "INTRGDPv5Y_NSA_P1M1ML12_3MMA",
]

# Terms of trade trends
tots = [
    "CTOT_NSA_P1M1ML12",
    "CTOT_NSA_P1W4WL1",
    "CTOT_NSA_P1M60ML1",
]


# International investment position trends
niip = [
    "NIIPGDP_NSA_D1Mv2YMA",
    "NIIPGDP_NSA_D1Mv5YMA",
    "IIPLIABGDP_NSA_D1Mv2YMA",
    "IIPLIABGDP_NSA_D1Mv5YMA",
]

# Government balances
dsus = [
    "GGSBGDPRATIO_NSA",
    "GGOBGDPRATIO_NSA",
    "GGPBGDPRATIO_NSA",
]

# Term premia estimates

dutp = [
    "DU02YETP_NSA",
    "DU05YETP_NSA",
    "DU10YETP_NSA",
]

# ALl macro categories

macro = igdp + tots + niip + xbch + dsus + dutp

# Market categories

blkl = [
    "FXTARGETED_NSA",
    "FXUNTRADABLE_NSA",
]

rets = [
    "LCBIRUSD_NSA",
    "LCBIXRUSD_NSA",
    "LCBIXRUSD_VT10",
]


mkts = blkl + rets


xcats = macro + mkts

# Tickers for download

single_tix = ["USD_GB10YXR_NSA", "EUR_FXXR_NSA", "USD_EQXR_NSA"]
tickers = (
    [cid + "_" + xcat for cid in cids for xcat in xcats]
    + single_tix
    + [cid + "_" + xcat for cid in ["USD"] for xcat in xcats]
)
# 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,
    )
Downloading data from JPMaQS.
Timestamp UTC:  2025-11-05 09:57:12
Connection successful!
Requesting data: 100%|██████████| 23/23 [00:04<00:00,  4.92it/s]
Downloading data: 100%|██████████| 23/23 [00:33<00:00,  1.44s/it]
Some expressions are missing from the downloaded data. Check logger output for complete list.
32 out of 445 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()`.
dfx = df.copy().sort_values(["cid", "xcat", "real_date"])
dfx.info()
<class 'pandas.core.frame.DataFrame'>
Index: 2572595 entries, 13484 to 2538884
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: 98.1+ MB

Re-namings, availability checks and blacklists #

Renaming quarterly categories #

dict_repl = {
    "CABGDPRATIO_SA_1QMAv20QMA": "CABGDPRATIO_SA_3MMAv60MMA",
}

dfx["xcat"] = dfx["xcat"].map(lambda v: dict_repl.get(v, v))

to_drop = set(dict_repl)           # faster membership tests than list/dict_keys
dfx = dfx[~dfx["xcat"].isin(to_drop)]

if hasattr(dfx["xcat"], "cat"):
    dfx["xcat"] = dfx["xcat"].cat.remove_categories(list(to_drop))
    dfx["xcat"] = dfx["xcat"].cat.remove_unused_categories()

Availability check #

xcatx = rets
cidx = cids_em

msm.check_availability(dfx, xcats = xcatx, cids = cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/1f03c2658bb371b903ad380cd8204f69b568e6fd5a553e572a53a2682407576e.png
xcatx = list(set([dict_repl.get(item, item) for item in xbch]))
cidx = cids_em

msm.check_availability(dfx, xcats = xcatx, cids = cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/17792a170be2f58197f9895d24bbf829725b214fb29c3b3a2000f2f652b0f10e.png
xcatx = igdp
cidx = cids_em

msm.check_availability(dfx, xcats = xcatx, cids = cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/81f3bb6c3ffdaf9ca92ae8571acf2c3fa9abc2ec0a1e78bfeb409be7da36214b.png
xcatx = list(set([dict_repl.get(item, item) for item in tots]))
cidx = cids_em

msm.check_availability(dfx, xcats = xcatx, cids = cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/288739656925415207910a4830ae2d5faafa0eeb03c967e4c52d7a7a11ca825e.png
xcatx = niip
cidx = cids_em

msm.check_availability(dfx, xcats = xcatx, cids = cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/92ca32e2178eafcdfd8974cbeb014113ee262af64619fd70d6d9e66f14522c64.png
xcatx = dsus
cidx = cids_em

msm.check_availability(dfx, xcats = xcatx, cids = cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/6dd123879f17e22923dc09ace5d7bbbdc966a66bca8fdb338449545748041b73.png
xcatx = dutp
cidx = cids_em

msm.check_availability(dfx, xcats = xcatx, cids = cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/bfc30b7efc1474e23cbe07b663d704db7a41cfab727c93e8a118d5a8cee51d9e.png

Blacklisting based on FX forward markets #

dfb = dfx[dfx["xcat"].isin(["FXUNTRADABLE_NSA"])].loc[
    :, ["cid", "xcat", "real_date", "value"]
]
dfba = (
    dfb.groupby(["cid", "real_date"])
    .aggregate(value=pd.NamedAgg(column="value", aggfunc="max"))
    .reset_index()
)
dfba["xcat"] = "FXBLACK"
fxblack = msp.make_blacklist(dfba, "FXBLACK")
fxblack
{'MYR': (Timestamp('2018-07-02 00:00:00'), Timestamp('2025-11-04 00:00:00')),
 'PEN': (Timestamp('2021-07-01 00:00:00'), Timestamp('2021-07-30 00:00:00')),
 'RUB': (Timestamp('2022-02-01 00:00:00'), Timestamp('2025-11-04 00:00:00')),
 'THB': (Timestamp('2007-01-01 00:00:00'), Timestamp('2008-11-28 00:00:00')),
 'TRY_1': (Timestamp('2000-01-03 00:00:00'), Timestamp('2003-09-30 00:00:00')),
 'TRY_2': (Timestamp('2020-01-01 00:00:00'), Timestamp('2024-07-31 00:00:00'))}

Local-currency bond index returns #

xcatx = [
    "LCBIRUSD_NSA",
]
cidx = sorted(list(set(cids_em) - set(["RUB"])))


msp.view_ranges(
    dfx,
    cids=cidx,
    xcats=xcatx,
    kind="box",
    sort_cids_by="std",
    title=None,
    ylab="% annualized",
    start="2000-01-01",
    title_fontsize=20,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/bd8e58c40766fd300fc38d31683edbe976c843a9f24a4bf474839fd0f5fa80b9.png
xcatx = ["LCBIRUSD_NSA", "LCBIXRUSD_NSA", "LCBIXRUSD_VT10"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    cumsum=True,
    same_y=False,
    height=2.0,
    size=(10, 10),
    all_xticks=True,
    blacklist=fxblack,
    title="Emerging markets local-currency bond index returns (% cumulative, temporary flatlining means untradability)",
    title_fontsize=22,
    xcat_labels=[
        "USD dollar returns",
        "USD excess returns",
        "Vol-targeted USD excess returns",
    ],
    
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/9eca0c858c8d3faeb6a84c61e391ec38de33a8c11567e093132917dfddcdea46.png
# Estimated short-term historical volatility

xcatx = ["LCBIRUSD_NSA", "LCBIXRUSD_NSA"]
cidx = cids_em

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

for xc in xcatx:
    dfaa = msp.historic_vol(
        dfx,
        xcat=xc,
        cids=cidx,
        lback_periods=21,
        lback_meth="ma",
        half_life=11,
        est_freq="M",
        blacklist=fxblack,
        postfix="_ASD",
    )
    dfa = msm.update_df(dfa, dfaa)

dfx = msm.update_df(dfx, dfa)
# Checkup of volatilities

xcatx = ["LCBIRUSD_NSA_ASD", "LCBIXRUSD_NSA_ASD"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    height=2.2,
    size=(10, 10),
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/215ab20daad3268eeda5a5f696ba912246a6a7d3f61a8cffff0c460f3c1c27b7.png

Factor construction and checks #

# Initiate labeling dictionary
dict_lab = {}
concept_factors_zn = {}

External balance improvement #

# All constituents, signs, and weights

dict_xbch = {
    "MTBGDPRATIO_SA_3MMAv60MMA": {"sign": 1, "weight": 1/4},
    "CABGDPRATIO_SA_3MMAv60MMA": {"sign": 1, "weight": 1/4},
    'MTBGDPRATIO_NSA_12MMA_D1M1ML3': {"sign": 1, "weight": 1/4},
    'MTBGDPRATIO_SA_6MMA_D1M1ML6': {"sign": 1, "weight": 1/4},
}
# Normalized categories with theoretical positive effects

dix = dict_xbch
cidx = cids_em

# Contingent negative values

if any (v["sign"] < 0 for v in dix.values()):
    calcs=[]
    dfa = pd.DataFrame(columns=dfx.columns)
    for xc, values in dix.items():
        if values["sign"] < 0:
            calcs.append(f"{xc}NEG = {xc} * -1")
            dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
    dfx = msm.update_df(dfx, dfa)

# Sequential normalization of categories

postfixes = ["NEG" if v["sign"] < 0 else "" for v in dix.values()]
xcatx = [k + pf for k, pf in zip(dix.keys(), postfixes)] 

for xc in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xc,
        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)

xbchz = [xc + "_ZN" for xc in xcatx]
# Conceptual factor score

dix = dict_xbch
xcatx = xbchz
cidx = cids_em

# Composite score

weights = [v["weight"] for v in dix.values()]
cfs = "XBCH"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    weights=weights,
    new_xcat=cfs,
    complete_xcats=False,
)
dfx= msm.update_df(dfx, dfa)

# Re-score

dfa = msp.make_zn_scores(
    dfx,
    xcat=cfs,
    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)

cfs_zn = f"{cfs}_ZN"
concept_factors_zn[cfs_zn] = "External balance improvement"
# Visualize

xcatx = xbchz + ["XBCH_ZN"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    height=2.2,
    size=(10, 10),
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/89505887e75fbae81f51d63f06598a5936a07bc442e218aa99b1ece1d1c7720b.png
# checking - to delete later 

cidx = cids_em
sig = "XBCH_ZN"
targ = "LCBIXRUSD_VT10"

cr = msp.CategoryRelations(
        dfx,
        xcats=[sig, targ],
        cids=cidx,
        freq="M",
        blacklist=fxblack,
        lag=1,
        xcat_aggs=["last", "sum"],
        start="2000-01-01",
      
    )
cr.reg_scatter(coef_box="upper left", prob_est="map")


srr = mss.SignalReturnRelations(
    dfx,
    cids=cidx,
    sigs=sig,
    rets=targ,
    freqs="M",
    start="2000-01-01",
    blacklist=fxblack,
)
display(srr.multiple_relations_table().round(3))
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/7e534b4aa156c9e3877c526bbeec05411a0fe0401a0c975cc629cd4816569c9c.png
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
LCBIXRUSD_VT10 XBCH_ZN M last 0.524 0.524 0.5 0.568 0.591 0.456 0.032 0.044 0.023 0.034 0.524

Relative GDP growth #

# Preparatory: relative values to U.S.

xcatx = igdp
cidx = cids

cidx = cids

for xc in xcatx:
    dfa = msp.make_relative_value(
        dfx,
        xcats=xcatx,
        cids=cidx,
        basket=["USD"],  
        rel_meth="subtract",
        postfix="vUSD",
    )
    dfx = msm.update_df(df=dfx, df_add=dfa)
# All constituents, signs, and weights

dict_rgdp = {
    'INTRGDP_NSA_P1M1ML12vUSD': {"sign": 1, "weight": 1/3},
    'INTRGDP_NSA_P1M1ML12_3MMAvUSD': {"sign": 1, "weight": 1/3},
    'INTRGDPv5Y_NSA_P1M1ML12_3MMAvUSD': {"sign": 1, "weight": 1/3},
}
# Normalized categories with theoretical positive effects

dix = dict_rgdp
cidx = cids_em

# Contingent negative values

if any (v["sign"] < 0 for v in dix.values()):
    calcs=[]
    dfa = pd.DataFrame(columns=dfx.columns)
    for xc, values in dix.items():
        if values["sign"] < 0:
            calcs.append(f"{xc}NEG = {xc} * -1")
            dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
    dfx = msm.update_df(dfx, dfa)

# Sequential normalization of categories

postfixes = ["NEG" if v["sign"] < 0 else "" for v in dix.values()]
xcatx = [k + pf for k, pf in zip(dix.keys(), postfixes)] 

for xc in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xc,
        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)

rgdpz = [xc + "_ZN" for xc in xcatx]
# Conceptual factor score

dix = dict_rgdp
xcatx = rgdpz
cidx = cids

# Composite score

weights = [v["weight"] for v in dix.values()]
cfs = "RGDP"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    weights=weights,
    new_xcat=cfs,
    complete_xcats=False,
)
dfx= msm.update_df(dfx, dfa)

# Re-score

dfa = msp.make_zn_scores(
    dfx,
    xcat=cfs,
    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)

cfs_zn = f"{cfs}_ZN"
concept_factors_zn[cfs_zn] = "Relative GDP growth"
# Visualize

xcatx = rgdpz + ["RGDP_ZN"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    height=2.2,
    size=(10, 10),
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/3d7e5a14f24322412d3b6c77c608cf17ea1543807cb20266597ce3f3467f596f.png
# checking - to delete later 
cidx = cids_em
sig = "RGDP_ZN"
targ = "LCBIXRUSD_VT10"

cr = msp.CategoryRelations(
        dfx,
        xcats=[sig, targ],
        cids=cidx,
        freq="M",
        blacklist=fxblack,
        lag=1,
        xcat_aggs=["last", "sum"],
        start="2000-01-01",
      
    )
cr.reg_scatter(coef_box="upper left", prob_est="map")


srr = mss.SignalReturnRelations(
    dfx,
    cids=cidx,
    sigs=sig,
    rets=targ,
    freqs="M",
    start="2000-01-01",
    blacklist=fxblack,
)
display(srr.multiple_relations_table().round(3))
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/1447a8d693f0bee9bcef2e4552d74d135a886ecd8fb38d8fa7b01453655e82a5.png
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
LCBIXRUSD_VT10 RGDP_ZN M last 0.525 0.51 0.626 0.565 0.572 0.447 0.029 0.075 0.022 0.039 0.509

Terms-of-trade improvement #

# All constituents, signs, and weights

dict_tot = {
    "CTOT_NSA_P1M1ML12": {"sign": 1, "weight": 1/3},
    "CTOT_NSA_P1W4WL1": {"sign": 1, "weight": 1/3},
    "CTOT_NSA_P1M60ML1": {"sign": 1, "weight": 1/3},  
}
# Normalized categories with theoretical positive effects

dix = dict_tot
cidx = cids_em

# Contingent negative values

if any (v["sign"] < 0 for v in dix.values()):
    calcs=[]
    dfa = pd.DataFrame(columns=dfx.columns)
    for xc, values in dix.items():
        if values["sign"] < 0:
            calcs.append(f"{xc}NEG = {xc} * -1")
            dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
    dfx = msm.update_df(dfx, dfa)

# Sequential normalization of categories

postfixes = ["NEG" if v["sign"] < 0 else "" for v in dix.values()]
xcatx = [k + pf for k, pf in zip(dix.keys(), postfixes)] 

for xc in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xc,
        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)

totz = [xc + "_ZN" for xc in xcatx]
# Conceptual factor score

dix = dict_tot
xcatx = totz
cidx = cids_em

# Composite score

weights = [v["weight"] for v in dix.values()]
cfs = "TOT"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    weights=weights,
    new_xcat=cfs,
    complete_xcats=False,
)
dfx= msm.update_df(dfx, dfa)

# Re-score

dfa = msp.make_zn_scores(
    dfx,
    xcat=cfs,
    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)

cfs_zn = f"{cfs}_ZN"
concept_factors_zn[cfs_zn] = "Terms of trade improvement"
# Visualize

xcatx = totz + ["TOT_ZN"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    height=2.2,
    size=(10, 10),
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/becc60929d6d2b3ee76ca6815b452efe031df5a9799f3c40c457cc03070fce3f.png
# checking - to delete later 
cidx = cids_em
sig = "TOT_ZN"
targ = "LCBIXRUSD_VT10"

cr = msp.CategoryRelations(
        dfx,
        xcats=[sig, targ],
        cids=cidx,
        freq="M",
        blacklist=fxblack,
        lag=1,
        xcat_aggs=["last", "sum"],
        start="2000-01-01",
       
    )
cr.reg_scatter(coef_box="upper left", prob_est="map")


srr = mss.SignalReturnRelations(
    dfx,
    cids=cidx,
    sigs=sig,
    rets=targ,
    freqs="M",
    start="2000-01-01",
    blacklist=fxblack,
)
display(srr.multiple_relations_table().round(3))
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/4ec1663ae8821d1bf0c0a46870dc514bd494a35eaf7067d6b087692266db6095.png
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
LCBIXRUSD_VT10 TOT_ZN M last 0.515 0.517 0.486 0.568 0.586 0.449 0.037 0.02 0.027 0.012 0.518

International investment dynamics #

# All constituents, signs, and weights

dict_iipd = {
    'NIIPGDP_NSA_D1Mv2YMA': {"sign": 1, "weight": 1/4},
    'NIIPGDP_NSA_D1Mv5YMA': {"sign": 1, "weight": 1/4},
    'IIPLIABGDP_NSA_D1Mv2YMA': {"sign": -1, "weight": 1/4},
    'IIPLIABGDP_NSA_D1Mv5YMA': {"sign": -1, "weight": 1/4},
}
# Normalized categories with theoretical positive effects

dix = dict_iipd
cidx = cids_em

# Contingent negative values

if any (v["sign"] < 0 for v in dix.values()):
    calcs=[]
    dfa = pd.DataFrame(columns=dfx.columns)
    for xc, values in dix.items():
        if values["sign"] < 0:
            calcs.append(f"{xc}NEG = {xc} * -1")
            dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
    dfx = msm.update_df(dfx, dfa)

# Sequential normalization of categories

postfixes = ["NEG" if v["sign"] < 0 else "" for v in dix.values()]
xcatx = [k + pf for k, pf in zip(dix.keys(), postfixes)] 

for xc in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xc,
        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)

iipdz = [xc + "_ZN" for xc in xcatx]
# Conceptual factor score

dix = dict_iipd
xcatx = iipdz
cidx = cids_em

# Composite score

weights = [v["weight"] for v in dix.values()]
cfs = "IIPD"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    weights=weights,
    new_xcat=cfs,
    complete_xcats=False,
)
dfx= msm.update_df(dfx, dfa)

# Re-score

dfa = msp.make_zn_scores(
    dfx,
    xcat=cfs,
    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)

cfs_zn = f"{cfs}_ZN"
concept_factors_zn[cfs_zn] = "International investment dynamics"
# Visualize

xcatx = iipdz + ["IIPD_ZN"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    height=2.2,
    size=(10, 10),
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/f7b1420c418e9018b06ee14669448057fa870eda52a678a6014bd03fb2e21285.png
# checking - to delete later 
cidx = cids_em
sig = "IIPD_ZN"
targ = "LCBIXRUSD_VT10"

cr = msp.CategoryRelations(
        dfx,
        xcats=[sig, targ],
        cids=cidx,
        freq="M",
        blacklist=fxblack,
        lag=1,
        xcat_aggs=["last", "sum"],
        start="2000-01-01",
       # xcat_trims=[10, 10]  # maybe trim outlier
    )
cr.reg_scatter(coef_box="upper left", prob_est="map")


srr = mss.SignalReturnRelations(
    dfx,
    cids=cidx,
    sigs=sig,
    rets=targ,
    freqs="M",
    start="2000-01-01",
    blacklist=fxblack,
)
display(srr.multiple_relations_table().round(3))
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/942a16d42efbf93daba1ef4d98dfebabfa3d7add4a0379af1b3eed3ddd13ec89.png
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
LCBIXRUSD_VT10 IIPD_ZN M last 0.538 0.536 0.514 0.567 0.602 0.47 0.056 0.0 0.046 0.0 0.536

Fiscal balance strength #

# Preparatory calculation

calcs = [
    "XGGPBGDPRATIO_NSA = GGPBGDPRATIO_NSA + 3",
    "XGGOBGDPRATIO_NSA = GGOBGDPRATIO_NSA + 3",
    "XGGSBGDPRATIO_NSA = GGSBGDPRATIO_NSA + 3",
]
dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cids_em)
dfx = msm.update_df(dfx, dfa)
# All constituents, signs, and weights

dict_gbal = {
    'XGGPBGDPRATIO_NSA': {"sign": 1, "weight": 1/3},
    'XGGOBGDPRATIO_NSA': {"sign": 1, "weight": 1/3},
    'XGGSBGDPRATIO_NSA': {"sign": 1, "weight": 1/3},
}
# Normalized categories with theoretical positive effects

dix = dict_gbal
cidx = cids_em

# Contingent negative values

if any (v["sign"] < 0 for v in dix.values()):
    calcs=[]
    dfa = pd.DataFrame(columns=dfx.columns)
    for xc, values in dix.items():
        if values["sign"] < 0:
            calcs.append(f"{xc}NEG = {xc} * -1")
            dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
    dfx = msm.update_df(dfx, dfa)

# Sequential normalization of categories

postfixes = ["NEG" if v["sign"] < 0 else "" for v in dix.values()]
xcatx = [k + pf for k, pf in zip(dix.keys(), postfixes)] 

for xc in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xc,
        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)

xgbalz = [xc + "_ZN" for xc in xcatx]
# Conceptual factor score

dix = dict_gbal
xcatx = xgbalz
cidx = cids_em

# Composite score

weights = [v["weight"] for v in dix.values()]
cfs = "XGBAL"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    weights=weights,
    new_xcat=cfs,
    complete_xcats=False,
)
dfx= msm.update_df(dfx, dfa)

# Re-score

dfa = msp.make_zn_scores(
    dfx,
    xcat=cfs,
    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)

cfs_zn = f"{cfs}_ZN"
concept_factors_zn[cfs_zn] = "Fiscal balance strength"
# Visualize

xcatx = xgbalz + ["XGBAL_ZN"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    height=2.2,
    size=(10, 10),
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/1d7eec6aa543b9ac39544348b38910b44bbbb5f23a19db2e932a46233cd70257.png
# checking - to delete later 
cidx = cids_em
sig = "XGBAL_ZN"
targ = "LCBIXRUSD_VT10"

cr = msp.CategoryRelations(
        dfx,
        xcats=[sig, targ],
        cids=cidx,
        freq="M",
        blacklist=fxblack,
        lag=1,
        xcat_aggs=["last", "sum"],
        start="2000-01-01",
       # xcat_trims=[10, 10]  # maybe trim outlier
    )
cr.reg_scatter(coef_box="upper left", prob_est="map")


srr = mss.SignalReturnRelations(
    dfx,
    cids=cidx,
    sigs=sig,
    rets=targ,
    freqs="M",
    start="2000-01-01",
    blacklist=fxblack,
)
display(srr.multiple_relations_table().round(3))
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/933cd03032a12c517058b83f3534d41d44208a96a663f91fcdd62e4cc66125de.png
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
LCBIXRUSD_VT10 XGBAL_ZN M last 0.532 0.513 0.651 0.568 0.577 0.449 0.005 0.744 0.01 0.348 0.512

Duration term premia #

# All constituents, signs, and weights

dict_dutp = {
    "DU02YETP_NSA": {"sign": 1, "weight": 1/3},
    "DU05YETP_NSA": {"sign": 1, "weight": 1/3},
    "DU10YETP_NSA": {"sign": 1, "weight": 1/3},
}
# Normalized categories with theoretical positive effects

dix = dict_dutp
cidx = cids_em

# Contingent negative values

if any (v["sign"] < 0 for v in dix.values()):
    calcs=[]
    dfa = pd.DataFrame(columns=dfx.columns)
    for xc, values in dix.items():
        if values["sign"] < 0:
            calcs.append(f"{xc}NEG = {xc} * -1")
            dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
    dfx = msm.update_df(dfx, dfa)

# Sequential normalization of categories

postfixes = ["NEG" if v["sign"] < 0 else "" for v in dix.values()]
xcatx = [k + pf for k, pf in zip(dix.keys(), postfixes)] 

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

dutpz = [xc + "_ZN" for xc in xcatx]
# Global imputation of missing premia

xcatx = ["DU02YETP_NSA_ZN", "DU05YETP_NSA_ZN", "DU10YETP_NSA_ZN"]
cidx = cids_em

dfi = msp.impute_panel(dfx, cids=cidx, xcats=xcatx, threshold=0.5)
dfx = msm.update_df(dfx, dfi)
# Conceptual factor score

dix = dict_dutp
xcatx = dutpz
cidx = cids_em

# Composite score

weights = [v["weight"] for v in dix.values()]
cfs = "DUTP"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    weights=weights,
    new_xcat=cfs,
    complete_xcats=False,
)
dfx= msm.update_df(dfx, dfa)

# Re-score

dfa = msp.make_zn_scores(
    dfx,
    xcat=cfs,
    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)

cfs_zn = f"{cfs}_ZN"
concept_factors_zn[cfs_zn] = "Duration term premium"
# Visualize

xcatx = dutpz + ["DUTP_ZN"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    height=2.2,
    size=(10, 10),
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/8316879224cbe32a4c3ea64dfe87137d9234da5e7b73f441c910b896a4cb36d6.png
# checking - to delete later 
cidx = cids_em
sig = "DUTP_ZN"
targ = "LCBIXRUSD_VT10"

cr = msp.CategoryRelations(
        dfx,
        xcats=[sig, targ],
        cids=cidx,
        freq="M",
        blacklist=fxblack,
        lag=1,
        xcat_aggs=["last", "sum"],
        start="2000-01-01",
       # xcat_trims=[10, 10]  # maybe trim outlier
    )
cr.reg_scatter(coef_box="upper left", prob_est="map")


srr = mss.SignalReturnRelations(
    dfx,
    cids=cidx,
    sigs=sig,
    rets=targ,
    freqs="M",
    start="2000-01-01",
    blacklist=fxblack,
)
display(srr.multiple_relations_table().round(3))
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/0dc37b7b68e1ae31f44bc7f2d45c984e12823957acb21f8ebed86cc05ca76d2d.png
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
LCBIXRUSD_VT10 DUTP_ZN M last 0.515 0.518 0.473 0.558 0.577 0.459 0.016 0.326 0.014 0.226 0.518

Factor overview and combination #

# Weighted linear combinations
xcatx = list(concept_factors_zn.keys())
cidx = cids_em
sdate = "2000-01-01"
new_cat = "COMP"

dfa = msp.linear_composite(
    dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    start=sdate,
    new_xcat=new_cat,
)
dfx = msm.update_df(dfx, dfa)

dfa = msp.make_zn_scores(
    dfx,
    xcat=new_cat,
    cids=cidx,
    sequential=True,
    min_obs=261 * 5,
    neutral="zero",
    pan_weight=1,
    thresh=3,
    postfix="_ZN",
    est_freq="m",
)
dfx = msm.update_df(dfx, dfa)

cfs_zn = f"{new_cat}_ZN"
concept_factors_zn[cfs_zn] = "Composite"
xcatx = ["COMP_ZN"]
cidx = cids_em

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=4,
    start="2000-01-01",
    same_y=False,
    title="Composite macro factor score for local EM bond market positions",
    title_fontsize=22,
    height=2,
    size=(10, 10),
    all_xticks=True,
    blacklist=fxblack,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/4095a8ff6992ea9b1a1716dde6fa8a988aceba8144a1a236042f88c163a617ac.png
xcatx = list(concept_factors_zn.keys())
cidx = cids_em
sdate = "2000-01-01"

msp.correl_matrix(
    dfx,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    title="Cross-correlation of macro factors, monthly frequency, 16 local EMs since 2000",
    title_fontsize=20,
    size=(16, 12),
    max_color=0.8,
    xcat_labels=concept_factors_zn,
    show=True,
    annot=True,
    start=sdate,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/f273bfd61fc2cc07bd5d208ba9c2b8eb09dc3259dbbce297a9fe9cefdad1fc34.png
list(concept_factors_zn.keys())
['XBCH_ZN', 'RGDP_ZN', 'TOT_ZN', 'IIPD_ZN', 'XGBAL_ZN', 'DUTP_ZN', 'COMP_ZN']
xcatx = ['COMP_ZN']
cidx = cids_em
sdate = "2000-01-01"

msp.correl_matrix(
    dfx,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    title="Correlation of composite macro factor across countries, monthly frequency, since 2000",
    title_fontsize=20,
    size=(18, 10),
    max_color=0.8,
    xcat_labels=concept_factors_zn,
    show=True,
    annot=True,
    start=sdate,
    cluster=True,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/5e720e7552f5819506642a43a96f91692171e914d7c2389a6c58bf231c782869.png

Macro-quantamental scorecards #

Snapshot #

xcatx = list(concept_factors_zn.keys())
cidx = cids_em

# Set data of snapshot
backdate = datetime.strptime("2008-09-01", "%Y-%m-%d")
lastdate = datetime.strptime(END_DATE, "%Y-%m-%d")
snapdate = lastdate

sv = ScoreVisualisers(
    df=dfx,
    cids=cidx,
    xcats = xcatx,
    xcat_labels=concept_factors_zn,
    xcat_comp="COMP_ZN",
    no_zn_scores=True,
    rescore_composite=False,
    blacklist=fxblack,
)

sv.view_snapshot(
    cids=cidx,
    date=snapdate,
    transpose=True,
    sort_by_composite = True,
    title=f"EM local markets: Tactical macro allocation scores {snapdate.strftime("%B %d, %Y")}",
    title_fontsize=20,
    figsize=(16, 6),
    xcats=xcatx,
    xcat_labels=concept_factors_zn,
    round_decimals=1,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/d741bb71a7126a60780fd8e03748dfb7ec1e0e40f0416fa451b0b14670ea68df.png

Global history #

cidx = cids_em

sv.view_score_evolution(
    xcat="COMP_ZN",
    cids=cidx,
    freq="A",
    include_latest_day=True,
    transpose=False,
    title="EM local markets: Evolution of composite tactical macro scores (gray areas are untradable periods)",
    title_fontsize=20,
    start="2000-01-01",
    figsize=(18, 10),
    round_decimals=1,
)
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/2e168806ca65e0a246fafc394adb18d6c2c9bc07ef1cf72bb54e4e77b8733ea4.png
cidx = cids_em

sv.view_score_evolution(
    xcat="COMP_ZN",
    cids=cidx,
    freq="Q",
    include_latest_day=True,
    transpose=False,
    title="EM local markets: Recent evolution of composite tactical macro scores",
    title_fontsize=20,
    start="2022-07-01",
    figsize=(18, 10),
    round_decimals=1,
)
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/2a54cbf2e52540f862106ff3e34b5eb1cc7d560b5f7950ea730b8f0e0fa6c9ec.png

Thematic history #

xcatx = list(concept_factors_zn.keys())
cidx = cids_em

for xcat in xcatx:
    xc_name = concept_factors_zn[xcat]
    sv.view_score_evolution(
        xcat=xcat,
        cids=cidx,
        freq="A",
        transpose=False,
        title=f"{xc_name}: Evolution of macro factor score across countries and time",
        title_fontsize=20,
        figsize=(18, 8),
        round_decimals=1,
        start="2000-01-01",
    )
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/7957b2dfebd8a809df7b325406778f2e870ed30779f848ab569f2a7ea9002b68.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/8168272f63688d38714c0304d5d963b6992c3ca85c59cb06d26bc7fda3402a18.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/7ccb6350f586c8a2d05951df229016d81cdc8e45b1ef80265f633fa50dfb267f.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/215f3fd21cf99000f66aee419056a5701e551789e08724aa9d33972854d0729f.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/e429137c869412d607854165a6279816a9fb3b497dde4b53da75443f64fd7a78.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/cd0ed1c80e2ffc83853b1a373084f738c64d976e72270dc1d20e5f1cc6c534f2.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/42aaaf3a5492483cae27664e70d474634546c804417f8b158648378f27b8409f.png

Country history #

xcatx = list(concept_factors_zn.keys())
cidx = cids_em

for cid in cidx:

    sv.view_cid_evolution(
        cid=cid,
        xcats=xcatx,
        xcat_labels=concept_factors_zn,
        freq="A",
        transpose=False,
        title=f"{cid}: Evolution of macro allocation factor scores and composite",
        title_fontsize=20,
        figsize=(18, 6),
        round_decimals=1,
        start="2000-01-01",
    )
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/be1e0fce1046579b06f28d56bb1e363f7226c529dda76fa55da0045c4a253cfc.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/679e946b6c04f56909f386783b69dd26409e71d495010b0d23f70f112507bebd.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/35590dd757a67736cbdf1e2f987a014b8ddb76fd6a9ccc5d3a76162cb651b300.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/c10ffca269bd2e8ed7757444ac0a8002df8389f7f5c7920cdc9192656af6349d.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/3e9360df1f07947c8cfc29e47fe1ec806d8f0eeaa85e4909971b8dfc7b853f4c.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/fbfa7043bc522168292cd4d3192c9c5836db71d5dbd5e397447650736a99fe1c.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/d448f887ea8866314ff2ca6a86507a9d7555c81122cf2f6753b7033532184e33.png
Latest day:  2018-06-29 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/a0255e10f9d22619e3f87a34acdee5e7906eeb9f573085187261003c0991aa36.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/46c4abbafb3c9d1701760fca7a7662f02ff77e3265153d0bbd704f62d6dca7d3.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/0e848e76d7ddcfa914c420b4d428513aafdc21b63fbe5e0272cb0fafe28b76df.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/099986210b1f98576beaf4e47e6980455eff7fa1f9d6894b6c17c99932046186.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/afa91a12d0c6af12d652299dd40c0784bb4cecff105a116fb4d93f1a1e676fc5.png
Latest day:  2022-01-31 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/ecbcde9e17e962d189934647b79f544b31e03f1242f29274206a13ab785b588a.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/38706bc0af79763e5ef9939b4bf116b3d3df9bd27642fc59e74f41486b49b14e.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/893162af8a53a46ddf6b33b10a09e1b098f95ab3dbf60586ee7209b2be81dba5.png
Latest day:  2025-11-04 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/f39f2113f9de557d56ab5689f033a682386897d9750a2c1e641a196365c24498.png

Value checks #

Specs and panel test #

dict_lm = {
    "sigs": list(concept_factors_zn.keys()),
    "targ": "LCBIXRUSD_VT10",
    "cidx": cids_em,
    "black": fxblack,
    "start": "2000-01-01",
    "srr": None,
    "pnls": None,
}
dix = dict_lm

sigs = [k for k in concept_factors_zn.keys() if k != 'COMP_ZN']
targ = dix["targ"]
cidx = dix["cidx"]
blax = dix["black"]
start = dix["start"]

# Dictionary to store CategoryRelations objects
crx = []

# Create CategoryRelations objects for all signals
for sig in sigs:
    crx.append(
        msp.CategoryRelations(
        dfx,
        xcats=[sig, targ],
        cids=cidx,
        freq="M",
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
        blacklist=blax,
        xcat_trims=[None, None],
        )
    )

# Display scatter plots for all signals
msv.multiple_reg_scatter(
    cat_rels=crx,
    coef_box="lower left",
    ncol=3,
    nrow=2,
    xlab="Macro factor score, end of month",
    ylab="EM bond index returns in USD, next month, vol-targeted",
    title="EM local markets: Macro factor scores and subsequent vol-targeted local bond index return",
    title_fontsize=20,
    figsize=(14, 10),
    prob_est="map",
    subplot_titles=[concept_factors_zn[sig] for sig in sigs],
    share_axes=False,
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/7509d4cc86df0c36b7ec278d8767580251f364ed356d09471f93332fee8e294c.png
dix = dict_lm

sig = "COMP_ZN"
targ = dix["targ"]
cidx = dix["cidx"]
blax = dix["black"]
start = dix["start"]

crx = msp.CategoryRelations(
    dfx,
    xcats=[sig, targ],
    cids=cidx,
    freq="m",
    lag=1,
    slip=1,
    xcat_aggs=["last", "sum"],
    start=start,
    blacklist=blax,
    xcat_trims=[20, 20],  # purely for better plot viewing, little impact on results
)

crx.reg_scatter(
    labels=False,
    coef_box="lower left",
    xlab="Macro factor score, end of month",
    ylab="EM bond index returns in USD, next month, vol-targeted",
    title="EM local markets: Composite macro score and subsequent vol-targeted local bond index return",
    title_fontsize=20,
    size=(12, 8),
    prob_est="map",
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/245e0ddde039b0def22569e053077a3ca33c5a57532574cd2b6c347c72e13fd3.png

Accuracy and correlation check #

dix = dict_lm

sigs = dix["sigs"]
targ = dix["targ"]
cidx = dix["cidx"]
blax = dix["black"]
start = dix["start"]

srr = mss.SignalReturnRelations(
    dfx,
    cids=cidx,
    sigs=sigs,
    rets=targ,
    freqs="M",
    start=start,
    blacklist=blax,
)

dix["srr"] = srr
dix = dict_lm
srrx = dix["srr"]
display(srrx.multiple_relations_table().round(3))

srrx.accuracy_bars(type="signals", 
                  freq="m",
                  size=(12, 4),
                  title="Macro factor scores accuracy for next month local bond index returns (since 2000)",
                  title_fontsize=15,
                  x_labels=concept_factors_zn,
                  x_labels_rotate=45,
                  )
accuracy bal_accuracy pos_sigr pos_retr pos_prec neg_prec pearson pearson_pval kendall kendall_pval auc
Return Signal Frequency Aggregation
LCBIXRUSD_VT10 COMP_ZN M last 0.540 0.527 0.606 0.568 0.589 0.464 0.066 0.000 0.051 0.000 0.526
DUTP_ZN M last 0.515 0.518 0.473 0.558 0.577 0.459 0.016 0.326 0.014 0.226 0.518
IIPD_ZN M last 0.538 0.536 0.514 0.567 0.602 0.470 0.056 0.000 0.046 0.000 0.536
RGDP_ZN M last 0.525 0.510 0.626 0.565 0.572 0.447 0.029 0.075 0.022 0.039 0.509
TOT_ZN M last 0.515 0.517 0.486 0.568 0.586 0.449 0.037 0.020 0.027 0.012 0.518
XBCH_ZN M last 0.524 0.524 0.500 0.568 0.591 0.456 0.032 0.044 0.023 0.034 0.524
XGBAL_ZN M last 0.532 0.513 0.651 0.568 0.577 0.449 0.005 0.744 0.010 0.348 0.512
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/f5556cd47ac4e38e0573e2bb379a6a304022a6bd5cb7669f016021ed77ef79cc.png

Naive PnLs #

dix = dict_lm

sig = "COMP_ZN"
targ = dix["targ"]
cidx = dix["cidx"]
blax = dix["black"]
start = dix["start"]

pnls = msn.NaivePnL(
    df=dfx,
    ret=targ,
    sigs=[sig],
    cids=cidx,
    start=sdate,
    blacklist=fxblack,
    bms=["USD_GB10YXR_NSA", "EUR_FXXR_NSA", "USD_EQXR_NSA"],
)

pnls.make_pnl(
    sig=sig,
    sig_add=0,
    sig_op="raw", # "zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    thresh=2,
    rebal_slip=1,
    vol_scale=10,
    pnl_name="PNL0",
)

pnls.make_pnl(
    sig=sig,
    sig_add=1,
    sig_op="zn_score_pan", # "zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    thresh=1,
    rebal_slip=1,
    vol_scale=10,
    pnl_name="PNL1",
    winsorize_first=True,
)

pnls.make_long_pnl(vol_scale=10, label="Risk parity")
dix["pnls"] = pnls
pnls = dix["pnls"]

pnl_labels = {
    "PNL0": "Long-short macro factor strategy",
    "PNL1": "Long only macro factor strategy",
    "Risk parity": "Long only risk parity portfolio",
}

pnls.plot_pnls(
    title="Naive PnLs of macro factor strategies in EM local bond markets",
    title_fontsize=16,
    figsize=(12, 7),
    xcat_labels=pnl_labels,
)
display(pnls.evaluate_pnls(["PNL0", "PNL1", "Risk parity"]))

pnls.signal_heatmap(
    pnl_name="PNL1",
    freq="M",
    title="Average applied signal values",
    title_fontsize=None,
    figsize=(14, 6)
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/9283ec89d578dc14c3a87e8a9d8b77e99b50fd425f5ac8de86717ace0ac38733.png
xcat PNL0 PNL1 Risk parity
Return % 10.410447 7.645079 5.130162
St. Dev. % 10.0 10.0 10.0
Sharpe Ratio 1.041045 0.764508 0.513016
Sortino Ratio 1.526962 1.075313 0.710554
Max 21-Day Draw % -16.146929 -22.946575 -22.638608
Max 6-Month Draw % -18.377232 -24.691496 -25.167761
Peak to Trough Draw % -26.572656 -31.286012 -53.009082
Top 5% Monthly PnL Share 0.520985 0.631905 0.902861
USD_GB10YXR_NSA correl -0.027386 0.023568 0.038606
EUR_FXXR_NSA correl 0.128432 0.526235 0.575365
USD_EQXR_NSA correl 0.079573 0.243739 0.257852
Traded Months 311 311 311
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/1a5ee30805ecf237373253b88cde09a6a0b6292e5ec5cf19610c8ea64dd17770.png
cids_em
['BRL',
 'CLP',
 'COP',
 'CZK',
 'HUF',
 'IDR',
 'MXN',
 'MYR',
 'PEN',
 'PHP',
 'PLN',
 'RON',
 'RUB',
 'THB',
 'TRY',
 'ZAR']
dix = dict_lm

sigs = sigs = list(set(dix["sigs"]) - set(["COMP_ZN"]))
ret = dix["targ"]
cidx = dix["cidx"]
blax = dix["black"]
start = dix["start"]

single_pnls = msn.NaivePnL(
    df=dfx,
    ret=targ,
    sigs=sigs,
    cids=cidx,
    start=start,
    blacklist=blax,
    bms=["USD_GB10YXR_NSA", "EUR_FXXR_NSA", "USD_EQXR_NSA"],
)

for sig in sigs:
    single_pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        rebal_freq="monthly",
        neutral="zero",
        thresh=2,
        rebal_slip=1,
        vol_scale=10,
    )

single_pnls.plot_pnls(
    title=None,
    title_fontsize=18,
    xcat_labels=None,
    facet=True,
    figsize=(12, 10)
)
https://macrosynergy.com/notebooks.build/dashboards/em-local-bonds-scorecard/_images/67c4fe519be59fb6aa03a8b710c654e1f2113cb855d94983f2fa9f68b9b82880.png