factrix.metrics.monotonicity ¶
Monotonicity test for cross-sectional panels.
Measures whether factor quantile groups exhibit monotonic return ordering. Per-period: split into n_groups by factor rank, compute mean return per group, Spearman corr between group index and return.
Notes
Pipeline. Per-period Spearman corr between quantile index and group mean return (cross-section step), then non-overlapping cross-asset t on the per-period series.
Input. DataFrame with date, asset_id, factor, forward_return.
factrix.metrics.monotonicity.monotonicity ¶
monotonicity(data: DataFrame, overlap_periods: int = DEFAULT_FORWARD_PERIODS, n_groups: int = 10, factor_cols: Sequence[str] = ('factor',), return_col: str = 'forward_return', tie_policy: TiePolicy = 'ordinal', direction: MRDirection = 'increasing', n_resamples: int = 999, rng: Rng = None, *, expected_warnings: tuple[str, ...] = ()) -> dict[str, MetricResult]
Quantile return monotonicity — Patton-Timmermann (2010) MR test.
value / stat = the MR statistic J = min_i mean_t Delta_{i,t},
the smallest average adjacent bucket-return difference, in return units.
p_value is its stationary-bootstrap p.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Panel with |
required |
n_groups
|
int
|
Number of quantile groups (default 10 for a ~2000-name
universe). Use 5 for |
10
|
tie_policy
|
TiePolicy
|
Bucketing tie-break policy — the same knob and values as
|
'ordinal'
|
direction
|
MRDirection
|
Which monotone pattern H1 asserts — |
'increasing'
|
n_resamples
|
int
|
Bootstrap resamples for the MR null distribution. Must
be at least |
999
|
rng
|
Rng
|
Bootstrap seed — an |
None
|
expected_warnings
|
tuple[str, ...]
|
Warning codes the caller declares as part of the
study design. The structured record is retained; only the
per-call |
()
|
Returns:
| Type | Description |
|---|---|
dict[str, MetricResult]
|
MetricResult with |
dict[str, MetricResult]
|
|
dict[str, MetricResult]
|
stay in metadata. |
Notes
Per non-overlap date t, assets are bucketed into n_groups by
factor rank and each bucket's mean return is taken. The MR test then
works on the adjacent differences Delta_{i,t} = mu_{i,t} -
mu_{i-1,t}: J = min_i mean_t Delta_{i,t}, with
H0: min_i E[Delta_i] <= 0 ("the relation is not monotonically
increasing") against H1: min_i E[Delta_i] > 0. The null
distribution comes from recentring the per-period difference matrix at
zero — the least-favourable configuration under H0 — and resampling
whole blocks of periods with the stationary bootstrap, which keeps both
the within-period cross-bucket dependence and the serial dependence
across periods. This is the test Patton-Timmermann (2010) actually
propose, and the one this metric previously cited without implementing.
Why the headline is no longer mean |Spearman|. mean_t |rho_t|
has a large null floor that depends on n_groups: on a factor drawn
independently of returns (T=400, N=100) it reads 0.66 at
n_groups=3, 0.43 at 5 and 0.27 at 10, because E|rho| > 0 under
H0 by Jensen's inequality. A reader seeing "value = 0.43,
Patton-Timmermann (2010)" took it as MR evidence when it was the noise
floor for five buckets. The Spearman summaries remain in metadata as
descriptive shape statistics —
mean_abs_spearman (magnitude, always >= 0) and mean_signed
(direction consistency) — where a high magnitude with a near-zero
signed mean still tells the useful story that the factor sorts returns
but flips sign across dates.
The secondary signed-Spearman t-test remains two-sided: it asks whether
the mean signed correlation differs from zero in either direction. Its
signed_spearman_alternative metadata labels that tail explicitly,
because the headline is a different, one-sided MR hypothesis whose
alternative="greater" must not be borrowed for this p-value.
Deviation from the paper. Patton-Timmermann bootstrap the raw (unstudentised) differences, which is what runs here. Their studentised variant is not implemented.
If the post-stride cross-section cannot fill n_groups, the
short-circuit result reports n_obs as the median finite factor
cross-section on that same non-overlapping sample. It does not report
the raw-panel median across periods that the buckets never use.
Examples:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.monotonicity import monotonicity
>>> panel = compute_forward_return(
... fx.datasets.make_cs_panel(n_assets=200, n_dates=180, rng=0),
... forward_periods=5,
... )
>>> result = monotonicity(
... panel, overlap_periods=5, n_groups=5, n_resamples=199, rng=0
... )
>>> result["factor"].name == ""
True
Use cases¶
-
Decile-curve monotonicity test
The Patton-Timmermann (2010) MR test on the quantile-bucket return curve: \(J = \min_i \overline{\Delta}_i\) over the adjacent bucket differences, with \(H_0\) that the relation is not monotone and a stationary-bootstrap p. A strictly increasing decile curve is a stronger requirement than a positive long-short spread, which only needs the two end buckets to separate.
-
Magnitude vs direction, as descriptive shape
metadata["mean_abs_spearman"](magnitude, \(\geq 0\)) andmetadata["mean_signed"](direction consistency) still read separately: high magnitude with a near-zero signed mean flags a factor that sorts returns but flips direction across dates. They are metadata, not the headline — \(\mathbb{E}|\rho| > 0\) under \(H_0\) by Jensen, so mean \(|\rho|\) has ann_groups-dependent noise floor (0.67 / 0.42 / 0.27 at \(K = 3 / 5 / 10\)) that reads like evidence. The accompanyingsigned_spearman_p_valueis explicitly two-sided and reportssigned_spearman_alternative="two-sided"; it is separate from the headline MR test's one-sidedalternative="greater".
Per-bucket cardinality floor
min_assets_per_group = 50 (Patton-Timmermann 2010) — the slice-
test function downscales n_groups automatically so each bucket meets
the floor; below it, per-date bucket means are noise-dominated and
the rank statistic is unreliable. Defaults: n_groups=10 for
universes around 2000 stocks; drop to 5 for \(n_{assets} < 1000\)
and 3 for \(n_{assets} < 200\).
Worked example — Spearman direction test on quantile-bucket returns¶
monotonicity on a synthetic cross-sectional panel
import factrix as fx
from factrix.metrics.monotonicity import monotonicity
from factrix.preprocess import compute_forward_return
raw = fx.datasets.make_cs_panel(
n_assets=2000, n_dates=500, ic_target=0.08, rng=2024,
)
panel = compute_forward_return(raw, forward_periods=5)
# monotonicity returns dict[str, MetricResult], keyed by factor column
out = monotonicity(panel, overlap_periods=5, n_groups=10, rng=0)["factor"]
print(out.value, out.p_value,
out.metadata["mr_adjacent_diffs"],
out.metadata["mean_abs_spearman"])
# value = stat = J, the smallest average adjacent bucket-return
# difference (return units); p_value is its bootstrap p under
# H0 "not monotonically increasing"; mr_adjacent_diffs shows which
# step binds; mean_abs_spearman is the descriptive magnitude.
Declare the direction, do not search it
direction="increasing" (default) or "decreasing" states what \(H_1\)
asserts. Running both and reporting the smaller p is a two-sided search
charged at a one-sided level. For a factor hypothesised to be negatively
related to returns, pass direction="decreasing" (or flip the factor's
sign upstream) rather than reading the increasing test and inverting it.
Deviation from the paper
Patton-Timmermann bootstrap the raw (unstudentised) adjacent differences, which is what runs here; their studentised variant is not implemented. The bootstrap is the library's shared stationary (Politis-Romano 1994) resampler with the Politis-White (2004) automatic block length, applied to the whole \((T, K-1)\) difference matrix under one row-index draw so within-period cross-bucket dependence is preserved. Empirical p uses the Davison-Hinkley \(+1\) smoothing shared with the rest of the library, so p is never exactly 0.
See also¶
-
quantile_spread/compute_group_returns
The decile-curve chart input and the long-short headline spread over the same buckets.
-
by_slice
Axis-agnostic slice dispatcher for per-slice monotonicity summaries.
-
slice_pairwise_test/slice_joint_test
Cross-slice inference (Wald \(\chi^2\) + Holm / Romano-Wolf adjusted \(p\)).
-
Statistical methods
The MR bootstrap, and the DDOF convention behind the descriptive signed-Spearman \(t\).
-
Metric applicability reference
Per-bucket sample-size floor (Patton-Timmermann 2010) and the
n_groupsdownscaling contract. -
Individual × Continuous landing
Adjacent metrics in the same cell.