Sectoral equity factors #

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

On the back of our initial exploration of the relationship between macroeconomic trends and sectoral equity indices , we explore a comprehensive set of ideas for capital allocation across sectors within the same country.

In order to prevent potential CPU and RAM issues the user might have faced while running the analysis, we have decided to split the content in two blocks. You can find the initial set of data manipulation and visualisation in the notebook “Sectoral equity indicators”. This second notebook covers the following:

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

  • Value checks: This is the most critical section, where the notebook calculates and implements the trading strategies based on the hypotheses tested in the post. This section involves backtesting simple trading strategies.

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 numpy as np
import pandas as pd
from pandas import Timestamp
import matplotlib.pyplot as plt
import seaborn as sns

import copy
import warnings
import os
from IPython.display import HTML

from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import (make_scorer, mean_squared_error)

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

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.

# Commodity cross sections
comm_cids = [
    "ALM", "CPR", "LED", "NIC", "TIN", "ZNC",  # non-precious metals
    "BRT", "WTI", "NGS", "GSO", "HOL",  # energy
    "CLB"  # lumber
]

# 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_extra = []

cids = sorted(cids_dmeq + cids_extra)
cids_eqx = 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())

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, some of the indicators used in this notebook can be found in Consumer price inflation trends , Producer price inflation , Manufacturing confidence , Services confidence , Private consumption , Foreign trade trends , Labor market dynamics , Technical GDP Growth , and Industrial production trends .

# Economic indicators
manuf = [
    "MBCSCORE_SA",
    "MBCSCORE_SA_D3M3ML3",
    "MBCSCORE_SA_WG",
    "MBCSCORE_SA_D3M3ML3_WG",
    "XIP_SA_P1M1ML12_3MMA",
    "XIP_SA_P1M1ML12_3MMA_WG",
]
servi = [
    "SBCSCORE_SA",
    "SBCSCORE_SA_D3M3ML3",
    "SBCSCORE_SA_WG",
    "SBCSCORE_SA_D3M3ML3_WG",
]
constr = [
    "CBCSCORE_SA",
    "CBCSCORE_SA_D3M3ML3",
    "CBCSCORE_SA_WG",
    "CBCSCORE_SA_D3M3ML3_WG",
    "XCSTR_SA_P1M1ML12_3MMA",
    "XCSTR_SA_P1M1ML12_3MMA_WG", 
]
consu = [
    "CCSCORE_SA",
    "CCSCORE_SA_D3M3ML3",
    "CCSCORE_SA_WG",
    "CCSCORE_SA_D3M3ML3_WG",
    "XPCREDITBN_SJA_P1M1ML12",
    "XPCREDITBN_SJA_P1M1ML12_WG",    
    "XNRSALES_SA_P1M1ML12_3MMA",
    "XRRSALES_SA_P1M1ML12_3MMA",
    "XNRSALES_SA_P1M1ML12_3MMA_WG",
    "XRRSALES_SA_P1M1ML12_3MMA_WG", 
    "XRPCONS_SA_P1M1ML12_3MMA",
    "XRPCONS_SA_P1M1ML12_3MMA_WG",
]
cpi = [
    "XCPIC_SA_P1M1ML12",
    "XCPIH_SA_P1M1ML12",
    "XCPIE_SA_P1M1ML12",
    "XCPIF_SA_P1M1ML12",
    "XCPIE_SA_P1M1ML12_WG",
    "XCPIF_SA_P1M1ML12_WG",
]
ppi = [
    "XPPIH_NSA_P1M1ML12",
]

labour = [
    "UNEMPLRATE_NSA_3MMA_D1M1ML12",
    "UNEMPLRATE_SA_3MMAv5YMA",
    "UNEMPLRATE_NSA_3MMA_D1M1ML12_WG",
    "UNEMPLRATE_SA_3MMAv5YMA_WG",
    "XEMPL_NSA_P1M1ML12_3MMA",
    "XEMPL_NSA_P1M1ML12_3MMA_WG",
    "XRWAGES_NSA_P1M1ML12",
]
exps = [
    # Trade and competitiveness
    "XEXPORTS_SA_P1M1ML12_3MMA",
    
    "CXPI_NSA_P1M12ML1",
    "CMPI_NSA_P1M12ML1",
    "CTOT_NSA_P1M12ML1",
    "REEROADJ_NSA_P1M12ML1",
]

ecos = manuf + servi + consu + cpi + ppi + labour + exps + constr

# Complementary economic indicators
ecox = [
    "XRGDPTECH_SA_P1M1ML12_3MMA",
    "XRGDPTECH_SA_P1M1ML12_3MMA_WG",
    
    # Financial conditions
    "INTLIQGDP_NSA_D1M1ML1",
    "INTLIQGDP_NSA_D1M1ML6",
    "HHINTNETGDP_SA_D1M1ML12",    
    "HHINTNETGDP_SA_D1M1ML12_WG",
    "CORPINTNETGDP_SA_D1Q1QL4",
    "CORPINTNETGDP_SA_D1Q1QL4_WG",
    "XGGDGDPRATIOX10_NSA",

    # Financial markets
    "RIR_NSA",
    "RYLDIRS02Y_NSA",
    "RYLDIRS05Y_NSA",
    "RSLOPEMIDDLE_NSA",
    "BMLCOCRY_SAVT10_21DMA",
    "BMLXINVCSCORE_SA", 
    "REFIXINVCSCORE_SA", 
    "BASEXINVCSCORE_SA",
    "COXR_VT10vWTI_21DMA"
]

# Market indicators
eqrets = ["EQC" + sec + ret for sec in secx for ret in ["XR_NSA", "R_NSAvALL", "R_VT10vALL"]]
fxrets = []

marks = eqrets + fxrets

# All indicators
xcats = [x+suff for x in ecos + ecox for suff in ["_ZN", "_ZN_NEG"]] + marks

# Resultant tickers
bmk_tickers = ["USD_EQXR_NSA", "USD_EQXR_VT10"]
tickers = [
    cid + "_" + xcat 
    for cid in cids 
    for xcat in xcats
] + bmk_tickers
print(f"Maximum number of tickers is {len(tickers)}")
Maximum number of tickers is 1994
# Assumes the two notebooks are in the same folder
INPUT_PATH = os.path.join(os.getcwd(), "..\..\..\..\equity_sectoral_notebook_data.csv")

dfx = pd.read_csv(INPUT_PATH, index_col=0)
dfx["real_date"] = pd.to_datetime(dfx["real_date"]).dt.date

dfx = msm.utils.standardise_dataframe(dfx)
dfx = dfx.sort_values(["cid", "xcat", "real_date"])

Availability #

It is important to assess data availability before conducting any analysis. It allows identifying any potential gaps or limitations in the dataset, which can impact the validity and reliability of analysis and ensure that a sufficient number of observations for each selected category and cross-section is available as well as determining the appropriate time periods for analysis.

ecos_xq = [x + '_ZN' for x in ecos + ecox]
msm.missing_in_df(dfx, xcats=ecos_xq, cids=cids_eqx)
No missing XCATs across DataFrame.
Missing cids for BASEXINVCSCORE_SA_ZN:                []
Missing cids for BMLCOCRY_SAVT10_21DMA_ZN:            []
Missing cids for BMLXINVCSCORE_SA_ZN:                 []
Missing cids for CBCSCORE_SA_D3M3ML3_WG_ZN:           ['AUD', 'GBP', 'NOK', 'NZD', 'SGD']
Missing cids for CBCSCORE_SA_D3M3ML3_ZN:              ['AUD', 'GBP', 'NOK', 'NZD', 'SGD']
Missing cids for CBCSCORE_SA_WG_ZN:                   ['AUD', 'GBP', 'NOK', 'NZD', 'SGD']
Missing cids for CBCSCORE_SA_ZN:                      ['AUD', 'GBP', 'NOK', 'NZD', 'SGD']
Missing cids for CCSCORE_SA_D3M3ML3_WG_ZN:            ['SGD']
Missing cids for CCSCORE_SA_D3M3ML3_ZN:               ['SGD']
Missing cids for CCSCORE_SA_WG_ZN:                    ['SGD']
Missing cids for CCSCORE_SA_ZN:                       ['SGD']
Missing cids for CMPI_NSA_P1M12ML1_ZN:                []
Missing cids for CORPINTNETGDP_SA_D1Q1QL4_WG_ZN:      ['ILS', 'SEK', 'SGD']
Missing cids for CORPINTNETGDP_SA_D1Q1QL4_ZN:         ['ILS', 'SEK', 'SGD']
Missing cids for COXR_VT10vWTI_21DMA_ZN:              []
Missing cids for CTOT_NSA_P1M12ML1_ZN:                []
Missing cids for CXPI_NSA_P1M12ML1_ZN:                []
Missing cids for HHINTNETGDP_SA_D1M1ML12_WG_ZN:       ['ILS', 'SEK', 'SGD']
Missing cids for HHINTNETGDP_SA_D1M1ML12_ZN:          ['ILS', 'SEK', 'SGD']
Missing cids for INTLIQGDP_NSA_D1M1ML1_ZN:            []
Missing cids for INTLIQGDP_NSA_D1M1ML6_ZN:            []
Missing cids for MBCSCORE_SA_D3M3ML3_WG_ZN:           []
Missing cids for MBCSCORE_SA_D3M3ML3_ZN:              []
Missing cids for MBCSCORE_SA_WG_ZN:                   []
Missing cids for MBCSCORE_SA_ZN:                      []
Missing cids for REEROADJ_NSA_P1M12ML1_ZN:            []
Missing cids for REFIXINVCSCORE_SA_ZN:                []
Missing cids for RIR_NSA_ZN:                          []
Missing cids for RSLOPEMIDDLE_NSA_ZN:                 []
Missing cids for RYLDIRS02Y_NSA_ZN:                   []
Missing cids for RYLDIRS05Y_NSA_ZN:                   []
Missing cids for SBCSCORE_SA_D3M3ML3_WG_ZN:           ['NOK']
Missing cids for SBCSCORE_SA_D3M3ML3_ZN:              ['NOK']
Missing cids for SBCSCORE_SA_WG_ZN:                   ['NOK']
Missing cids for SBCSCORE_SA_ZN:                      ['NOK']
Missing cids for UNEMPLRATE_NSA_3MMA_D1M1ML12_WG_ZN:  []
Missing cids for UNEMPLRATE_NSA_3MMA_D1M1ML12_ZN:     []
Missing cids for UNEMPLRATE_SA_3MMAv5YMA_WG_ZN:       []
Missing cids for UNEMPLRATE_SA_3MMAv5YMA_ZN:          []
Missing cids for XCPIC_SA_P1M1ML12_ZN:                []
Missing cids for XCPIE_SA_P1M1ML12_WG_ZN:             ['NZD']
Missing cids for XCPIE_SA_P1M1ML12_ZN:                ['NZD']
Missing cids for XCPIF_SA_P1M1ML12_WG_ZN:             ['NZD']
Missing cids for XCPIF_SA_P1M1ML12_ZN:                ['NZD']
Missing cids for XCPIH_SA_P1M1ML12_ZN:                []
Missing cids for XCSTR_SA_P1M1ML12_3MMA_WG_ZN:        ['JPY']
Missing cids for XCSTR_SA_P1M1ML12_3MMA_ZN:           ['JPY']
Missing cids for XEMPL_NSA_P1M1ML12_3MMA_WG_ZN:       []
Missing cids for XEMPL_NSA_P1M1ML12_3MMA_ZN:          []
Missing cids for XEXPORTS_SA_P1M1ML12_3MMA_ZN:        []
Missing cids for XGGDGDPRATIOX10_NSA_ZN:              ['SGD']
Missing cids for XIP_SA_P1M1ML12_3MMA_WG_ZN:          []
Missing cids for XIP_SA_P1M1ML12_3MMA_ZN:             []
Missing cids for XNRSALES_SA_P1M1ML12_3MMA_WG_ZN:     []
Missing cids for XNRSALES_SA_P1M1ML12_3MMA_ZN:        []
Missing cids for XPCREDITBN_SJA_P1M1ML12_WG_ZN:       []
Missing cids for XPCREDITBN_SJA_P1M1ML12_ZN:          []
Missing cids for XPPIH_NSA_P1M1ML12_ZN:               []
Missing cids for XRGDPTECH_SA_P1M1ML12_3MMA_WG_ZN:    []
Missing cids for XRGDPTECH_SA_P1M1ML12_3MMA_ZN:       []
Missing cids for XRPCONS_SA_P1M1ML12_3MMA_WG_ZN:      []
Missing cids for XRPCONS_SA_P1M1ML12_3MMA_ZN:         []
Missing cids for XRRSALES_SA_P1M1ML12_3MMA_WG_ZN:     []
Missing cids for XRRSALES_SA_P1M1ML12_3MMA_ZN:        []
Missing cids for XRWAGES_NSA_P1M1ML12_ZN:             []
msm.check_availability(dfx, xcats=[x + '_ZN' for x in manuf], cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/49723a207ba2f2f32a436239802b586551afa049d42b237f76ea9caedb9c3f72.png
msm.check_availability(dfx, xcats=[x + '_ZN' for x in servi+consu], cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/a81d9afc162feb16ce3e776822fdf9cff0dcc30853aaccd19d694d9e85fa108f.png
msm.check_availability(dfx, xcats=[x + '_ZN' for x in constr], cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/a25a79873c41e0998d4f42586b7ce1b66da195b965398b1e4b02ebf8f562bf06.png
msm.check_availability(dfx, xcats=[x + '_ZN' for x in labour+exps], cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/b6e66180c27d01dbd789bef69e215b9404ea32393ec180bcb380b701741511fb.png
msm.check_availability(dfx, xcats=[x + '_ZN' for x in cpi+ppi], cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/8def2a3f5113aeb1f65a93f058528c342a1f867888cfed674b833c94a4d0b038.png
msm.check_availability(dfx, xcats=[x + '_ZN' for x in ecox], cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/60b4ed0ba3d2be42b74b841372953a624084aab51c86d21fd674b046e575e527.png
msm.check_availability(dfx, xcats=eqrets, cids=cids_eqx, missing_recent=False)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/aa35426b14e7a79fdcc53cf1dd419a679fe2916dc5d9f0ace60b556aa82c28ca.png

Sector return charts #

xcatx = [f'EQC{sec.upper()}R_NSAvALL' for sec in secx[1:]]
cidx = ["USD"]
start_date="2000-01-01"
 
msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title="Cumulative relative sectoral equity returns for the U.S. market",
    title_fontsize=27,
    cumsum=True,
    ncol=3,
    same_y=True,
    size=(12, 7),
    aspect=1.6,
    all_xticks=False,
    xcat_grid=True,
    xcat_labels=[sector_labels[key] for key in secx[1:]]
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/a0d4eccf47267b70313cd959b381989b9c7c0a4858c1d4d79e8ec8b3b4570522.png
xcatx = [f'EQC{sec.upper()}R_NSAvALL' for sec in secx[1:]]
cidx = ["EUR"]
start_date="2000-01-01"
 
msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title="Cumulative relative sectoral equity returns for the euro area",
    title_fontsize=27,
    cumsum=True,
    ncol=3,
    same_y=True,
    size=(12, 7),
    aspect=1.6,
    all_xticks=False,
    xcat_grid=True,
    xcat_labels=[sector_labels[key] for key in secx[1:]]
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/8bd062f87c20804f8b2026943199ad8257c62c58fdc6b0505625e04ae14d5270.png

Value checks #

Energy sector #

Factors and panel tests #

sector = "ENR"
label = "Energy"

sigs = {
    "XEXPORTS_SA_P1M1ML12_3MMA_ZN": "Excess export growth, local currency, %oya, 3mma",
    "XRGDPTECH_SA_P1M1ML12_3MMA_WG_ZN": "Excess GDP growth, %oya, 3mma, weighted global",
    "XIP_SA_P1M1ML12_3MMA_WG_ZN": "Excess industrial production growth, %oya, 3mma, weighted global",
    "XCPIE_SA_P1M1ML12_WG_ZN": "Excess energy CPI growth, %oya, weighted global",
    "BASEXINVCSCORE_SA_ZN_NEG": "Energy raw materials, inventory score, negative",
    "REEROADJ_NSA_P1M12ML1_ZN_NEG": "Real openness-adj. appreciation, % vs 12m, negative",

}

enr_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = enr_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
EQCENRR_NSAvALL misses: ['CHF'].
EQCENRR_NSAvALL misses: ['CHF'].
EQCENRR_NSAvALL misses: ['CHF'].
XCPIE_SA_P1M1ML12_WG_ZN misses: ['NZD'].
EQCENRR_NSAvALL misses: ['CHF'].
EQCENRR_NSAvALL misses: ['CHF'].
EQCENRR_NSAvALL misses: ['CHF'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/38078324bca8d760fce458b83c25187fdfb4b886338b5c893f45702826d08be5.png

Conceptual parity and panel test #

# Calculate conceptual parity average z-score

dix = enr_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = enr_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
EQCENRR_NSAvALL misses: ['CHF'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/fa520cee69ab5540e94268beff985d9f5aa1ce397afebaaff809a1bca7a8df7a.png

Conceptual parity and naive PnL #

dix = enr_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = enr_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/3f11ce4b0ccd014eccfd2ff08d6087c7b6c4de4d5ed5e48608c302882e90964b.png
xcat Long only PNL_ENR_AVZ
Return % 2.50 6.29
St. Dev. % 10.00 10.00
Sharpe Ratio 0.25 0.63
Sortino Ratio 0.36 0.89
Max 21-Day Draw % -20.46 -23.91
Max 6-Month Draw % -32.79 -29.49
Peak to Trough Draw % -100.81 -42.27
Top 5% Monthly PnL Share 1.97 0.89
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/0bc3cb026df85989fcef7292918ffdfc6c7ad1ec4e567072d5c1650209c3f93d.png

Materials sector #

Factors and panel tests #

sector = "MAT"
label = "Materials"

sigs = {
    "MBCSCORE_SA_WG_ZN": "Manufacturing confidence score, sa",
    "MBCSCORE_SA_D3M3ML3_WG_ZN": "Manufacturing confidence score, sa, diff 3m/3m",
    "XPPIH_NSA_P1M1ML12_ZN": "Excess producer prices growth, %oya",
    "CTOT_NSA_P1M12ML1_ZN": "Commodity-based terms of trade, % vs prev. 12 months",
    "REEROADJ_NSA_P1M12ML1_ZN_NEG": "Open.-adj. real appreciation, % vs prev. 12 months, negative",
    "COXR_VT10vWTI_21DMA_ZN": "Refined energy returns vs. crude returns, past month",
}

mat_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = mat_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/0b8d4bbf3854bd21ad5f302ce434d07e2419ead1403e67d6d24f996fac204f3c.png

Conceptual parity and panel tests #

# Calculate conceptual parity average z-score

dix = mat_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = mat_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/2c11de326517929c26acc7b1759e140956e55a508b6f6d97dace46f35b8866cb.png

Conceptual parity and naive PnL #

dix = mat_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = mat_dict
sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/38f9b426c65d6cc518b75c44227d8a2f48bc501df44afa72f64a3ad0cf365302.png
xcat Long only PNL_MAT_AVZ
Return % 0.20 2.47
St. Dev. % 10.00 10.00
Sharpe Ratio 0.02 0.25
Sortino Ratio 0.03 0.35
Max 21-Day Draw % -21.65 -14.67
Max 6-Month Draw % -40.34 -25.48
Peak to Trough Draw % -88.32 -38.39
Top 5% Monthly PnL Share 18.61 1.56
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/30ee72925b8bfa0ed27b4fcefc7c595974166efe1b5d43036d302c1fecea462d.png

Industrials sector #

Factors and panel tests #

sector = "IND"
label = "Industrials"

sigs = {
    "XEXPORTS_SA_P1M1ML12_3MMA_ZN": "Excess export growth, local currency, %oya, 3mma",
    "MBCSCORE_SA_WG_ZN": "Manufacturing confidence score, sa, weighted global",
    "MBCSCORE_SA_D3M3ML3_WG_ZN": "Manufacturing confidence score, diff 3m/3m, sar, weighted global",    
    "XCPIE_SA_P1M1ML12_ZN_NEG": "Excess energy CPI growth, %oya, negative",
    "REEROADJ_NSA_P1M12ML1_ZN_NEG": "Open.-adj. real appreciation, % vs prev. 12 months, negative",   
    "RYLDIRS05Y_NSA_ZN_NEG": "Real 5-year (IRS) interest rates, negative",
}

ind_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = ind_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
XCPIE_SA_P1M1ML12_ZN_NEG misses: ['NZD'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/c097c3f23c5267fba1773173d678b0414746b1188d2f61c2dabf11076d1741b2.png

Conceptual parity and panel tests #

# Calculate conceptual parity average z-score

dix = ind_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = ind_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/c4e9294dedb72f1ccf86c295785af88aacdaf06c72af34c97f1e7ce6cae65d11.png

Conceptual parity and naive PnL #

dix = ind_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = ind_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/6c495da58a7833c7095fb2ffed5997333a1e97b0d50d3e16459757748804b1e0.png
xcat Long only PNL_IND_AVZ
Return % 1.66 2.37
St. Dev. % 10.00 10.00
Sharpe Ratio 0.17 0.24
Sortino Ratio 0.23 0.34
Max 21-Day Draw % -18.72 -18.74
Max 6-Month Draw % -24.97 -30.14
Peak to Trough Draw % -40.24 -37.69
Top 5% Monthly PnL Share 2.37 1.85
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/244ffd7dc96bd0b82105ae22c0c25cca7daf2c5dc8eef7dd4ef0ee19284ffdeb.png

Consumer discretionary sector #

Factors and panel tests #

sector = "COD"
label = "Consumer discretionary"

sigs = {
    "CCSCORE_SA_D3M3ML3_WG_ZN": "Consumer confidence score, diff 3m/3m, sa, weighted global",
    "CCSCORE_SA_WG_ZN_NEG": "Consumer confidence, score, sa, negative, weighted global",
    "XRPCONS_SA_P1M1ML12_3MMA_WG_ZN_NEG": "Excess private consumption, %oya, 3mma, negative, weighted global",
    "XRRSALES_SA_P1M1ML12_3MMA_WG_ZN_NEG": "Excess real retail sales, %oya, 3mma, negative, weighted global", 
    "XCPIH_SA_P1M1ML12_ZN_NEG": "Excess CPI inflation, %oya, negative",
    "RYLDIRS02Y_NSA_ZN_NEG": "Real 5-year (IRS) interest rates, negative",  
}

cod_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = cod_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
CCSCORE_SA_D3M3ML3_WG_ZN misses: ['SGD'].
CCSCORE_SA_WG_ZN_NEG misses: ['SGD'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/c4ebd748f59e3f457d171cc881f1be14e8c4639e1ca7e5f022ec329b5f983d5f.png

Conceptual parity and panel tests #

# Calculate conceptual parity average z-score

dix = cod_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = cod_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/39a01dc916f69a077c21a46a5e1d926d7a9775f59c91c96328d4d25690006aef.png

Conceptual parity and naive PnL #

dix = cod_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = cod_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/a1ee4accf8f6b15e72b9d58579b18f25559f853d4c16082342252aa100c103fc.png
xcat Long only PNL_COD_AVZ
Return % -0.16 5.04
St. Dev. % 10.00 10.00
Sharpe Ratio -0.02 0.50
Sortino Ratio -0.02 0.74
Max 21-Day Draw % -28.20 -23.96
Max 6-Month Draw % -33.92 -28.01
Peak to Trough Draw % -74.75 -35.24
Top 5% Monthly PnL Share -23.99 0.98
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/c813dbdc08f439c80f653b80d85369ca05a15d4ea1d6ca7c8679ad146b8de941.png

Consumer staples sector #

Factors and panel tests #

sector = "COS"
label = "Consumer staples"

sigs = {
    "XEMPL_NSA_P1M1ML12_3MMA_ZN": "Excess employment growth, %oya, 3mma",
    "UNEMPLRATE_NSA_3MMA_D1M1ML12_ZN_NEG": "Unemployment rate, diff oya, 3mma, negative",
    "XNRSALES_SA_P1M1ML12_3MMA_ZN": "Excess nominal retail sales growth, %oya, 3mma",
    "XRWAGES_NSA_P1M1ML12_ZN": "Excess real wage growth, %oya",
    "XCPIF_SA_P1M1ML12_ZN": "Excess CPI food inflation, %oya",
    "RYLDIRS05Y_NSA_ZN": "Real 2-year (IRS) interest rates",
}

cos_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = cos_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
XCPIF_SA_P1M1ML12_ZN misses: ['NZD'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/50920b5509cfa63a81879853d174ce6c61b170dc8919036159e44aa0c70f2ae6.png

Conceptual parity and panel tests #

# Calculate conceptual parity average z-score

dix = cos_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = cos_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=dix["start"],
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns (2000-2024)",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/42fe3bdca491b4ea5863ba2d2c1fcffaa1ac6e660bab22cb296ee338b0eb4096.png

Conceptual parity and naive PnL #

dix = cos_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = cos_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/4c81845d7ff42f244e5e78d2246071dd68731d2f8dd0aefd96cf7b3b8f1e408a.png
xcat Long only PNL_COS_AVZ
Return % 1.90 5.97
St. Dev. % 10.00 10.00
Sharpe Ratio 0.19 0.60
Sortino Ratio 0.27 0.86
Max 21-Day Draw % -13.58 -21.58
Max 6-Month Draw % -25.49 -18.13
Peak to Trough Draw % -55.49 -33.32
Top 5% Monthly PnL Share 2.78 0.89
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/8862b3f49144a82958fc64d38724c2f25cd6228ef045b44abf5decb7412132a6.png

Healthcare sector #

Factors and panel tests #

sector = "HLC"
label = "Health care"

sigs = {
    "SBCSCORE_SA_ZN": "Services business confidence score, sa",
    "SBCSCORE_SA_D3M3ML3_ZN": "Services business confidence score, diff 3m/3m, sa",
    "XRGDPTECH_SA_P1M1ML12_3MMA_ZN": "Excess 'technical' GDP growth, %oya, 3mma",
    "CBCSCORE_SA_ZN": "Consumer confidence, score, sa",   
    "XCPIC_SA_P1M1ML12_ZN": "Excess core CPI inflation, %oya", 
    "RYLDIRS02Y_NSA_ZN": "Real 2-year (IRS) interest rates", # Comparatively vs other sectors
}

hlc_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = hlc_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
SBCSCORE_SA_ZN misses: ['NOK'].
SBCSCORE_SA_D3M3ML3_ZN misses: ['NOK'].
CBCSCORE_SA_ZN misses: ['AUD', 'GBP', 'NOK', 'NZD', 'SGD'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/a484f420a44d5e9212f51320b462522e0cdca6eadddf508484118a6f8aeaa71a.png

Conceptual parity and panel test #

# Calculate conceptual parity average z-score

dix = hlc_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = hlc_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/75ad63ca1ca037e7a784e2bb77f8149d9adbb4421b0228e7ee354d957556f689.png

Conceptual parity and naive PnL #

dix = hlc_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = hlc_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/61303cc9efa43f431f4154bfdf72928f101ce7a78939c7e10bd58aa19acdd307.png
xcat Long only PNL_HLC_AVZ
Return % 1.53 2.85
St. Dev. % 10.00 10.00
Sharpe Ratio 0.15 0.28
Sortino Ratio 0.22 0.42
Max 21-Day Draw % -16.61 -14.69
Max 6-Month Draw % -25.61 -19.55
Peak to Trough Draw % -48.87 -33.95
Top 5% Monthly PnL Share 2.96 1.87
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/84cf243e4cbd9d77c5e67c2752ff2d2853b15746c5ad27e7375b007ccfdb9d6c.png

Financial sector #

Factors and panel tests #

Specs and panel tests #

sector = "FIN"
label = "Financials"

sigs = {
    "XPCREDITBN_SJA_P1M1ML12_ZN": "Excess private credit growth, %oya",
    "UNEMPLRATE_SA_3MMAv5YMA_ZN": "Unemployment rate, sa, 3mma vs. prev. 5yma",
    "CORPINTNETGDP_SA_D1Q1QL4_ZN_NEG": "Corporate debt service ratio, % of GDP, diff oya, negative",
    "HHINTNETGDP_SA_D1M1ML12_ZN_NEG": "Household debt service ratio, % of GDP, diff oya, negative",
    "XGGDGDPRATIOX10_NSA_ZN_NEG": "Excess extrapol. government debt ratio, % of GDP, negative",
    "RSLOPEMIDDLE_NSA_ZN": "Real 5-year/2-year IRS yield spread, % ar",
}

fin_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = fin_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
CORPINTNETGDP_SA_D1Q1QL4_ZN_NEG misses: ['ILS', 'SEK', 'SGD'].
HHINTNETGDP_SA_D1M1ML12_ZN_NEG misses: ['ILS', 'SEK', 'SGD'].
XGGDGDPRATIOX10_NSA_ZN_NEG misses: ['SGD'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/3d0ae34080ce90fbc179809e0d33ca862baa823fbb88e229b8d1b1213daf19c7.png

Conceptual parity and panel test #

# Calculate conceptual parity average z-score

dix = fin_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = fin_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/b57d2c701f18b9952fbafc418535dd6748e4a54255887118e3a094fad2f100a2.png

Conceptual parity and naive PnL #

dix = fin_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = fin_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/59b2eddb0138dac4adf2015f837ee012481ba19b0586714f0b5b32cbaf631563.png
xcat Long only PNL_FIN_AVZ
Return % -0.10 3.49
St. Dev. % 10.00 10.00
Sharpe Ratio -0.01 0.35
Sortino Ratio -0.01 0.53
Max 21-Day Draw % -17.81 -17.86
Max 6-Month Draw % -36.88 -39.47
Peak to Trough Draw % -74.34 -51.15
Top 5% Monthly PnL Share -43.90 1.39
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/a510b6aced52145a6435916a8f5861af65442e432863e8a4aa5070c1d8104303.png

Information technology sector #

Factors and panel tests #

sector = "ITE"
label = "Information technology"

sigs = {
    "XRPCONS_SA_P1M1ML12_3MMA_WG_ZN_NEG": "Excess private consumption, %oya, 3mma, negative",
    "MBCSCORE_SA_WG_ZN_NEG": "Manufacturing confidence score, sa, negative",
    "MBCSCORE_SA_D3M3ML3_WG_ZN_NEG": "Manufacturing confidence score, diff 3m/3m, sa, negative",
    "SBCSCORE_SA_ZN_NEG": "Services business confidence score, sa, negative", 
    "XCPIH_SA_P1M1ML12_ZN_NEG": "Excess CPI inflation, %oya, negative",
    "RYLDIRS02Y_NSA_ZN_NEG": "Real 2-year (IRS) interest rates, negative",
}

ite_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = ite_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
SBCSCORE_SA_ZN_NEG misses: ['NOK'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/fc61b8e7a8d0d16f347a5059e0071dce7f677a3297f24bc6b8baa7cbddac4055.png

Conceptual parity and panel test #

# Calculate conceptual parity average z-score

dix = ite_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = ite_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/e2a54ca21cef0ed21b6f1643ca15d7d76dc664b1a88830b4be7c75eac9281355.png

Conceptual parity and naive PnL #

dix = ite_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = ite_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/3db4944a84bc5ae059b2cdbf2e1bb7546fbbdf8ab8c101c6cc32716b0a12e876.png
xcat Long only PNL_ITE_AVZ
Return % -2.14 5.86
St. Dev. % 10.00 10.00
Sharpe Ratio -0.21 0.59
Sortino Ratio -0.29 0.87
Max 21-Day Draw % -23.00 -21.14
Max 6-Month Draw % -52.52 -21.38
Peak to Trough Draw % -145.88 -30.17
Top 5% Monthly PnL Share -2.42 1.07
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/dfcd73a17201fcb362482ab60cfeca0b183efafae02a2f67fbbe255984ef8798.png

Communication services sector #

Factors and panel tests #

Specs and panel tests #

sector = "CSR"
label = "Communication services"

sigs = {
    "SBCSCORE_SA_ZN": "Services business confidence score, sa", 
    "SBCSCORE_SA_D3M3ML3_ZN": "Services confidence score, sa, diff 3m/3m",
    "UNEMPLRATE_NSA_3MMA_D1M1ML12_ZN_NEG": "Unemployment rate, diff oya, 3mma, negative",  
    "XCPIC_SA_P1M1ML12_ZN": "Excess core CPI inflation, %oya",
    "REEROADJ_NSA_P1M12ML1_ZN": "Open.-adj. real appreciation, % vs prev. 12 months",
    "RYLDIRS02Y_NSA_ZN_NEG": "Real 2-year (IRS) interest rates, negative",
}

csr_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = csr_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
SBCSCORE_SA_ZN misses: ['NOK'].
SBCSCORE_SA_D3M3ML3_ZN misses: ['NOK'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/af42eaae461fe5f129dfc5a754f92b8326a32163b067724626b7ed62cc68c248.png

Conceptual parity and panel test #

# Calculate conceptual parity average z-score

dix = csr_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = csr_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/8cca5a4d77c81452bb04d27453ab3192a9a596c8982adf22d02b4dbdc647d285.png

Conceptual parity and naive PnL #

dix = csr_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = csr_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/57fba2219cdc6aa8aa33d3301cb48f661bca417312bcea46b46fd1120e6692f4.png
xcat Long only PNL_CSR_AVZ
Return % -5.56 7.34
St. Dev. % 10.00 10.00
Sharpe Ratio -0.56 0.73
Sortino Ratio -0.78 1.08
Max 21-Day Draw % -21.63 -14.40
Max 6-Month Draw % -45.31 -16.44
Peak to Trough Draw % -166.15 -19.86
Top 5% Monthly PnL Share -0.76 0.65
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/de57bfac22b394416a937aede20773e9b0c9ff9a41de47f2654a7a9223ce5f42.png

Utilities sector #

Factors and panel tests #

sector = "UTL"
label = "Utilities"

sigs = {
    "XRGDPTECH_SA_P1M1ML12_3MMA_ZN": "Excess GDP growth (nowcast), %oya, 3mma",
    "XIP_SA_P1M1ML12_3MMA_ZN": 'Excess industrial production growth, %oya, 3mma',
    "MBCSCORE_SA_ZN": "Manufacturing confidence score, sa",
    "SBCSCORE_SA_ZN": "Services business confidence score, sa", 
    "CBCSCORE_SA_ZN": "Construction business confidence score, sal",
    "XCPIE_SA_P1M1ML12_ZN": "Excess energy CPI inflation, %oya",
}

utl_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = utl_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
SBCSCORE_SA_ZN misses: ['NOK'].
CBCSCORE_SA_ZN misses: ['AUD', 'GBP', 'NOK', 'NZD', 'SGD'].
XCPIE_SA_P1M1ML12_ZN misses: ['NZD'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/8bff85307bdfea83936c00309591cb087b21208792b3a00c0a1f0ca0b321dc92.png

Conceptual parity and panel test #

# Calculate conceptual parity average z-score

dix = utl_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = utl_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/371e3d6a2dbaeb0a6c5f61ab43fc64a28992237d6f9fb9f55af6018e9b1a58ce.png

Conceptual parity and naive PnL #

dix = utl_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = utl_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/9b1cd87c02310159621417aaf56d8933e951c858f737bd260a499b7f47a212c3.png
xcat Long only PNL_UTL_AVZ
Return % 2.40 4.45
St. Dev. % 10.00 10.00
Sharpe Ratio 0.24 0.45
Sortino Ratio 0.35 0.67
Max 21-Day Draw % -12.45 -12.81
Max 6-Month Draw % -25.69 -17.73
Peak to Trough Draw % -76.07 -36.77
Top 5% Monthly PnL Share 1.71 1.17
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/baa8065c9fa65f68c0c3d1802c296d59d0f4c7a3912406a5b90884175aaad8ed.png

Real estate sector #

Specs and panel tests #

sector = "REL"
label = "Real estate"

sigs = {
    "CBCSCORE_SA_ZN": "Construction business confidence score, sa",
    "CBCSCORE_SA_D3M3ML3_ZN": "Construction business confidence score, sa, diff 3m/3m",
    "SBCSCORE_SA_ZN": "Services business confidence score, sa",
    "SBCSCORE_SA_D3M3ML3_ZN": "Services business confidence score, sa, diff 3m/3m",
    "CCSCORE_SA_ZN": "Consumer confidence score, sa",
    'XCPIH_SA_P1M1ML12_ZN_NEG': "Excess CPI inflation, %oya, negative",
}

rel_dict = {
    "sname": label,
    "avz": f"{sector}_AVZ",
    "sigx": list(sigs.keys()),
    "signs": list(sigs.values()),
    "ret": f"EQC{sector}R_NSAvALL",
    "cidx": cids_eqx,
    "freq": "M",
    "start": "2000-01-01",
    "black": None,
    "srr": None,
    "pnls": None,
}
dix = rel_dict

sname = dix["sname"]
sigx = dix['sigx']
signs = dix["signs"]
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

catregs = {}
for sig in sigx:
    catregs[sig] = msp.CategoryRelations(
        dfx,
        xcats=[sig, ret],
        cids=cidx,
        freq=freq,
        lag=1,
        xcat_aggs=["last", "sum"],
        start=start,
    )

msv.multiple_reg_scatter(
    cat_rels=[v for k, v in catregs.items()],
    ncol=3,
    nrow=2,
    figsize=(16, 11),
    title=f"{sname}: factor candidates and subsequent relative sector returns, 12 markets, since 2000",
    title_xadj=0.5,
    title_yadj=0.99,
    title_fontsize=20,
    xlab=None,
    ylab="Return of local-currency sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    prob_est="map",
    single_chart=True,
    subplot_titles=signs,
)
CBCSCORE_SA_ZN misses: ['AUD', 'GBP', 'NOK', 'NZD', 'SGD'].
CBCSCORE_SA_D3M3ML3_ZN misses: ['AUD', 'GBP', 'NOK', 'NZD', 'SGD'].
SBCSCORE_SA_ZN misses: ['NOK'].
SBCSCORE_SA_D3M3ML3_ZN misses: ['NOK'].
CCSCORE_SA_ZN misses: ['SGD'].
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/9a8ac144be46afca9a3b9f4df24f9a8c58cc0c52975dec26ea81251b5f5fd5fc.png

Conceptual parity and panel test #

# Calculate conceptual parity average z-score

dix = rel_dict

sigx = dix['sigx']
cidx = dix["cidx"]
freq = dix["freq"]
start = dix["start"]

dfa = msp.linear_composite(
    df=dfx,
    xcats=sigx,
    complete_xcats=False,
    cids=cidx,
    new_xcat=dix['avz'],
    start=start,
)
dfx = msm.update_df(dfx, dfa)
dix = rel_dict

sname = dix["sname"]
avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
start = dix["start"]
freq = dix["freq"]

cr = msp.CategoryRelations(
    dfx,
    xcats=[avz, ret],
    cids=dix["cidx"],
    freq="M",
    lag=1,
    xcat_aggs=["last", "sum"],
    start=start,
)

cr.reg_scatter(
    title=f"{sname}: Conceptual parity score and subsequent relative sector returns since 2000",
    labels=False,
    prob_est="map",
    separator=2013,
    xlab="Conceptual parity score across all factors at month end",
    ylab="Return of sectoral equity index versus global average, %, next month",
    coef_box="lower right",
    size=(10, 7),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/5d05b57d7a7ed5ffb1dbb9739a1bf9e2a3185d4bf8f0d063c0b7d6515955591b.png

Conceptual parity and naive PnL #

dix = rel_dict

avz = dix['avz']
ret = dix['ret']
cidx = dix["cidx"]
freq = dix["freq"]
blax = dix["black"]
start = dix["start"]


pnls = msn.NaivePnL(
    df=dfx,
    ret=ret,
    sigs=[avz],
    cids=cidx,
    start=start,
    blacklist=blax,
)

pnls.make_pnl(
    sig=avz,
    sig_op="zn_score_pan",
    rebal_freq="monthly",
    neutral="zero",
    rebal_slip=1,
    vol_scale=10,
    thresh=2,
)
pnls.make_long_pnl(vol_scale=10, label="Long only")

dix["pnls"] = pnls
dix = rel_dict

sname = dix["sname"]
pnls = dix["pnls"]

pnls.plot_pnls( 
    title=f"{sname}: Naive PnLs of relative sectoral equity positions in 12 markets",
    title_fontsize=14,
    xcat_labels=["Conceptual parity signal", "Long versus all sectors"],
)

df_eval = pnls.evaluate_pnls(pnl_cats = [f"PNL_{avz}", "Long only"])
display(df_eval.astype("float").round(2))

pnls.signal_heatmap(
    pnl_name=f"PNL_{avz}",
    title=f"{sname}: Conceptual parity signals for relative sectoral positions",
    freq="q",
    start="2000-01-01",
    figsize=(12, 4),
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/44c8075f4cd5c3f183c356f2f1738d549eac0f2df0ca3b63ec9da59943371c2b.png
xcat Long only PNL_REL_AVZ
Return % 1.25 4.18
St. Dev. % 10.00 10.00
Sharpe Ratio 0.13 0.42
Sortino Ratio 0.17 0.61
Max 21-Day Draw % -20.81 -14.86
Max 6-Month Draw % -33.23 -15.07
Peak to Trough Draw % -68.96 -25.72
Top 5% Monthly PnL Share 3.11 1.13
Traded Months 294.00 294.00
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/c2b0d9878efeb4429dc57dbdaeb991c75bd3b2583d204fb88486c641250b35c8.png

Combination of sector equity factors #

secs = ["enr", "mat", "ind", "cod", "cos", "hlc", "fin", "ite", "csr", "utl", "rel"]
slabs =[]

ma_pnl = msn.MultiPnL()
for sec in secs:
    ma_pnl.add_pnl(globals()[f"{sec}_dict"]["pnls"], pnl_xcats=[f"PNL_{sec.upper()}_AVZ"])
    slabs += [globals()[f"{sec}_dict"]["sname"]]
ma_pnl.plot_pnls(
    pnl_xcats=[
        f"PNL_{sec.upper()}_AVZ" for sec in secs
    ],
    title="Naive PnLs for relative sector strategies, scaled to 10% annualized volatility",
    xcat_labels=slabs,
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/74073a1ce4156c17570767782d7e4ca5e6a5e35154139a7dc8fe1a92199d26fc.png
macro_sector_pnl = ma_pnl.combine_pnls(
    pnl_xcats=[f"PNL_{sec.upper()}_AVZ" for sec in secs],
    composite_pnl_xcat="Average of relative sector strategies",
    weights=None,
)
ma_pnl.plot_pnls(
    ["Average of relative sector strategies"],
    title="Combined naive PnL based on cross-sectoral allocation",
)
https://macrosynergy.com/notebooks.build/trading-factors/sectoral-equity-factors/_images/f6b0115604a0b906c6bacc77d1a687a1a0b3c8917c372037dd67fccbac253ab3.png
tbr = ma_pnl.evaluate_pnls()

dict_pnames = {
    f"PNL_{sec.upper()}_AVZ/EQC{sec.upper()}R_NSAvALL": sector_labels[sec.upper()] for sec in secs
}
tbr = tbr.iloc[[i for i in range(len(tbr)) if i != 1], :]
tbr.rename(columns=dict_pnames, inplace=True)

tbr = tbr.style.format("{:.2f}").set_caption(
    f"Naive PnL statistics for customised sector equity indices strategies").set_table_styles(
    [{"selector": "caption", "props": [("text-align", "center"), ("font-weight", "bold"), ("font-size", "17px")]}])

display(tbr)
Naive PnL statistics for customised sector equity indices strategies
Average of relative sector strategies Cons. discretionary Cons. staples Communication services Energy Financials Healthcare Industrials Information tech Materials Real estate Utilities
Return % 4.59 5.04 5.97 7.34 6.29 3.49 2.85 2.37 5.86 2.47 4.18 4.45
Sharpe Ratio 1.30 0.50 0.60 0.73 0.63 0.35 0.28 0.24 0.59 0.25 0.42 0.45
Sortino Ratio 1.99 0.74 0.86 1.08 0.89 0.53 0.42 0.34 0.87 0.35 0.61 0.67
Max 21-Day Draw % -4.76 -23.96 -21.58 -14.40 -23.91 -17.86 -14.69 -18.74 -21.14 -14.67 -14.86 -12.81
Max 6-Month Draw % -4.03 -28.01 -18.13 -16.44 -29.49 -39.47 -19.55 -30.14 -21.38 -25.48 -15.07 -17.73
Peak to Trough Draw % -8.42 -35.24 -33.32 -19.86 -42.27 -51.15 -33.95 -37.69 -30.17 -38.39 -25.72 -36.77
Top 5% Monthly PnL Share 0.48 0.98 0.89 0.65 0.89 1.39 1.87 1.85 1.07 1.56 1.13 1.17
Traded Months 294.00 294.00 294.00 294.00 294.00 294.00 294.00 294.00 294.00 294.00 294.00 294.00