'''Shared production-style historical ranking helpers for research runners.''' from __future__ import annotations from datetime import date def _period_percentiles( observations: list[dict], value_key: str ) -> dict[tuple[str, str], float]: '''Rank one deterministic ticker observation per historical period.''' by_period: dict[tuple, list[dict]] = {} seen: set[tuple[str, str]] = set() for row in observations: identity = (str(row['symbol']), str(row['date'])) if identity in seen: raise ValueError(f'Duplicate universe rank observation: {identity}') seen.add(identity) if row.get(value_key) is None: continue period = tuple(row['ranking_period']) by_period.setdefault(period, []).append(row) result: dict[tuple[str, str], float] = {} for group in by_period.values(): ordered = sorted( group, key=lambda row: (float(row[value_key]), str(row['symbol'])), ) denominator = len(ordered) - 1 for rank, row in enumerate(ordered): result[(str(row['symbol']), str(row['date']))] = round( rank / denominator * 100.0 if denominator > 0 else 100.0, 2, ) return result def _live_universe_rank_map( observations: list[dict], benchmark_closes: dict[date, float], momentum_weight: float, ) -> dict[tuple[str, str], dict[str, float | None]]: '''Historical equivalent of production compute_activation_ranks. Every ticker contributes at most once per session. Residual momentum starts only once 252 benchmark closes were point-in-time available; earlier dates use the same raw-momentum fallback as production. ''' identities = [(str(row['symbol']), str(row['date'])) for row in observations] if len(identities) != len(set(identities)): raise ValueError('Universe ranking requires one observation per ticker/date') raw_pct = _period_percentiles(observations, 'momentum') residual_pct = _period_percentiles(observations, 'residual_momentum') vol_pct = _period_percentiles(observations, 'vol_6m') benchmark_ords = sorted(value.toordinal() for value in benchmark_closes) residual_start_ord = benchmark_ords[251] if len(benchmark_ords) >= 252 else None ranks: dict[tuple[str, str], dict[str, float | None]] = {} for row in observations: identity = (str(row['symbol']), str(row['date'])) asof_ord = date.fromisoformat(identity[1]).toordinal() momentum_pct = ( residual_pct.get(identity) if residual_start_ord is not None and asof_ord >= residual_start_ord else raw_pct.get(identity) ) volatility_pct = vol_pct.get(identity) strategy_rank = ( round( momentum_pct * momentum_weight + volatility_pct * (1.0 - momentum_weight), 2, ) if momentum_pct is not None and volatility_pct is not None else momentum_pct ) ranks[identity] = { 'momentum_percentile': momentum_pct, 'volatility_percentile': volatility_pct, 'strategy_rank': strategy_rank, } return ranks