factrix.metrics.predictive_beta ¶
Single-asset predictive regression beta.
Notes
Pipeline. One asset, dense factor, time-series OLS with Newey-West heteroskedasticity-and-autocorrelation-consistent (HAC) standard error on the slope.
Input. Long panel with date, asset_id, factor, forward_return where
asset_id has one unique value.
Output. MetricResult.value is the predictive slope beta and
p_value tests H0: beta = 0.
factrix.metrics.predictive_beta.predictive_beta ¶
predictive_beta(data: DataFrame, *, newey_west_lags: int | None = None, forward_periods: int = 5, adf_threshold: float | None = 0.1, factor_col: str = 'factor', return_col: str = 'forward_return') -> MetricResult
Predictive beta for a single-asset dense factor.
Fits the direct predictive regression
\(R_{t+h} = \alpha + \beta F_t + \varepsilon_t\) on one asset and tests
H0: beta = 0 with a Newey-West HAC standard error. The Bartlett lag
defaults to the Newey-West (1994) automatic rule, floored at
forward_periods - 1 so overlapping forward-return windows do not
understate the standard error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Single-asset long panel with |
required |
newey_west_lags
|
int | None
|
Optional explicit Bartlett lag. |
None
|
forward_periods
|
int
|
Forward-return horizon injected by |
5
|
adf_threshold
|
float | None
|
Augmented Dickey-Fuller p-value above which the
factor is flagged as persistent. |
0.1
|
factor_col
|
str
|
Predictor column. |
'factor'
|
return_col
|
str
|
Forward-return column. |
'forward_return'
|
Returns:
| Type | Description |
|---|---|
MetricResult
|
|
MetricResult
|
and |
Notes
This is not a common_beta fallback. common_beta tests the
cross-asset mean of per-asset betas and therefore remains a PANEL
metric. predictive_beta is the explicit TIMESERIES dense metric
for a single asset.
Use cases¶
-
Single-asset dense predictive slope
predictive_betais the(*, DENSE, TIMESERIES)metric for a one-asset panel. It fitsforward_return ~ factorand tests whether the slope differs from zero. -
Not a
common_betafallback
common_betatests the cross-asset mean of per-asset betas and remainsPANEL-only.predictive_betais the explicit single-series predictive regression. -
HAC inference for overlapping returns
The slope test uses Newey-West HAC covariance. The lag defaults to the Newey-West automatic bandwidth, floored at
forward_periods - 1so overlapping forward-return windows do not understate standard errors. -
Persistent predictor diagnostic
The metric also runs a lightweight ADF check on the factor series. When
adf_pexceedsadf_threshold, metadata setsunit_root_suspected=Trueand the result carriesWarningCode.PERSISTENT_REGRESSOR. The beta is still returned; the warning tells you to read the slope as a persistent-regressor risk, not as an automatically corrected estimate. -
Stability is a workflow
predictive_betareturns the full-sample HAC slope test. Rolling or expanding beta checks should be treated as descriptive stability diagnostics with pre-declared windows, not as a second family of p-values to rank or feed into multiple-testing correction.
Worked example¶
single-asset panel -> predictive_beta
import polars as pl
import factrix as fx
from factrix.metrics import predictive_beta
from factrix.preprocess import compute_forward_return
raw = fx.datasets.make_cs_panel(n_assets=4, n_dates=180, seed=0)
asset = raw["asset_id"].unique().sort()[0]
panel = compute_forward_return(
raw.filter(pl.col("asset_id") == asset),
forward_periods=5,
)
out = fx.evaluate(
panel,
metrics={"predictive_beta": predictive_beta()},
factor_cols=["factor"],
forward_periods=5,
)["factor"].metrics["predictive_beta"]
print(out.value, out.stat, out.p_value, out.metadata["unit_root_suspected"])
Stability workflow¶
For a single-asset dense factor, the first question is still the full-sample
HAC slope: does factor_t have a statistically legible relation to
forward_return_{t+h}? Stability checks come after that and should answer a
different question: whether the slope is broadly persistent through time or
mostly supported by one segment of the sample.
Use pre-declared windows and read the rolling betas descriptively. The windowed
calls below reuse the same predictive_beta estimator, but the resulting
t_stat / p_value values are not independent tests because overlapping
windows share observations.
rolling beta series as a diagnostic
import polars as pl
import factrix as fx
from factrix.metrics import directional_hit_rate, predictive_beta
from factrix.preprocess import compute_forward_return
raw = fx.datasets.make_cs_panel(n_assets=4, n_dates=180, seed=0)
asset = raw["asset_id"].unique().sort()[0]
panel = compute_forward_return(
raw.filter(pl.col("asset_id") == asset),
forward_periods=5,
)
full = predictive_beta(panel, forward_periods=5)
hit = directional_hit_rate(panel, forward_periods=5)
dates = panel.select("date").unique().sort("date")["date"].to_list()
window_periods = 60
step_periods = 20
rows = []
for end_idx in range(window_periods, len(dates) + 1, step_periods):
window_dates = dates[end_idx - window_periods : end_idx]
window = panel.filter(
pl.col("date").is_between(window_dates[0], window_dates[-1])
)
result = predictive_beta(window, forward_periods=5)
rows.append(
{
"date": window_dates[-1],
"window_start": window_dates[0],
"window_end": window_dates[-1],
"beta": result.value,
"t_stat": result.stat,
"n_periods": result.metadata["n_periods"],
"r_squared": result.metadata["r_squared"],
}
)
beta_series = pl.DataFrame(rows)
reference_sign = 1.0 if full.value >= 0.0 else -1.0
stability = beta_series.select(
((pl.col("beta") * reference_sign) > 0).mean().alias("sign_consistency"),
pl.col("beta").median().alias("median_beta"),
pl.col("beta").last().alias("recent_beta"),
pl.col("beta").min().alias("min_beta"),
pl.col("beta").max().alias("max_beta"),
)
print("full beta:", full.value, "p:", full.p_value)
print("directional hit:", hit.value, "p:", hit.p_value)
print(stability)
Read this output as a stability profile:
full.value/full.p_valueis the canonical single-asset dense inference.directional_hit_ratechecks whether the factor gets the return sign right.sign_consistencyasks how often rolling betas keep the full-sample sign.recent_beta,median_beta,min_beta, andmax_betashow whether the signal is decaying, flipping, or concentrated in one segment.
Avoid turning this workflow into automatic model selection. Do not choose the
window after looking at the strongest beta, do not feed overlapping-window
p_value values into bhy, and do not interpret sign_consistency as a
formal structural-break test. If the stability profile points to a regime
change, split that regime hypothesis explicitly and test it as a separate
research design.