factrix.metrics.trend ¶
Information coefficient (IC) trend analysis using Theil-Sen estimator.
Theil-Sen is preferred over ordinary least squares (OLS) because it has a breakdown point of 29.3%, making it robust to outliers (e.g. COVID-era IC spikes).
Notes
Pipeline. Time-series only, Theil-Sen median pairwise slope on a 1-D series; CI from the rank-based pairwise slope distribution.
Input. DataFrame with date, value (typically an IC series).
Output. Slope + confidence interval for trend detection.
factrix.metrics.trend.ic_trend ¶
ic_trend(series: DataFrame, value_col: str = 'value', *, name: str = 'ic_trend', adf_threshold: float | None = 0.1) -> MetricOutput
Theil-Sen median slope of a time-indexed series.
Answers "is this factor getting better or worse over time?" - slope ≈ 0: stable - slope significantly < 0: decaying (crowding / alpha erosion) - slope significantly > 0: improving
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
series
|
DataFrame
|
DataFrame with |
required |
name
|
str
|
MetricOutput.name for the returned output. Defaults to
|
'ic_trend'
|
adf_threshold
|
float | None
|
Augmented Dickey-Fuller (ADF) p-value above which the input is flagged as
unit-root suspect. Default |
0.1
|
Returns:
| Type | Description |
|---|---|
MetricOutput
|
MetricOutput with value = slope, t_stat from Theil-Sen confidence interval. |
Notes
Theil-Sen median pairwise slope: slope = median{(y_j - y_i) /
(j - i) : i < j}. Approximate t-stat is reconstructed from the
rank-based 95% confidence interval [low, high]:
SE ≈ (high - low) / 2 / 1.96 and t ≈ slope / SE. An ADF
unit-root pre-check on the input flags series for which the slope
null is rejected at inflated rates regardless of the true trend.
factrix uses Theil-Sen rather than OLS because its 29.3% breakdown point absorbs information coefficient (IC) outliers (e.g. COVID-era spikes) that would dominate an OLS slope; the trade-off is the SE recovered from the rank-CI is approximate, not asymptotically exact.
References
Sen 1968: Theil-Sen median pairwise slope.
Lou-Polk 2022: momentum-crowding evidence
as one suggestive economic channel for time-varying IC;
McLean-Pontiff 2016 is the cleaner
cite for post-publication IC decay specifically.
Stock-Watson 1988: practitioner
unit-root background for the ADF persistence flag.
Dickey-Fuller 1979: ADF persistence
diagnostic on the input series.
MacKinnon 1996: ADF p-value response surface
used by _adf_pvalue_interp.
Examples:
Trend on the per-date IC series produced by
:func:~factrix.metrics.ic.compute_ic:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.ic import compute_ic
>>> from factrix.metrics.trend import ic_trend
>>> 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)
>>> result = ic_trend(ic_df, value_col="ic")
>>> result.name
'ic_trend'
Use cases¶
-
Detect alpha decay / crowding on an information coefficient (IC) series
ic_trendis a(*, CONTINUOUS, *, TIMESERIES)diagnostic — input is a 1-D(date, value)series, typically the per-date IC fromcompute_ic. Slope \(\approx 0\) = stable; slope significantly \(< 0\) = decaying (crowding / alpha erosion in the Lou-Polk 2022 sense); slope significantly \(> 0\) = improving. -
Outlier-robust slope via Theil-Sen
Median pairwise slope \(\mathrm{median}\{(y_j - y_i)/(j - i): i < j\}\) has a 29.3 % breakdown point — absorbs IC outliers (e.g. COVID- era spikes) that would dominate an ordinary least squares (OLS) slope. The trade-off is the SE recovered from the rank-CI is approximate, not asymptotically exact.
-
Unit-root pre-check on the input
adf_threshold(default 0.10, the Stock-Watson cutoff) drives an augmented Dickey-Fuller (ADF) persistence diagnostic on the input series. Above the cutoff the slope null is rejected at inflated rates regardless of the true trend; the slope value is still returned butmetadata["unit_root_suspected"] = Trueflags it for sceptical reading. Passadf_threshold=Noneto skip the check entirely. -
Reusable on any post-PANEL series
The
name=argument letsEventFactor.caar_trend/MacroPanelFactor.beta_trendpass their own primitive names so method / cache key / primitive name stay three-point unified — the same Theil-Sen primitive serves caar series, rolling-\(\beta\) series, and spread series, not just IC.
Worked example — IC series fed into ic_trend¶
compute_ic → ic_trend with ADF persistence check
import factrix as fx
from factrix.metrics.ic import compute_ic
from factrix.metrics.trend import ic_trend
from factrix.preprocess import compute_forward_return
raw = fx.datasets.make_cs_panel(
n_assets=100, n_dates=1000, ic_target=0.08, seed=2024,
)
panel = compute_forward_return(raw, forward_periods=5)
# The series diagnostic consumes (date, value); the value column on
# the compute_ic output is named ``ic``.
ic_df = compute_ic(panel)
out = ic_trend(ic_df, value_col="ic")
print(out.value, out.stat, out.metadata["p_value"])
# -3.2e-05 -0.91 0.36 (approximate; flat slope)
print(out.metadata["ci_low"], out.metadata["ci_high"],
out.metadata["ci_excludes_zero"])
# -1.0e-04 3.5e-05 False
print(out.metadata["adf_p"], out.metadata["unit_root_suspected"])
# 0.003 False (stationary; slope read is trustworthy)
See also¶
-
compute_ic/compute_spread_series
Canonical producers of the
(date, value)series this diagnostic consumes — IC series, spread series, or any other factor-mimicking- portfolio return series. -
hit_rate/oos
Sibling series diagnostics on the same input shape — sign significance and IS/out-of-sample (OOS) persistence.
-
compute_rolling_mean_beta
Common-factor source of a rolling-\(\beta\) series for stability trend analysis via the same primitive.
-
Statistical methods
Theil-Sen breakdown point, ADF persistence diagnostic (MacKinnon response surface), and the rank-CI to approximate-\(t\) recovery.
-
Metric applicability reference
When this metric applies and the sample-size guards that gate it (\(n \geq 10\)).
-
Series diagnostics landing
Adjacent axis-agnostic series diagnostics.