feat: compare legacy and live ranking universes
This commit is contained in:
@@ -1040,15 +1040,19 @@ def _replay_candidates_for_period(
|
||||
start_date: date,
|
||||
cadence: str = DEFAULT_BACKTEST_CADENCE,
|
||||
include_short_candidates: bool = False,
|
||||
include_universe_rank_observations: bool = False,
|
||||
) -> list[dict]:
|
||||
"""Slim picklable replay used by local event studies.
|
||||
|
||||
Unlike the full report worker it skips factor-series construction and only
|
||||
evaluates setup dates on or after ``start_date``. Long-only remains the
|
||||
compatibility default. Set ``include_short_candidates`` when the caller
|
||||
needs the production-faithful cross-sectional ranking universe; shorts can
|
||||
then contribute to percentiles while the portfolio simulator still trades
|
||||
only qualified longs.
|
||||
needs the legacy full-backtest candidate-ranking universe; shorts can then
|
||||
contribute to those historical percentiles while the portfolio simulator
|
||||
still trades only qualified longs. ``include_universe_rank_observations``
|
||||
additionally marks exactly one row per ticker/session for a live-like rank
|
||||
across tickers rather than across directional setup candidates. If no setup
|
||||
exists on that session, a non-tradeable rank-only row is emitted.
|
||||
"""
|
||||
date_ords, opens, highs, lows, closes, volumes = columns
|
||||
bars = [
|
||||
@@ -1079,10 +1083,19 @@ def _replay_candidates_for_period(
|
||||
)
|
||||
vol_6m = _realized_vol_6m(window_closes, len(window) - 1)
|
||||
iso = bars[i].date.isocalendar()
|
||||
for setup in _window_setups(window, config, activation):
|
||||
if not include_short_candidates and setup["direction"] != "long":
|
||||
continue
|
||||
candidates.append({
|
||||
raw_momentum = (
|
||||
window_closes[-22] / window_closes[-253] - 1.0
|
||||
if len(window_closes) >= 253 and window_closes[-253] > 0
|
||||
else None
|
||||
)
|
||||
setups = [
|
||||
setup
|
||||
for setup in _window_setups(window, config, activation)
|
||||
if include_short_candidates or setup["direction"] == "long"
|
||||
]
|
||||
observation_emitted = False
|
||||
for setup in setups:
|
||||
candidate = {
|
||||
"symbol": symbol,
|
||||
"date": bars[i].date.isoformat(),
|
||||
"iso_week": (iso[0], iso[1]),
|
||||
@@ -1101,6 +1114,24 @@ def _replay_candidates_for_period(
|
||||
"meets_core": setup["meets_core"],
|
||||
"action": setup["action"],
|
||||
"risk_level": setup["risk_level"],
|
||||
}
|
||||
if include_universe_rank_observations and not observation_emitted:
|
||||
candidate["_universe_rank_observation"] = True
|
||||
observation_emitted = True
|
||||
candidates.append(candidate)
|
||||
if include_universe_rank_observations and not observation_emitted:
|
||||
candidates.append({
|
||||
"symbol": symbol,
|
||||
"date": bars[i].date.isoformat(),
|
||||
"iso_week": (iso[0], iso[1]),
|
||||
"ranking_period": _ranking_period(bars[i].date, cadence),
|
||||
"direction": "rank_only",
|
||||
"momentum": raw_momentum,
|
||||
"residual_momentum": residual_momentum,
|
||||
"vol_6m": vol_6m,
|
||||
"meets_core": False,
|
||||
"_universe_rank_observation": True,
|
||||
"_rank_only": True,
|
||||
})
|
||||
return candidates
|
||||
|
||||
|
||||
Reference in New Issue
Block a user