promote residual momentum ranking
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Unit tests for the cross-sectional 12-1 momentum ranking."""
|
||||
"""Unit tests for the cross-sectional activation momentum ranking."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -35,6 +35,21 @@ async def _seed(session, symbol: str, rate: float, n: int = 280) -> None:
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def _seed_closes(session, symbol: str, closes: list[float]) -> None:
|
||||
t = Ticker(symbol=symbol)
|
||||
session.add(t)
|
||||
await session.flush()
|
||||
base = date(2024, 1, 1)
|
||||
for i, close in enumerate(closes):
|
||||
session.add(OHLCVRecord(
|
||||
ticker_id=t.id,
|
||||
date=base + timedelta(days=i),
|
||||
open=close, high=close, low=close, close=close,
|
||||
volume=1_000_000,
|
||||
))
|
||||
await session.commit()
|
||||
|
||||
|
||||
def test_compute_momentum_insufficient_history():
|
||||
assert ms.compute_12_1_momentum([100.0] * 100) is None
|
||||
|
||||
@@ -47,7 +62,11 @@ def test_compute_momentum_value():
|
||||
assert m > 0
|
||||
|
||||
|
||||
async def test_ranks_universe_into_percentiles(session):
|
||||
async def test_ranks_universe_into_raw_percentiles_when_benchmark_missing(session, monkeypatch):
|
||||
async def no_benchmark(_db):
|
||||
return {}
|
||||
|
||||
monkeypatch.setattr(ms, "_load_activation_benchmark", no_benchmark)
|
||||
await _seed(session, "HIGH", rate=1.010) # strong uptrend → top momentum
|
||||
await _seed(session, "MID", rate=1.002)
|
||||
await _seed(session, "LOW", rate=0.999) # declining → bottom momentum
|
||||
@@ -58,7 +77,31 @@ async def test_ranks_universe_into_percentiles(session):
|
||||
assert pct["LOW"] == 0.0
|
||||
|
||||
|
||||
async def test_short_history_ticker_is_unranked(session):
|
||||
async def test_ranks_universe_into_residual_percentiles_when_benchmark_available(session, monkeypatch):
|
||||
base = date(2024, 1, 1)
|
||||
n = 280
|
||||
benchmark = {base + timedelta(days=i): 100.0 * (1.001 ** i) for i in range(n)}
|
||||
|
||||
async def with_benchmark(_db):
|
||||
return benchmark
|
||||
|
||||
monkeypatch.setattr(ms, "_load_activation_benchmark", with_benchmark)
|
||||
market = [benchmark[base + timedelta(days=i)] for i in range(n)]
|
||||
await _seed_closes(session, "DRIFT", [market[i] * (1.0008 ** i) for i in range(n)])
|
||||
await _seed_closes(session, "BETA", market)
|
||||
await _seed_closes(session, "LAG", [market[i] * (0.9992 ** i) for i in range(n)])
|
||||
|
||||
pct = await ms.compute_momentum_percentiles(session)
|
||||
assert pct["DRIFT"] == 100.0
|
||||
assert pct["BETA"] == 50.0
|
||||
assert pct["LAG"] == 0.0
|
||||
|
||||
|
||||
async def test_short_history_ticker_is_unranked(session, monkeypatch):
|
||||
async def no_benchmark(_db):
|
||||
return {}
|
||||
|
||||
monkeypatch.setattr(ms, "_load_activation_benchmark", no_benchmark)
|
||||
await _seed(session, "LONG", rate=1.005)
|
||||
await _seed(session, "SHORTHX", rate=1.005, n=100) # < 1y → no momentum
|
||||
|
||||
@@ -67,5 +110,9 @@ async def test_short_history_ticker_is_unranked(session):
|
||||
assert "SHORTHX" not in pct
|
||||
|
||||
|
||||
async def test_empty_universe_returns_empty(session):
|
||||
async def test_empty_universe_returns_empty(session, monkeypatch):
|
||||
async def no_benchmark(_db):
|
||||
return {}
|
||||
|
||||
monkeypatch.setattr(ms, "_load_activation_benchmark", no_benchmark)
|
||||
assert await ms.compute_momentum_percentiles(session) == {}
|
||||
|
||||
Reference in New Issue
Block a user