58 lines
2.0 KiB
Python
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)
|