factrix.metrics.ic ¶
IC (Information Coefficient) computation for cross-sectional panels.
Notes
Pipeline. Per-date Spearman rank IC (cross-section step) → IC time series, then non-overlapping cross-asset t or Newey-West (NW) heteroskedasticity-and-autocorrelation-consistent (HAC) t on its mean; the regime variant slices the same pipeline.
Input. DataFrame with date, asset_id, factor, forward_return.
Output. Time-indexed IC series (date, ic) that can be fed
into any series/ tool (oos, trend, significance, positive_rate).
factrix.metrics.ic.ic ¶
ic(ic_df: DataFrame, forward_periods: int = 5, inference: NonOverlapping | NeweyWest | StationaryBootstrap = NON_OVERLAPPING, expected_warnings: tuple[str, ...] = ()) -> MetricResult
Information coefficient (IC) mean significance: is mean IC significantly different from zero?
The periods floor is dynamic — the minimum input length scales with the forward_periods parameter and the inference method — so it is declared as a resolver (a callable sample_threshold) rather than a constant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ic_df
|
DataFrame
|
Output of |
required |
forward_periods
|
int
|
Overlap horizon of the forward returns; the non-overlapping stride and the HAC bandwidth floor both key off it. |
5
|
inference
|
NonOverlapping | NeweyWest | StationaryBootstrap
|
Significance-test method. |
NON_OVERLAPPING
|
Returns:
| Type | Description |
|---|---|
MetricResult
|
MetricResult with value=mean IC and the inference method's t/p. |
Notes
Given the per-date IC series \(\mathrm{IC}_t\), \(H_0:
\mathbb{E}[\mathrm{IC}] = 0\). The non-overlapping path strides the
series at forward_periods (discarding \(h-1\) of every \(h\)
observations) to avoid the lag floor implied by overlapping
forward returns; the Newey-West path keeps every observation and
absorbs the induced MA(\(h-1\)) autocorrelation through HAC standard
errors (Bartlett kernel, NW1994 auto-bandwidth floored at
\(h - 1\)).
References
Grinold 1989: IC as the canonical density-quality measure under the Fundamental Law of Active Management. Hansen-Hodrick 1980: K-period overlapping returns carry MA(K-1) autocorrelation — the motivation for the non-overlap stride used here.
Method selection
The default NON_OVERLAPPING path tests on roughly
n / forward_periods effective observations; when that
post-stride sample is thin it emits
WarningCode.UNRELIABLE_SE_SHORT_PERIODS (now surfaced on the
returned result's warning_codes). NEWEY_WEST keeps every
observation and absorbs the overlap-induced autocorrelation in the
HAC standard error, so on a thin series it retains more test power.
The guidance is one-directional: prefer NEWEY_WEST when the
non-overlapping effective sample is too thin; there is no symmetric
reason to switch back to non-overlapping once the sample is ample.
STATIONARY_BOOTSTRAP drops the HAC asymptotic-variance
assumption entirely in favour of a block-bootstrap empirical p —
prefer it when the IC series is heavy-tailed / skewed enough that
a HAC t-test's normal-approximation p-value is itself suspect, not
only when the sample is short. ic never changes inference
for you — the choice stays explicit.
Examples:
Chain from :func:compute_ic output:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.ic import compute_ic, ic
>>> panel = compute_forward_return(
... fx.datasets.make_cs_panel(n_assets=80, n_dates=180, seed=0),
... forward_periods=5,
... )
>>> ic_df = compute_ic(panel)["factor"]
>>> result = ic(ic_df, forward_periods=5)
>>> result.name == ""
True
factrix.metrics.ic.ic_ir ¶
ic_ir(ic_df: DataFrame, expected_warnings: tuple[str, ...] = ()) -> MetricResult
\(\mathrm{ICIR} = \mathrm{mean}(\mathrm{IC}) / \mathrm{std}(\mathrm{IC})\).
Signed ratio — positive when information coefficient (IC) is consistently positive, negative when consistently negative. Analogous to a Sharpe ratio for the factor density.
This is a descriptive statistic, not a hypothesis test (t_stat=None).
For significance testing, use ic().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ic_df
|
DataFrame
|
Output of |
required |
Returns:
| Type | Description |
|---|---|
MetricResult
|
MetricResult with value=IC_IR (signed), t_stat=None. |
Notes
\(\mathrm{ICIR} = \mathrm{mean}(\mathrm{IC}) / \mathrm{std}(\mathrm{IC})\)
over the per-date IC series — a Sharpe-style ratio describing
time-series stability of the density. Reported as a descriptive
statistic; no inference is attached because the significance test
on \(\mathrm{mean}(\mathrm{IC})\) lives in ic (optionally with
inference=fx.inference.NEWEY_WEST for the HAC-corrected SE).
References
Grinold 1989: ICIR is the time-stability normalisation that completes the IR decomposition.
Examples:
Chain from :func:compute_ic output:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.ic import compute_ic, ic_ir
>>> panel = compute_forward_return(
... fx.datasets.make_cs_panel(n_assets=80, n_dates=180, seed=0),
... forward_periods=5,
... )
>>> ic_df = compute_ic(panel)["factor"]
>>> result = ic_ir(ic_df)
>>> result.name == ""
True
Use cases¶
-
Compute per-date information coefficient (IC)
Build the per-date Spearman IC series (with
tie_ratiodiagnostics) from a long-format panel before any inferential test. Pre-step foric/ic_ir. -
Mean-IC significance, non-overlapping
Test \(H_0: \mathbb{E}[\mathrm{IC}] = 0\) on the every-
forward_periodssubsample to avoid the autocorrelation induced by overlapping forward returns. Default for the IC cell. -
Mean-IC significance, heteroskedasticity-and-autocorrelation-consistent (HAC)
Same null, but keep every overlapping observation and absorb the induced MA dependence through a Newey-West HAC standard error. Invoked via
ic(inference=fx.inference.NEWEY_WEST). -
IC stability (signed IR)
mean(IC) / std(IC)over the per-date series — a Sharpe-style descriptive statistic for signal time-stability. No inference attached.
Choosing a function¶
| Goal | Function |
|---|---|
| Per-date IC table for downstream inspection / slicing | compute_ic |
| Mean-IC significance (non-overlapping or Newey-West HAC) | ic |
| Time-stability ratio (no inference) | ic_ir |
All three are invoked indirectly via evaluate(data, metrics={"ic": ic()})
— they're documented here for callers who want the standalone numerical
output without the evaluation framing.
Worked example — per-date IC then mean significance¶
compute_ic → ic on a synthetic cross-sectional panel
import factrix as fx
from factrix.metrics.ic import compute_ic, ic
from factrix.preprocess import compute_forward_return
raw = fx.datasets.make_cs_panel(
n_assets=100, n_dates=500, ic_target=0.08, seed=2024,
)
panel = compute_forward_return(raw, forward_periods=5)
ic_df = compute_ic(panel)["factor"]
print(ic_df.head())
# ┌────────────┬───────────┬───────────┐
# │ date ┆ ic ┆ tie_ratio │
# ├────────────┼───────────┼───────────┤
# │ 2024-01-01 ┆ 0.083 ┆ 0.000 │
# │ 2024-01-02 ┆ 0.071 ┆ 0.000 │
# │ ... ┆ ... ┆ ... │
# └────────────┴───────────┴───────────┘
out = ic(ic_df, forward_periods=5, inference=fx.inference.NEWEY_WEST)
print(out.value, out.stat, out.p_value)
# 0.0722 14.60 2.13e-40
Cross-slice IC analysis
For per-slice IC summaries (regime / universe / sector / ...), use
by_slice on an IC frame joined with slice labels.
For inferential contrasts (pairwise Wald χ² + Holm / Romano-Wolf
adjusted p), use slice_pairwise_test. For a full
walk-through of regime-conditioned IC, see the
Slice analysis guide.
See also¶
-
by_slice
Axis-agnostic slice dispatcher for per-slice IC summaries.
-
slice_pairwise_test/slice_joint_test
Cross-slice inference (Wald χ² + Holm / Romano-Wolf adjusted p).
-
Slice analysis guide
Slicing and cross-slice inference end-to-end.
-
Metric applicability reference
When this metric applies and the sample-size guards that gate it.
-
Statistical methods
HAC SE, false discovery rate (FDR), robust-scale, unit-root disciplines that govern the inference.
-
Individual × Continuous landing
Adjacent metrics in the same cell.