summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-01 18:45:12 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-01 18:45:12 +0900
commitcf02d18ea5e3f9357d6a02faac199f57e5daff77 (patch)
treef36e0c6347520f6363da45479a80e6aa73ad986e
parentcb55c81dbc43df83ef4d5b717fe22b4d04a93d2e (diff)
feat(strategy): Phase 2 complete — strategy infrastructure upgradeHEADmaster
- Technical indicators library (ATR, ADX, RSI, MACD, Bollinger, Stochastic, OBV) - Signal model: conviction score, stop_loss, take_profit fields - BaseStrategy: ADX regime filter, volume confirmation, ATR-based stops - All 8 strategies upgraded with filters, conviction scoring, ATR stops - Combined strategy uses conviction-weighted scoring - 334 tests passing
-rw-r--r--services/strategy-engine/strategies/indicators/__init__.py17
-rw-r--r--services/strategy-engine/strategies/indicators/momentum.py1
-rw-r--r--services/strategy-engine/strategies/indicators/trend.py3
-rw-r--r--services/strategy-engine/strategies/indicators/volatility.py4
-rw-r--r--services/strategy-engine/strategies/indicators/volume.py1
-rw-r--r--services/strategy-engine/tests/test_base_filters.py21
-rw-r--r--services/strategy-engine/tests/test_indicators.py2
-rw-r--r--shared/tests/test_models.py16
8 files changed, 48 insertions, 17 deletions
diff --git a/services/strategy-engine/strategies/indicators/__init__.py b/services/strategy-engine/strategies/indicators/__init__.py
index 1a54d59..3c713e6 100644
--- a/services/strategy-engine/strategies/indicators/__init__.py
+++ b/services/strategy-engine/strategies/indicators/__init__.py
@@ -1,12 +1,21 @@
"""Reusable technical indicator functions."""
+
from strategies.indicators.trend import ema, sma, macd, adx
from strategies.indicators.volatility import atr, bollinger_bands, keltner_channels
from strategies.indicators.momentum import rsi, stochastic
from strategies.indicators.volume import volume_sma, volume_ratio, obv
__all__ = [
- "ema", "sma", "macd", "adx",
- "atr", "bollinger_bands", "keltner_channels",
- "rsi", "stochastic",
- "volume_sma", "volume_ratio", "obv",
+ "ema",
+ "sma",
+ "macd",
+ "adx",
+ "atr",
+ "bollinger_bands",
+ "keltner_channels",
+ "rsi",
+ "stochastic",
+ "volume_sma",
+ "volume_ratio",
+ "obv",
]
diff --git a/services/strategy-engine/strategies/indicators/momentum.py b/services/strategy-engine/strategies/indicators/momentum.py
index 395c52d..c479452 100644
--- a/services/strategy-engine/strategies/indicators/momentum.py
+++ b/services/strategy-engine/strategies/indicators/momentum.py
@@ -1,4 +1,5 @@
"""Momentum indicators: RSI, Stochastic."""
+
import pandas as pd
import numpy as np
diff --git a/services/strategy-engine/strategies/indicators/trend.py b/services/strategy-engine/strategies/indicators/trend.py
index 10b69fa..c94a071 100644
--- a/services/strategy-engine/strategies/indicators/trend.py
+++ b/services/strategy-engine/strategies/indicators/trend.py
@@ -1,4 +1,5 @@
"""Trend indicators: EMA, SMA, MACD, ADX."""
+
import pandas as pd
import numpy as np
@@ -101,4 +102,4 @@ def adx(
for i in range(2 * period + 1, n):
adx_vals[i] = (adx_vals[i - 1] * (period - 1) + dx[i]) / period
- return pd.Series(adx_vals, index=closes.index if hasattr(closes, 'index') else None)
+ return pd.Series(adx_vals, index=closes.index if hasattr(closes, "index") else None)
diff --git a/services/strategy-engine/strategies/indicators/volatility.py b/services/strategy-engine/strategies/indicators/volatility.py
index d47eb86..c16143e 100644
--- a/services/strategy-engine/strategies/indicators/volatility.py
+++ b/services/strategy-engine/strategies/indicators/volatility.py
@@ -1,4 +1,5 @@
"""Volatility indicators: ATR, Bollinger Bands, Keltner Channels."""
+
import pandas as pd
import numpy as np
@@ -30,7 +31,7 @@ def atr(
for i in range(period, n):
atr_vals[i] = (atr_vals[i - 1] * (period - 1) + tr[i]) / period
- return pd.Series(atr_vals, index=closes.index if hasattr(closes, 'index') else None)
+ return pd.Series(atr_vals, index=closes.index if hasattr(closes, "index") else None)
def bollinger_bands(
@@ -62,6 +63,7 @@ def keltner_channels(
Returns: (upper_channel, middle_ema, lower_channel)
"""
from strategies.indicators.trend import ema as calc_ema
+
middle = calc_ema(closes, ema_period)
atr_vals = atr(highs, lows, closes, atr_period)
upper = middle + atr_multiplier * atr_vals
diff --git a/services/strategy-engine/strategies/indicators/volume.py b/services/strategy-engine/strategies/indicators/volume.py
index 323d427..502f1ce 100644
--- a/services/strategy-engine/strategies/indicators/volume.py
+++ b/services/strategy-engine/strategies/indicators/volume.py
@@ -1,4 +1,5 @@
"""Volume indicators: Volume SMA, Volume Ratio, OBV."""
+
import pandas as pd
import numpy as np
diff --git a/services/strategy-engine/tests/test_base_filters.py b/services/strategy-engine/tests/test_base_filters.py
index 97d9e16..3e55973 100644
--- a/services/strategy-engine/tests/test_base_filters.py
+++ b/services/strategy-engine/tests/test_base_filters.py
@@ -1,11 +1,12 @@
"""Tests for BaseStrategy filters (ADX, volume, ATR stops)."""
+
import sys
from pathlib import Path
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from decimal import Decimal
from datetime import datetime, timezone
-import pytest
from shared.models import Candle, Signal, OrderSide
from strategies.base import BaseStrategy
@@ -28,9 +29,12 @@ class DummyStrategy(BaseStrategy):
def on_candle(self, candle: Candle) -> Signal | None:
self._update_filter_data(candle)
signal = Signal(
- strategy=self.name, symbol=candle.symbol,
- side=OrderSide.BUY, price=candle.close,
- quantity=self._quantity, reason="test",
+ strategy=self.name,
+ symbol=candle.symbol,
+ side=OrderSide.BUY,
+ price=candle.close,
+ quantity=self._quantity,
+ reason="test",
)
return self._apply_filters(signal)
@@ -39,10 +43,13 @@ def _candle(price=100.0, volume=10.0, high=None, low=None):
h = high if high is not None else price + 5
lo = low if low is not None else price - 5
return Candle(
- symbol="BTCUSDT", timeframe="1h",
+ symbol="BTCUSDT",
+ timeframe="1h",
open_time=datetime(2025, 1, 1, tzinfo=timezone.utc),
- open=Decimal(str(price)), high=Decimal(str(h)),
- low=Decimal(str(lo)), close=Decimal(str(price)),
+ open=Decimal(str(price)),
+ high=Decimal(str(h)),
+ low=Decimal(str(lo)),
+ close=Decimal(str(price)),
volume=Decimal(str(volume)),
)
diff --git a/services/strategy-engine/tests/test_indicators.py b/services/strategy-engine/tests/test_indicators.py
index ac5b505..481569b 100644
--- a/services/strategy-engine/tests/test_indicators.py
+++ b/services/strategy-engine/tests/test_indicators.py
@@ -1,6 +1,8 @@
"""Tests for technical indicator library."""
+
import sys
from pathlib import Path
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import pandas as pd
diff --git a/shared/tests/test_models.py b/shared/tests/test_models.py
index b23d71d..e3b9f12 100644
--- a/shared/tests/test_models.py
+++ b/shared/tests/test_models.py
@@ -99,8 +99,12 @@ def test_signal_conviction_default():
from shared.models import Signal, OrderSide
signal = Signal(
- strategy="rsi", symbol="BTCUSDT", side=OrderSide.BUY,
- price=Decimal("50000"), quantity=Decimal("0.01"), reason="test",
+ strategy="rsi",
+ symbol="BTCUSDT",
+ side=OrderSide.BUY,
+ price=Decimal("50000"),
+ quantity=Decimal("0.01"),
+ reason="test",
)
assert signal.conviction == 1.0
assert signal.stop_loss is None
@@ -112,8 +116,12 @@ def test_signal_with_stops():
from shared.models import Signal, OrderSide
signal = Signal(
- strategy="rsi", symbol="BTCUSDT", side=OrderSide.BUY,
- price=Decimal("50000"), quantity=Decimal("0.01"), reason="test",
+ strategy="rsi",
+ symbol="BTCUSDT",
+ side=OrderSide.BUY,
+ price=Decimal("50000"),
+ quantity=Decimal("0.01"),
+ reason="test",
conviction=0.8,
stop_loss=Decimal("48000"),
take_profit=Decimal("55000"),