Skip to content

Warning codes

Structured WarningCode payloads attached to every EvaluationResult.warnings. Use these as the SSOT when you need to filter, route, or trigger downstream behaviour from factrix output without parsing free-text strings.

  • WarningCode — risk flags surfaced on EvaluationResult.warnings. Does not affect MetricResult.p_value; the user decides whether to pre-filter on warnings before multi-factor Benjamini-Hochberg-Yekutieli (BHY).

Each member's trigger / meaning is sourced from factrix._codes.WarningCode.description (single source of truth, also surfaced at runtime on each result's warnings). For the per-procedure breakdown of which codes a given pipeline can emit, see Architecture § Procedure pipelines.

The scalar statistics that populate EvaluationResult.metrics are not enum-keyed — each MetricResult exposes its statistic on MetricResult.stat with stat_type / h0 / method in MetricResult.metadata; see Stat keys by metric.

WarningCode

WarningCode Trigger / meaning
unreliable_se_short_periods n_periods is below the WARN floor (~30); NW HAC SE may be biased. Reused across panel time-series guards (MIN_PERIODS_WARN) and primitive inference (MIN_FM_PERIODS_WARN); both default to 30.
event_window_overlap Two events on one asset sat fewer than overlap_periods apart, so their forward-return windows (t, t+h] overlapped and they are not independent draws. Every event metric that pools per-event scores (caar / bmp_z / corrado_rank / event_hit_rate / event_ic / event_skewness) strides its event axis before pooling and fires this once. bmp_z / corrado_rank / event_hit_rate / event_ic / event_skewness stride the event rows per asset, because a (t, t+h] window can only overlap another on the same asset; caar collapses each date to one panel-wide cross-sectional mean first and strides that event-period series, which has no asset axis left to stride. The counts are in metadata['n_events_overlapping'] / ['n_events_sampled']. The statistic is the calibrated one — it runs on the surviving non-overlapping events — so read the code as the cost in sample of a trigger that fires in bursts, not as a defect. It cannot fire at overlap_periods = 1 (consecutive events are already independent).
persistent_regressor The predictive regressor is in a regime the corrected test is less well sized in: ADF p exceeds the configured threshold (unit-root suspect), or the measured Stambaugh channel |rho_hat * phi_corrected| exceeds 0.7, or the bias-corrected AR(1) coefficient came out at or above one. The Stambaugh (1999) bias itself is CORRECTED via the Amihud-Hurvich (2004) augmented regression, so this is not 'beta may be biased' - at overlap_periods=1 it is 'the strongest Stambaugh cells leave the corrected test at 6-8% against a nominal 5%, where the calibrated cells sit at 4-6%'. This code is about the regressor, not overlapping windows; OVERLAPPING_PREDICTIVE_INFERENCE reports the latter separately. Read the p against a raised hurdle.
overlapping_predictive_inference predictive_beta was run with overlap_periods > 1, where the reported Amihud-Hurvich-adjusted HAR slope test is not calibrated at a nominal 5% and measures 7.5-14.5% null rejection. The estimate and p-value are returned, but use a raised significance hurdle and compare an h=1 or genuinely non-overlapping design. If the adjusted design cannot be formed, the metric short-circuits instead of substituting plain OLS-Newey-West inference. This is distinct from EVENT_WINDOW_OVERLAP, which removes overlapping event-study observations before testing.
serial_correlation_detected The tested per-period series is persistent beyond the overlap horizon: lag-1 autocorrelation above PERSISTENT_SERIES_AUTOCORR (0.3) on the series strided at overlap_periods, which must itself reach MIN_SERIES_PERIODS_HARD (10) observations — below that the screen is withheld, because a lag-1 autocorrelation estimated from three to nine points is noise and the shortage of periods is what UNRELIABLE_SE_SHORT_PERIODS reports. positive_rate screens its dichotomised hit indicators at POSITIVE_RATE_HIT_AUTOCORR (~0.194), the Gaussian sign-process equivalent of the same calibrated boundary; this is a warning threshold, not a universal latent-correlation estimate. The MA(h-1) autocorrelation that overlapping h-period forward returns induce is absorbed by the HAC bandwidth floor and the bootstrap block-length floor and does not fire this code; what does is persistence the stride cannot remove. No HAC or bootstrap path is calibrated there — measured 13-17% (NW), 12-19% (bootstrap) and 32-34% (plain t) at a nominal 5% on an AR(0.6) per-period series, worse above — so read the p-value against a raised hurdle (Harvey-Liu-Zhu 2016: t > 3) or lengthen the sample; switching inference member does not fix it. The single-restriction regression contrasts (common_asymmetry, common_quantile_spread) run the same screen on their per-period FACTOR: their HAC reference is calibrated on a non-persistent common factor (3.3-8.0% across T x h). Under the documented AR(0.9), N=50, T=60, h=5 null, 600 draws measured 10.5% +/- 1.3 percentage points for common_asymmetry and 19.5% +/- 1.6 percentage points for common_quantile_spread. These are per-metric measured cells, not ceilings; see reference/inference-calibration.
few_assets Cross-section asset count is below the relevant WARN floor (panel-wide MIN_ASSETS_WARN=30, per-period MIN_IC_ASSETS_WARN=10, or per-period MIN_FM_ASSETS_WARN=10). The statistic is returned, but small n_assets inflates critical values or leaves minimal residual degrees of freedom. Severity scales with n_assets; read the relevant n_assets metadata. A by-design few-asset study declares the regime once via evaluate(..., expected_warnings=('few_assets',)): the record is kept and marked expected=True, and the per-run UserWarning echo stops.
thin_quantile_groups A quantile-bucketing metric split the selected factor's median finite cross-section into n_groups buckets leaving fewer than MIN_GROUP_ASSETS (5) assets per bucket; each bucket mean rests on a handful of names, so the result can be dominated by individual assets. Applies to quantile_spread, quantile_spread_vw, monotonicity, and turnover metrics that reuse those buckets. Advisory only — reduce n_groups (the warning suggests a value) or treat the result as a fragile small-cross-section diagnostic. Distinct from few_assets, which keys off the absolute cross-section size.
thin_quantile_periods common_quantile_spread leaves fewer than 5 periods per historical factor bucket on average. Each bucket mean rests on a thin time-series sample; reduce n_groups or interpret the conditional means cautiously.
high_tie_ratio Median per-period tie_ratio exceeds TIE_RATIO_WARN_THRESHOLD (0.3). For quantile_spread with ordinal tie-breaking, low factor cardinality injects sorting-artifact noise; use tie_policy='average' or fewer groups. For ic / ic_ir, Spearman is already tie-corrected, but heavy ties shrink the attainable range of rho below +-1, so IC magnitudes are not comparable across different tie densities.
sparse_magnitude_weighted Sparse factor column is mixed-sign and not a clean ±1 ternary. caar preserves magnitude (Sefcik-Thompson), while bmp_z and corrado_rank use sign only; apply .sign() before evaluation when the three metrics should share sign-flip semantics.
few_events An event-axis metric (caar / corrado_rank / bmp_z / event_hit_rate / event_ic / event_skewness) with a raw event count below MIN_EVENTS_WARN (30) x overlap_periods. The floor is scaled because every one of these first strides its event axis at overlap_periods — keeping at most one event per overlap window (per asset, except caar, which strides its panel-wide event-period series) — so a raw series must carry overlap_periods x 30 events to land on 30 independent ones. The message states the scaled floor, the raw count and the count that survived sampling; caar and corrado_rank count event periods on that axis (caar an equal-weight calendar-time portfolio, corrado_rank the per-period mean signed rank), the others count events. bmp_z fires on a second trigger as well: once events share periods its effective sample is the distinct event periods, not the event count (the Kolari-Pynnönen adjustment cannot manufacture independent periods; measured ~10% size at 8 periods, ~7% at 15, clearing by ~30, nominal 5%). A sub-30 effective sample is power-thin for the asymptotic distribution — read borderline p-values cautiously. event_skewness publishes no p-value; there the code says its third moment is estimated on a thin sample.
borderline_portfolio_periods top_concentration with MIN_PORTFOLIO_PERIODS_HARD ≤ n_periods < MIN_PORTFOLIO_PERIODS_WARN (3..19); the mean effective number of names is averaged over few periods and moves substantially between draws. top_concentration is descriptive at every sample size — it publishes no p-value (the withdrawn one-sided t against ratio ≥ 0.5 never rejected: the null ratio is ~0.91, measured 0 of 300 draws at a nominal 5%) — so this code is about the precision of value and ratio_eff_to_total, not about a test. Lengthen the history before comparing panels.
few_directional_pairs directional_hit_rate with MIN_DIRECTIONAL_PAIRS_HARD ≤ n_pairs < MIN_DIRECTIONAL_PAIRS_WARN (10..29); the Pesaran-Timmermann hit rate is returned but n counts pooled non-overlapping (date, asset) directional trials, and the normal approximation to S_n is power-thin below ~30 pooled pairs — read borderline p-values cautiously. Below the HARD floor the metric short-circuits to NaN.
few_ordering_pairs directional_pair_accuracy with MIN_PAIR_ACCURACY_PAIRS_HARD ≤ n_pairs < MIN_PAIR_ACCURACY_PAIRS_WARN (10..29); the descriptive ordering accuracy is returned but n counts pooled non-overlapping within-period asset pairs after factor/return ties are removed. Below the HARD floor the metric short-circuits to NaN.
rect_kernel_negative_variance Rectangular-kernel HAC variance-of-mean came out negative (no PSD guarantee, Andrews 1991); clamped to 0 → SE=0, so the t-test is not computable and returns NaN (also flagged degenerate_variance). Fires only on short / mildly anti-correlated samples.
hac_bandwidth_ill_conditioned The resolved Bartlett bandwidth L exceeds n_periods / 5 on a sample of at least 30 periods, so the HAC long-run variance is estimated from too few lag products to be stable. Below 30 periods this bandwidth-specific code is suppressed and the consumer's short-sample contract applies. Usually a long overlap_periods against a short sample. The test still runs with the effective-df correction, but treat the p-value as indicative only and lengthen the sample or shorten the horizon. In this regime the effective-df cap at n_periods / overlap_periods binds hard, so if the series carries less dependence than overlap_periods implies the test is conservative rather than oversized (measured 0.2-3.5% against a nominal 5% on an iid series at h=21).
degenerate_variance The sample admits no test statistic: every observation is identical (zero dispersion), the HAC SE collapsed to zero, or a Wald restriction's covariance is singular. The metric keeps its value and reports stat=None / p_value=None (NaN on a raw InferenceResult) — an identical, non-zero sample is degenerate in the maximum-evidence direction, not evidence of a null, so t=0 / p=1 would invert the reading.
common_beta_iid_fallback common_beta received a hand-built beta table without the calendar-time portfolio variance emitted by compute_common_betas, so it reports the textbook iid cross-asset t. Betas with a shared residual component are not independent draws; use compute_common_betas(panel) for the calibrated calendar-time SE or treat this p-value as an unadjusted reference.
romano_wolf_uncentred_bootstrap romano_wolf_adjusted_p received bootstrap-statistic columns whose absolute mean exceeds one columnwise standard deviation. Romano-Wolf requires joint null-centred, consistently studentized draws; centring is the caller's job, and adjusted p-values from an uncentred reference distribution cannot be repaired after the fact.
romano_wolf_few_resamples romano_wolf_adjusted_p received fewer than 99 joint bootstrap resamples. The algorithm remains defined for deterministic reference checks, but the empirical p-value grid has resolution 1 / (B + 1); use at least 999 resamples for ordinary two-sided 5% work.
bmp_return_vol_fallback bmp_z ran without a price column: the estimation-window volatility falls back to the per-asset rolling std of forward_return, lagged by overlap_periods so it ends before the event's forward window. This is a coarser, horizon-overlapping vol proxy than a price-derived one-period std — supply price for the clean BMP standardiser. Either path's horizon scale (1/sqrt(overlap_periods) on the price path) is exact only on the unsampled full grid, where the stamped overlap_periods counts the periods the forward return spans; the p-value does not depend on it, because the factor is common to every event and cancels in z = mean(SAR) / (std(SAR) / sqrt(N)) and in the scale-free Kolari-Pynnonen deflator. Only the descriptive metadata['std_sar'] and the mean-SAR value shift, by a constant, on a resampled evaluation grid.
upstream_unavailable DAG-executor consumer skipped because an upstream producer short-circuited. The downstream MetricResult carries metadata['upstream'] / ['upstream_reason'] for the original cause.
metric_unavailable Metric short-circuited on its own precondition (missing input column / config, or insufficient sample at its own floor); the NaN MetricResult's metadata['reason'] carries the specific cause. Distinct from UPSTREAM_UNAVAILABLE, which flags a dependency failure.
structure_mismatch Metric's declared factor cell (scope / density / data structure) does not match the detected factor cell; under strict=False the metric short-circuits to NaN instead of executing.
low_cardinality_dense_signal Dense factor has few distinct finite values but no sparse event contract. Sparse event metrics require the {0, R} zero-value event contract and a sparse_ratio above the routing threshold; always-in-market states such as {-1, +1} stay dense and should use dense / directional metrics.
frequent_event_signal Sparse event metric explicitly ran on a factor with zero-valued rows but sparse_ratio below the automatic SPARSE routing threshold. The metric treats zeros as non-events; confirm that zero encodes the intended event contract. Events are frequent, so read event-study inference cautiously and inspect clustering / overlap diagnostics.
factor_scope_unidentifiable The inspected factor column has no finite cell on any period, so FactorScope cannot be observed. Scope detection reads finite cells only — null, NaN and ±inf are missing observations, not distinct factor values — and a period with none is ignored. With every period ignored, routing falls back to the unrestricted FactorScope.INDIVIDUAL instead of asserting a COMMON broadcast structure no observation supports. Read it as a missing factor column: every sample floor is violated at n_pairs=0.
cross_factor_density_mismatch Factor columns carry inconsistent FactorDensity (dense and sparse mixed).
cross_factor_scope_mismatch Factor columns carry inconsistent FactorScope (individual and common mixed).
single_asset_event_data Single-asset event-shaped data (TIMESERIES + SPARSE, n_assets=1): event-axis metrics run over the event cross-section (n_events) and are usable on a single name. Metrics that need the asset cross-section — same-period event clustering (clustering_hhi) is degenerate at one event per period — need n_assets>=2 and are unavailable. Do not pool unrelated assets to clear this; that mixes return-generating processes.
excessive_period_drops An upstream PANEL→SERIES primitive dropped more than DROP_RATE_WARN_THRESHOLD of periods at its cross-sectional filter; the metric was computed on a shortened sample. Exact counts are in MetricResult.metadata (n_periods_in / n_periods_out / dropped_periods / drop_rate / drop_reason).
excessive_asset_drops An upstream primitive dropped more than DROP_RATE_WARN_THRESHOLD of assets at its per-asset filter (e.g. compute_common_betas dropping assets with insufficient history or zero factor variance); the cross-asset aggregate was computed on a shortened sample. Exact counts are in MetricResult.metadata (n_assets_in / n_assets_out / dropped_assets / drop_rate / drop_reason).
slice_boundary_truncation by_slice partitioned a panel on a date-axis column (one whose value varies within an asset over time, e.g. calendar year or regime label) while the metric declares MetricSpec.slice_boundary_sensitive (a capability of the estimator, not inferred from its aggregation category). Each slice is evaluated on its own rows, so a rolling window / per-asset time-series regression / event window sees truncated history at the slice boundary — the per-slice value differs from the full-sample value decomposed by period. A price_data= panel does not widen that history: on a date-axis partition it is restricted to the slice's own period range, so the baseline and raggedness a metric reads off it describe the slice rather than the full span. Metrics that don't declare the flag and cross-sectional partitions (constant within an asset, e.g. sector) are unaffected and do not trigger.
short_slice_joint_test slice_period_joint_test ran on K >= 3 slices with the shortest below 150 periods. On a true null the omnibus over-rejects there (measured 8–9% analytic and 10–15% bootstrap at a nominal 5% for K=5 with 50–90-period slices) because each slice's small-sample HAC variance is a noisy estimate inverted across K-1 restrictions. The test still runs — read the p-value as indicative and prefer the pairwise contrasts on the same slices. The result row carries the code in warning_codes (and in unexpected_warning_codes unless declared via expected_warnings) alongside k_slices, n_periods_min and the short_slice_periods threshold that triggered it.
one_signed_factor top_concentration ran with weight_by='abs_factor' on a factor that never changes sign across the panel. |factor| is a density weight only when zero is the factor's neutral point; the HHI of |f| is not location-invariant, so an uncentred factor's concentration reading moves with an arbitrary shift. Centre the factor (cross-sectional z-score) or use alpha_contribution.
event_clustering_adjusted A pooled event statistic found its units correlated and deflated itself by the Kish design effect 1/sqrt(1 + (n_eff - 1) * r_hat) — the same Kolari-Pynnonen (2010) machinery bmp_z and directional_hit_rate use. event_hit_rate and event_ic key it on the within-period intraclass correlation of their own per-event score (events sharing a period share that period's shock, so they are not separate trials). It fires only when the deflation is material — r_hat > 0 and a scale below KP_MATERIAL_SCALE (0.95); above that the statistic is left alone and event_hit_rate keeps the exact binomial. The point estimate is untouched; the p-value widens. Measured on a true null: event_hit_rate 63.5% -> nominal at 20 assets sharing 40 event dates. metadata['kolari_pynnonen_r'] / ['kolari_pynnonen_scaling'] disclose the estimate and the deflator.
estimation_window_contaminated A mean-adjusted event test (caar / bmp_z / corrado_rank / event_hit_rate / event_ic / event_skewness) found that, averaged over the tested events, more than ESTIMATION_WINDOW_EVENT_SHARE_WARN (25%) of each event's estimation-window periods lie inside another event's forward-return window on the same asset (metadata['estimation_window_event_share']). The window then estimates the neighbours' realised event returns rather than the asset's unconditional mean, the per-event abnormal returns are negatively correlated through the shared periods, and the cross-event variance overstates the variance of their mean: the test is conservative, not liberal (measured on an iid null at h = 21: bmp_z 0.3% size at nominal 5%). A dense trigger or a long horizon is the cause. A supplied market-adjusted 'abnormal_return' does not remove it (measured 0.3% -> 0.3%) and removes the effect itself when every name fires on the same periods, so the statistic is returned unchanged: read the p-value as an upper bound, or shorten the horizon / thin the trigger.
zero_mad_std_fallback A preprocess scale estimator fell back from the robust MAD to the non-robust per-date sample standard deviation because >50% of the cross-section ties at the median (bucketed / binary factors are the common case). The output column then mixes robust and non-robust dates; the fallback keeps the factor finite and rank-preserving but the scale is no longer outlier-proof.
sparse_winsorize_skipped mad_winsorize left a date unwinsorized: the factor is a sparse {0, R} trigger column whose median and MAD are both 0, so the standard-deviation fallback is driven by the triggers themselves and collapses as the trigger rate falls (at 1 trigger in 50 names a 3-std band clipped a unit event to 0.42). Skipping preserves the event magnitudes the downstream sparse-factor metrics measure.
insufficient_scale_assets A date carries fewer than MIN_SCALE_ASSETS_HARD (3) finite factor values, so no robust per-date scale exists. cross_sectional_zscore returns null there and mad_winsorize skips the clip, rather than fabricating a score (n=1 used to yield 0.0, n=2 a constant +-0.6745 regardless of the values).
non_finite_input_dropped NaN / +-Inf inputs were excluded at the producer boundary. Preprocessing normalizers blank them to null; compute_caar drops affected event rows and reports the count in n_events_dropped_non_finite. A non-finite tick is a data error, not an extreme value to coerce into a plausible statistic.
insufficient_regression_df orthogonalize_factor skipped a date whose cross-section left fewer than MIN_ORTHOGONALIZE_RESIDUAL_ASSETS residual degrees of freedom (n_assets - n_base - 1). Raw R2 is mechanically ~K/(N-1) even at a true R2 of 0, so fitting there reports noise as explanatory power while removing most of the factor's variance. Skipped dates keep their original values and are counted in n_dates_skipped.
rank_deficient_design orthogonalize_factor met a rank-deficient per-date design matrix — most often a full industry dummy set alongside the always-prepended intercept. np.linalg.lstsq does not raise there; it returns the minimum-norm solution, so the residual (a unique projection) stays correct but the betas are an arbitrary point in the solution space. mean_betas is suppressed. Drop one dummy category as the reference level.
ragged_period_grid compute_forward_return or an event metric saw per-asset date grids that do not agree: at least one asset is missing periods that others have. Horizons, estimation windows, lags and event offsets are all counted on the panel's period grid, so they still span the requested number of periods; the asset with the hole has no observation to pair at the exit period, and fewer observations inside an estimation window, than the other names. Reindex the panel onto a common grid if the estimates must be comparable across names.
uneven_evaluation_grid compute_forward_return(dates=) kept an evaluation grid whose adjacent kept rows are not a constant number of periods apart. Series-mean inference (NonOverlapping, NeweyWest) is calibrated on such a grid; the paths that assume a constant spacing are the regression HAC tests (predictive_beta, spanning_alpha, the K-restriction Wald / slice period joint tests), the ADF / autocorrelation persistence screens, event-study estimation windows and offsets, and adjacent-period metrics (turnover / rank autocorrelation, rolling windows) — read their results on such a grid with that in mind. The remedy is on the caller's side: pass dates= at a constant stride on the panel's period grid if those paths must be calibrated; factrix does not resample.

Bases: StrEnum

Procedure-degradation flags.

Each value carries a one-line description gloss used by API docs, MetricResult.warning_codes, and EvaluationResult.warnings.