trading/shared/strategies/value.py

98 lines
2.6 KiB
Python

"""Value strategy — trade on fundamental valuation metrics."""
from datetime import datetime, timezone
from shared.schemas.trading import MarketSnapshot, SentimentContext, SignalDirection, TradeSignal
from shared.strategies.base import BaseStrategy
class ValueStrategy(BaseStrategy):
"""Generate signals from fundamental financial data.
Computes a composite score from PEG ratio, P/E ratio, EPS, revenue
growth, profit margin, and debt-to-equity ratio.
**Buy signal** (LONG):
Composite score > 0.3 (undervalued).
**Sell signal** (SHORT):
Composite score < -0.3 (overvalued).
Signal strength = ``abs(score) / 2.0``, clamped to [0, 1].
"""
name: str = "value"
async def evaluate(
self,
ticker: str,
market: MarketSnapshot,
sentiment: SentimentContext | None = None,
) -> TradeSignal | None:
if market.fundamentals is None:
return None
f = market.fundamentals
if f.peg_ratio is None or f.pe_ratio is None:
return None
score = 0.0
# PEG ratio scoring
if f.peg_ratio < 1.0:
score += 0.3
elif f.peg_ratio > 3.0:
score -= 0.3
# P/E ratio scoring
if f.pe_ratio < 15:
score += 0.3
elif f.pe_ratio > 40:
score -= 0.3
# EPS scoring
if f.eps_ttm is not None:
if f.eps_ttm > 0:
score += 0.2
elif f.eps_ttm < 0:
score -= 0.3
# Revenue growth scoring
if f.revenue_growth_yoy is not None:
if f.revenue_growth_yoy > 0.1:
score += 0.2
elif f.revenue_growth_yoy < -0.1:
score -= 0.2
# Profit margin scoring
if f.profit_margin is not None:
if f.profit_margin > 0.15:
score += 0.1
elif f.profit_margin < 0:
score -= 0.2
# Debt-to-equity scoring
if f.debt_to_equity is not None:
if f.debt_to_equity > 3.0:
score -= 0.2
elif f.debt_to_equity < 0.5:
score += 0.1
# Determine direction
if score > 0.3:
direction = SignalDirection.LONG
elif score < -0.3:
direction = SignalDirection.SHORT
else:
return None
strength = max(0.0, min(1.0, abs(score) / 2.0))
return TradeSignal(
ticker=ticker,
direction=direction,
strength=strength,
strategy_sources=[self.name],
timestamp=datetime.now(tz=timezone.utc),
)