Add outlier-robust reversal_rank alpha and investable-universe filter
reversal_rank weights the 5-day reversal signal by bounded cross-sectional rank instead of z-score, so a few extreme A-share pct_change outliers (newly listed / post-suspension / limit-up names) can no longer dominate the book. compute_alpha gains an optional per-date investable-universe mask (tradable, non-ST, seasoned, top-liquidity) applied to the signal before weighting, exposed via --liquid-universe/--universe-top-n. combo combine now accepts a single alpha as an identity passthrough so a one-alpha pipeline run needs no synthetic second input. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
"""Outlier-robust short-horizon reversal alpha."""
|
||||
import pandas as pd
|
||||
|
||||
from pipeline.alpha.base import BaseAlpha
|
||||
from pipeline.alpha.registry import register_alpha
|
||||
|
||||
|
||||
@register_alpha
|
||||
class ReversalRankAlpha(BaseAlpha):
|
||||
"""Reversal weighted by cross-sectional rank instead of z-score.
|
||||
|
||||
The signal is the same trailing-return reversal as :class:`ReversalAlpha`,
|
||||
but :meth:`to_weights` converts it with a cross-sectional rank that is then
|
||||
demeaned. Rank weighting is bounded and monotone, so it does not dump the
|
||||
book into a handful of extreme movers the way raw z-scoring does — the
|
||||
failure mode that makes plain ``reversal`` collapse on the A-share universe,
|
||||
where newly listed / post-suspension / limit-up names produce huge
|
||||
``pct_change`` outliers.
|
||||
"""
|
||||
|
||||
name = "reversal_rank"
|
||||
|
||||
def __init__(self, lookback: int = 5):
|
||||
self.lookback = lookback
|
||||
|
||||
def signal(self, close: pd.DataFrame) -> pd.DataFrame:
|
||||
return -close.pct_change(self.lookback, fill_method=None)
|
||||
|
||||
def to_weights(self, signal: pd.DataFrame) -> pd.DataFrame:
|
||||
signal = signal.dropna(how="all")
|
||||
ranks = signal.rank(axis=1)
|
||||
weights = ranks.subtract(ranks.mean(axis=1), axis=0)
|
||||
return weights.fillna(0.0)
|
||||
Reference in New Issue
Block a user