Interaction Factors

Interaction Factors#

In this notebook we are going to compute industry-specific style factors. This is done leveraging the interaction factor specification. The use case that we have here is that we have a portfolio that holds a broad range of assets, but has a specific focus on one sector, for example the energy sector. The model that we will build has the traditional structure (Market, Industry, Style), but we want to split out the styles into styles that are non-energy and energy-specific.

Under the hood, think of the style exposures to SizeEnergy being the traditional Size exposures for an energy firm, and zero otherwise. On the other hand, the style exposures to SizeNonEnergy are zero for an energy firm, and the traditional Size exposures for anything else. We achieve this structure by multiplying a Energy / NonEnergy dummy variable by the styles.

from bayesline.api.equity import (
    ExposureSettings,
    FactorRiskModelSettings,
    ModelConstructionSettings,
    UniverseSettings,
    CategoricalExposureGroupSettings,
    ContinuousExposureGroupSettings,
    InteractionExposureGroupSettings,
    HierarchyGroups,
    CategoricalFilterSettings,
)
from bayesline.apiclient import BayeslineApiClient
bln = BayeslineApiClient.new_client(
    endpoint="https://[ENDPOINT]",
    api_key="[API-KEY]",
)

The crux is in the specification of the InteractionExposureGroupSettings. This special type of exposure group settings take in a list of exposure groups. The first creates the Energy / NotEnergy dummy. We do this by creating a custom grouping. While the grouping could a list of all industries that are not Energy, it is easier to define the complement using the ~ symbol. The second is simply the styles that we already have, we filter down the list a bit for display purposes.

factorriskmodel_settings = FactorRiskModelSettings(
    universe=UniverseSettings(
        dataset="Bayesline-US-All-1y",
        categorical_filters=[  # filter down the universe for display purposes
            CategoricalFilterSettings(
                hierarchy="trbc",
                include=["Energy", "Industrials", "Financials", "Utilities", "Healthcare"],
            )
        ],
    ),
    exposures=ExposureSettings(
        exposures=[
            ContinuousExposureGroupSettings(hierarchy="market"),
            CategoricalExposureGroupSettings(hierarchy="trbc"),
            InteractionExposureGroupSettings(
                exposure_groups=[
                    CategoricalExposureGroupSettings(
                        hierarchy=HierarchyGroups(
                            name="trbc", 
                            groupings={"Energy": ["Energy"], "NotEnergy": ["~Energy"]},
                        )
                    ),
                    ContinuousExposureGroupSettings(
                        hierarchy="style",
                        include=["Size", "Value", "Momentum"],
                    ),
                ],
            )
        ]
    ),
    modelconstruction=ModelConstructionSettings(
        estimation_universe=None,
        zero_sum_constraints={"trbc": "mcap_weighted"}
    ),
)
risk_model = bln.equity.riskmodels.load(factorriskmodel_settings).get_model()
risk_model.fret()
shape: (249, 13)
datemarket.Markettrbc.Energytrbc.Industrialstrbc.Financialstrbc.Healthcaretrbc.Utilitiestrbc:style.Energy:Sizetrbc:style.Energy:Valuetrbc:style.Energy:Momentumtrbc:style.NotEnergy:Sizetrbc:style.NotEnergy:Valuetrbc:style.NotEnergy:Momentum
datef32f32f32f32f32f32f32f32f32f32f32f32
2024-08-27-0.0066160.000144-0.0015390.001515-0.000426-0.005404-0.001859-0.0001060.0043730.001236-0.0019760.003978
2024-08-28-0.007884-0.001341-0.0015450.001812-0.001111-0.002003-0.0001070.0010450.0030170.0040410.0016230.00068
2024-08-290.0059140.0045150.002137-0.001566-0.000570.0004410.0013560.001469-0.001470.0005780.000499-0.001859
2024-08-300.003044-0.0059710.002869-0.0001430.00011-0.000450.0023350.0004760.0032480.0016880.0004830.001686
2024-09-03-0.023505-0.015439-0.010180.0062220.0005060.0100370.0032150.0002190.0110510.00379-0.0002750.000139
2025-08-19-0.00676-0.008143-0.0011160.002769-0.0044790.0058080.005024-0.0032030.0014490.0051560.001911-0.005334
2025-08-20-0.0021190.011205-0.0070350.0002010.003149-0.002061-0.000380.0000730.0038610.001648-0.0014620.002824
2025-08-210.0014340.00410.000223-0.0024380.004896-0.002414-0.0006060.0029710.005806-0.0024140.0001140.002047
2025-08-220.0238570.0082610.00669-0.001733-0.003819-0.00685-0.0044050.005008-0.014-0.0001030.004566-0.006411
2025-08-25-0.0070470.0071310.0007050.0019-0.007194-0.0034640.0013210.007022-0.001736-0.0004290.0017690.002211

Let’s look at the factors this created:

  • The market factor stems from the first block, called “market”.

  • The industry factors stem from second block, called “trbc”. We filtered the list down a bit in the universe settings.

  • The style factors are now stored in a “trbc:style” interaction block. This block can be renamed using the factor_group argument. Both the “Energy:Size” and “NotEnergy:Size” factors are present.