Skip to content

fix(ops): guard Rsquare expanding (N==0) path against degenerate windows#2298

Open
DogInfantry wants to merge 1 commit into
microsoft:mainfrom
DogInfantry:fix/rsquare-expanding-degenerate-window
Open

fix(ops): guard Rsquare expanding (N==0) path against degenerate windows#2298
DogInfantry wants to merge 1 commit into
microsoft:mainfrom
DogInfantry:fix/rsquare-expanding-degenerate-window

Conversation

@DogInfantry

Copy link
Copy Markdown

Fixes #2297

Problem

Rsquare masks windows whose std is ≈0 to NaN so a degenerate 0/0 regression
(near-constant window) does not leak inf/garbage R² out of the Cython kernel.
That guard was only on the rolling (N != 0) branch; the expanding (N == 0)
branch was unguarded, so Rsquare($feature, 0) returned inf/spurious finite
values on near-constant windows. qlib/data/ops.py also sets
np.seterr(invalid="ignore"), so the bad values propagate silently into
features (Alpha158/Alpha360) and models.

Fix

Mirror the existing guard on the expanding branch, using expanding std:

if self.N == 0:
    series = pd.Series(expanding_rsquare(_series.values), index=_series.index)
    series.loc[np.isclose(_series.expanding(min_periods=1).std(), 0, atol=2e-05)] = np.nan
else:
    ...

Slope/Resi are unaffected — they divide by the x-variance (index 1..N),
which is always well-conditioned; only Rsquare divides by the y-variance.

Test

Adds tests/ops/test_rsquare.py exercising Rsquare._load_internal directly
(no market data needed). It fails before this change and passes after:

# before fix, expanding path:
[nan, nan, nan, inf, 0.01717987, inf]   -> AssertionError: expanding Rsquare leaked inf
# after fix:
3 passed

Rsquare masks windows whose std is ~0 to NaN so that the degenerate 0/0
regression (a near-constant window) does not leak `inf` or a spurious finite
R^2 from the Cython kernel. This guard was only applied on the rolling
(`N != 0`) branch; the expanding (`N == 0`) branch was unguarded, so
`Rsquare($feature, 0)` returned `inf`/garbage on near-constant windows,
silently poisoning downstream features and models (qlib/data/ops.py also sets
`np.seterr(invalid="ignore")`, hiding the warning).

Apply the same std~0 -> NaN mask on the expanding branch, using expanding std.

Added tests/ops/test_rsquare.py (fails before this change, passes after).
@DogInfantry DogInfantry force-pushed the fix/rsquare-expanding-degenerate-window branch from d5ef304 to 9905d6d Compare July 9, 2026 08:03
@DogInfantry

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Rsquare(N=0) expanding path leaks inf/garbage on near-constant windows (rolling path is guarded)

1 participant