summaryrefslogtreecommitdiff
path: root/services/strategy-engine
diff options
context:
space:
mode:
Diffstat (limited to 'services/strategy-engine')
-rw-r--r--services/strategy-engine/src/strategy_engine/config.py1
-rw-r--r--services/strategy-engine/src/strategy_engine/engine.py5
-rw-r--r--services/strategy-engine/src/strategy_engine/main.py9
-rw-r--r--services/strategy-engine/src/strategy_engine/plugin_loader.py7
-rw-r--r--services/strategy-engine/strategies/volume_profile_strategy.py3
-rw-r--r--services/strategy-engine/tests/conftest.py1
-rw-r--r--services/strategy-engine/tests/test_bollinger_strategy.py2
-rw-r--r--services/strategy-engine/tests/test_ema_crossover_strategy.py2
-rw-r--r--services/strategy-engine/tests/test_engine.py3
-rw-r--r--services/strategy-engine/tests/test_grid_strategy.py16
-rw-r--r--services/strategy-engine/tests/test_macd_strategy.py2
-rw-r--r--services/strategy-engine/tests/test_plugin_loader.py2
-rw-r--r--services/strategy-engine/tests/test_rsi_strategy.py2
-rw-r--r--services/strategy-engine/tests/test_volume_profile_strategy.py54
-rw-r--r--services/strategy-engine/tests/test_vwap_strategy.py2
15 files changed, 68 insertions, 43 deletions
diff --git a/services/strategy-engine/src/strategy_engine/config.py b/services/strategy-engine/src/strategy_engine/config.py
index 2864b09..e3a49c2 100644
--- a/services/strategy-engine/src/strategy_engine/config.py
+++ b/services/strategy-engine/src/strategy_engine/config.py
@@ -1,4 +1,5 @@
"""Strategy Engine configuration."""
+
from shared.config import Settings
diff --git a/services/strategy-engine/src/strategy_engine/engine.py b/services/strategy-engine/src/strategy_engine/engine.py
index 09dbf65..d401aee 100644
--- a/services/strategy-engine/src/strategy_engine/engine.py
+++ b/services/strategy-engine/src/strategy_engine/engine.py
@@ -1,4 +1,5 @@
"""Strategy Engine: consumes candle events and publishes signals."""
+
import logging
from shared.broker import RedisBroker
@@ -36,9 +37,7 @@ class StrategyEngine:
try:
signal = strategy.on_candle(candle)
except Exception as exc:
- logger.error(
- "Strategy %s raised on candle: %s", strategy.name, exc
- )
+ logger.error("Strategy %s raised on candle: %s", strategy.name, exc)
continue
if signal is not None:
diff --git a/services/strategy-engine/src/strategy_engine/main.py b/services/strategy-engine/src/strategy_engine/main.py
index 2e3c4ac..53681d1 100644
--- a/services/strategy-engine/src/strategy_engine/main.py
+++ b/services/strategy-engine/src/strategy_engine/main.py
@@ -1,4 +1,5 @@
"""Strategy Engine Service entry point."""
+
import asyncio
from pathlib import Path
@@ -20,7 +21,9 @@ async def run() -> None:
config = StrategyConfig()
log = setup_logging("strategy-engine", config.log_level, config.log_format)
metrics = ServiceMetrics("strategy_engine")
- notifier = TelegramNotifier(bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id)
+ notifier = TelegramNotifier(
+ bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id
+ )
broker = RedisBroker(config.redis_url)
@@ -53,7 +56,9 @@ async def run() -> None:
while True:
last_id = await engine.process_once(stream, last_id)
- metrics.events_processed.labels(service="strategy-engine", event_type="candle").inc()
+ metrics.events_processed.labels(
+ service="strategy-engine", event_type="candle"
+ ).inc()
except Exception as exc:
log.error("fatal_error", error=str(exc))
await notifier.send_error(str(exc), "strategy-engine")
diff --git a/services/strategy-engine/src/strategy_engine/plugin_loader.py b/services/strategy-engine/src/strategy_engine/plugin_loader.py
index f99b670..62e4160 100644
--- a/services/strategy-engine/src/strategy_engine/plugin_loader.py
+++ b/services/strategy-engine/src/strategy_engine/plugin_loader.py
@@ -1,4 +1,5 @@
"""Dynamic plugin loader for strategy modules."""
+
import importlib.util
import sys
from pathlib import Path
@@ -29,11 +30,7 @@ def load_strategies(strategies_dir: Path) -> list[BaseStrategy]:
for attr_name in dir(module):
obj = getattr(module, attr_name)
- if (
- isinstance(obj, type)
- and issubclass(obj, BaseStrategy)
- and obj is not BaseStrategy
- ):
+ if isinstance(obj, type) and issubclass(obj, BaseStrategy) and obj is not BaseStrategy:
instance = obj()
yaml_path = config_dir / f"{path.stem}.yaml"
if yaml_path.exists():
diff --git a/services/strategy-engine/strategies/volume_profile_strategy.py b/services/strategy-engine/strategies/volume_profile_strategy.py
index 684c33c..e9463bf 100644
--- a/services/strategy-engine/strategies/volume_profile_strategy.py
+++ b/services/strategy-engine/strategies/volume_profile_strategy.py
@@ -39,9 +39,8 @@ class VolumeProfileStrategy(BaseStrategy):
if len(data) < self._lookback_period:
return None
- recent = data[-self._lookback_period:]
+ recent = data[-self._lookback_period :]
prices = np.array([c[0] for c in recent])
- volumes = np.array([c[1] for c in recent])
min_price = prices.min()
max_price = prices.max()
diff --git a/services/strategy-engine/tests/conftest.py b/services/strategy-engine/tests/conftest.py
index c9ef308..eb31b23 100644
--- a/services/strategy-engine/tests/conftest.py
+++ b/services/strategy-engine/tests/conftest.py
@@ -1,4 +1,5 @@
"""Pytest configuration: ensure strategies/ is importable."""
+
import sys
from pathlib import Path
diff --git a/services/strategy-engine/tests/test_bollinger_strategy.py b/services/strategy-engine/tests/test_bollinger_strategy.py
index b3d17ac..348a9e0 100644
--- a/services/strategy-engine/tests/test_bollinger_strategy.py
+++ b/services/strategy-engine/tests/test_bollinger_strategy.py
@@ -1,8 +1,8 @@
"""Tests for the Bollinger Bands strategy."""
+
from datetime import datetime, timezone
from decimal import Decimal
-import pytest
from shared.models import Candle, OrderSide
from strategies.bollinger_strategy import BollingerStrategy
diff --git a/services/strategy-engine/tests/test_ema_crossover_strategy.py b/services/strategy-engine/tests/test_ema_crossover_strategy.py
index 5a40319..0cf767b 100644
--- a/services/strategy-engine/tests/test_ema_crossover_strategy.py
+++ b/services/strategy-engine/tests/test_ema_crossover_strategy.py
@@ -1,8 +1,8 @@
"""Tests for the EMA Crossover strategy."""
+
from datetime import datetime, timezone
from decimal import Decimal
-import pytest
from shared.models import Candle, OrderSide
from strategies.ema_crossover_strategy import EmaCrossoverStrategy
diff --git a/services/strategy-engine/tests/test_engine.py b/services/strategy-engine/tests/test_engine.py
index 33ad4dd..ac9a596 100644
--- a/services/strategy-engine/tests/test_engine.py
+++ b/services/strategy-engine/tests/test_engine.py
@@ -1,4 +1,5 @@
"""Tests for the StrategyEngine."""
+
from datetime import datetime, timezone
from decimal import Decimal
from unittest.mock import AsyncMock, MagicMock
@@ -6,7 +7,7 @@ from unittest.mock import AsyncMock, MagicMock
import pytest
from shared.models import Candle, Signal, OrderSide
-from shared.events import CandleEvent, SignalEvent
+from shared.events import CandleEvent
from strategy_engine.engine import StrategyEngine
diff --git a/services/strategy-engine/tests/test_grid_strategy.py b/services/strategy-engine/tests/test_grid_strategy.py
index d96ebba..79eb22a 100644
--- a/services/strategy-engine/tests/test_grid_strategy.py
+++ b/services/strategy-engine/tests/test_grid_strategy.py
@@ -1,8 +1,8 @@
"""Tests for the Grid strategy."""
+
from datetime import datetime, timezone
from decimal import Decimal
-import pytest
from shared.models import Candle, OrderSide
from strategies.grid_strategy import GridStrategy
@@ -23,12 +23,14 @@ def make_candle(close: float) -> Candle:
def _configured_strategy() -> GridStrategy:
strategy = GridStrategy()
- strategy.configure({
- "lower_price": 48000,
- "upper_price": 52000,
- "grid_count": 5,
- "quantity": "0.01",
- })
+ strategy.configure(
+ {
+ "lower_price": 48000,
+ "upper_price": 52000,
+ "grid_count": 5,
+ "quantity": "0.01",
+ }
+ )
return strategy
diff --git a/services/strategy-engine/tests/test_macd_strategy.py b/services/strategy-engine/tests/test_macd_strategy.py
index e1ae2a3..9931b43 100644
--- a/services/strategy-engine/tests/test_macd_strategy.py
+++ b/services/strategy-engine/tests/test_macd_strategy.py
@@ -1,8 +1,8 @@
"""Tests for the MACD strategy."""
+
from datetime import datetime, timezone
from decimal import Decimal
-import pytest
from shared.models import Candle, OrderSide
from strategies.macd_strategy import MacdStrategy
diff --git a/services/strategy-engine/tests/test_plugin_loader.py b/services/strategy-engine/tests/test_plugin_loader.py
index 9496bab..5191fc3 100644
--- a/services/strategy-engine/tests/test_plugin_loader.py
+++ b/services/strategy-engine/tests/test_plugin_loader.py
@@ -1,7 +1,7 @@
"""Tests for the plugin loader."""
+
from pathlib import Path
-import pytest
from strategy_engine.plugin_loader import load_strategies
diff --git a/services/strategy-engine/tests/test_rsi_strategy.py b/services/strategy-engine/tests/test_rsi_strategy.py
index 90fface..2a2f4e7 100644
--- a/services/strategy-engine/tests/test_rsi_strategy.py
+++ b/services/strategy-engine/tests/test_rsi_strategy.py
@@ -1,8 +1,8 @@
"""Tests for the RSI strategy."""
+
from datetime import datetime, timezone
from decimal import Decimal
-import pytest
from shared.models import Candle, OrderSide
from strategies.rsi_strategy import RsiStrategy
diff --git a/services/strategy-engine/tests/test_volume_profile_strategy.py b/services/strategy-engine/tests/test_volume_profile_strategy.py
index be123b0..71f0eca 100644
--- a/services/strategy-engine/tests/test_volume_profile_strategy.py
+++ b/services/strategy-engine/tests/test_volume_profile_strategy.py
@@ -1,8 +1,8 @@
"""Tests for the Volume Profile strategy."""
+
from datetime import datetime, timezone
from decimal import Decimal
-import pytest
from shared.models import Candle, OrderSide
from strategies.volume_profile_strategy import VolumeProfileStrategy
@@ -39,17 +39,27 @@ def test_volume_profile_no_signal_insufficient_data():
def test_volume_profile_buy_at_value_area_low():
"""Concentrate volume around 95-105, price drops to 88, bounces back to 99."""
strategy = VolumeProfileStrategy()
- strategy.configure({
- "lookback_period": 10,
- "num_bins": 5,
- "value_area_pct": 0.7,
- "quantity": "0.01",
- })
+ strategy.configure(
+ {
+ "lookback_period": 10,
+ "num_bins": 5,
+ "value_area_pct": 0.7,
+ "quantity": "0.01",
+ }
+ )
# Build profile: 10 candles with volume concentrated around 95-105
profile_data = [
- (95, 50), (97, 50), (99, 100), (100, 100), (101, 100),
- (103, 50), (105, 50), (100, 100), (99, 100), (101, 50),
+ (95, 50),
+ (97, 50),
+ (99, 100),
+ (100, 100),
+ (101, 100),
+ (103, 50),
+ (105, 50),
+ (100, 100),
+ (99, 100),
+ (101, 50),
]
for price, vol in profile_data:
strategy.on_candle(make_candle(price, vol))
@@ -67,17 +77,27 @@ def test_volume_profile_buy_at_value_area_low():
def test_volume_profile_sell_at_value_area_high():
"""Concentrate volume around 95-105, price rises to 112, pulls back to 101."""
strategy = VolumeProfileStrategy()
- strategy.configure({
- "lookback_period": 10,
- "num_bins": 5,
- "value_area_pct": 0.7,
- "quantity": "0.01",
- })
+ strategy.configure(
+ {
+ "lookback_period": 10,
+ "num_bins": 5,
+ "value_area_pct": 0.7,
+ "quantity": "0.01",
+ }
+ )
# Build profile: 10 candles with volume concentrated around 95-105
profile_data = [
- (95, 50), (97, 50), (99, 100), (100, 100), (101, 100),
- (103, 50), (105, 50), (100, 100), (99, 100), (101, 50),
+ (95, 50),
+ (97, 50),
+ (99, 100),
+ (100, 100),
+ (101, 100),
+ (103, 50),
+ (105, 50),
+ (100, 100),
+ (99, 100),
+ (101, 50),
]
for price, vol in profile_data:
strategy.on_candle(make_candle(price, vol))
diff --git a/services/strategy-engine/tests/test_vwap_strategy.py b/services/strategy-engine/tests/test_vwap_strategy.py
index 37d35bc..5d76b04 100644
--- a/services/strategy-engine/tests/test_vwap_strategy.py
+++ b/services/strategy-engine/tests/test_vwap_strategy.py
@@ -1,8 +1,8 @@
"""Tests for the VWAP strategy."""
+
from datetime import datetime, timezone
from decimal import Decimal
-import pytest
from shared.models import Candle, OrderSide
from strategies.vwap_strategy import VwapStrategy