Files
Hydra-Engine/hydra/strategy/signal.py
2026-03-30 13:19:11 +09:00

58 lines
2.0 KiB
Python

# hydra/strategy/signal.py
import time
from dataclasses import dataclass
_EMA_FAST = "EMA_9"
_EMA_SLOW = "EMA_20"
_RSI = "RSI_14"
_RSI_OVERSOLD = 30.0
_RSI_OVERBOUGHT = 70.0
_TREND_UP_RSI_LOW = 45.0
_TREND_UP_RSI_HIGH = 75.0
_TREND_DOWN_RSI_LOW = 25.0
_TREND_DOWN_RSI_HIGH = 55.0
@dataclass
class Signal:
signal: str # "BUY" | "SELL" | "HOLD"
reason: str
price: float
ts: int
class SignalGenerator:
def generate(self, indicators: dict, regime: str, close: float) -> Signal:
ema9 = indicators.get(_EMA_FAST)
ema20 = indicators.get(_EMA_SLOW)
rsi = indicators.get(_RSI)
ts = int(time.time() * 1000)
if regime == "volatile":
return Signal(signal="HOLD", reason="volatile: skip", price=close, ts=ts)
if regime == "trending_up":
if ema9 is not None and ema20 is not None and ema9 > ema20:
if rsi is not None and _TREND_UP_RSI_LOW < rsi < _TREND_UP_RSI_HIGH:
return Signal(signal="BUY", reason="trend_up: ema_cross+rsi",
price=close, ts=ts)
return Signal(signal="HOLD", reason="trend_up: no_entry", price=close, ts=ts)
if regime == "trending_down":
if ema9 is not None and ema20 is not None and ema9 < ema20:
if rsi is not None and _TREND_DOWN_RSI_LOW < rsi < _TREND_DOWN_RSI_HIGH:
return Signal(signal="SELL", reason="trend_down: ema_cross+rsi",
price=close, ts=ts)
return Signal(signal="HOLD", reason="trend_down: no_entry", price=close, ts=ts)
# ranging (default)
if rsi is not None:
if rsi < _RSI_OVERSOLD:
return Signal(signal="BUY", reason="ranging: rsi_oversold",
price=close, ts=ts)
if rsi > _RSI_OVERBOUGHT:
return Signal(signal="SELL", reason="ranging: rsi_overbought",
price=close, ts=ts)
return Signal(signal="HOLD", reason="ranging: neutral", price=close, ts=ts)