Skip to content

factrix.metrics.positive_rate

Positive-rate computation for any time-indexed series.

Notes

Pipeline. Time-series only, sampled non-overlapping on a 1-D series; binomial test against p = 0.5.

Input. DataFrame with date, value or a 1-D array.

Output. Proportion of periods where the value satisfies a condition (default: value > 0).

factrix.metrics.positive_rate.positive_rate

positive_rate(series: DataFrame, value_col: str = 'value', overlap_periods: int = DEFAULT_FORWARD_PERIODS, *, expected_warnings: tuple[str, ...] = ()) -> MetricResult

Positive rate = proportion of periods where value > 0.

Parameters:

Name Type Description Default
series DataFrame

DataFrame with date and value_col.

required
overlap_periods int

Sampling interval for non-overlapping dates.

DEFAULT_FORWARD_PERIODS

Returns:

Type Description
MetricResult

MetricResult with value = positive rate (0.0-1.0).

Notes

rate = (#{t : value_t > 0}) / n on a non-overlapping subsample at stride overlap_periods. Two-sided exact binomial test against H0: p = 0.5 (:func:scipy.stats.binomtest) at every n; stat is the hit count (stat_type="binomial_hits") — the sufficient statistic of the test actually run — never a Gaussian z.

Why exact rather than the normal-approximation score test: the uncorrected z = (rate - 0.5)·√n / 0.5 is anti-conservative (n=20, 15 hits: p=0.025 vs exact 0.041), and the exact test is negligible in cost at any series length. Non-overlap stride mirrors the information coefficient (IC) pipeline so autocorrelation from overlapping forward returns does not leak in. If the strided hit indicators remain serially persistent, the exact binomial independence assumption is not calibrated; the result carries serial_correlation_detected rather than leaving that regime silent. Its warning threshold maps the project-wide persistence boundary through the Gaussian sign-correlation identity; this is a calibration anchor, not a latent-correlation model for arbitrary inputs.

References

Hansen-Hodrick 1980: overlapping-return autocorrelation horizon motivating the non-overlap stride.

Hurt 1973: sign correlation of a zero-mean stationary Gaussian sequence.

Examples:

Hit rate of a 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.positive_rate import positive_rate
>>> panel = compute_forward_return(
...     fx.datasets.make_cs_panel(n_assets=80, n_dates=180, rng=0),
...     forward_periods=5,
... )
>>> series = compute_ic(panel)["factor"].rename({"ic": "value"}).select("date", "value")
>>> result = positive_rate(series, overlap_periods=5)
>>> result.name == ""
True

Use cases

  • Sign-significance on a factor-return series


    positive_rate is a standalone series diagnostic — input is a 1-D series with (date, value), not the raw panel. Typical pipes: per-date information coefficient (IC) from compute_ic, quantile spread from compute_spread_series, or any other factor-mimicking-portfolio return series. Reports the fraction of periods with value > 0 against \(H_0: p = 0.5\).

  • Exact binomial at every \(n\)


    The \(p\)-value is the exact two-sided binomial test (scipy.stats.binomtest) at every sample size; stat is the hit count (stat_type="binomial_hits"), the test's sufficient statistic. An earlier version switched to the uncorrected normal-approximation \(z\) above \(n = 20\); that branch was anti-conservative (e.g. \(n=20\), 15 hits: \(p = 0.025\) vs exact \(0.041\)) and has been removed — the exact test is \(O(n)\) and free.

  • Non-overlap stride matches the IC pipeline


    The series is sub-sampled at stride overlap_periods before the test so the MA dependence induced by overlapping forward returns does not leak in; same convention as quantile_spread and the other non-overlap inference paths. If the surviving hit indicators remain serially persistent, the result carries serial_correlation_detected: the exact binomial p-value assumes independent trials and is not calibrated in that regime.

Choosing a function

Goal Function
Hit-rate significance vs \(p = 0.5\) on a (date, value) series positive_rate
Per-date hit indicator (value > 0 cast to float) for slice-test plumbing per_date_series

Worked example — IC series fed into positive_rate

compute_ic → positive_rate

import factrix as fx
from factrix.metrics.ic import compute_ic
from factrix.metrics.positive_rate import positive_rate
from factrix.preprocess import compute_forward_return

raw   = fx.datasets.make_cs_panel(
    n_assets=100, n_dates=500, ic_target=0.08, rng=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)["factor"]
out   = positive_rate(ic_df, value_col="ic", overlap_periods=5)
print(out.value, out.stat, out.p_value, out.metadata["method"])
# 0.62  19  0.011   exact-binomial   (approximate)

See also

  • compute_ic / compute_spread_series


    Canonical producers of the (date, value) series this diagnostic consumes. compute_ic emits (date, ic, tie_ratio); compute_spread_series emits (date, spread, ...).

    api/metrics/ic →

  • trend / oos


    Sibling series diagnostics on the same input shape — slope detection and IS/out-of-sample (OOS) persistence.

    api/metrics/trend →

  • by_slice


    Per-slice hit-rate summaries; uses per_date_series as the slice-test capability hook.

    api/by-slice →

  • Statistical methods


    Two-sided binomial branching, non-overlap stride discipline, and the Hansen-Hodrick autocorrelation floor that motivates it.

    reference/statistical-methods →

  • Metric applicability reference


    When this metric applies and the sample-size guards that gate it (MIN_SERIES_PERIODS_HARD floor on the sampled series).

    reference/metric-applicability →

  • Series diagnostics landing


    Adjacent axis-agnostic series diagnostics.

    api/metrics/series-tools →