Equity country allocation scorecard #

Get packages and JPMaQS data #

import tqdm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import warnings
import os
import gc
from datetime import date, datetime

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

from macrosynergy.panel.panel_imputer import MeanPanelImputer
from macrosynergy.panel.adjust_weights import adjust_weights

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

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

warnings.simplefilter("ignore")
# Cross-sections of interest
cids_dmeq = ["AUD", "CAD", "CHF", "EUR", "GBP", "JPY", "SEK", "USD"]
cids_eueq = ["DEM", "ESP", "FRF", "ITL", "NLG"]
cids_aseq = ["CNY", "HKD", "INR", "KRW", "MYR", "SGD", "THB", "TWD"]
cids_exeq = ["BRL", "TRY", "ZAR"]
cids_nueq = ["MXN", "PLN"]
cids_no_jpmaqs = ["HKD", "TRY"]

cids_eq = sorted(list(set(cids_dmeq + cids_eueq + cids_aseq + cids_exeq + cids_nueq) - set(cids_no_jpmaqs + cids_eueq)))
cids = cids_eq
# Category tickers

inf = (
    [
        f"{cat}_{at}"
        for cat in [
            "CPIC",
            "CPIH",
        ]
        for at in [
            "SA_P1M1ML12",
            "SA_P1Q1QL4",
        ]
    ] + ["PPIH_NSA_P1M1ML12"]
)

surveys = [
    f"{cat}_{at}"
    for cat in ["CCSCORE", "MBCSCORE"]
    for at in [
        "SA_D3M3ML3",
        "SA_D1Q1QL1",
        "SA_D1M1ML12",
        "SA_D1Q1QL4",
    ]
]


flows = [
    f"{flow}_NSA_D1M1ML12" for flow in ["NIIPGDP", "IIPLIABGDP"]
]

trade = (
    [
        f"{cat}_{at}"
        for cat in ["CABGDPRATIO", "MTBGDPRATIO"]
        for at in [
            "SA_3MMAv60MMA", "SA_1QMAv20QMA",
            "SA_6MMA_D1M1ML6", "NSA_12MMA_D1M1ML3"
        ]
    ]
)

fxrel = [
    f"{cat}_NSA_{t}"
    for cat in ["REEROADJ", "CTOT"]
    for t in ["P1W4WL1", "P1M1ML12", "P1M12ML1"]
] + ["PPPFXOVERVALUE_NSA_P1M12ML1"]

eq = ["EQCRR_VT10", "EQCRY_VT10", "EQCRR_NSA", "EQCRY_NSA"]

add = [
    "INFTEFF_NSA",
]

eco = (
    inf
    + surveys
    + flows
    + trade
    + fxrel
    + eq
    + add
)

mkt = [
    "EQXR_NSA",
    "EQCALLRUSD_NSA",
    "FXTARGETED_NSA",
    "FXUNTRADABLE_NSA",
]

xcats = eco + mkt

# Tickers for download
single_tix = ["USD_GB10YXR_NSA", "EUR_FXXR_NSA"]
tickers = [cid + "_" + xcat for cid in cids for xcat in xcats] + single_tix
# Download series from J.P. Morgan DataQuery by tickers

start_date = "1990-12-31"
end_date = (pd.Timestamp.today() - pd.offsets.BDay(1)).strftime('%Y-%m-%d')

# Retrieve credentials

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

# Download from DataQuery

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

dfx = df.copy()
dfx.info()
Downloading data from JPMaQS.
Timestamp UTC:  2025-09-24 09:40:10
Connection successful!
Requesting data: 100%|██████████| 38/38 [00:07<00:00,  4.93it/s]
Downloading data: 100%|██████████| 38/38 [00:24<00:00,  1.56it/s]
Some expressions are missing from the downloaded data. Check logger output for complete list.
189 out of 743 expressions are missing. To download the catalogue of all available expressions and filter the unavailable expressions, set `get_catalogue=True` in the call to `JPMaQSDownload.download()`.
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3994576 entries, 0 to 3994575
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: 121.9+ MB

Renaming, availability and blacklisting #

# Removing Australia and New Zealand CPI multiple transformation frequencies - maintaining monthly version now
dfx = dfx.loc[
    ~(
        (dfx["cid"].isin(["AUD", "NZD"])) & (dfx["xcat"].isin(inf)) & (dfx["xcat"].str.contains("Q"))
     )
]

Renaming quarterly categories #

dict_repl = {
    "CPIC_SJA_P1Q1QL1AR": "CPIC_SJA_P3M3ML3AR",
    "CPIC_SJA_P2Q2QL2AR": "CPIC_SJA_P6M6ML6AR",
    "CPIC_SA_P1Q1QL4": "CPIC_SA_P1M1ML12",

    "CPIH_SJA_P1Q1QL1AR": "CPIH_SJA_P3M3ML3AR",
    "CPIH_SJA_P2Q2QL2AR": "CPIH_SJA_P6M6ML6AR",
    "CPIH_SA_P1Q1QL4": "CPIH_SA_P1M1ML12",
    
    "CCSCORE_SA_D1Q1QL1": "CCSCORE_SA_D3M3ML3",
    "MBCSCORE_SA_D1Q1QL1": "MBCSCORE_SA_D3M3ML3",
    "CCSCORE_SA_D1Q1QL4": "CCSCORE_SA_D1M1ML12",
    "MBCSCORE_SA_D1Q1QL4": "MBCSCORE_SA_D1M1ML12",
    
    "CABGDPRATIO_SA_1QMAv20QMA": "CABGDPRATIO_SA_3MMAv60MMA",
    "MTBGDPRATIO_SA_1QMAv20QMA": "MTBGDPRATIO_SA_3MMAv60MMA",
}

dfx["xcat2"] = dfx["xcat"].map(dict_repl).fillna(dfx["xcat"])
dfx = dfx.drop(columns="xcat").rename(columns={"xcat2": "xcat"})

Check availability #

xcatx = sorted(list(set(inf) - set(dict_repl.keys())))
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_eq, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/7d2b1c8efbed59505a98915feef0807a5c077bde531042c43564ddba8ec940b8.png
xcatx = sorted(list(set(surveys) - set(dict_repl.keys())))
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_eq, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/a78bd42eb13fd0d4b6ae07d7c5ec98bd3ba9541ea258af7a798c08d82f994321.png
xcatx = sorted(list(set(trade) - set(dict_repl.keys()))) 
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_eq, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/6446c7ed98c0c7bcffd61061077673e4b2adfcee1db6334aad5e3f9b4cda6989.png
xcatx = sorted(list(set(eq) - set(dict_repl.keys()))) 
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_eq, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/7b005bf2e3477267702c5f9903e379760e8fe5f30dfacb368a85e94a5ce4a895.png
xcatx = sorted(list(set(mkt) - set(dict_repl.keys()))) 
msm.check_availability(df=dfx, xcats=xcatx, cids=cids_eq, missing_recent=False)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/61e17cf0843d460c7c0c54f412b8c8dd143896d21d57f837465a138dbae990c7.png

Blacklisting dictionary for empirical research #

# At present no blacklisting for cash equity

equsdblack={}

Equity returns #

cidx = cids_eq
xcatx = ["EQCALLRUSD_NSA"]
start_date = "1995-01-01"

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    blacklist=equsdblack,  # Using the FX Blacklisting for returns in USD
    rel_meth="subtract",
    complete_cross=False,
    postfix="vGLB",
)
dfx = msm.update_df(dfx, dfa)

Factor construction and checks #

# Initiate category dictionary for thematic factors

dict_facts = {}

# Initiate labeling dictionary

dict_lab = {}

Auxiliary categories #

# Stitching for India and China: extending core with corresponding headline

cidx = ["INR", "CNY"]

merging_xcatx = {
    "CPIC_SA_P1M1ML12": ["CPIC_SA_P1M1ML12", "CPIH_SA_P1M1ML12"],
}

for new_cat, xcatx in merging_xcatx.items():
    dfa = msm.merge_categories(df=dfx, xcats=xcatx, new_xcat=new_cat, cids=cidx)
    dfx = msm.update_df(dfx, dfa)

Relative real equity carry #

# Real index carry versions

xcatx = ["CPIH_SA_P1M1ML12", "CPIC_SA_P1M1ML12", "PPIH_NSA_P1M1ML12"]
cidx = cids_eq

calcs = [
    "EQCRR_CPIH = EQCRY_NSA + CPIH_SA_P1M1ML12",
    "EQCRR_CPIC = EQCRY_NSA + CPIC_SA_P1M1ML12",
    "EQCRR_PPIH = EQCRY_NSA + PPIH_NSA_P1M1ML12",
]

dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)
ecrr = ["EQCRR_NSA", "EQCRR_CPIH", "EQCRR_CPIC", "EQCRR_PPIH"]
# Relative value scores

xcatx = ecrr
cidx = cids_eq
start_date = "1995-01-01"

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    blacklist=equsdblack,
    rel_meth="subtract",
    complete_cross=False,
    postfix="vGLB",
)
dfx = msm.update_df(dfx, dfa)

for xcat in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xcat+"vGLB",
        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)
# Conceptual relative factor score

xcatx = [xc + "vGLB_ZN" for xc in ecrr]
cidx = cids_eq
cfs = "EQCRRvGLB"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    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)

dict_facts[cfs] = "Relative real equity carry"

Relative real currency weakness #

# Imputing PPP for USD. # TWD unavailable for this indicator (will only use REERs)

cidx = list(set(cids_eq) - {"USD"})
dfu = msp.linear_composite(
    dfx,
    xcats="PPPFXOVERVALUE_NSA_P1M12ML1",
    cids=cidx,
    weights=None,
    complete_cids=False,
    complete_xcats=False,
    new_cid='USD'
)
dfx = msm.update_df(dfx, dfu)

# Define negative categories

xcatx = [
    "PPPFXOVERVALUE_NSA_P1M12ML1",
    "REEROADJ_NSA_P1M12ML1",
    "REEROADJ_NSA_P1M1ML12",
    "REEROADJ_NSA_P1W4WL1",
]
calcs = []
for xc in xcatx:
    calcs.append(f"{xc}N = - {xc}")

dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cids_eq)
dfx = msm.update_df(dfx, dfa)
# All constituents

fxweak = [
    'PPPFXOVERVALUE_NSA_P1M12ML1N',
    'REEROADJ_NSA_P1M12ML1N',
    'REEROADJ_NSA_P1M1ML12N',
    'REEROADJ_NSA_P1W4WL1N'
]
# Relative value scores

xcatx = fxweak
cidx = cids_eq
start_date = "1995-01-01"

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    blacklist=equsdblack,
    rel_meth="subtract",
    complete_cross=False,
    postfix="vGLB",
)
dfx = msm.update_df(dfx, dfa)

for xcat in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xcat+"vGLB",
        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)
# Conceptual relative factor score

xcatx = [xc + "vGLB_ZN" for xc in fxweak]
cidx = cids_eq
cfs = "FXWEAKvGLB"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    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)

dict_facts[cfs] = "Relative real currency weakness"

Relative terms-of-trade improvements #

# All constituents
tot = ['CTOT_NSA_P1M12ML1', 'CTOT_NSA_P1M1ML12', 'CTOT_NSA_P1W4WL1']
# Relative value scores

xcatx = tot
cidx = cids_eq
start_date = "1995-01-01"

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    blacklist=equsdblack,
    rel_meth="subtract",
    complete_cross=False,
    postfix="vGLB",
)
dfx = msm.update_df(dfx, dfa)

for xcat in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xcat+"vGLB",
        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)
# Conceptual relative factor score

xcatx = [xc + "vGLB_ZN" for xc in tot]
cidx = cids_eq
cfs = "TOTDvGLB"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    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)

dict_facts[cfs] = "Relative terms-of-trade improvement"

Relative external balances strength #

# All constituents
xbal = [
    'MTBGDPRATIO_SA_3MMAv60MMA',
    'CABGDPRATIO_SA_3MMAv60MMA',
    'MTBGDPRATIO_NSA_12MMA_D1M1ML3',
    'MTBGDPRATIO_SA_6MMA_D1M1ML6'
]
# Relative value scores

xcatx = xbal
cidx = cids_eq
start_date = "1995-01-01"

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    blacklist=equsdblack,
    rel_meth="subtract",
    complete_cross=False,
    postfix="vGLB",
)
dfx = msm.update_df(dfx, dfa)

for xcat in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xcat+"vGLB",
        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)
# Conceptual relative factor score

xcatx = [xc + "vGLB_ZN" for xc in xbal]
cidx = cids_eq
cfs = "XBSvGLB"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    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)

dict_facts[cfs] = "Relative external balances strength"

Relative investment position improvements #

# Change a sign

calcs = ["IIPLIABGDP_NSA_D1M1ML12N = - IIPLIABGDP_NSA_D1M1ML12"]
dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cids_eq)
dfx = msm.update_df(dfx, dfa)
# All constituents

iip = [
    'IIPLIABGDP_NSA_D1M1ML12N',
    'NIIPGDP_NSA_D1M1ML12',
]
# Relative value scores

xcatx = iip
cidx = cids_eq
start_date = "1995-01-01"

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    blacklist=equsdblack,
    rel_meth="subtract",
    complete_cross=False,
    postfix="vGLB",
)
dfx = msm.update_df(dfx, dfa)

for xcat in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xcat+"vGLB",
        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)
# Conceptual relative factor score

xcatx = [xc + "vGLB_ZN" for xc in iip]
cidx = cids_eq
cfs = "IIPDvGLB"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    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)

dict_facts[cfs] = "Relative investment position improvement"

Relative confidence improvement #

# All constituents

conf = [
    'MBCSCORE_SA_D3M3ML3', 'MBCSCORE_SA_D1M1ML12',
    'CCSCORE_SA_D3M3ML3', 'CCSCORE_SA_D1M1ML12',
]
# Relative value scores

xcatx = conf
cidx = cids_eq
start_date = "1995-01-01"

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    blacklist=equsdblack,
    rel_meth="subtract",
    complete_cross=False,
    postfix="vGLB",
)
dfx = msm.update_df(dfx, dfa)

for xcat in xcatx:
    dfa = msp.make_zn_scores(
        dfx,
        xcat=xcat+"vGLB",
        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)
# Conceptual relative factor score

xcatx = [xc + "vGLB_ZN" for xc in conf]
cidx = cids_eq
cfs = "CONDvGLB"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    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)

dict_facts[cfs] = "Relative confidence improvement"

Overview visuals of all relative factors #

dict_factz = {k+"_ZN": v for k, v in dict_facts.items()}
xcatx = list(dict_factz.keys())
msm.check_availability(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    missing_recent=False,
    start="1995-01-01",
    xcat_labels=dict_factz,
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/fd7845e826fc39295bc6ecf564063f6cc069d927a5f01decf6f1153d1a634325.png
cidx = cids_eq
xcatx = list(dict_factz.keys())
start = "1995-01-01"

msp.correl_matrix(
    dfx,
    cids=cidx,
    xcats=xcatx,
    max_color=1.0,
    annot=True,
    fmt=".1f",
    freq="M",
    cluster=False,
    title=f"Relative conceptual factor scores: cross correlations for all {str(len(cids_eq))} currency areas, since 1995",
    title_fontsize=20,
    xcat_labels=dict_factz,
    start=start,
    size=(14, 10),
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/e756964783216be599f0803c88d289edfc1cf75b262fce79ca0005c63692308f.png

Global condensed scorecard #

Snapshot #

xcatx = list(dict_factz.keys())
cidx = cids_eq

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


sv = ScoreVisualisers(
    df=dfx,
    cids=cidx,
    xcats = xcatx,
    xcat_labels=dict_factz,
    xcat_comp="Composite",
    no_zn_scores=True,
    rescore_composite=True,
    blacklist=equsdblack,
)

sv.view_snapshot(
    cids=cidx,
    date=snapdate,
    transpose=True,
    sort_by_composite = True,
    title=f"Global equity: Tactical cross-country macro allocation scores {snapdate.strftime("%B %d, %Y")}",
    title_fontsize=18,
    figsize=(16, 6),
    xcats=xcatx + ["Composite"],
    xcat_labels=dict_factz,
    round_decimals=1,
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/57bbbf8b25eeab80a2572a94144992fb957816ce1f31eaf8dab32cc16f9bc864.png

Global history #

sv.view_score_evolution(
    xcat="Composite",
    cids=cidx,
    freq="A",
    include_latest_day=True,
    transpose=False,
    title="Global equity: Evolution of cross-country composite macro allocation scores",
    start="2000-01-01",
    figsize=(18, 10),
)
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/b612218e364dc5c11e4307cd63ba28d5f4bc07b2e3a61a58d83feb767814ed2e.png
sv.view_score_evolution(
    xcat="Composite",
    cids=cidx,
    freq="Q",
    include_latest_day=True,
    transpose=False,
    title="Global equity: Recent dynamics of cross-country composite macro allocation scores",
    start="2022-01-01",
    figsize=(18, 8),
)
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/10186cbdbfb445a2e549b7d26a2294009b8603fafbd42e039a68732e9bc97149.png

Country history #

xcatx = list(dict_factz.keys()) + ["Composite"]
cidx = cids_eq

for cid in cids_eq:

    sv.view_cid_evolution(
        cid=cid,
        xcats=xcatx,
        xcat_labels=dict_lab,
        freq="A",
        transpose=False,
        title=f"{cid}: Evolution of macro allocation factor scores and composite",
        title_fontsize=18,
        figsize=(18, 6),
        round_decimals=1,
        start="2000-01-01",
    )
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/a3d6071fd1519b8553108c4c91b6f0b2b2dd300e306256ed556dea0bf485fe55.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/95f69b1297eec8f424cbc2525138dc8b34361db801834cd5a2da63009b8bfc00.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/fdf4b737d0509fe4b5123aa3baaf0837ac8ede4b94b977fda9bd6fa20ad09c3d.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/a5130caaf57c019e94628c4cb3afc3805594d150ae5ea4c7e704c4902f39aedc.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/e7b8509c1382ff4dae8d8c128d83713c8a4e7bf785b735319b2bf1feef48653a.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/4b9ee631399f523e6e15ddc3de2413bc267fbca55dc017295332589b2588ca13.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/529f045866876ba5cbcfb05c25a646264b38fee0c01ef0269f53d87118b54675.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/deb7a1720a230052a00b82f46f7ceb30f3e40185ae55c845c5de33cc905da799.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/14baf4dd490da0878a31d9d0798b5aa7beae72d3161b8df452fa09fe19812356.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/f8beaa9751fd30c54dfb871b160a1731167fe4101d731fef9c7b46d38029eb25.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/2d19dd3490aedd15aaee1b7971270ea0f9ef1c8facfdb7b2ccce8fc467d381cc.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/6025f5b0b9ac1f5f9ae91ffe0ff7e042c8fc174f0b9176e02de7fade5de63a6a.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/6ce299ae6d9e8b580349185aa531c222d37d050cac26e5fa950976759f72f157.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/9f1e957899af22f9e7d09fa7260db1c5ae1cdc59a255e62b37990548a164b652.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/1b928f2538796946ef837ed339f809f23792d25898c991a3108ada6001d4b08a.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/f4a1545201f910cc0adacea530d9a446099eff4def8b2f8167b6c2a60f8fcdc2.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/5ea8125c8b447322329ab965c2b5f2541d76295be57447e8de7607a0ba2227d6.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/8fb52be5919bf8301ef826258cd5d0e9fb5598f67eb8dc78a0603ebbf98d7f33.png
Latest day:  2025-09-23 00:00:00
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/758189620b60611d7aae6032d74d001a4760300df22446d20c2fa46d309c3cd7.png

Predictive power and economic value #

Predictive power of factors #

xcatx = list(dict_factz.keys())
cidx = cids_eq
start = "2000-01-01"

catregs = {
    sig : msp.CategoryRelations(
        dfx,
        xcats=[sig, "EQCALLRUSD_NSAvGLB"],
        cids=cidx,
        freq="Q",
        lag=1,
        slip=1,
        blacklist=equsdblack,
        xcat_aggs=["last", "sum"],
        start=start,
    )
    for sig in xcatx
}

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    figsize=(15,10),
    ncol=3,
    nrow=2,
    title="Relative macro factor sores and subsequent relative equity returns (19 developed and emerging markets, since 2000)",
    title_fontsize=18,
    xlab="Relative macro factor scores, end of quarter",
    ylab="Relative USD return of local equity basket versus global basket, % next quarter",
    coef_box="lower right",
    prob_est="map",
    share_axes=True,
    subplot_titles=[dict_factz[x] for x in xcatx],
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/f0d7ebd00d9a164d8393affb428575b1ed066d62ae45854a7f5b877794a542c4.png

Predictive power of composite #

# Conceptual relative factor score

xcatx = list(dict_factz.keys())
cidx = cids_eq
cfs = "ALLvGLB"

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cids_eq,
    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)

dict_alls = {cfs: "Relative overall factor"} | dict_facts
dict_allz = {cfs+"_ZN": "Relative overall factor"} | dict_factz
xcatx = ["ALLvGLB_ZN"]
cidx = cids_eq

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    ncol=5,
    start="2000-01-01",
    same_y=True,
    height=1.7,
    title="Composite relative macro factor scores for country equity allocation",
    title_fontsize=22,
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/c66fd5952eed3206d0646161034309d2c9f4d1ac1b80d0647bceffe494671f11.png
sig = "ALLvGLB_ZN"
ret = "EQCALLRUSD_NSAvGLB"
cidx = cids_eq
freq = "m"
start = "2000-01-01"

crx = msp.CategoryRelations(
    dfx,
    xcats=[sig, ret],
    cids=cidx,
    freq="Q",
    lag=1,
    slip=1,
    xcat_aggs=["last", "sum"],
    start=start,
    blacklist=None,
)

crx.reg_scatter(
    labels=False,
    coef_box="lower left",
    xlab="Composite relative macro score for equity allocation, end of quarter",
    ylab="USD return of local equity basket versus global basket, %, next quarter",
    title="Composite relative macro score and subsequent relative returns, since 2000, 19 DM and EM",
    title_fontsize=18,
    size=(12, 8),
    prob_est="map",
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/64f6dd28472271183ae310d796995ab17ced52351fe43f94ae924ab16cfe9ab0.png

Stylized relative position PnL #

sig = "ALLvGLB_ZN"
ret = "EQCALLRUSD_NSAvGLB"
cidx = cids_eq
blax = None
sdate = "2000-01-01"

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

pnls.make_pnl(
    sig=sig,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    thresh=3,
    rebal_slip=1,
    vol_scale=10,
    pnl_name="PROP_PNL",
)

pnls.make_pnl(
    sig=sig,
    sig_op="binary",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    pnl_name="BIN_PNL",
)

pnls.plot_pnls(
    title="Naive PnLs of cross-country relative cash equity positions for 19 developed and emerging markets",
    title_fontsize=16,
    xcat_labels=["Proportionate signals", "Binary signals"],
    figsize=(14,6)
)
display(pnls.evaluate_pnls(["PROP_PNL", "BIN_PNL"]))
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/3fe87627e36e56410a9a8d4ee6eccd1e72660172538c86755a388e66217f4b98.png
xcat PROP_PNL BIN_PNL
Return % 8.717149 6.215304
St. Dev. % 10.0 10.0
Sharpe Ratio 0.871715 0.62153
Sortino Ratio 1.287248 0.907071
Max 21-Day Draw % -9.331289 -7.623355
Max 6-Month Draw % -15.498525 -14.325042
Peak to Trough Draw % -16.769772 -16.163088
Top 5% Monthly PnL Share 0.492196 0.544589
USD_GB10YXR_NSA correl 0.029689 0.050645
EUR_FXXR_NSA correl 0.112782 0.125806
USD_EQXR_NSA correl -0.079334 -0.12193
Traded Months 309 309
sigs = list(dict_factz.keys())
targ = "EQCALLRUSD_NSAvGLB"
cidx = cids_eq
blax = None
sdate = "2003-01-01"

single_pnls = msn.NaivePnL(
    df=dfx,
    ret=targ,
    sigs=sigs,
    cids=cidx,
    start=sdate,
    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",
        rebal_slip=1,
        vol_scale=10,
    )

single_pnls.plot_pnls(
    title="Naive PnLs of vol-targeted Equity index future positions for 19 DM and EM countries (scaled to 10% vol)",
    title_fontsize=18,
    xcat_labels=list(dict_factz.values()),
    facet=True,
    figsize=(12, 10)
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/3ee25d761205d86869b50ce135a189fc107a39b848bb98ca194c1e37a42f8830.png

Wealth enhancement #

Weights and pseudo-weights #

# Equal weight calculation

cidx = cids_eq
# Simple weight in the global equity basket for each country when the equity futures start being traded
calcs = [
    "GEQPWGT = 0 * EQCALLRUSD_NSA + 1",
]
dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
dfa["tot_cids"] = dfa.groupby(["real_date", "xcat"])["value"].transform("count")
dfa["value"] = dfa["value"] / dfa["tot_cids"]
dfx = msm.update_df(dfx, dfa.drop(columns=["tot_cids"]))
# Define appropriate sigmoid function for adjusting weights

amplitude = 2
steepness = 5
midpoint = 0

def sigmoid(x, a=amplitude, b=steepness, c=midpoint):
    return a / (1 + np.exp(-b * (x - c)))


ar = np.array([i / 4 for i in range(-16, 18)])
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(ar, sigmoid(ar))
plt.title("Sigmoid function that transforms normalized relative value macro scores into weight modifiers")
plt.show()
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/0f3cb88066baa63f54457e17114321fe73f092c86ffa3a91146ec372a9b0daf7.png
# Adjusted weights calculation

cidx = cids_eq

dfj = adjust_weights(
    dfx,
    weights_xcat="GEQPWGT",
    adj_zns_xcat="ALLvGLB_ZN",
    method="generic",
    adj_func=sigmoid,
    blacklist=equsdblack,
    cids=cidx,
    adj_name="GEQPWGT_MOD",
)
dfx = msm.update_df(dfx, dfj)
# Visualize weights

cidx = cids_eq
xcatx = ["GEQPWGT", "GEQPWGT_MOD"]

# Reduce frequency to monthly in accordance with PnL simulation
for xc in xcatx:
    dfa = dfx.loc[dfx["xcat"] == xc]
    dfa["last_period_bd"] = pd.to_datetime(dfa["real_date"]) + pd.tseries.offsets.BQuarterEnd(0)
    mask = pd.to_datetime(dfa["last_period_bd"]) == pd.to_datetime(dfa["real_date"])
    dfa.loc[~mask, "value"] = np.nan
    dfa["value"] = dfa.groupby("cid")["value"].ffill(limit=75) # max 25 business days 
    dfa = dfa.drop(columns="xcat").assign(xcat=f"{xc}_M")
    dfx = msm.update_df(dfx, dfa)
 
xcatxx =[xc + "_M" for xc in xcatx]

msp.view_timelines(
    dfx,
    xcats=xcatxx,
    cids=cidx,
    ncol=4,
    start="2003-01-01",
    same_y=False,
    cumsum=False,
    title="Equal and macro signal-based weights of countries in a global USD-based equity portfolio",
    title_fontsize=22,
    xcat_labels=["Equal weighting allocation", "Macro signal-based allocation"],
    height=1.5,
    aspect=2.2,
    legend_fontsize=16,
)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/31e48217d5b46afddb6c963c6dd9d140a76eadc3174a51550adf1e61118e6ebb.png
# U.S. only "weight"

cidx = ["USD"]
calcs = [
    # Always long one unit of US equity
    "USEQPWGT = 0 * EQCALLRUSD_NSA + 1",
]
dfa = msp.panel_calculator(dfx, calcs=calcs, cids=cidx)
dfx = msm.update_df(dfx, dfa)

Evaluation #

dict_mod = {
    "sigs": ["GEQPWGT_MOD", "GEQPWGT", "USEQPWGT"],
    "targ": "EQCALLRUSD_NSA",
    "cidx": cids_eq,
    "start": "2000-01-01",
    "black": equsdblack,
    "srr": None,
    "pnls": None,
}
dix = dict_mod

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

pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=start,
    blacklist=black,
)
for sig in sigs:
    pnls.make_pnl(
        sig=sig,
        sig_op="raw",
        rebal_freq="quarterly",
        neutral="zero",
        rebal_slip=1,
    )

dix["pnls"] = pnls
dix = dict_mod
pnls = dix["pnls"]
sigs = dix["sigs"]
pnl_cats = ["PNL_" + sig for sig in sigs]

pnls.plot_pnls(
    pnl_cats=pnl_cats,
    title="Approximate cumulative U.S. dollar returns of global equity portfolios (19 developed and EM countries)",
    title_fontsize=16,
    compounding=True,
    xcat_labels={
        "PNL_GEQPWGT": "Equal weights for all tradable currency areas, quarterly rebalanced",
        "PNL_GEQPWGT_MOD": "Macro signal-based weights for all tradable currency areas, quarterly rebalanced",
        "PNL_USEQPWGT": "Long-only US equity",
    }, 
    figsize=(14,6)
)
pnls.evaluate_pnls(pnl_cats=pnl_cats)
https://macrosynergy.com/notebooks.build/dashboards/equity-country-allocation-scorecard/_images/c3c797a65ecc7bc559f6d1b58ec94084e1352835a145d4296d1b1e91003488d6.png
xcat PNL_GEQPWGT_MOD PNL_GEQPWGT PNL_USEQPWGT
Return % 12.089165 9.650414 9.902037
St. Dev. % 15.887407 15.31639 19.678504
Sharpe Ratio 0.760928 0.630071 0.503191
Sortino Ratio 1.071502 0.872544 0.712426
Max 21-Day Draw % -44.234854 -46.065109 -38.21883
Max 6-Month Draw % -74.633423 -72.113718 -55.794825
Peak to Trough Draw % -81.359015 -79.047799 -68.560921
Top 5% Monthly PnL Share 0.563015 0.662894 0.587019
Traded Months 309 309 309