Input contracts:
evaluateconsumes an evaluation panel with the four-column floor documented in Data schema.evaluate_horizonsconsumes a raw canonical panel withpriceand factor columns, then computesforward_returninternally for each horizon. Scalar post-processing helpers such asbreakeven_costandnet_spreadare direct-call utilities, notevaluate()metrics.
factrix.evaluate ¶
evaluate(data: DataInput, *, metrics: dict[str, MetricBase], factor_cols: list[str], forward_periods: int | None = None, strict: bool = True, expected_warnings: tuple[str, ...] = ()) -> dict[str, EvaluationResult]
Evaluate one or more factors against forward returns through the DAG executor.
Closed-set DAG dispatch — every spec referenced by another spec's
requires is auto-pulled into the executor, batched stage-1
producers run once across the whole factor batch (IC's
compute_ic etc.), and per-factor consumers run once per factor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataInput
|
Long-format data satisfying the four-column floor
|
required |
metrics
|
dict[str, MetricBase]
|
|
required |
factor_cols
|
list[str]
|
Names of factor columns on |
required |
forward_periods
|
int | None
|
The data's overlap horizon (rows of the time axis).
Normally omitted — :func: |
None
|
strict
|
bool
|
When |
True
|
expected_warnings
|
tuple[str, ...]
|
:class: |
()
|
Returns:
| Type | Description |
|---|---|
dict[str, EvaluationResult]
|
|
dict[str, EvaluationResult]
|
|
dict[str, EvaluationResult]
|
per-metric outputs, panel structural stats ( |
dict[str, EvaluationResult]
|
|
Raises:
| Type | Description |
|---|---|
UserInputError
|
|
Examples:
Single-factor IC + IC information ratio (IR):
>>> import factrix as fx
>>> from factrix.metrics import ic, ic_ir
>>> raw = fx.datasets.make_cs_panel(n_assets=15, n_dates=80)
>>> data = fx.preprocess.compute_forward_return(raw, forward_periods=5)
>>> results = fx.evaluate(
... data,
... metrics={"ic": ic(), "ic_ir": ic_ir()},
... factor_cols=["factor"],
... forward_periods=5,
... )
>>> "ic" in results["factor"].metrics
True
>>> "ic_ir" in results["factor"].metrics
True
factrix.evaluate_horizons ¶
evaluate_horizons(data: DataInput, *, metrics: dict[str, MetricBase], factor_cols: list[str], forward_periods: list[int], strict: bool = True, expected_warnings: tuple[str, ...] = ()) -> list[EvaluationResult]
Sweep evaluate across several overlap horizons of one raw panel.
A thin composition over the existing primitives — for each horizon it
rebuilds the panel with
:func:factrix.preprocess.compute_forward_return and runs a single
:func:evaluate, then flattens the per-factor results into one list.
No new type is introduced and the single-horizon contract of
evaluate is untouched: every inner run still evaluates one panel at
one stamped horizon.
The horizon must be rebuilt from the raw panel for each value —
compute_forward_return is not idempotent (it drops the last
forward_periods + 1 rows per asset and stamps the horizon), so a
horizon cannot be re-derived from an already-attached panel. This
wrapper exists to make that rebuild-per-horizon loop hard to get wrong.
Identity of a swept result is the composite (factor, forward_periods),
not a unique scalar factor key — so the return is a flat
list[EvaluationResult] (the native shape of the aggregation layer),
not the factor-keyed dict that evaluate returns at a fixed
horizon. factor and forward_periods are existing native
attributes of :class:EvaluationResult; the list feeds straight into
:func:compare and into
:func:factrix.multi_factor.bhy. Pool all horizons when selection may
choose across them; use expand_over=('forward_periods',) only for
predeclared horizon-specific screens that are selected and reported
separately.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataInput
|
A raw panel carrying |
required |
metrics
|
dict[str, MetricBase]
|
Same contract as :func: |
required |
factor_cols
|
list[str]
|
Same contract as :func: |
required |
forward_periods
|
list[int]
|
The horizons to sweep, as a non-empty |
required |
strict
|
bool
|
Forwarded unchanged to each inner :func: |
True
|
expected_warnings
|
tuple[str, ...]
|
Forwarded unchanged to each inner
:func: |
()
|
Returns:
| Type | Description |
|---|---|
list[EvaluationResult]
|
Flat |
list[EvaluationResult]
|
|
list[EvaluationResult]
|
|
Raises:
| Type | Description |
|---|---|
UserInputError
|
|
Notes
Comparability across horizons is a scale alignment, not a free
lunch: compute_forward_return divides by forward_periods so rank-IC is
directly comparable across horizons, but signed-return-mean metrics
carry a compounding bias that grows with forward_periods (see
:func:factrix.preprocess.compute_forward_return Notes). Treat a
cross-horizon sweep of signed-mean metrics as descriptive.
Examples:
>>> import factrix as fx
>>> from factrix.metrics import ic
>>> raw = fx.datasets.make_cs_panel(n_assets=20, n_dates=300)
>>> results = fx.evaluate_horizons(
... raw,
... metrics={"ic": ic()},
... factor_cols=["factor"],
... forward_periods=[5, 10, 20],
... )
>>> [r.forward_periods for r in results]
[5, 10, 20]
>>> board = fx.compare(results, metrics=["ic"]) # one row per horizon
>>> board.height
3
Use cases¶
-
Single-factor significance
One panel of factor data → one result carrying the mainstream metric's
p_valueand the cell-specific statistics. -
Batch screening with false discovery rate (FDR)
Loop
evaluateover candidate signal columns and feed the resultingEvaluationResultlist tobhyfor false-discovery-rate control. See Multi-factor FDR. -
Cross-cell apples-to-apples
Compare information coefficient (IC) rank-ordering against Fama-MacBeth λ on the same panel, or individual-asset factors against broadcast macro factors. Return shape is identical across cells.
-
TIMESERIES dispatch
At
n_assets == 1there is no cross-section, so anyDENSEmetric whose cell isPANEL—Individual × Continuous(ic,fm_beta) andCommon × Continuous(common_beta,common_quantile,common_asymmetry) — raisesIncompatibleAxisError(or NaN +structure_mismatchunderstrict=False). Single-asset data runs through the same entry point withpredictive_betafor dense predictive-regression slopes, sparse metrics whose cell wildcard allowsTIMESERIES, and panel-input wildcard metrics such asdirectional_hit_rate. Two-column diagnostics (positive_rate,oos_decay,ic_trend) are standalone(date, value)tools; inevaluate()they layer on panel IC series, not raw single-asset dense panels.
Worked example — single-factor smoke test¶
Synthetic panel → evaluate → read value + p_value
Full runnable example complementing the doctest snippets in Examples above with realistic console output.
import factrix as fx
from factrix.metrics import ic, quantile_spread
# 1. Create dummy panel data
raw = fx.datasets.make_cs_panel(n_assets=15, n_dates=80)
data = fx.preprocess.compute_forward_return(raw, forward_periods=5)
# 2. Run evaluation
results = fx.evaluate(
data,
metrics={"ic": ic(), "spread": quantile_spread(n_groups=5)},
factor_cols=["factor"],
forward_periods=5,
)
# 3. Retrieve and inspect results
res = results["factor"]
print(f"Factor: {res.factor}")
print(f"Cell: {res.cell}")
print(f"Plan: \n{res.plan}")
# Access metrics result group
ic_res = res.metrics["ic"]
print(f"IC Value: {ic_res.value:.4f}")
print(f"IC p-value: {ic_res.p_value:.4f}")
Sensitivity grids¶
For exploratory grids across asset counts, horizons, or factor families, run
with strict=False and stack each result's long-form table. The table carries
is_applicable and reason, so the grid can keep running while still making
failed metric/input combinations visible.
import polars as pl
results = fx.evaluate(
data,
metrics={"ic": ic(), "spread": quantile_spread(n_groups=2)},
factor_cols=["factor"],
forward_periods=5,
strict=False,
)
status = pl.concat([r.to_frame() for r in results.values()])
failed = status.filter(~pl.col("is_applicable"))
Evaluating under different cell contexts¶
Metric behaviors are defined by instantiating metric classes directly. The DAG executor handles dispatch automatically depending on the cell registered by the metric.
import factrix as fx
from factrix.metrics import ic, caar, common_beta
# 1. Individual × Continuous (e.g. Information Coefficient)
results_ic = fx.evaluate(
data,
metrics={"ic": ic()},
factor_cols=["factor"],
forward_periods=5
)
# 2. Individual × Sparse (e.g. Event Study CAAR, requires a 'price' column)
results_caar = fx.evaluate(
data_with_price,
metrics={"caar": caar()},
factor_cols=["event_factor"],
forward_periods=5
)
# 3. Common × Continuous (e.g. Time-Series Beta)
results_common_beta = fx.evaluate(
data,
metrics={"common_beta": common_beta()},
factor_cols=["macro_factor"],
forward_periods=5
)
Per-cell required / optional columns and the DataStructure (PANEL vs TIMESERIES) derivation are automatically resolved at dispatch time.
Next steps¶
-
Multi-factor FDR
Wires
evaluateinto the multi-factor FDR pipeline: pass candidate results to BHY; choose betweenbhy/partial_conjunction/bhy_hierarchical; mixed-cell batches. -
Data schema
New to the fixed-horizon input contract? Start here for the evaluation panel floor (
date,asset_id,factor,forward_return), dtype semantics, and optional columns that activate extra metrics.
See also¶
-
Timeseries-mode conventions
The
n_assets == 1dispatch rules and SE conventions for the per-asset time-series stage. -
Panel vs timeseries sample guard
Sample-size floors and the
InsufficientSampleErrorrecovery path.