Skip to content

factrix.list_metrics

list_metrics(scope: FactorScope, signal: Signal, *, format: Literal['text', 'json'] = 'text', with_import: bool = False) -> list[str] | list[dict[str, Any]]

Return standalone metrics applicable to (scope, signal).

Mode is intentionally not an input — applicability does not change across PANEL / TIMESERIES (per docs/reference/metric-applicability.md). Source of truth is the Matrix-row: tag in each metric module's docstring, parsed by :mod:factrix._metric_index.

Parameters:

Name Type Description Default
scope FactorScope

Cell axis to filter on (FactorScope.INDIVIDUAL or FactorScope.COMMON).

required
signal Signal

Cell axis to filter on (Signal.CONTINUOUS or Signal.SPARSE).

required
format Literal['text', 'json']

"text" (default) returns metric names sorted by (module, name). "json" returns list[dict] rows with keys name, module, cell, agg_order, inference_se, import_path, input_kind, docs_anchor, emitted_name — JSON-serialisable, suitable for tooling. docs_anchor follows :data:factrix._metric_index.DOCS_ANCHOR_FMT (a docs-root-relative path + mkdocstrings symbol fragment). emitted_name is the literal MetricOutput.name value at runtime — usually equal to name (the function name), but differs for a small set of historical exceptions. Consumers holding a MetricOutput should resolve via emitted_name.

'text'
with_import bool

"text" only. When True, returns a two-column "name → factrix.metrics.<module>" list so each row is copy-paste-ready into from factrix.metrics import <name>. Ignored under format="json" (the import_path field is always present there).

False

Raises:

Type Description
IncompatibleAxisError

(scope, signal) matches no registered metric. In practice all four combos are populated, so this is defensive.

Notes

Filter input_kind == "panel" on the JSON form to find candidates for the date-slicing dispatcher :func:factrix.by_slice; "scalar" rows (breakeven_cost, net_spread) consume pre-aggregated scalars and are not eligible.

Examples:

Discover standalone metrics for an INDIVIDUAL × CONTINUOUS cell:

>>> import factrix as fx
>>> names = fx.list_metrics(
...     fx.FactorScope.INDIVIDUAL, fx.Signal.CONTINUOUS,
... )

JSON form (for tooling — adds module / cell / import_path keys):

>>> rows = fx.list_metrics(
...     fx.FactorScope.INDIVIDUAL, fx.Signal.CONTINUOUS, format="json",
... )

Programmatic discovery of standalone metrics applicable to a given analysis cell. Complements describe_analysis_modes — that helper enumerates the primary procedure per cell (IC / FM / CAAR / TS-β); list_metrics returns the full set of standalone callables under factrix.metrics that the user can additionally invoke once evaluate() has produced a FactorProfile.

Mode axis is not an input

Mode is intentionally not a parameter — applicability does not change across PANEL / TIMESERIES (see Metric applicability for the underlying matrix). See the docstring Examples block above for the canonical text-list and JSON-form calls.

Discover-then-import workflow

Pass with_import=True to render a copy-paste-ready two-column view that pairs each metric with its submodule path:

print("\n".join(fx.list_metrics(
    fx.FactorScope.INDIVIDUAL, fx.Signal.CONTINUOUS, with_import=True,
)))
# ic                       → factrix.metrics.ic
# ic_ir                    → factrix.metrics.ic
# fama_macbeth             → factrix.metrics.fama_macbeth
# breakeven_cost           → factrix.metrics.tradability
# ...

Every name on the right is also re-exported from factrix.metrics, so the canonical wire-up is always:

from factrix.metrics import ic, fama_macbeth, breakeven_cost

The submodule path is shown so you know where the implementation lives — useful when reading source, jumping in an IDE, or checking the module-level Matrix-row: tag.

Structured output for tooling

fx.list_metrics(
    fx.FactorScope.INDIVIDUAL,
    fx.Signal.CONTINUOUS,
    format="json",
)
# -> [
#     {
#         "name": "ic",
#         "module": "ic",
#         "cell": "(INDIVIDUAL, CONTINUOUS, IC, PANEL)",
#         "agg_order": "cs-first",
#         "inference_se": "NW HAC / cross-asset t",
#         "import_path": "factrix.metrics.ic",
#         "input_kind": "panel",
#     },
#     ...
# ]

The JSON form is the single-source-of-truth row produced by parsing the Matrix-row: tag in each metric module's docstring (the same parser MkDocs uses to render the cross-metric matrix). Keys:

Key Meaning
name Function name as exported under factrix.metrics
module Submodule stem (e.g. ic, fama_macbeth)
cell Raw cell string from the Matrix-row: tag
agg_order Aggregation order (cs-first, ts-first, ts-only, static-cs, per-event)
inference_se Inference / SE method or no formal H₀ for descriptive metrics
import_path Fully-qualified submodule (factrix.metrics.<module>) — also re-exported from factrix.metrics
input_kind "panel" for the standard (date-keyed DataFrame, **kwargs) -> MetricOutput contract; "scalar" for pre-aggregated-scalar utilities

panel vs scalar — and why it matters

Most metrics take a date-keyed DataFrame as their first positional argument; a few (breakeven_cost, net_spread in factrix.metrics.tradability) consume pre-aggregated scalars (gross_spread: float, turnover: float, …) and return a MetricOutput directly. The date-slicing dispatcher by_slice only accepts the panel shape — there is no date column in a scalar to slice on.

Filter the JSON output to enumerate by_slice-eligible metrics:

panel_metrics = [
    r for r in fx.list_metrics(
        fx.FactorScope.INDIVIDUAL, fx.Signal.CONTINUOUS, format="json",
    )
    if r["input_kind"] == "panel"
]

Errors

IncompatibleAxisError is raised when (scope, signal) matches no registered metric. All four real combinations are populated, so this is defensive — surfaced for symmetry with the rest of the API.

Source of truth

list_metrics and the docs matrix share one parser (factrix._metric_index). Adding a new metric only requires adding a Matrix-row: tag to the new module's docstring; both surfaces pick it up automatically.