feat: trading strategies — momentum, mean reversion, news-driven
This commit is contained in:
parent
e483e9987f
commit
60bd1ccd2a
6 changed files with 581 additions and 0 deletions
56
shared/strategies/mean_reversion.py
Normal file
56
shared/strategies/mean_reversion.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""Mean reversion strategy — buy oversold, sell overbought using RSI."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from shared.schemas.trading import MarketSnapshot, SentimentContext, SignalDirection, TradeSignal
|
||||
from shared.strategies.base import BaseStrategy
|
||||
|
||||
|
||||
class MeanReversionStrategy(BaseStrategy):
|
||||
"""Trade on the assumption that extreme RSI readings will revert to the mean.
|
||||
|
||||
**Buy signal** (LONG):
|
||||
RSI < 30 (oversold).
|
||||
|
||||
**Sell signal** (SHORT):
|
||||
RSI > 70 (overbought).
|
||||
|
||||
Signal strength is proportional to how far the RSI is from its
|
||||
threshold, clamped to [0, 1].
|
||||
|
||||
* Buy strength = ``(30 - rsi) / 30``
|
||||
* Sell strength = ``(rsi - 70) / 30``
|
||||
"""
|
||||
|
||||
name: str = "mean_reversion"
|
||||
|
||||
async def evaluate(
|
||||
self,
|
||||
ticker: str,
|
||||
market: MarketSnapshot,
|
||||
sentiment: SentimentContext | None = None,
|
||||
) -> TradeSignal | None:
|
||||
if market.rsi is None:
|
||||
return None
|
||||
|
||||
rsi = market.rsi
|
||||
|
||||
if rsi < 30:
|
||||
direction = SignalDirection.LONG
|
||||
raw_strength = (30 - rsi) / 30
|
||||
elif rsi > 70:
|
||||
direction = SignalDirection.SHORT
|
||||
raw_strength = (rsi - 70) / 30
|
||||
else:
|
||||
# RSI in neutral territory — no opinion.
|
||||
return None
|
||||
|
||||
strength = max(0.0, min(1.0, raw_strength))
|
||||
|
||||
return TradeSignal(
|
||||
ticker=ticker,
|
||||
direction=direction,
|
||||
strength=strength,
|
||||
strategy_sources=[self.name],
|
||||
timestamp=datetime.now(tz=timezone.utc),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue