diff options
| author | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2026-04-01 18:45:12 +0900 |
|---|---|---|
| committer | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2026-04-01 18:45:12 +0900 |
| commit | cf02d18ea5e3f9357d6a02faac199f57e5daff77 (patch) | |
| tree | f36e0c6347520f6363da45479a80e6aa73ad986e | |
| parent | cb55c81dbc43df83ef4d5b717fe22b4d04a93d2e (diff) | |
- 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
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"), |
