44 lines
1.3 KiB
Python
44 lines
1.3 KiB
Python
"""Momentum strategy signal (§3.1 — Cross-Sectional Momentum).
|
|
|
|
Computes 12-1 month price momentum: cumulative return over months [-12, -1]
|
|
skipping the most recent month to avoid short-term reversal.
|
|
|
|
Reference:
|
|
Kakushadze & Serur, "151 Trading Strategies", §3.1
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import pandas as pd
|
|
|
|
from .base import BaseStrategy, StrategySignal
|
|
from ._data import get_ohlcv
|
|
|
|
|
|
class MomentumStrategy(BaseStrategy):
|
|
name = "Momentum (§3.1)"
|
|
roles = ["market", "researcher"]
|
|
|
|
def compute(self, ticker: str, date: str, context: dict[str, Any] | None = None) -> StrategySignal | None:
|
|
df = get_ohlcv(ticker, date, context)
|
|
if df is None or len(df) < 252:
|
|
return None
|
|
|
|
close = df["Close"].values
|
|
# 12-1 month momentum: return from 252 days ago to 21 days ago
|
|
ret = (close[-21] - close[-252]) / close[-252]
|
|
|
|
strength = max(-1.0, min(1.0, ret)) # clamp
|
|
direction = "bullish" if strength > 0.05 else ("bearish" if strength < -0.05 else "neutral")
|
|
|
|
return StrategySignal(
|
|
name=self.name,
|
|
ticker=ticker,
|
|
date=date,
|
|
signal_strength=round(strength, 4),
|
|
direction=direction,
|
|
detail=f"12-1 month return: {ret:+.2%}",
|
|
)
|