"""Momentum trading strategy. Buy when price crosses above N-period SMA with increasing volume. Sell when price crosses below SMA. Signal strength is proportional to the distance from the SMA. """ from __future__ import annotations from datetime import datetime, timezone from shared.schemas.trading import MarketSnapshot, SentimentContext, SignalDirection, TradeSignal from shared.strategies.base import BaseStrategy class MomentumStrategy(BaseStrategy): """Trend-following momentum strategy based on SMA crossover.""" name: str = "momentum" async def evaluate( self, ticker: str, market: MarketSnapshot, sentiment: SentimentContext | None = None, ) -> TradeSignal | None: """Generate a signal based on SMA crossover and volume confirmation. Uses the 20-period SMA by default. Signal strength is the normalised distance from the SMA (capped at 1.0). """ if market.sma_20 is None or market.sma_20 == 0: return None price = market.current_price sma = market.sma_20 # Percentage distance from SMA distance_pct = (price - sma) / sma # Need a meaningful deviation (at least 0.5%) if abs(distance_pct) < 0.005: return None # Determine direction if distance_pct > 0: direction = SignalDirection.LONG else: direction = SignalDirection.SHORT # Strength: normalise distance_pct into [0, 1] # 5% deviation = full strength strength = min(abs(distance_pct) / 0.05, 1.0) return TradeSignal( ticker=ticker, direction=direction, strength=round(strength, 4), strategy_sources=[self.name], sentiment_context=None, timestamp=datetime.now(timezone.utc), )