summaryrefslogtreecommitdiff
path: root/services/strategy-engine/strategies/macd_strategy.py
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 09:19:31 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 09:19:31 +0900
commit3a256abb8c04ef07f125b0fb41f8f9090d97b136 (patch)
tree4ae95445bff10b2e74b589fd55a0015c44d66cb5 /services/strategy-engine/strategies/macd_strategy.py
parentda6c9598f92057e2fcbb206aa7466b6997a455f3 (diff)
feat(strategy): add RSI divergence detection and MACD signal-line crossover
Diffstat (limited to 'services/strategy-engine/strategies/macd_strategy.py')
-rw-r--r--services/strategy-engine/strategies/macd_strategy.py50
1 files changed, 44 insertions, 6 deletions
diff --git a/services/strategy-engine/strategies/macd_strategy.py b/services/strategy-engine/strategies/macd_strategy.py
index 67c5e44..4ce0737 100644
--- a/services/strategy-engine/strategies/macd_strategy.py
+++ b/services/strategy-engine/strategies/macd_strategy.py
@@ -18,6 +18,8 @@ class MacdStrategy(BaseStrategy):
self._quantity: Decimal = Decimal("0.01")
self._closes: deque[float] = deque(maxlen=500)
self._prev_histogram: float | None = None
+ self._prev_macd: float | None = None
+ self._prev_signal: float | None = None
@property
def warmup_period(self) -> int:
@@ -54,6 +56,8 @@ class MacdStrategy(BaseStrategy):
def reset(self) -> None:
self._closes.clear()
self._prev_histogram = None
+ self._prev_macd = None
+ self._prev_signal = None
def _macd_conviction(self, histogram_value: float, price: float) -> float:
"""Map histogram magnitude to conviction (0.1-1.0).
@@ -81,13 +85,45 @@ class MacdStrategy(BaseStrategy):
histogram = macd_line - signal_line
current_histogram = float(histogram.iloc[-1])
- signal = None
+ macd_val = float(macd_line.iloc[-1])
+ signal_val = float(signal_line.iloc[-1])
+ result_signal = None
+
+ # Signal-line crossover detection (MACD crosses signal line directly)
+ if self._prev_macd is not None and self._prev_signal is not None:
+ # Bullish: MACD crosses above signal
+ if self._prev_macd <= self._prev_signal and macd_val > signal_val:
+ distance_from_zero = abs(macd_val) / float(candle.close) * 1000
+ conv = min(max(distance_from_zero, 0.3), 1.0)
+ result_signal = Signal(
+ strategy=self.name,
+ symbol=candle.symbol,
+ side=OrderSide.BUY,
+ price=candle.close,
+ quantity=self._quantity,
+ conviction=conv,
+ reason=f"MACD signal-line bullish crossover",
+ )
+ # Bearish: MACD crosses below signal
+ elif self._prev_macd >= self._prev_signal and macd_val < signal_val:
+ distance_from_zero = abs(macd_val) / float(candle.close) * 1000
+ conv = min(max(distance_from_zero, 0.3), 1.0)
+ result_signal = Signal(
+ strategy=self.name,
+ symbol=candle.symbol,
+ side=OrderSide.SELL,
+ price=candle.close,
+ quantity=self._quantity,
+ conviction=conv,
+ reason=f"MACD signal-line bearish crossover",
+ )
- if self._prev_histogram is not None:
+ # Histogram crossover detection (existing logic, as secondary signal)
+ if result_signal is None and self._prev_histogram is not None:
conviction = self._macd_conviction(current_histogram, float(candle.close))
# Bullish crossover: histogram crosses from negative to positive
if self._prev_histogram <= 0 and current_histogram > 0:
- signal = Signal(
+ result_signal = Signal(
strategy=self.name,
symbol=candle.symbol,
side=OrderSide.BUY,
@@ -98,7 +134,7 @@ class MacdStrategy(BaseStrategy):
)
# Bearish crossover: histogram crosses from positive to negative
elif self._prev_histogram >= 0 and current_histogram < 0:
- signal = Signal(
+ result_signal = Signal(
strategy=self.name,
symbol=candle.symbol,
side=OrderSide.SELL,
@@ -109,6 +145,8 @@ class MacdStrategy(BaseStrategy):
)
self._prev_histogram = current_histogram
- if signal is not None:
- return self._apply_filters(signal)
+ self._prev_macd = macd_val
+ self._prev_signal = signal_val
+ if result_signal is not None:
+ return self._apply_filters(result_signal)
return None