Cross-country sectoral equity strategy

Contents

Cross-country sectoral equity strategy #

This notebook offers the necessary code to replicate the research findings discussed in the corresponding Macrosynergy research post. Its primary objective is to inspire readers to explore and conduct additional investigations whilst also providing a foundation for testing their own unique ideas.

The notebook leverages nine sets of macro quantamental indicators to construct a relative signal for each of the 11 GICS sectors across 12 developed market equity markets. We use a benchmark signal in the form of conceptual risk parity score to show that a relatively simple yet robust statistical learning produces added value in this setting, when a set of economically plausible features is selected. Moreover, we show that a combination of the positions across sectors has superior performance vs the same strategy applied at country-index level.

The notebook covers the three main parts:

  • Get Packages and JPMaQS Data: This section is responsible for installing and importing the necessary Python packages used throughout the analysis.

  • Transformations and checks: This section computes the approproate quantamental information, builds the combined sector aggregates, applies imputation across countries, and construct relative country scores.

  • Value checks: This section builds statistical learning and conceptual parity signals across all sectors, and analyses the quality of the predictions as well as the PnL.

It is important to note that while the notebook covers a selection of indicators and strategies used for the post’s main findings, users can explore countless other possible indicators and approaches. Users can modify the code to test different hypotheses and strategies based on their research and ideas. Best of luck with your research!

Get packages and JPMaQS data #

This notebook primarily relies on the standard packages available in the Python data science stack. However, there is an additional package macrosynergy that is required for two purposes:

  • Downloading JPMaQS data: The macrosynergy package facilitates the retrieval of JPMaQS data, which is used in the notebook.

  • For the analysis of quantamental data and value propositions: The macrosynergy package provides functionality for performing quick analyses of quantamental data and exploring value propositions.

For detailed information and a comprehensive understanding of the macrosynergy package and its functionalities, please refer to the “Introduction to Macrosynergy package” notebook on the Macrosynergy Quantamental Academy or visit the following link on Kaggle .

import copy
import warnings
import os
from tqdm import tqdm
import gc

import numpy as np
import pandas as pd
from pandas import Timestamp
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.linear_model import LinearRegression, Ridge
from sklearn.ensemble import RandomForestRegressor
from sklearn.pipeline import Pipeline
from sklearn.metrics import make_scorer, mean_squared_error

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.download import JPMaQSDownload

warnings.simplefilter("ignore")

This notebook downloads selected indicators for the following cross-sections: AUD (Australian dollar), CAD (Canadian dollar), CHF (Swiss franc), EUR (euro), GBP (British pound), HKD (Hong Kong dollar), ILS (Israeli shekel), JPY (Japanese yen), NOK (Norwegian krone), NZD (New Zealand dollar), SEK (Swedish krona), SGD (Singapore dollar), USD (U.S. dollar) as well as five main European currencies [‘DEM’, ‘ESP’, ‘FRF’, ‘ITL’, ‘NLG’], replaced by EUR.

# Equity sectoral cross-section lists: excluding HKD
cids_dmeq = ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'ILS', 'JPY', 'NOK', 'NZD', 'SEK', 'SGD', 'USD']
cids_eueq = ['DEM', 'ESP', 'FRF', 'ITL', 'NLG']

cids = sorted(cids_dmeq + cids_eueq)
cids_eqx = sorted(list(set(cids_dmeq) - {'HKD'}))
sector_labels = {
    "ALL": "All sectors", 
    "COD": "Cons. discretionary",
    "COS": "Cons. staples",
    "CSR": "Communication services",
    "ENR": "Energy",
    "FIN": "Financials",
    "HLC": "Healthcare",
    "IND": "Industrials",
    "ITE": "Information tech",
    "MAT": "Materials",
    "REL": "Real estate",
    "UTL": "Utilities",
}
secx = list(sector_labels.keys())
secs = list(sector_labels.keys())[1:]

JPMaQS indicators are conveniently grouped into 6 main categories: Economic Trends, Macroeconomic balance sheets, Financial conditions, Shocks and risk measures, Stylized trading factors, and Generic returns. Each indicator has a separate page with notes, description, availability, statistical measures, and timelines for main currencies. The description of each JPMaQS category is available either under JPMorgan Markets (password protected) or the Macro Quantamental Academy .

In particular, the indicators used in this notebook can be found under consumer price inflation trends , labor market dynamics , demographic trends , real effective appreciation , terms of trade , manufacuring scores changes , external ratio trends , monetary aggregates , and private consumption trends .

# Category tickers

# Economic indicators

ecos_groups = {
    "XINF_NEG": [
        "CPIH_SA_P1M1ML12",
        "CPIH_SJA_P6M6ML6AR",
        "CPIC_SA_P1M1ML12",
        "CPIC_SJA_P6M6ML6AR",
        "INFTEFF_NSA",
    ],
    "LAB_SLACK": [
        "EMPL_NSA_P1M1ML12_3MMA",
        "EMPL_NSA_P1Q1QL4",
        "UNEMPLRATE_NSA_3MMA_D1M1ML12",
        "UNEMPLRATE_NSA_D1Q1QL4",
        "WFORCE_NSA_P1Y1YL1",
        "WFORCE_NSA_P1Q1QL4",
    ],
    "FX_DEPREC": [
        "REEROADJ_NSA_P1M1ML12",
        "NEEROADJ_NSA_P1M1ML12",
        "REER_NSA_P1M1ML12",
    ],
    "EASE_FIN": [
        "RIR_NSA",
    ],
    "CTOT_PCH": [
        "CTOT_NSA_P1M1ML12",
        "CTOT_NSA_P1M12ML1",
        "CTOT_NSA_P1W4WL1",
    ],
    "MCONF_CHG": [
        # Manufacturing confidence scores
        "MBCSCORE_SA_D3M3ML3",
        "MBCSCORE_SA_D6M6ML6",
    ],
    "MTB_CHG": [
        "MTBGDPRATIO_SA_3MMA_D1M1ML3",
        "MTBGDPRATIO_SA_6MMA_D1M1ML6",
    ],
    "MONEY_PCHG": [
        "MNARROW_SJA_P1M1ML12",
        "MBROAD_SJA_P1M1ML12",
    ],
    "CONS_NEG": [
        # retail sales
        "RRSALES_SA_P1M1ML12_3MMA",
        "RRSALES_SA_P1Q1QL4",
        # Real private consumption trend
        "RPCONS_SA_P1M1ML12_3MMA",
        "RPCONS_SA_P1Q1QL4",
    ],
}

main_ecos = [cat for cat_grp in ecos_groups.values() for cat in cat_grp]

# Complementary economic indicators
added_ecos = [
    "RGDP_SA_P1Q1QL4_20QMM",
    "INFE1Y_JA",
]

all_ecos = main_ecos + added_ecos

# Market indicators
eqrets = ["EQC" + sec + ret for sec in secx for ret in ["XR_NSA", "XR_VT10", "R_NSA", "R_VT10"]]
eqblack = ["EQC" + sec + "UNTRADABLE_NSA" for sec in secx]
bmrs = ["USD_EQXR_NSA", "USD_EQXR_VT10"]  # U.S. equity returns for correlation analysis
marks = eqrets + eqblack

# All indicators
xcats = all_ecos + marks

# Resultant tickers
tickers = [cid + "_" + xcat for cid in cids for xcat in xcats] + bmrs
print(f"Maximum number of tickers is {len(tickers)}")
Maximum number of tickers is 1532

The JPMaQS indicators we consider are downloaded using the J.P. Morgan Dataquery API interface within the macrosynergy package. This is done by specifying ticker strings, formed by appending an indicator category code to a currency area code <cross_section>. These constitute the main part of a full quantamental indicator ticker, taking the form DB(JPMAQS,<cross_section>_<category>,<info>) , where denotes the time series of information for the given cross-section and category. The following types of information are available:

value giving the latest available values for the indicator eop_lag referring to days elapsed since the end of the observation period mop_lag referring to the number of days elapsed since the mean observation period grade denoting a grade of the observation, giving a metric of real-time information quality.

After instantiating the JPMaQSDownload class within the macrosynergy.download module, one can use the download(tickers,start_date,metrics) method to easily download the necessary data, where tickers is an array of ticker strings, start_date is the first collection date to be considered and metrics is an array comprising the times series information to be downloaded. For more information see here .

client_id: str = os.getenv("DQ_CLIENT_ID")
client_secret: str = os.getenv("DQ_CLIENT_SECRET")

with JPMaQSDownload(oauth=True, client_id=client_id, client_secret=client_secret) as dq:
    assert dq.check_connection()
    df = dq.download(
        tickers=tickers,
        start_date="1990-01-01",
        suppress_warning=True,
        metrics=["value"],
        show_progress=True,
    )
    assert isinstance(df, pd.DataFrame) and not df.empty
Downloading data from JPMaQS.
Timestamp UTC:  2025-06-25 13:01:40
Connection successful!
Requesting data:  38%|███▊      | 29/77 [00:05<00:09,  4.94it/s]
Requesting data: 100%|██████████| 77/77 [00:16<00:00,  4.81it/s]
Downloading data: 100%|██████████| 77/77 [01:31<00:00,  1.19s/it]
Some expressions are missing from the downloaded data. Check logger output for complete list.
146 out of 1532 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()

Renaming and availability #

# Rename quarterly tickers to equivalent monthly tickers

dict_repl = {
    # labour
    "EMPL_NSA_P1Q1QL4": "EMPL_NSA_P1M1ML12_3MMA",
    "UNEMPLRATE_NSA_D1Q1QL4": "UNEMPLRATE_NSA_3MMA_D1M1ML12",
    "WFORCE_NSA_P1Y1YL1": "WFORCE_NSA_P1Q1QL4",
    # private consumption
    "RPCONS_SA_P1Q1QL4": "RPCONS_SA_P1M1ML12_3MMA",
    "RRSALES_SA_P1Q1QL4": "RRSALES_SA_P1M1ML12_3MMA",
}

for key, value in dict_repl.items():
    # Replace in dataframe
    dfx["xcat"] = dfx["xcat"].str.replace(key, value)

    # Remove quarterly categories in ecos_groups dictionary
    for grp_name, cat_grp in ecos_groups.items():
        if key in cat_grp:
            ecos_groups[grp_name] = list(set(cat_grp) - {key})
            
ecos = [cat for cat_grp in ecos_groups.values() for cat in cat_grp]
xcatx = list(set(ecos + added_ecos) - set(dict_repl.keys()))
cidx = cids_eqx

msm.check_availability(dfx, xcats=xcatx, cids=cidx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/2809c905cc82159a118cbabb7e9c16f01a20a30a7cb190b3e91d0ce6d1d5e126.png

Transformations and checks #

Directional factors #

Inflation shortfall #

# Negative of excess inflation

cidx = cids_eqx

cpi_cats = [
    "CPIH_SA_P1M1ML12",
    "CPIH_SJA_P6M6ML6AR",
    "CPIC_SA_P1M1ML12",
    "CPIC_SJA_P6M6ML6AR",
]
inf_calcs = {f"X{cpi_cat}_NEG": f"- {cpi_cat} + INFTEFF_NSA" for cpi_cat in cpi_cats}

dfa = msp.panel_calculator(
    dfx, calcs=[" = ".join([k, v]) for k, v in inf_calcs.items()], cids=cidx
)
dfx = msm.update_df(dfx, dfa)
# Combined excess CPI inflation indicators

cidx = cids_eqx
xcatx = list(inf_calcs.keys())


macro_cat = "XINF_NEG"
combined_macro = {}  # initiate summary dictionary for factors and constituents
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["XINF_NEG"] + ["XINF_NEG"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/7e5f9cdb922392bddb550faf93758bc39ec605f1c0290baec374368c8a14772c.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/bfa5d258929be000ed73cf43fef70596ad411c25c18e8dfe4b32947f814e8573.png

Labour market slackening #

cidx = cids_eqx

lab_calcs = {
     # Excess employment growth
    "XEMPL_NSA_P1M1ML12_3MMA_NEG": "- EMPL_NSA_P1M1ML12_3MMA + WFORCE_NSA_P1Q1QL4",
    "UNEMPLRATE_NSA_3MMA_D1M1ML12": "UNEMPLRATE_NSA_3MMA_D1M1ML12"
}
dfa = msp.panel_calculator(
    dfx, 
    calcs=[" = ".join([k, v]) for k, v in lab_calcs.items()], 
    cids=cidx
)

dfx = msm.update_df(dfx, dfa)
# Combined labor market indicator

cidx = cids_eqx
xcatx = list(lab_calcs.keys())

macro_cat = "LAB_SLACK"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["LAB_SLACK"] + ["LAB_SLACK"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/5cf5c390a9d40de5bc2b772032dfd78b3ac58b9d146a6afc1ab9d65870032b62.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/5d239cff9e8625e406b5480363484faac50a80a5b8d8e30f66dac3e75ad8aedc.png

Effective currency depreciation #

cidx = cids_eqx

fxd_calcs = {
    f"{depr}_NEG": f"- {depr}" for depr in ecos_groups["FX_DEPREC"]
}
dfa = msp.panel_calculator(
    dfx, 
    calcs=[" = ".join([k, v]) for k, v in fxd_calcs.items()], 
    cids=cidx
)

dfx = msm.update_df(dfx, dfa)
# Combined depreciation metric

cidx = cids_eqx
xcatx = list(fxd_calcs.keys())


macro_cat = "FX_DEPREC"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["FX_DEPREC"] + ["FX_DEPREC"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/f509e25db19d84110408ba7819bb35e7f05a2ff4de4453aa8004cc2ad44dfc7d.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/7632fd0653cad618cde97cde47c4656c761ec139347b804ee6f0ab13cb350aa3.png

Ease of local finance #

# Real interest rate levels and changes

cidx = cids_eqx

elf_calcs = {
    "XRIR_NSA_NEG": "- ( RIR_NSA - INFTEFF_NSA + INFE1Y_JA ) + RGDP_SA_P1Q1QL4_20QMM - WFORCE_NSA_P1Q1QL4",
    "XRIR_NSA_NEG_P1M1ML12": "XRIR_NSA_NEG.rolling(21).mean() - XRIR_NSA_NEG.rolling(21).mean().shift(262)",
}
dfa = msp.panel_calculator(
    dfx, 
    calcs=[" = ".join([k, v]) for k, v in elf_calcs.items()], 
    cids=cidx
)

dfx = msm.update_df(dfx, dfa)
# Combined excess CPI inflation indicators

cidx = cids_eqx
xcatx = list(elf_calcs.keys())


macro_cat = "EASE_FIN"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["EASE_FIN"] + ["EASE_FIN"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/ade43623fb0a6d194dfe8bf9a46c957f446f2d97d3803a42369a09851ded0d06.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/ebef17c3356372f99b40adeab657d8cb8e58700028c33019cc1d3da736007f7a.png

Terms of trade improvement #

# Winsorized terms-of-trade changes

tot_calcs = {
    "CTOT_NSA_P1M1ML12W10": "CTOT_NSA_P1M1ML12.clip(lower=-10, upper=10)",
    "CTOT_NSA_P1M12ML1ARW10": "( 2 * CTOT_NSA_P1M12ML1 ).clip(lower=-10, upper=10)",
}
dfa = msp.panel_calculator(
    dfx, 
    calcs=[" = ".join([k, v]) for k, v in tot_calcs.items()], 
    cids=cids
)

dfx = msm.update_df(dfx, dfa)
# Combined terms-of-trade dynamics

cidx = cids_eqx
xcatx = list(tot_calcs.keys())


macro_cat = "CTOT_PCH"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["CTOT_PCH"] + ["CTOT_PCH"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/814faa4cca2b50f73f10f5f71db4d97c1e14cf89e699fabc1ed4867cb2e55557.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/b8880cd9066eb84a17091826f2337477dfe5b786a6c696f9f2b2b0061fe86b6e.png

Manufacturing confidence improvement #

cidx = cids_eqx

man_calcs = {
     # Excess employment growth
    "MBCSCORE_SA_D3M3ML3AR": "4 * MBCSCORE_SA_D3M3ML3",
    "MBCSCORE_SA_D6M6ML6AR": "2 * MBCSCORE_SA_D6M6ML6"
}
dfa = msp.panel_calculator(
    dfx, 
    calcs=[" = ".join([k, v]) for k, v in man_calcs.items()], 
    cids=cidx
)

dfx = msm.update_df(dfx, dfa)
# Combined confidence increase indicator

cidx = cids_eqx
xcatx = list(man_calcs.keys())


macro_cat = "MCONF_CHG"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["MCONF_CHG"] + ["MCONF_CHG"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/b0c842249e3f7c16129cb03ed1f07114823aa7a66eb5dba9c55384026e342c64.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/c729a8f4e2465f3a73c9518161940799c2b7d0cc41ae618551b4bef6403ac47e.png

Trade balance improvement #

cidx = cids_eqx

mtb_calcs = {
    "MTBGDPRATIO_SA_3MMA_D1M1ML3AR": "4 * MTBGDPRATIO_SA_3MMA_D1M1ML3",
    "MTBGDPRATIO_SA_6MMA_D1M1ML6AR": "2 * MTBGDPRATIO_SA_6MMA_D1M1ML6"
}
dfa = msp.panel_calculator(
    dfx, 
    calcs=[" = ".join([k, v]) for k, v in mtb_calcs.items()], 
    cids=cidx
)

dfx = msm.update_df(dfx, dfa)
# Combined confidence increase indicator

cidx = cids_eqx
xcatx = list(mtb_calcs.keys())


macro_cat = "MTB_CHG"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["MTB_CHG"] + ["MTB_CHG"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/83abc35892d3e59f7768d59c3198fbc5bf204a1c474db94bd33cacb52eb89687.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/373247332ce87ce087b881c6f300b7f11042274c5a831fb310be64aaebd81d16.png

Money growth #

# Combined money growth

cidx = cids_eqx
xcatx = ecos_groups["MONEY_PCHG"]


macro_cat = "MONEY_PCHG"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["MONEY_PCHG"] + ["MONEY_PCHG"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/058d76ae5c3d8006b2a9a924cf57b47d9a85120174c3f33895b2dec7c8e34f3d.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/8779d01e886de2041aac4b076724f0f536f62ee8d870e3f1de43ed34fc637a80.png

Excess private consumption growth #

con_calcs = {
    "XRPCONS_SA_P1M1ML12_3MMA_NEG": "- RPCONS_SA_P1M1ML12_3MMA + RGDP_SA_P1Q1QL4_20QMM",
    "XRRSALES_SA_P1M1ML12_3MMA_NEG": "- RRSALES_SA_P1M1ML12_3MMA + RGDP_SA_P1Q1QL4_20QMM",
}
dfa = msp.panel_calculator(
    dfx, 
    calcs=[" = ".join([k, v]) for k, v in con_calcs.items()], 
    cids=cids
)

dfx = msm.update_df(dfx, dfa)
# Combined confidence increase indicator

cidx = cids_eqx
xcatx = list(con_calcs.keys())


macro_cat = "CONS_NEG"
combined_macro[macro_cat] = xcatx  # update summary dictionary

# combining the single indicators into a group-level indicator
dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat=macro_cat,
)
dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
xcatx = combined_macro["CONS_NEG"] + ["CONS_NEG"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    kind="bar",
)

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/e0cc292ecc7a150e52d51266f3c27c47b8be5e41fb1a9a8096c690f347273786.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/11b39bc219f302cf02d7a2924a61bd58b7cc341899331c64a088438b834c721a.png

Directional factors with imputations #

xcatx = [x for x in combined_macro.keys()]
msm.check_availability(dfx, xcats=xcatx, cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/9bc103f60070b2450f49b8b715b546442e0c122fb65560cc3c9404679287ac7a.png
# Set parameters
impute_missing_cids = True
min_ratio_cids = 0.5

cidx = cids_eqx
# Impute cross-sectional values if majority of cross sections are available
macro_xcatx = [x for x in combined_macro.keys()]
# Exclude categories than cannot logically be imputed
non_imputables = []
imputables = list(set(macro_xcatx) - set(non_imputables))

if impute_missing_cids:
    
    general_imputer = MeanPanelImputer(
        df=dfx,
        xcats=xcatx,
        cids=cidx,
        start="1990-01-01",
        end=dfx.real_date.max().strftime("%Y-%m-%d"),
        min_cids=round(min_ratio_cids * len(cidx)),        
        postfix="", # keeping the same category names
    )
    df_imputed = general_imputer.impute()
    dfx = msm.update_df(dfx, df_imputed)
macro_xcatx = [x for x in combined_macro.keys()]
msm.check_availability(dfx, xcats=macro_xcatx, cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/761074616e2dea234fedacd78060c9b6a113306e1d2d877f934e2d79443a7df2.png

Standardized relative factors #

xcatx = [x for x in combined_macro.keys()]
cidx = cids_eqx

dfa = pd.DataFrame(columns=dfx.columns)
for xcat in xcatx:
    dfaa = msp.make_relative_value(
        dfx,
        xcats=xcatx,
        cids=cidx,
        start="1990-01-01",
        rel_meth="subtract",
        postfix="vGLB",
        blacklist=None
    )  
    dfa = msm.update_df(dfa, dfaa)
    
dfx = msm.update_df(dfx, dfa)

relative_factors = list(dfa['xcat'].unique())
xcatx = relative_factors
cidx = cids_eqx

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

dfx = msm.update_df(dfx, dfa)

rn_factors = list(dfa['xcat'].unique())
# Labelling dictionary

rnf_labels = {
    "XINF_NEGvGLB_ZN": "Relative inflation shortfall",
    "LAB_SLACKvGLB_ZN": "Relative labour market slack",
    "FX_DEPRECvGLB_ZN": "Relative FX depreciation",
    "EASE_FINvGLB_ZN": "Relative real rates conditions",
    "CTOT_PCHvGLB_ZN": "Relative terms-of-trade changes",
    "MCONF_CHGvGLB_ZN": "Relative industry confidence change",
    "MTB_CHGvGLB_ZN": "Relative trade balance change",
    "MONEY_PCHGvGLB_ZN": "Relative money growth",
    "CONS_NEGvGLB_ZN": "Relative consumption shortfall",
}
cidx = cids_eqx
xcatx = ["XINF_NEGvGLB_ZN", "LAB_SLACKvGLB_ZN", "CONS_NEGvGLB_ZN"]

xcatx_labels = [rnf_labels[xc] for xc in xcatx]

msp.view_timelines(
    dfx,
    xcats=xcatx,
    xcat_labels=xcatx_labels,
    legend_fontsize=16,
    cids=cidx,
    title="Relative conceptual factor scores related to cost pressure and policy tightening (higher is presumed better)",
    title_fontsize=24,
    cumsum=False,
    ncol=4,
    same_y=True,
    aspect=1.4,
    size=(12, 7),
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/b804688ca939c90cfebf7fc06554126679fb71b0bc72a67e6ebe092cab8e305c.png
cidx = cids_eqx
xcatx =["FX_DEPRECvGLB_ZN", "EASE_FINvGLB_ZN", "MONEY_PCHGvGLB_ZN"]

xcatx_labels = [rnf_labels[xc] for xc in xcatx]

msp.view_timelines(
    dfx,
    xcats=xcatx,
    xcat_labels=xcatx_labels,
    legend_fontsize=16,
    cids=cidx,
    title="Relative conceptual factor scores related to monetary and financial conditions (higher is presumed better)",
    title_fontsize=24,
    cumsum=False,
    ncol=4,
    same_y=True,
    aspect=1.4,
    size=(12, 7),
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/24faa92b45e45ff1afe23e4fee463f31ae52f8c55e5fa3d37bd74ccf40226ee1.png
cidx = cids_eqx
xcatx =["MCONF_CHGvGLB_ZN", "CTOT_PCHvGLB_ZN", "MTB_CHGvGLB_ZN"]

xcatx_labels = [rnf_labels[xc] for xc in xcatx]

msp.view_timelines(
    dfx,
    xcats=xcatx,
    xcat_labels=xcatx_labels,
    legend_fontsize=16,
    cids=cidx,
    title="Relative conceptual factor scores related to competitiveness (higher is presumed better)",
    title_fontsize=24,
    cumsum=False,
    ncol=4,
    same_y=True,
    aspect=1.4,
    size=(12, 7),
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/bb45cd250fcc4ecaf0cc462665c299b2a1528832825d7710a82a4a8f3a7bc2e1.png

Conceptual parity signal #

xcatx = rn_factors
cidx = cids_eqx

dfa = msp.linear_composite(
    df=dfx,
    xcats=xcatx,
    cids=cidx,
    complete_xcats=False,
    new_xcat="MACROvGLB_ZN",
)
dfx = msm.update_df(dfx, dfa)
msp.view_timelines(
    dfx,
    xcats=["MACROvGLB_ZN"],
    cids=sorted(cidx),
    title=None,
    cumsum=False,
    ncol=4,
    same_y=True,
    size=(12, 7),
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/545e1d29b1e9756e7d80d5306435d3d860da95f2450c53bfd473d0606aa006c4.png
xcatx = rn_factors # + ["MACROvGLB_ZN"]
cidx = cids_eqx
xcatx_labels = [rnf_labels[xc][9:].capitalize() for xc in xcatx]

msp.correl_matrix(
    dfx,
    xcats=xcatx,
    xcat_labels=xcatx_labels,
    cids=cidx,
    title="Cross-sectional correlation matrix of relative conceptual factors based on 11 country panel since 2000",
    size=(16, 10),
    show=True,
    annot=True
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/754b86807eafe966a09e2bc5906e05a011e6808032960b4b45a79a9e0c291965.png

Target: Intra-sector cross-country relative returns #

secx = secs + ["ALL"]
xcatx = [f"EQC{sec}XR_{adj}" for sec in secx for adj in ["NSA", "VT10"]]
cidx = cids_eqx

dfa = msp.make_relative_value(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start="1990-01-01",
    rel_meth="subtract",
    postfix="vGLB",
)

dfx = msm.update_df(dfx, dfa)
cidx = cids_eqx
tss = ["IND", "FIN", "CSR"]
xcatx = [f"EQC{ts}XR_NSAvGLB" for ts in tss]

msp.view_timelines(
    dfx,
    xcats=xcatx,
    xcat_labels=[sector_labels[ts] for ts in tss],
    legend_fontsize=16,
    cids=cidx,
    title="Cumulative relative vol-targeted returns for three sectors (% for 10% ar target vol)",
    title_fontsize=24,
    cumsum=True,
    ncol=4,
    same_y=True,
    aspect=1.4,
    size=(12, 7),
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/9bdda6e9106e3d798f1fa57343a9ded863958e5552211375ad1da3508e988e82.png
cidx = cids_eqx
xcatx = f"EQCALLXR_NSAvGLB"

msp.view_timelines(
    dfx,
    xcats=xcatx,
    legend_fontsize=16,
    cids=cidx,
    title="Cumulative relative vol-targeted returns average for all sectors (% for 10% ar target vol)",
    title_fontsize=24,
    cumsum=True,
    ncol=4,
    same_y=True,
    aspect=1.4,
    size=(12, 7),
    all_xticks=False,
    xcat_grid=False,
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/8a03ca2088229c36b52aabd31489110c0ff1cec08a50e4656525056b5f511866.png

Sectoral return blacklisting #

Blacklisting is relevant when constructing the relative performance of sectoral indices across countries.

sector_blacklist = {}
for sec in secs:
    dfb = dfx[dfx["xcat"] == f"EQC{sec}UNTRADABLE_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"] = f"EQC{sec}BLACK"
    
    sector_blacklist[sec] = msp.make_blacklist(dfba, f"EQC{sec}BLACK")

Value checks #

Statistical learning parameters #

default_target_type = "XR_VT10vGLB" 

default_learn_config = {
    "scorer": {"negmse": make_scorer(mean_squared_error, greater_is_better=False)}, 
    "splitter": {"Expanding": msl.ExpandingKFoldPanelSplit(n_splits=3)},
     # retraining interval in months
    "test_size": 3,
    # minimum number of cids to start predicting
    "min_cids": 2,
    # minimum number of periods to start predicting
    "min_periods": 24,
    "split_functions": {"Expanding": lambda n: n // 24}
}
# List of dictionaries for two learning pipelines

learning_models = [
    {
        "ols": Pipeline(
          [
              ("predictor", msl.ModifiedLinearRegression(method = "analytic", fit_intercept=False)),
          ]
        ),
        "twls": Pipeline(
          [
              ("predictor", msl.ModifiedTimeWeightedLinearRegression(method = "analytic", fit_intercept=False)),
          ]
        ),        
    },
]


# Hyperparameter grid   
learning_grid = [
    {
        "ols": {
            "predictor__positive": [True, False],
        },
        "twls": {
           "predictor__positive": [True, False],
           "predictor__half_life": [12, 24, 36, 60],
        },

    },
]

# list of tuples containg both the model specification and the corresponding hyperparameter grid search
model_and_grids = list(zip(learning_models, learning_grid))
# Wrapper to avoid repetitive code below

def run_single_signal_optimizer(
    df: pd.DataFrame,
    xcats: list,
    cids: list,
    blacklist,
    signal_freq: str,
    signal_name: str,
    models: dict,
    hyperparameters: dict,
    learning_config: dict,
) -> tuple:
    """
    Wrapping function around msl.SignalOptimizer for a given set of models and hyperparameters
    
    """
    assert set(models.keys()) == set(hyperparameters.keys()), "The provided pair of model and grid names do not match."
    
    required_config = ["scorer", "splitter", "test_size", "min_cids", "min_periods", "split_functions"]
    assert all([learning_config.get(x, None) is not None for x in required_config])

    so = msl.SignalOptimizer(
        df=df,
        xcats=xcats,
        cids=cids,
        blacklist=blacklist,
        freq=signal_freq,
        lag=1,
        xcat_aggs=["last", "sum"],
    )
    so.calculate_predictions(
        name=signal_name,
        models=models,
        scorers=learning_config.get("scorer"),
        hyperparameters=hyperparameters,
        inner_splitters=learning_config.get("splitter"),
        test_size=learning_config.get("test_size"),
        min_cids=learning_config.get("min_cids"), 
        min_periods=learning_config.get("min_periods"),
        n_jobs_outer=-1,
        split_functions=learning_config.get("split_functions"),
    )
    # cleanup
    gc.collect()

    return so    

PnL generation parameters #

default_start_date = "2003-01-31"  # start date for the PnL analysis

Sector average #

Specify analysis #

sector = "ALL"

all_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": None,
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = all_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over ALL returns.
dix = all_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/83f954addf9c4868d563933761a4208590ffe645e6b6ab54ce287905d4597094.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/88be2f64d56201779c76db6b9d5d46535fd361b2adbafbca05d584b38b3e4dae.png

Signal quality check #

dix = all_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = all_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 8),
    title=f"{name} basket: macro signals and subsequent relative vol-targeted equity returns",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab="Composite macro factor score",
    ylab="Relative excess vol-targeted returns, local versus global",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=["Conceptual parity", "Statistical learning"],
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/850c41be37572677194daddaf1dfe60752406e60571426b324ad68ee2faabc88.png

Naive PnL #

dix = all_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = all_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} basket: naive PnLs of local positions versus global basket",
    xcat_labels=["Conceptual parity", "Statistical learning"],
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/87b4c7641ea375c5236ad7785cf66d19b50cbeae4c71dbe35eba792c18962236.png
xcat PNL_MACROvGLB_ZNALL_PZN PNL_OLS-TWLSALL_PZN
Return % 13.428011 13.959151
St. Dev. % 40.315002 36.858897
Sharpe Ratio 0.333077 0.378719
Sortino Ratio 0.474101 0.541732
Max 21-Day Draw % -29.51674 -45.429908
Max 6-Month Draw % -66.808246 -45.730189
Peak to Trough Draw % -130.831782 -82.983875
Top 5% Monthly PnL Share 1.009462 0.787044
USD_EQXR_NSA correl -0.034755 -0.014689
Traded Months 270 270

Energy sector #

Specify analysis #

sector = "ENR"

enr_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": list(set(cids_eqx) - {"CHF"}),
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = enr_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over ENR returns.
dix = enr_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/e393ed7a232fc6e98f0e085ee61e24cdc06b888bf0020c2caf1e995249000c03.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/0b4de11484c40a58e7bbd13d0199ec0c9be865b8ff5da1cc5394c9819a2ecae2.png

Signal quality check #

dix = enr_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = enr_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/c238a1710538e9cc1cbb4a235618f161dd748dd4e58466e20ab62217c77113a9.png

Naive PnL #

dix = enr_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = enr_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/42b96a6ddd767a315d3ea58a0250cd56d159616c3e651a61b5f44e554e8e5b66.png
xcat PNL_MACROvGLB_ZNENR_PZN PNL_OLS-TWLSENR_PZN
Return % 8.199527 13.03825
St. Dev. % 41.718803 49.259418
Sharpe Ratio 0.196543 0.264685
Sortino Ratio 0.275661 0.462764
Max 21-Day Draw % -56.583297 -73.22633
Max 6-Month Draw % -118.519606 -116.444803
Peak to Trough Draw % -207.41885 -148.610715
Top 5% Monthly PnL Share 1.681662 1.337034
USD_EQXR_NSA correl 0.002408 0.088484
Traded Months 270 270

Materials sector #

Specify analysis #

sector = "MAT"

mat_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = mat_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over MAT returns.
dix = mat_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/05627442520d912d224bda364b44eccb92afad8efef9641d7538251beac05669.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/1abb59ba9cab209e8166f29408829089d3876583fc3e554c1bee1a8d34a9ad5f.png

Signal quality check #

dix = mat_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = mat_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/55c69afa9634f25ca6c0b8cda58ea400cc1f2e2b426f4cce0eefb7a5b45c9f25.png

Naive PnL #

dix = mat_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = mat_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    xcat_labels=["Conceptual parity", "Statistical learning"],
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/40e61b022221eb7cb9587d786524a697a831ca0b0d5a77cd56bd638773be3cea.png
xcat PNL_MACROvGLB_ZNMAT_PZN PNL_OLS-TWLSMAT_PZN
Return % 8.984824 17.218912
St. Dev. % 42.265617 38.5298
Sharpe Ratio 0.21258 0.446899
Sortino Ratio 0.303836 0.644812
Max 21-Day Draw % -72.164209 -38.717062
Max 6-Month Draw % -98.873117 -53.893542
Peak to Trough Draw % -120.904296 -115.980527
Top 5% Monthly PnL Share 1.577782 0.793229
USD_EQXR_NSA correl -0.019187 -0.025388
Traded Months 270 270

Industrials sector #

Specify analysis #

sector = "IND"

ind_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = ind_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over IND returns.
dix = ind_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/1ffe61e50cfcc676b757072a48f628e989fb24fb9a92a93cdb24896c1534f253.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/b47126800cb2226bf3782bc411bfeba97e59347ca85cdda4756d1e691ddfb739.png

Signal quality check #

dix = ind_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = ind_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/1c9ccded56112f74a78fdffc74a4fcd8dcf3aef30e73fa08823711eb229c6452.png

Naive PnL #

dix = ind_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = ind_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/97eb5ce7227067801423f28c9f85a6de613793b66cf64ef88e0c7c75959c62f5.png
xcat PNL_MACROvGLB_ZNIND_PZN PNL_OLS-TWLSIND_PZN
Return % 13.403918 14.697856
St. Dev. % 42.25178 35.902524
Sharpe Ratio 0.317239 0.409382
Sortino Ratio 0.45406 0.595161
Max 21-Day Draw % -56.820359 -34.103984
Max 6-Month Draw % -80.728482 -45.824238
Peak to Trough Draw % -127.936523 -105.13389
Top 5% Monthly PnL Share 1.164493 0.819394
USD_EQXR_NSA correl -0.011215 -0.016097
Traded Months 270 270

Consumer discretionary sector #

Specify analysis #

sector = "COD"

cod_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = cod_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over COD returns.
dix = cod_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/ef69cb11c306704ddc7c0bd7a9296b5c2ae10b3a88b4e065718fb38b2e92313b.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/549a95b6e03840c386aaba64c45aac37b262a19a6dabe86f4374a91f39cdac76.png

Signal quality check #

dix = cod_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = cod_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/30ccd6ad2fedfeb6cec8cb5b784c4e286db17cd048f219135ae174632db5f304.png

Naive PnL #

dix = cod_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = cod_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/ff138dd6919d799222b18ba6b0c5b2fed9a552d19bfa6dee845ad85e6099c9e9.png
xcat PNL_MACROvGLB_ZNCOD_PZN PNL_OLS-TWLSCOD_PZN
Return % -3.634681 15.861081
St. Dev. % 43.407227 36.071948
Sharpe Ratio -0.083734 0.439707
Sortino Ratio -0.117468 0.626975
Max 21-Day Draw % -51.664769 -38.529648
Max 6-Month Draw % -76.637675 -58.975539
Peak to Trough Draw % -242.882791 -133.932291
Top 5% Monthly PnL Share -4.032316 0.84325
USD_EQXR_NSA correl -0.038254 -0.010886
Traded Months 270 270

Consumer staples sector #

Specify analysis #

sector = "COS"

cos_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = cos_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over COS returns.
dix = cos_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/df40621e9d5f7c74680fbf62bf24df6f59f2106ebd1fd2ce0b206630a594016a.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/a8aebe5c1b814038e1f1d00d8fec7a9226c4892d5db4551470695d0c1446ab00.png

Signal quality check #

dix = cos_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = cos_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/283263338518b57b33891d88d9612ec21412c29c6beabe830853b8c360c76116.png

Naive PnL #

dix = cos_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = cos_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    xcat_labels=["Conceptual parity", "Statistical learning"],
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/292178804fee3052c02c9acc91f2584b48b1af45ddb3c2ec373e0b84da1706c3.png
xcat PNL_MACROvGLB_ZNCOS_PZN PNL_OLS-TWLSCOS_PZN
Return % 14.114995 24.643291
St. Dev. % 44.830384 37.549116
Sharpe Ratio 0.314853 0.656295
Sortino Ratio 0.457285 0.979023
Max 21-Day Draw % -46.922105 -34.003514
Max 6-Month Draw % -84.576069 -55.596956
Peak to Trough Draw % -199.198396 -96.441888
Top 5% Monthly PnL Share 1.43204 0.62189
USD_EQXR_NSA correl -0.023089 0.008028
Traded Months 270 270

Healthcare sector #

Specify analysis #

sector = "HLC"

hlc_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = hlc_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over HLC returns.
dix = hlc_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/42b0cefce7a2b132fa8673be2bd4d2ffdb9ba7829f5d77e3539d25551e6cafed.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/6c9d7fd0a1c4e7feb51182efe5c1e186404ccaddfd807894c1d1ffa752e122a1.png

Signal quality check #

dix = hlc_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = hlc_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/032ff2c30edbd82c9606b29c255da1cf389d13193c3d80c23689e7a436af3f17.png

Naive PnL #

dix = hlc_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = hlc_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/2cefe98348e009861044a41c2ba9c47ceb7b1ae084893082401f9cbd5a42c3d9.png
xcat PNL_MACROvGLB_ZNHLC_PZN PNL_OLS-TWLSHLC_PZN
Return % 4.076964 3.279667
St. Dev. % 45.118469 45.347081
Sharpe Ratio 0.090361 0.072324
Sortino Ratio 0.128565 0.101055
Max 21-Day Draw % -53.510572 -63.550958
Max 6-Month Draw % -106.95642 -72.090941
Peak to Trough Draw % -280.158304 -152.849695
Top 5% Monthly PnL Share 4.536188 4.915592
USD_EQXR_NSA correl 0.006993 0.021717
Traded Months 270 270

Financials sector #

Specify analysis #

sector = "FIN"

fin_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = fin_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over FIN returns.
dix = fin_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/e316a7d5c701cb6d223ca23c1f12b6de6fb4ffb0fc681e5f7d4e17021b41ee0c.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/6acfbd91249fdc0824650ddee3565879b88d8bab4817a3d2b3d48cc158c4617f.png

Signal quality check #

dix = fin_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = fin_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/b6cdb77fe3f727e6f58c404bb6bb81a7a28ed4caa5a56a72506bc4b67ced9a2a.png

Naive PnL #

dix = fin_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = fin_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/24819b85633747c3f5323c592eeaf1031d3d34ffe1d0b4c3b7e4fe5cdfc1117e.png
xcat PNL_MACROvGLB_ZNFIN_PZN PNL_OLS-TWLSFIN_PZN
Return % 8.366293 11.303682
St. Dev. % 40.612126 33.766371
Sharpe Ratio 0.206005 0.334762
Sortino Ratio 0.29158 0.476841
Max 21-Day Draw % -37.889604 -43.916408
Max 6-Month Draw % -93.378595 -55.893943
Peak to Trough Draw % -113.713877 -72.083553
Top 5% Monthly PnL Share 1.547918 1.023057
USD_EQXR_NSA correl -0.043692 -0.012496
Traded Months 270 270

Technology sector #

Specify analysis #

sector = "ITE"

ite_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = ite_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over ITE returns.
dix = ite_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/566fbbf93873c2be3adc0ce3c060b92206c4d406cff3b67576dad0cc6ded875e.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/dc66557e470ab09eaf9a8659c11cd4a0f6cdd705f3a65051b00a7b0caf7f3ca5.png

Signal quality check #

dix = ite_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = ite_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/98faa2264c1e1ab7e9f289ae03a21d91a157d29c3d47005b23d7ea0a37576e91.png

Naive PnL #

dix = ite_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = ite_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/fab609ec32696a425c5a6968d4df91216b74dad9381edf08e1dfacb48b262480.png
xcat PNL_MACROvGLB_ZNITE_PZN PNL_OLS-TWLSITE_PZN
Return % -0.961242 2.124139
St. Dev. % 41.969935 34.059972
Sharpe Ratio -0.022903 0.062365
Sortino Ratio -0.032522 0.086907
Max 21-Day Draw % -49.270235 -44.539324
Max 6-Month Draw % -83.453984 -64.125697
Peak to Trough Draw % -168.026005 -147.32876
Top 5% Monthly PnL Share -16.451344 5.074857
USD_EQXR_NSA correl -0.024926 0.02115
Traded Months 270 270

Communication services sector #

Specify analysis #

sector = "CSR"

csr_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = csr_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over CSR returns.
dix = csr_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/371a6aef1c247b703b68d3b49a2e328ca3227395266dbf7106c7295400ae3cf3.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/a80b38f251a20158fd5cf8b07839626e51c90c5d4d75329aeab89a23566f03af.png

Signal quality check #

dix = csr_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = csr_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/cd79e1f461e8012278ff6b74717ca674e8e9e6886f7a785ec1f26f05e6ac046c.png

Naive PnL #

dix = csr_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = csr_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/507f6858f12c94ad347bc2903219721102b1234ef4a5a021d0baeecd124f10c9.png
xcat PNL_MACROvGLB_ZNCSR_PZN PNL_OLS-TWLSCSR_PZN
Return % 1.753256 4.114442
St. Dev. % 44.016648 48.271475
Sharpe Ratio 0.039832 0.085235
Sortino Ratio 0.05633 0.120035
Max 21-Day Draw % -44.925243 -56.577144
Max 6-Month Draw % -85.41486 -68.763017
Peak to Trough Draw % -208.643625 -175.251904
Top 5% Monthly PnL Share 8.826635 3.928669
USD_EQXR_NSA correl -0.012689 0.057995
Traded Months 270 270

Utilities sector #

Specify analysis #

sector = "UTL"

utl_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = utl_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over UTL returns.
dix = utl_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/25f2fd7befe9e9a20c0f84f82280fc81de4d16f337016c609ea5837928872a02.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/d364e7ebcc8e605714905e324f8a74b4c75ae6ab3553649dcc2dac17c30eda15.png

Signal quality check #

dix = utl_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = utl_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/dfe6f97d0eef41069d7884e54cbc99d6a1197a9e738f4dcdc844f022a0e320bc.png

Naive PnL #

dix = utl_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = utl_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    xcat_labels=["Conceptual parity", "Statistical learning"],
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/d612f69602958cf1e4279bfc3df573fbd5db44f0135972f65509874872a63d87.png
xcat PNL_MACROvGLB_ZNUTL_PZN PNL_OLS-TWLSUTL_PZN
Return % 2.345796 2.027715
St. Dev. % 43.699569 35.087138
Sharpe Ratio 0.05368 0.057791
Sortino Ratio 0.077177 0.082794
Max 21-Day Draw % -50.436456 -49.365197
Max 6-Month Draw % -110.802333 -67.6857
Peak to Trough Draw % -209.189339 -161.5625
Top 5% Monthly PnL Share 6.373579 5.885944
USD_EQXR_NSA correl 0.003395 0.019892
Traded Months 270 270

Real estate sector #

Specify analysis #

sector = "REL"

rel_dict = {
    "sec": sector,
    "name": sector_labels[sector],
    "factors": rn_factors,
    "cidx": cids_eqx,
    "ret": f"EQC{sector}{default_target_type}",
    "freq": "M",
    "black": sector_blacklist[sector],
    "models": None,
    "signals": None,
    "catregs": None,
    "pnls": None,
}

General learning models and signals #

dix = rel_dict

sec = dix["sec"]
factors = dix["factors"]
ret = dix["ret"]
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]

trained_models = {}
for pair in model_and_grids:
    
    model, grid = pair
    
    opt_pipeline_name = '-'.join(list(model.keys()))
    signal_name = opt_pipeline_name.upper() + sec.upper()
    
    print(
        f"Running the signal learning for {opt_pipeline_name} over {sec} returns."
    )
    
    trained_models[signal_name] = run_single_signal_optimizer(
        df=dfx,
        xcats=factors + [ret],
        cids=cidx,
        blacklist=blax,
        signal_freq=freq,
        signal_name=signal_name,
        models=model,
        hyperparameters=grid,
        learning_config=default_learn_config,
    )
    
    dfa = trained_models[signal_name].get_optimized_signals()
    dfx = msm.update_df(dfx, dfa)

dix["models"] = trained_models.values()
dix["signals"] = ["MACROvGLB_ZN"] + list(trained_models.keys())
Running the signal learning for ols-twls over REL returns.
dix = rel_dict

sec = dix["sec"]
trained_models = list(dix["models"])
sigx = dix["signals"][-1]

trained_models[0].models_heatmap(
    sigx,
    cap=10,
    figsize=(12, 5),
    title=f"{sector_labels[sec.upper()]} sector: OLS model selection heatmap",
)

trained_models[0].coefs_stackedbarplot(
    name=sigx,
    figsize=(12, 5),
    ftrs_renamed=rnf_labels,
    title=f"{sector_labels[sec.upper()]}: OLS annual averages of most important feature coefficients",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/20b4ca86064bd3ee0ca5c39109867c9eadf7a25de8b76cc092a9494b7e001367.png https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/49656bce4e3a6df23427d071e4a3fed06158696e39be1af46920e7c8d72bba9b.png

Signal quality check #

dix = rel_dict

cidx = dix["cidx"]
sec = dix["name"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

catregs = {
    x: msp.CategoryRelations(
        df=dfx,
        xcats=[x, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        blacklist=blax,
        xcat_aggs=["last", "sum"],
        slip=1,
    )
    for x in sigs
}

dix["catregs"] = catregs
dix = rel_dict
catregs = dix["catregs"]
sigs = dix["signals"]
name = dix["name"]

msv.multiple_reg_scatter(
    cat_rels=list(catregs.values()),
    ncol=2,
    nrow=1,
    figsize=(15, 6),
    title=name,
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab=None,
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=list(sigs),
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/e8e3e55040013f7707e5041122bfd7b24f45554533404e57a3c36caa64c5ceb5.png

Naive PnL #

dix = rel_dict

cidx = dix["cidx"]
sigs = dix["signals"]
ret = dix["ret"]
freq = dix["freq"]
blax = dix["black"]

pnl = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=sigs,
    cids=cidx,
    start=default_start_date,
    blacklist=blax,
    bms=["USD_EQXR_NSA"],
)

for sig in sigs:
    pnl.make_pnl(
        sig=sig,
        sig_op="zn_score_pan",
        rebal_freq="monthly",
        neutral="zero",
        rebal_slip=1,
        vol_scale = None,
        thresh=3,
        pnl_name=f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN",
    )

dix["pnls"] = pnl
dix = rel_dict

pnl = dix["pnls"]
name = dix["name"]
sigs = dix["signals"]
pns = [f"PNL_{sig}_PZN" if sig != "MACROvGLB_ZN" else f"PNL_{sig}{dix['sec']}_PZN" for sig in sigs]

pnl.plot_pnls(
    pnl_cats=pnl.pnl_names,
    title=f"{name} sector: naive PnLs of local positions versus global basket",
    title_fontsize=14
)

display(pnl.evaluate_pnls(pnl_cats=pnl.pnl_names))
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/8c3ee8ea9ae6b1f88a36c0f2878652d56def080658a841f581d2939ea9b4a76b.png
xcat PNL_MACROvGLB_ZNREL_PZN PNL_OLS-TWLSREL_PZN
Return % 5.576526 4.927557
St. Dev. % 42.525632 39.318262
Sharpe Ratio 0.131133 0.125325
Sortino Ratio 0.189045 0.179054
Max 21-Day Draw % -46.224732 -53.499191
Max 6-Month Draw % -77.170638 -60.404551
Peak to Trough Draw % -179.794348 -135.86673
Top 5% Monthly PnL Share 2.297708 2.693036
USD_EQXR_NSA correl -0.013788 0.030614
Traded Months 270 270

Combination of sector equity factors #

sectors_pnls = {
    # "all": all_dict["pnls"],
    "enr": enr_dict["pnls"], 
    "mat": mat_dict["pnls"],
    "ind": ind_dict["pnls"],
    "cod": cod_dict["pnls"],
    "cos": cos_dict["pnls"],
    "hlc": hlc_dict["pnls"],
    "fin": fin_dict["pnls"],
    "ite": ite_dict["pnls"],
    "csr": csr_dict["pnls"],
    "utl": utl_dict["pnls"],
    "rel": rel_dict["pnls"],
}

signal_families = {
    "OLS-TWLS": "statistical learning",
    "MACROvGLB_ZN": "conceptual risk parity",
}
multisignal_multisector_pnls = msn.MultiPnL()
multisignal_multisector_pnls.performance_summary = {}

for signal_type, signal_desc in signal_families.items():

    signal_family_xcats = []

    for sec, sec_pnl in sectors_pnls.items():

        # specifying the name of the PnL to import from the single sector PnL object into the multisector one
        single_pnl_xcats = [f"PNL_{signal_type}{sec.upper()}_PZN"]
        signal_family_xcats.extend(single_pnl_xcats)
        # Adding the PnL from the sector
        multisignal_multisector_pnls.add_pnl(sec_pnl, pnl_xcats=single_pnl_xcats)

    # computing the average across all sectors for this family of signals
    signal_family_combo = f"Average for {signal_desc} signals"
    multisignal_multisector_pnls.combine_pnls(
        pnl_xcats=signal_family_xcats,
        composite_pnl_xcat=signal_family_combo,
        weights=None,
    )
    signal_family_xcats.extend([signal_family_combo])
    
    # Calculating the return statistics
    multisignal_multisector_pnls.performance_summary[signal_type] = multisignal_multisector_pnls.evaluate_pnls(pnl_xcats=signal_family_xcats).rename(
        columns={
            f"PNL_{signal_type}{sec.upper()}_PZN/EQC{sec.upper()}XR_VT10vGLB": sector_labels[sec.upper()] for sec in secs
        }
    )
multisignal_multisector_pnls.plot_pnls(
    [
        f"Average for {signal_desc} signals"
        for signal_type, signal_desc in signal_families.items()
    ],
    title="Unweighted average of sectoral PnLs of local (vol-targeted) positions versus global basket",
)
https://macrosynergy.com/notebooks.build/trading-factors/cross-country-sectoral-equity-strategy/_images/20218f4483017ff14d25c2bf29d71a4b4cd15652008075a8d47c42392a83fd41.png
for signal_type, signal_desc in signal_families.items():

    summary_table = multisignal_multisector_pnls.performance_summary.get(signal_type).transpose().style.format("{:.2f}").set_caption(
        f"Naive PnL statistics for {signal_families[signal_type]} signals"
    ).set_table_styles(
        [
            {
                "selector": "caption", "props": [("text-align", "center"), ("font-weight", "bold"), ("font-size", "17px")]
            }
        ]
    )
    display(summary_table)
Naive PnL statistics for statistical learning signals
Return % St. Dev. % Sharpe Ratio Sortino Ratio Max 21-Day Draw % Max 6-Month Draw % Peak to Trough Draw % Top 5% Monthly PnL Share USD_EQXR_NSA correl Traded Months
Energy 13.04 49.26 0.26 0.46 -73.23 -116.44 -148.61 1.34 0.09 270.00
Materials 17.22 38.53 0.45 0.64 -38.72 -53.89 -115.98 0.79 -0.03 270.00
Industrials 14.70 35.90 0.41 0.60 -34.10 -45.82 -105.13 0.82 -0.02 270.00
Cons. discretionary 15.86 36.07 0.44 0.63 -38.53 -58.98 -133.93 0.84 -0.01 270.00
Cons. staples 24.64 37.55 0.66 0.98 -34.00 -55.60 -96.44 0.62 0.01 270.00
Healthcare 3.28 45.35 0.07 0.10 -63.55 -72.09 -152.85 4.92 0.02 270.00
Financials 11.30 33.77 0.33 0.48 -43.92 -55.89 -72.08 1.02 -0.01 270.00
Information tech 2.12 34.06 0.06 0.09 -44.54 -64.13 -147.33 5.07 0.02 270.00
Communication services 4.11 48.27 0.09 0.12 -56.58 -68.76 -175.25 3.93 0.06 270.00
Utilities 2.03 35.09 0.06 0.08 -49.37 -67.69 -161.56 5.89 0.02 270.00
Real estate 4.93 39.32 0.13 0.18 -53.50 -60.40 -135.87 2.69 0.03 270.00
Average for statistical learning signals 8.75 18.86 0.46 0.67 -22.13 -26.48 -41.26 0.76 nan 270.00
Naive PnL statistics for conceptual risk parity signals
Return % St. Dev. % Sharpe Ratio Sortino Ratio Max 21-Day Draw % Max 6-Month Draw % Peak to Trough Draw % Top 5% Monthly PnL Share USD_EQXR_NSA correl Traded Months
Energy 8.20 41.72 0.20 0.28 -56.58 -118.52 -207.42 1.68 0.00 270.00
Materials 8.98 42.27 0.21 0.30 -72.16 -98.87 -120.90 1.58 -0.02 270.00
Industrials 13.40 42.25 0.32 0.45 -56.82 -80.73 -127.94 1.16 -0.01 270.00
Cons. discretionary -3.63 43.41 -0.08 -0.12 -51.66 -76.64 -242.88 -4.03 -0.04 270.00
Cons. staples 14.11 44.83 0.31 0.46 -46.92 -84.58 -199.20 1.43 -0.02 270.00
Healthcare 4.08 45.12 0.09 0.13 -53.51 -106.96 -280.16 4.54 0.01 270.00
Financials 8.37 40.61 0.21 0.29 -37.89 -93.38 -113.71 1.55 -0.04 270.00
Information tech -0.96 41.97 -0.02 -0.03 -49.27 -83.45 -168.03 -16.45 -0.02 270.00
Communication services 1.75 44.02 0.04 0.06 -44.93 -85.41 -208.64 8.83 -0.01 270.00
Utilities 2.35 43.70 0.05 0.08 -50.44 -110.80 -209.19 6.37 0.00 270.00
Real estate 5.58 42.53 0.13 0.19 -46.22 -77.17 -179.79 2.30 -0.01 270.00
Average for conceptual risk parity signals 4.73 25.39 0.19 0.27 -22.20 -34.59 -108.67 1.67 nan 270.00