summaryrefslogtreecommitdiff
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/backtester/src/backtester/config.py1
-rw-r--r--services/backtester/src/backtester/engine.py5
-rw-r--r--services/backtester/src/backtester/main.py25
-rw-r--r--services/backtester/src/backtester/metrics.py23
-rw-r--r--services/backtester/src/backtester/reporter.py1
-rw-r--r--services/backtester/src/backtester/simulator.py1
-rw-r--r--services/backtester/tests/test_engine.py4
-rw-r--r--services/backtester/tests/test_metrics.py17
-rw-r--r--services/backtester/tests/test_reporter.py1
-rw-r--r--services/backtester/tests/test_simulator.py4
-rw-r--r--services/data-collector/src/data_collector/binance_rest.py5
-rw-r--r--services/data-collector/src/data_collector/binance_ws.py5
-rw-r--r--services/data-collector/src/data_collector/main.py12
-rw-r--r--services/data-collector/src/data_collector/storage.py1
-rw-r--r--services/data-collector/tests/test_binance_rest.py13
-rw-r--r--services/data-collector/tests/test_storage.py1
-rw-r--r--services/order-executor/src/order_executor/config.py1
-rw-r--r--services/order-executor/src/order_executor/executor.py21
-rw-r--r--services/order-executor/src/order_executor/main.py17
-rw-r--r--services/order-executor/src/order_executor/risk_manager.py6
-rw-r--r--services/order-executor/tests/test_executor.py1
-rw-r--r--services/order-executor/tests/test_risk_manager.py8
-rw-r--r--services/portfolio-manager/src/portfolio_manager/config.py1
-rw-r--r--services/portfolio-manager/src/portfolio_manager/main.py13
-rw-r--r--services/portfolio-manager/src/portfolio_manager/pnl.py1
-rw-r--r--services/portfolio-manager/src/portfolio_manager/portfolio.py1
-rw-r--r--services/portfolio-manager/tests/test_pnl.py1
-rw-r--r--services/portfolio-manager/tests/test_portfolio.py1
-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
43 files changed, 189 insertions, 113 deletions
diff --git a/services/backtester/src/backtester/config.py b/services/backtester/src/backtester/config.py
index bfbc196..5a912f3 100644
--- a/services/backtester/src/backtester/config.py
+++ b/services/backtester/src/backtester/config.py
@@ -1,4 +1,5 @@
"""Configuration for the backtester service."""
+
from pydantic_settings import BaseSettings
diff --git a/services/backtester/src/backtester/engine.py b/services/backtester/src/backtester/engine.py
index 386309b..0441011 100644
--- a/services/backtester/src/backtester/engine.py
+++ b/services/backtester/src/backtester/engine.py
@@ -1,4 +1,5 @@
"""Backtesting engine that runs strategies against historical candle data."""
+
from __future__ import annotations
from dataclasses import dataclass, field
@@ -98,9 +99,7 @@ class BacktestEngine:
)
for t in simulator.trades
]
- detailed = compute_detailed_metrics(
- trade_records, self._initial_balance, final_balance
- )
+ detailed = compute_detailed_metrics(trade_records, self._initial_balance, final_balance)
return BacktestResult(
strategy_name=self._strategy.name,
diff --git a/services/backtester/src/backtester/main.py b/services/backtester/src/backtester/main.py
index ab69ee1..c9b3890 100644
--- a/services/backtester/src/backtester/main.py
+++ b/services/backtester/src/backtester/main.py
@@ -1,22 +1,21 @@
"""Main entry point for the backtester service."""
-import sys
+
import os
+import sys
from decimal import Decimal
+from shared.db import Database # noqa: E402
+from shared.models import Candle # noqa: E402
+
+from backtester.config import BacktestConfig # noqa: E402
+from backtester.engine import BacktestEngine # noqa: E402
+from backtester.reporter import format_report # noqa: E402
+
# Allow importing strategies from the strategy-engine service
-_STRATEGY_ENGINE_PATH = os.path.join(
- os.path.dirname(__file__), "../../../../strategy-engine"
-)
+_STRATEGY_ENGINE_PATH = os.path.join(os.path.dirname(__file__), "../../../../strategy-engine")
if _STRATEGY_ENGINE_PATH not in sys.path:
sys.path.insert(0, _STRATEGY_ENGINE_PATH)
-from shared.db import Database
-from shared.models import Candle
-
-from backtester.config import BacktestConfig
-from backtester.engine import BacktestEngine
-from backtester.reporter import format_report
-
async def run_backtest() -> str:
"""Load strategy, fetch candles, run backtest, and return a formatted report."""
@@ -35,9 +34,7 @@ async def run_backtest() -> str:
strategy = strategy_cls()
strategy.configure({})
except Exception as exc:
- raise RuntimeError(
- f"Failed to load strategy '{config.strategy_name}': {exc}"
- ) from exc
+ raise RuntimeError(f"Failed to load strategy '{config.strategy_name}': {exc}") from exc
db = Database(config.database_url)
await db.connect()
diff --git a/services/backtester/src/backtester/metrics.py b/services/backtester/src/backtester/metrics.py
index 15be0e6..caf8477 100644
--- a/services/backtester/src/backtester/metrics.py
+++ b/services/backtester/src/backtester/metrics.py
@@ -1,4 +1,5 @@
"""Detailed backtest metrics: Sharpe, Sortino, drawdown, and more."""
+
from __future__ import annotations
import math
@@ -87,7 +88,9 @@ def compute_detailed_metrics(
)
pairs = _pair_trades(trades)
- total_return = float(final_balance - initial_balance) / float(initial_balance) if initial_balance else 0.0
+ total_return = (
+ float(final_balance - initial_balance) / float(initial_balance) if initial_balance else 0.0
+ )
if not pairs:
return DetailedMetrics(
@@ -114,7 +117,9 @@ def compute_detailed_metrics(
gross_profit = sum(p["pnl"] for p in wins)
gross_loss = abs(sum(p["pnl"] for p in losses))
- profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf") if gross_profit > 0 else 0.0
+ profit_factor = (
+ gross_profit / gross_loss if gross_loss > 0 else float("inf") if gross_profit > 0 else 0.0
+ )
avg_win = gross_profit / winning_trades if winning_trades else 0.0
avg_loss = gross_loss / losing_trades if losing_trades else 0.0
@@ -123,7 +128,11 @@ def compute_detailed_metrics(
# Holding periods
holding_periods = [p["holding_period"] for p in pairs]
- avg_holding = sum(holding_periods, timedelta(0)) / len(holding_periods) if holding_periods else timedelta(0)
+ avg_holding = (
+ sum(holding_periods, timedelta(0)) / len(holding_periods)
+ if holding_periods
+ else timedelta(0)
+ )
# Build equity curve from pairs
init_bal = float(initial_balance)
@@ -153,7 +162,11 @@ def compute_detailed_metrics(
max_dd = dd
# Duration: use pair exit times
if i <= len(pairs) and dd_start_idx < len(pairs):
- start_time = pairs[dd_start_idx]["exit_time"] if dd_start_idx < len(pairs) else pairs[0]["entry_time"]
+ start_time = (
+ pairs[dd_start_idx]["exit_time"]
+ if dd_start_idx < len(pairs)
+ else pairs[0]["entry_time"]
+ )
end_time = pairs[i - 1]["exit_time"]
max_dd_duration = end_time - start_time if end_time > start_time else timedelta(0)
@@ -171,7 +184,7 @@ def compute_detailed_metrics(
if len(returns) > 1:
mean_r = sum(returns) / len(returns)
downside = [min(r, 0.0) for r in returns]
- downside_var = sum(d ** 2 for d in downside) / (len(downside) - 1)
+ downside_var = sum(d**2 for d in downside) / (len(downside) - 1)
downside_std = math.sqrt(downside_var)
sortino = (mean_r / downside_std * math.sqrt(365)) if downside_std > 0 else 0.0
else:
diff --git a/services/backtester/src/backtester/reporter.py b/services/backtester/src/backtester/reporter.py
index e9e9936..cc5d67b 100644
--- a/services/backtester/src/backtester/reporter.py
+++ b/services/backtester/src/backtester/reporter.py
@@ -1,4 +1,5 @@
"""Report formatting for backtest results."""
+
from __future__ import annotations
import csv
diff --git a/services/backtester/src/backtester/simulator.py b/services/backtester/src/backtester/simulator.py
index b897c5a..33eeb76 100644
--- a/services/backtester/src/backtester/simulator.py
+++ b/services/backtester/src/backtester/simulator.py
@@ -1,4 +1,5 @@
"""Simulated order executor for backtesting."""
+
from dataclasses import dataclass, field
from datetime import datetime, timezone
from decimal import Decimal
diff --git a/services/backtester/tests/test_engine.py b/services/backtester/tests/test_engine.py
index 1a25e1c..6962477 100644
--- a/services/backtester/tests/test_engine.py
+++ b/services/backtester/tests/test_engine.py
@@ -1,13 +1,13 @@
"""Tests for the BacktestEngine."""
+
from datetime import datetime, timezone
from decimal import Decimal
from unittest.mock import MagicMock
-import pytest
from shared.models import Candle, Signal, OrderSide
-from backtester.engine import BacktestEngine, BacktestResult
+from backtester.engine import BacktestEngine
def make_candle(symbol: str, price: float, timeframe: str = "1h") -> Candle:
diff --git a/services/backtester/tests/test_metrics.py b/services/backtester/tests/test_metrics.py
index b222b8a..68bc0b5 100644
--- a/services/backtester/tests/test_metrics.py
+++ b/services/backtester/tests/test_metrics.py
@@ -1,4 +1,6 @@
"""Tests for detailed backtest metrics."""
+
+import math
from datetime import datetime, timedelta, timezone
from decimal import Decimal
@@ -21,9 +23,9 @@ def test_compute_metrics_basic():
"""Two round-trip trades: 1 win, 1 loss. Verify counts and win_rate."""
trades = [
_make_trade("BUY", "100", 0),
- _make_trade("SELL", "120", 10), # win: +20
+ _make_trade("SELL", "120", 10), # win: +20
_make_trade("BUY", "130", 20),
- _make_trade("SELL", "110", 30), # loss: -20
+ _make_trade("SELL", "110", 30), # loss: -20
]
metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10000"))
@@ -37,9 +39,9 @@ def test_compute_metrics_profit_factor():
"""Verify profit_factor = gross_profit / gross_loss."""
trades = [
_make_trade("BUY", "100", 0),
- _make_trade("SELL", "150", 10), # win: +50
+ _make_trade("SELL", "150", 10), # win: +50
_make_trade("BUY", "150", 20),
- _make_trade("SELL", "130", 30), # loss: -20
+ _make_trade("SELL", "130", 30), # loss: -20
]
metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10030"))
@@ -51,9 +53,9 @@ def test_compute_metrics_max_drawdown():
"""Max drawdown should be > 0 when there is a losing trade after a peak."""
trades = [
_make_trade("BUY", "100", 0),
- _make_trade("SELL", "150", 10), # win: equity goes up
+ _make_trade("SELL", "150", 10), # win: equity goes up
_make_trade("BUY", "150", 20),
- _make_trade("SELL", "120", 30), # loss: equity drops
+ _make_trade("SELL", "120", 30), # loss: equity drops
]
metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10020"))
@@ -91,6 +93,3 @@ def test_compute_metrics_empty_trades():
assert metrics.calmar_ratio == 0.0
assert metrics.max_drawdown == 0.0
assert metrics.monthly_returns == {}
-
-
-import math
diff --git a/services/backtester/tests/test_reporter.py b/services/backtester/tests/test_reporter.py
index aef3fc6..2ea49c0 100644
--- a/services/backtester/tests/test_reporter.py
+++ b/services/backtester/tests/test_reporter.py
@@ -1,4 +1,5 @@
"""Tests for the report formatter."""
+
import json
from datetime import timedelta
from decimal import Decimal
diff --git a/services/backtester/tests/test_simulator.py b/services/backtester/tests/test_simulator.py
index 9d8b23e..e8c80ec 100644
--- a/services/backtester/tests/test_simulator.py
+++ b/services/backtester/tests/test_simulator.py
@@ -1,9 +1,9 @@
"""Tests for the OrderSimulator."""
+
from decimal import Decimal
-import pytest
-from shared.models import Signal, OrderSide, OrderType
+from shared.models import Signal, OrderSide
from backtester.simulator import OrderSimulator
diff --git a/services/data-collector/src/data_collector/binance_rest.py b/services/data-collector/src/data_collector/binance_rest.py
index af0eb77..eaf4e30 100644
--- a/services/data-collector/src/data_collector/binance_rest.py
+++ b/services/data-collector/src/data_collector/binance_rest.py
@@ -1,4 +1,5 @@
"""Binance REST API helpers for fetching historical candle data."""
+
from datetime import datetime, timezone
from decimal import Decimal
@@ -35,7 +36,7 @@ async def fetch_historical_candles(
candles: list[Candle] = []
for row in rows:
- ts_ms, o, h, l, c, v = row
+ ts_ms, o, h, low, c, v = row
open_time = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc)
candles.append(
Candle(
@@ -44,7 +45,7 @@ async def fetch_historical_candles(
open_time=open_time,
open=Decimal(str(o)),
high=Decimal(str(h)),
- low=Decimal(str(l)),
+ low=Decimal(str(low)),
close=Decimal(str(c)),
volume=Decimal(str(v)),
)
diff --git a/services/data-collector/src/data_collector/binance_ws.py b/services/data-collector/src/data_collector/binance_ws.py
index 7a4bad2..a1c81d6 100644
--- a/services/data-collector/src/data_collector/binance_ws.py
+++ b/services/data-collector/src/data_collector/binance_ws.py
@@ -1,4 +1,5 @@
"""Binance WebSocket client for real-time kline/candle data."""
+
import asyncio
import json
import logging
@@ -96,9 +97,7 @@ class BinanceWebSocket:
except Exception:
if not self._running:
break
- logger.warning(
- "WebSocket disconnected. Reconnecting in %ds…", RECONNECT_DELAY
- )
+ logger.warning("WebSocket disconnected. Reconnecting in %ds…", RECONNECT_DELAY)
await asyncio.sleep(RECONNECT_DELAY)
def stop(self) -> None:
diff --git a/services/data-collector/src/data_collector/main.py b/services/data-collector/src/data_collector/main.py
index c778503..170e8b1 100644
--- a/services/data-collector/src/data_collector/main.py
+++ b/services/data-collector/src/data_collector/main.py
@@ -1,4 +1,5 @@
"""Data Collector Service entry point."""
+
import asyncio
from shared.broker import RedisBroker
@@ -18,7 +19,9 @@ async def run() -> None:
config = CollectorConfig()
log = setup_logging("data-collector", config.log_level, config.log_format)
metrics = ServiceMetrics("data_collector")
- 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
+ )
db = Database(config.database_url)
await db.connect()
@@ -28,7 +31,12 @@ async def run() -> None:
storage = CandleStorage(db=db, broker=broker)
async def on_candle(candle):
- log.info("candle_received", symbol=candle.symbol, timeframe=candle.timeframe, open_time=str(candle.open_time))
+ log.info(
+ "candle_received",
+ symbol=candle.symbol,
+ timeframe=candle.timeframe,
+ open_time=str(candle.open_time),
+ )
await storage.store(candle)
metrics.events_processed.labels(service="data-collector", event_type="candle").inc()
diff --git a/services/data-collector/src/data_collector/storage.py b/services/data-collector/src/data_collector/storage.py
index 1e40b82..aeeaaed 100644
--- a/services/data-collector/src/data_collector/storage.py
+++ b/services/data-collector/src/data_collector/storage.py
@@ -1,4 +1,5 @@
"""Candle storage: persists to DB and publishes to Redis."""
+
from shared.events import CandleEvent
from shared.models import Candle
diff --git a/services/data-collector/tests/test_binance_rest.py b/services/data-collector/tests/test_binance_rest.py
index 695dcf9..bf88210 100644
--- a/services/data-collector/tests/test_binance_rest.py
+++ b/services/data-collector/tests/test_binance_rest.py
@@ -1,4 +1,5 @@
"""Tests for binance_rest module."""
+
import pytest
from decimal import Decimal
from unittest.mock import AsyncMock, MagicMock
@@ -19,9 +20,7 @@ async def test_fetch_historical_candles_parses_response():
]
)
- candles = await fetch_historical_candles(
- mock_exchange, "BTC/USDT", "1m", since=ts, limit=500
- )
+ candles = await fetch_historical_candles(mock_exchange, "BTC/USDT", "1m", since=ts, limit=500)
assert len(candles) == 2
@@ -35,9 +34,7 @@ async def test_fetch_historical_candles_parses_response():
assert c.close == Decimal("30050.0")
assert c.volume == Decimal("1.5")
- mock_exchange.fetch_ohlcv.assert_called_once_with(
- "BTC/USDT", "1m", since=ts, limit=500
- )
+ mock_exchange.fetch_ohlcv.assert_called_once_with("BTC/USDT", "1m", since=ts, limit=500)
@pytest.mark.asyncio
@@ -46,8 +43,6 @@ async def test_fetch_historical_candles_empty_response():
mock_exchange = MagicMock()
mock_exchange.fetch_ohlcv = AsyncMock(return_value=[])
- candles = await fetch_historical_candles(
- mock_exchange, "BTC/USDT", "1m", since=1700000000000
- )
+ candles = await fetch_historical_candles(mock_exchange, "BTC/USDT", "1m", since=1700000000000)
assert candles == []
diff --git a/services/data-collector/tests/test_storage.py b/services/data-collector/tests/test_storage.py
index 6b27414..be85578 100644
--- a/services/data-collector/tests/test_storage.py
+++ b/services/data-collector/tests/test_storage.py
@@ -1,4 +1,5 @@
"""Tests for storage module."""
+
import pytest
from decimal import Decimal
from datetime import datetime, timezone
diff --git a/services/order-executor/src/order_executor/config.py b/services/order-executor/src/order_executor/config.py
index 856045f..6542a31 100644
--- a/services/order-executor/src/order_executor/config.py
+++ b/services/order-executor/src/order_executor/config.py
@@ -1,4 +1,5 @@
"""Order Executor configuration."""
+
from shared.config import Settings
diff --git a/services/order-executor/src/order_executor/executor.py b/services/order-executor/src/order_executor/executor.py
index 099520d..80f441d 100644
--- a/services/order-executor/src/order_executor/executor.py
+++ b/services/order-executor/src/order_executor/executor.py
@@ -1,4 +1,5 @@
"""Order execution logic."""
+
import structlog
from datetime import datetime, timezone
from decimal import Decimal
@@ -7,7 +8,7 @@ from typing import Any, Optional
from shared.broker import RedisBroker
from shared.db import Database
from shared.events import OrderEvent
-from shared.models import Order, OrderSide, OrderStatus, OrderType, Signal
+from shared.models import Order, OrderStatus, OrderType, Signal
from shared.notifier import TelegramNotifier
from order_executor.risk_manager import RiskManager
@@ -58,9 +59,7 @@ class OrderExecutor:
)
if not result.allowed:
- logger.warning(
- "risk_check_rejected", signal_id=str(signal.id), reason=result.reason
- )
+ logger.warning("risk_check_rejected", signal_id=str(signal.id), reason=result.reason)
return None
# Build the order model
@@ -77,7 +76,12 @@ class OrderExecutor:
if self.dry_run:
order.status = OrderStatus.FILLED
order.filled_at = datetime.now(timezone.utc)
- logger.info("order_filled_dry_run", side=str(order.side), quantity=str(order.quantity), symbol=order.symbol)
+ logger.info(
+ "order_filled_dry_run",
+ side=str(order.side),
+ quantity=str(order.quantity),
+ symbol=order.symbol,
+ )
else:
try:
await self.exchange.create_order(
@@ -88,7 +92,12 @@ class OrderExecutor:
)
order.status = OrderStatus.FILLED
order.filled_at = datetime.now(timezone.utc)
- logger.info("order_filled", side=str(order.side), quantity=str(order.quantity), symbol=order.symbol)
+ logger.info(
+ "order_filled",
+ side=str(order.side),
+ quantity=str(order.quantity),
+ symbol=order.symbol,
+ )
except Exception as exc:
order.status = OrderStatus.FAILED
logger.error("order_failed", signal_id=str(signal.id), error=str(exc))
diff --git a/services/order-executor/src/order_executor/main.py b/services/order-executor/src/order_executor/main.py
index 7f0578d..ab6ef4f 100644
--- a/services/order-executor/src/order_executor/main.py
+++ b/services/order-executor/src/order_executor/main.py
@@ -1,4 +1,5 @@
"""Order Executor Service entry point."""
+
import asyncio
from decimal import Decimal
@@ -21,7 +22,9 @@ async def run() -> None:
config = ExecutorConfig()
log = setup_logging("order-executor", config.log_level, config.log_format)
metrics = ServiceMetrics("order_executor")
- 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
+ )
db = Database(config.database_url)
await db.connect()
@@ -69,12 +72,18 @@ async def run() -> None:
event = Event.from_dict(msg)
if event.type == EventType.SIGNAL:
signal = event.data
- log.info("processing_signal", signal_id=str(signal.id), symbol=signal.symbol)
+ log.info(
+ "processing_signal", signal_id=str(signal.id), symbol=signal.symbol
+ )
await executor.execute(signal)
- metrics.events_processed.labels(service="order-executor", event_type="signal").inc()
+ metrics.events_processed.labels(
+ service="order-executor", event_type="signal"
+ ).inc()
except Exception as exc:
log.error("message_processing_failed", error=str(exc))
- metrics.errors_total.labels(service="order-executor", error_type="processing").inc()
+ metrics.errors_total.labels(
+ service="order-executor", error_type="processing"
+ ).inc()
if messages:
# Advance last_id to avoid re-reading — broker.read returns decoded dicts,
# so we track progress by re-reading with "0" for replaying or "$" for new only.
diff --git a/services/order-executor/src/order_executor/risk_manager.py b/services/order-executor/src/order_executor/risk_manager.py
index 8e8a72c..db162e1 100644
--- a/services/order-executor/src/order_executor/risk_manager.py
+++ b/services/order-executor/src/order_executor/risk_manager.py
@@ -1,4 +1,5 @@
"""Risk management for order execution."""
+
from dataclasses import dataclass
from decimal import Decimal
@@ -49,7 +50,10 @@ class RiskManager:
if position is not None:
current_position_value = position.quantity * position.current_price
- if balance > 0 and (current_position_value + order_cost) / balance > self.max_position_size:
+ if (
+ balance > 0
+ and (current_position_value + order_cost) / balance > self.max_position_size
+ ):
return RiskCheckResult(allowed=False, reason="Position size exceeded")
return RiskCheckResult(allowed=True, reason="OK")
diff --git a/services/order-executor/tests/test_executor.py b/services/order-executor/tests/test_executor.py
index 4836ffb..e64b6c0 100644
--- a/services/order-executor/tests/test_executor.py
+++ b/services/order-executor/tests/test_executor.py
@@ -1,4 +1,5 @@
"""Tests for OrderExecutor."""
+
from decimal import Decimal
from unittest.mock import AsyncMock, MagicMock
diff --git a/services/order-executor/tests/test_risk_manager.py b/services/order-executor/tests/test_risk_manager.py
index f6b5545..a122d16 100644
--- a/services/order-executor/tests/test_risk_manager.py
+++ b/services/order-executor/tests/test_risk_manager.py
@@ -1,9 +1,9 @@
"""Tests for RiskManager."""
+
from decimal import Decimal
-import pytest
-from shared.models import OrderSide, Position, Signal
+from shared.models import OrderSide, Signal
from order_executor.risk_manager import RiskManager
@@ -55,9 +55,7 @@ def test_risk_check_rejects_daily_loss_exceeded():
"""Daily PnL of -1100 on 10000 balance = -11%, exceeding -10% limit."""
rm = make_risk_manager(daily_loss_limit_pct="10.0")
signal = make_signal(side=OrderSide.BUY, price="100", quantity="0.1")
- result = rm.check(
- signal, balance=Decimal("10000"), positions={}, daily_pnl=Decimal("-1100")
- )
+ result = rm.check(signal, balance=Decimal("10000"), positions={}, daily_pnl=Decimal("-1100"))
assert result.allowed is False
assert result.reason == "Daily loss limit exceeded"
diff --git a/services/portfolio-manager/src/portfolio_manager/config.py b/services/portfolio-manager/src/portfolio_manager/config.py
index bbd5049..eaf53fd 100644
--- a/services/portfolio-manager/src/portfolio_manager/config.py
+++ b/services/portfolio-manager/src/portfolio_manager/config.py
@@ -1,4 +1,5 @@
"""Portfolio Manager configuration."""
+
from shared.config import Settings
diff --git a/services/portfolio-manager/src/portfolio_manager/main.py b/services/portfolio-manager/src/portfolio_manager/main.py
index 56624f7..a1c73be 100644
--- a/services/portfolio-manager/src/portfolio_manager/main.py
+++ b/services/portfolio-manager/src/portfolio_manager/main.py
@@ -1,4 +1,5 @@
"""Portfolio Manager Service entry point."""
+
import asyncio
from shared.broker import RedisBroker
@@ -18,7 +19,9 @@ async def run() -> None:
config = PortfolioConfig()
log = setup_logging("portfolio-manager", config.log_level, config.log_format)
metrics = ServiceMetrics("portfolio_manager")
- 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)
tracker = PortfolioTracker()
@@ -49,10 +52,14 @@ async def run() -> None:
)
positions = tracker.get_all_positions()
log.info("positions_updated", count=len(positions))
- metrics.events_processed.labels(service="portfolio-manager", event_type="order").inc()
+ metrics.events_processed.labels(
+ service="portfolio-manager", event_type="order"
+ ).inc()
except Exception as exc:
log.exception("message_processing_failed", error=str(exc))
- metrics.errors_total.labels(service="portfolio-manager", error_type="processing").inc()
+ metrics.errors_total.labels(
+ service="portfolio-manager", error_type="processing"
+ ).inc()
# Update last_id to the latest processed message id if broker returns ids
# Since broker.read returns parsed payloads (not ids), we use "$" to get new msgs
except Exception as exc:
diff --git a/services/portfolio-manager/src/portfolio_manager/pnl.py b/services/portfolio-manager/src/portfolio_manager/pnl.py
index 96f0da8..4c0fa56 100644
--- a/services/portfolio-manager/src/portfolio_manager/pnl.py
+++ b/services/portfolio-manager/src/portfolio_manager/pnl.py
@@ -1,4 +1,5 @@
"""PnL calculation functions for the portfolio manager."""
+
from decimal import Decimal
diff --git a/services/portfolio-manager/src/portfolio_manager/portfolio.py b/services/portfolio-manager/src/portfolio_manager/portfolio.py
index 59106bb..2c93643 100644
--- a/services/portfolio-manager/src/portfolio_manager/portfolio.py
+++ b/services/portfolio-manager/src/portfolio_manager/portfolio.py
@@ -1,4 +1,5 @@
"""Portfolio tracking for the portfolio manager service."""
+
from decimal import Decimal
from shared.models import Order, OrderSide, Position
diff --git a/services/portfolio-manager/tests/test_pnl.py b/services/portfolio-manager/tests/test_pnl.py
index 4462adc..5f5d807 100644
--- a/services/portfolio-manager/tests/test_pnl.py
+++ b/services/portfolio-manager/tests/test_pnl.py
@@ -1,4 +1,5 @@
"""Tests for PnL calculation functions."""
+
from decimal import Decimal
from portfolio_manager.pnl import calculate_realized_pnl, calculate_unrealized_pnl
diff --git a/services/portfolio-manager/tests/test_portfolio.py b/services/portfolio-manager/tests/test_portfolio.py
index 26319ca..92ff6ca 100644
--- a/services/portfolio-manager/tests/test_portfolio.py
+++ b/services/portfolio-manager/tests/test_portfolio.py
@@ -1,4 +1,5 @@
"""Tests for PortfolioTracker."""
+
from decimal import Decimal
from shared.models import Order, OrderSide, OrderStatus, OrderType
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