"""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)