summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 16:07:20 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 16:07:20 +0900
commit86a0fa84ca6662ca931182880523c0b87f617f73 (patch)
treef483d39698849a6d4cdbd4c79979217f05be78be
parent4747400168279c6cfc1196d86ec77b5d7b513c61 (diff)
fix: add session lock in StockSelector, remove unused HEALTH_PORT_OFFSET, lint fixesHEADmaster
- Add asyncio.Lock to StockSelector._ensure_session() to prevent race condition - Remove unused HEALTH_PORT_OFFSET constant from news-collector - Auto-fix import sorting and formatting from ruff
-rw-r--r--docker-compose.yml1
-rw-r--r--monitoring/prometheus.yml2
-rw-r--r--monitoring/prometheus/alert_rules.yml29
-rw-r--r--services/news-collector/src/news_collector/main.py3
-rw-r--r--services/strategy-engine/src/strategy_engine/stock_selector.py9
-rw-r--r--shared/alembic/versions/004_add_signal_detail_columns.py4
-rw-r--r--shared/src/shared/events.py4
7 files changed, 42 insertions, 10 deletions
diff --git a/docker-compose.yml b/docker-compose.yml
index d11db2c..60462ec 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -228,6 +228,7 @@ services:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
+ - ./monitoring/prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml
depends_on:
- data-collector
- strategy-engine
diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml
index b6dc853..e177c9c 100644
--- a/monitoring/prometheus.yml
+++ b/monitoring/prometheus.yml
@@ -1,5 +1,7 @@
global:
scrape_interval: 15s
+rule_files:
+ - "/etc/prometheus/alert_rules.yml"
scrape_configs:
- job_name: "trading-services"
authorization:
diff --git a/monitoring/prometheus/alert_rules.yml b/monitoring/prometheus/alert_rules.yml
new file mode 100644
index 0000000..aca2f1c
--- /dev/null
+++ b/monitoring/prometheus/alert_rules.yml
@@ -0,0 +1,29 @@
+groups:
+ - name: trading-platform
+ rules:
+ - alert: ServiceDown
+ expr: up == 0
+ for: 1m
+ labels:
+ severity: critical
+ annotations:
+ summary: "Service {{ $labels.job }} is down"
+ description: "{{ $labels.instance }} has been unreachable for 1 minute."
+
+ - alert: HighErrorRate
+ expr: rate(errors_total[5m]) > 10
+ for: 2m
+ labels:
+ severity: warning
+ annotations:
+ summary: "High error rate on {{ $labels.job }}"
+ description: "Error rate is {{ $value }} errors/sec over 5 minutes."
+
+ - alert: HighProcessingLatency
+ expr: histogram_quantile(0.95, rate(processing_seconds_bucket[5m])) > 5
+ for: 5m
+ labels:
+ severity: warning
+ annotations:
+ summary: "High p95 latency on {{ $labels.job }}"
+ description: "95th percentile processing time is {{ $value }}s."
diff --git a/services/news-collector/src/news_collector/main.py b/services/news-collector/src/news_collector/main.py
index 7265f00..c39fa67 100644
--- a/services/news-collector/src/news_collector/main.py
+++ b/services/news-collector/src/news_collector/main.py
@@ -25,9 +25,6 @@ from shared.sentiment import SentimentAggregator
from shared.sentiment_models import MarketSentiment
from shared.shutdown import GracefulShutdown
-# Health check port: base + 4
-HEALTH_PORT_OFFSET = 4
-
async def run_collector_once(collector, db: Database, broker: RedisBroker) -> int:
"""Run a single collector, store results in DB, publish to Redis.
diff --git a/services/strategy-engine/src/strategy_engine/stock_selector.py b/services/strategy-engine/src/strategy_engine/stock_selector.py
index 5acef0f..8657b93 100644
--- a/services/strategy-engine/src/strategy_engine/stock_selector.py
+++ b/services/strategy-engine/src/strategy_engine/stock_selector.py
@@ -1,5 +1,6 @@
"""3-stage stock selector engine: sentiment → technical → LLM."""
+import asyncio
import json
import logging
import re
@@ -218,11 +219,13 @@ class StockSelector:
self._model = anthropic_model
self._max_picks = max_picks
self._http_session: aiohttp.ClientSession | None = None
+ self._session_lock = asyncio.Lock()
async def _ensure_session(self) -> aiohttp.ClientSession:
- if self._http_session is None or self._http_session.closed:
- self._http_session = aiohttp.ClientSession()
- return self._http_session
+ async with self._session_lock:
+ if self._http_session is None or self._http_session.closed:
+ self._http_session = aiohttp.ClientSession()
+ return self._http_session
async def close(self) -> None:
if self._http_session and not self._http_session.closed:
diff --git a/shared/alembic/versions/004_add_signal_detail_columns.py b/shared/alembic/versions/004_add_signal_detail_columns.py
index 7a8a77b..4009b6e 100644
--- a/shared/alembic/versions/004_add_signal_detail_columns.py
+++ b/shared/alembic/versions/004_add_signal_detail_columns.py
@@ -12,7 +12,9 @@ down_revision = "003"
def upgrade():
- op.add_column("signals", sa.Column("conviction", sa.Float, nullable=False, server_default="1.0"))
+ op.add_column(
+ "signals", sa.Column("conviction", sa.Float, nullable=False, server_default="1.0")
+ )
op.add_column("signals", sa.Column("stop_loss", sa.Numeric, nullable=True))
op.add_column("signals", sa.Column("take_profit", sa.Numeric, nullable=True))
diff --git a/shared/src/shared/events.py b/shared/src/shared/events.py
index 61b85bd..37217a0 100644
--- a/shared/src/shared/events.py
+++ b/shared/src/shared/events.py
@@ -100,6 +100,4 @@ class Event:
try:
return cls.from_raw(data)
except KeyError as exc:
- raise ValueError(
- f"Missing required field in {event_type} event data: {exc}"
- ) from exc
+ raise ValueError(f"Missing required field in {event_type} event data: {exc}") from exc