Skip to content

factrix.AnalysisConfig dataclass

AnalysisConfig(scope: FactorScope, signal: Signal, metric: Metric | None, forward_periods: int = 5, estimator: HACEstimator = NeweyWest(), moment_estimator: MomentEstimator | None = None)

Three-axis spec for a single-factor analysis.

Construct via the four factory methods (the supported public API); direct construction works but bypasses no validation — every path runs through __post_init__.

Attributes:

Name Type Description
scope FactorScope

Factor scope axis. INDIVIDUAL = per-asset factor; COMMON = single broadcast value per date.

signal Signal

Signal type axis. CONTINUOUS = real-valued; SPARSE = {-1, 0, +1} trigger.

metric Metric | None

Procedure metric axis. Only populated for (INDIVIDUAL, CONTINUOUS, *) cells (IC or FM); None elsewhere.

forward_periods int

Forward-return horizon in rows of the panel's time axis, not calendar time. factrix never inspects date dtype or spacing; the caller owns frequency and regular spacing. forward_periods=5 therefore means 5 trading days on a daily panel, 5 weeks on a weekly panel, 5 minutes on a 1-min bar panel.

factrix.AnalysisConfig.individual_continuous classmethod

individual_continuous(*, metric: Metric = IC, forward_periods: int = 5, estimator: HACEstimator | None = None, moment_estimator: MomentEstimator | None = None) -> Self

Per-(date, asset) continuous factor.

Parameters:

Name Type Description Default
metric Metric

IC for rank predictive ordering; FM for unit-of-exposure premium (Fama-MacBeth λ).

IC
forward_periods int

Forward-return horizon (rows of the time axis).

5
estimator HACEstimator | None

HACEstimator driving evaluate-time inference; None defaults to NeweyWest(). Pass HansenHodrick() to swap the rectangular-kernel path.

None

Returns:

Type Description
Self

A validated AnalysisConfig for the

Self

(INDIVIDUAL, CONTINUOUS, metric) cell.

Examples:

>>> import factrix as fx
>>> cfg = fx.AnalysisConfig.individual_continuous(forward_periods=10)
>>> cfg.scope is fx.FactorScope.INDIVIDUAL
True
>>> cfg.signal is fx.Signal.CONTINUOUS
True
>>> cfg.metric is fx.Metric.IC
True

Switch metric to Fama-MacBeth λ:

>>> cfg_fm = fx.AnalysisConfig.individual_continuous(metric=fx.Metric.FM)
>>> cfg_fm.metric is fx.Metric.FM
True

factrix.AnalysisConfig.individual_sparse classmethod

individual_sparse(*, forward_periods: int = 5, estimator: HACEstimator | None = None, moment_estimator: MomentEstimator | None = None) -> Self

Per-(date, asset) sparse trigger ({-1, 0, +1}).

PANEL canonical procedure is the CAAR cross-event t-test; TIMESERIES (N=1) collapses to a dummy regression with Newey-West (NW) heteroskedasticity-and-autocorrelation-consistent (HAC) SE.

Parameters:

Name Type Description Default
forward_periods int

Forward-return horizon (rows of the time axis).

5
estimator HACEstimator | None

HACEstimator driving evaluate-time inference; None defaults to NeweyWest().

None

Returns:

Type Description
Self

A validated AnalysisConfig for the

Self

(INDIVIDUAL, SPARSE, None) cell.

Examples:

>>> import factrix as fx
>>> cfg = fx.AnalysisConfig.individual_sparse(forward_periods=5)
>>> cfg.scope is fx.FactorScope.INDIVIDUAL
True
>>> cfg.signal is fx.Signal.SPARSE
True
>>> cfg.metric is None
True

factrix.AnalysisConfig.common_continuous classmethod

common_continuous(*, forward_periods: int = 5, estimator: HACEstimator | None = None, moment_estimator: MomentEstimator | None = None) -> Self

Broadcast continuous factor (e.g. VIX).

Canonical procedure is the per-asset β estimate followed by a cross-asset t-test on E[β].

Parameters:

Name Type Description Default
forward_periods int

Forward-return horizon (rows of the time axis).

5
estimator HACEstimator | None

HACEstimator driving evaluate-time inference; None defaults to NeweyWest().

None

Returns:

Type Description
Self

A validated AnalysisConfig for the

Self

(COMMON, CONTINUOUS, None) cell.

Examples:

>>> import factrix as fx
>>> cfg = fx.AnalysisConfig.common_continuous(forward_periods=5)
>>> cfg.scope is fx.FactorScope.COMMON
True
>>> cfg.signal is fx.Signal.CONTINUOUS
True
>>> cfg.metric is None
True

factrix.AnalysisConfig.common_sparse classmethod

common_sparse(*, forward_periods: int = 5, estimator: HACEstimator | None = None, moment_estimator: MomentEstimator | None = None) -> Self

Broadcast sparse trigger (FOMC, policy, index rebalance).

PANEL canonical: per-asset β on dummy + cross-asset t-test. TIMESERIES (N=1): TS dummy regression + Newey-West (NW) heteroskedasticity-and-autocorrelation-consistent (HAC) SE.

Parameters:

Name Type Description Default
forward_periods int

Forward-return horizon (rows of the time axis).

5
estimator HACEstimator | None

HACEstimator driving evaluate-time inference; None defaults to NeweyWest().

None

Returns:

Type Description
Self

A validated AnalysisConfig for the

Self

(COMMON, SPARSE, None) cell.

Examples:

>>> import factrix as fx
>>> cfg = fx.AnalysisConfig.common_sparse(forward_periods=5)
>>> cfg.scope is fx.FactorScope.COMMON
True
>>> cfg.signal is fx.Signal.SPARSE
True
>>> cfg.metric is None
True

factrix.AnalysisConfig.to_dict

to_dict() -> dict[str, Any]

Serialise to a JSON-compatible dict.

Returns:

Type Description
dict[str, Any]

A dict with string-valued enums and integer

dict[str, Any]

forward_periods, suitable for JSON serialisation.

factrix.AnalysisConfig.from_dict classmethod

from_dict(d: dict[str, Any]) -> Self

Reconstruct from to_dict's output.

Goes through __post_init__, so an invalid triple raises IncompatibleAxisError instead of silently constructing.

estimator is rehydrated via factrix.stats.get_estimator registry lookup; missing key falls back to the NeweyWest() default for forward compatibility with v0.11 serialized configs.

Parameters:

Name Type Description Default
d dict[str, Any]

Mapping in the shape produced by to_dict.

required

Returns:

Type Description
Self

A validated AnalysisConfig.

Raises:

Type Description
IncompatibleAxisError

If the (scope, signal, metric) triple is not a legal cell, or if the estimator is not applicable to the cell.

UnknownEstimatorError

If d["estimator"] is not a name in factrix.stats._ESTIMATOR_REGISTRY.


Use cases

  • Selecting a dispatch cell


    Pick the factory whose (scope, signal, metric) tuple matches your factor — see Choosing a factory below. Construct → pass to evaluate.

  • Switching inference within a cell


    Same factory, different estimator= (e.g. HansenHodrick() instead of the default NeweyWest()) to swap the heteroskedasticity-and-autocorrelation-consistent (HAC) kernel.

  • Persisting an analysis spec


    Use to_dict / from_dict to cache the config alongside results, or to keep a backtest reproducible after a code change.

  • Failing fast on illegal cells


    Every construction path runs __post_init__. An illegal (scope, signal, metric) triple raises IncompatibleAxisError at construction time, not at evaluate time.

Choosing a factory

Your factor Factory Resulting cell
Per-asset real-valued signal, want rank information coefficient (IC) individual_continuous(metric=Metric.IC) (INDIVIDUAL, CONTINUOUS, IC)
Per-asset real-valued signal, want FM λ premium individual_continuous(metric=Metric.FM) (INDIVIDUAL, CONTINUOUS, FM)
Per-asset {0, R} event trigger (event flag or signed magnitude) individual_sparse() (INDIVIDUAL, SPARSE, None)
Broadcast real-valued factor (e.g. VIX) common_continuous() (COMMON, CONTINUOUS, None)
Broadcast event dummy (FOMC, index rebalance) common_sparse() (COMMON, SPARSE, None)

Direct construction (AnalysisConfig(scope=..., signal=..., metric=...)) also works and runs the same validation, but the factories are the documented public surface. Bypassing them buys nothing.

Worked example — construct, evaluate, inspect the cell

Build a config, evaluate, read the cell tuple back from the profile

import factrix as fx

cfg = fx.AnalysisConfig.individual_continuous(
    metric=fx.Metric.IC, forward_periods=5,
)

print(cfg)
# AnalysisConfig(scope=<FactorScope.INDIVIDUAL>, signal=<Signal.CONTINUOUS>,
#                metric=<Metric.IC>, forward_periods=5, ...)

profile = fx.evaluate(panel, cfg)
print(profile.diagnose()["cell"])
# {'scope': 'individual', 'signal': 'continuous', 'metric': 'ic', 'mode': 'panel'}

Illegal cells fail at construction:

# (COMMON, SPARSE, IC) is not a registered cell — Metric.IC is only legal
# for INDIVIDUAL × CONTINUOUS.
fx.AnalysisConfig(
    scope=fx.FactorScope.COMMON,
    signal=fx.Signal.SPARSE,
    metric=fx.Metric.IC,
)
# IncompatibleAxisError: (common, sparse, ic) is not a legal analysis cell.
#   Use one of the four factory methods:
#     AnalysisConfig.individual_continuous(metric=Metric.IC|Metric.FM)
#     AnalysisConfig.individual_sparse()
#     AnalysisConfig.common_continuous()
#     AnalysisConfig.common_sparse()

Persistence — round-trip via dict

to_dict / from_dict exist for caching configs alongside results and keeping a backtest replayable across code revisions.

Serialise to JSON and rehydrate via from_dict

import json

cfg = fx.AnalysisConfig.individual_continuous(metric=fx.Metric.FM)

payload = cfg.to_dict()
# {'scope': 'individual', 'signal': 'continuous', 'metric': 'fm',
#  'forward_periods': 5, 'estimator': 'newey_west', 'moment_estimator': None}

with open("cfg.json", "w") as f:
    json.dump(payload, f)

# ... later, possibly a different process / commit ...

with open("cfg.json") as f:
    restored = fx.AnalysisConfig.from_dict(json.load(f))
assert restored == cfg

from_dict runs the same __post_init__ validation as the factories, so a tampered or stale payload raises IncompatibleAxisError or UnknownEstimatorError up front rather than failing silently later.

See also