summaryrefslogtreecommitdiff
path: root/docs/superpowers/specs
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/specs')
-rw-r--r--docs/superpowers/specs/2026-04-01-crypto-trading-platform-design.md374
-rw-r--r--docs/superpowers/specs/2026-04-01-operations-and-strategy-expansion-design.md458
-rw-r--r--docs/superpowers/specs/2026-04-02-news-driven-stock-selector-design.md418
-rw-r--r--docs/superpowers/specs/2026-04-02-platform-upgrade-design.md257
4 files changed, 675 insertions, 832 deletions
diff --git a/docs/superpowers/specs/2026-04-01-crypto-trading-platform-design.md b/docs/superpowers/specs/2026-04-01-crypto-trading-platform-design.md
deleted file mode 100644
index aa32eb4..0000000
--- a/docs/superpowers/specs/2026-04-01-crypto-trading-platform-design.md
+++ /dev/null
@@ -1,374 +0,0 @@
-# Crypto Trading Platform — Design Spec
-
-## Overview
-
-Binance 현물 암호화폐 자동매매 플랫폼. 마이크로서비스 아키텍처 기반으로 데이터 수집, 전략 실행, 주문 처리, 포트폴리오 관리, 백테스팅을 독립 서비스로 운영한다. CLI로 제어하며, 전략은 플러그인 방식으로 확장 가능하다.
-
-- **시장:** 암호화폐 (Binance 현물)
-- **언어:** Python
-- **인터페이스:** CLI (Click)
-- **아키텍처:** 마이크로서비스 (Docker Compose)
-
----
-
-## Architecture
-
-### 서비스 구성
-
-```
-┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
-│ Data │───▶│ Message Broker │◀──│ Strategy │
-│ Collector │ │ (Redis Streams) │ │ Engine │
-└─────────────┘ └──────────────────┘ └─────────────────┘
- │ ▲ │
- ▼ │ ▼
- ┌──────────────────┐ ┌─────────────────┐
- │ Backtester │ │ Order │
- │ │ │ Executor │
- └──────────────────┘ └─────────────────┘
- │
- ┌────────────────────────┘
- ▼
- ┌──────────────────┐
- │ Portfolio │
- │ Manager │
- └──────────────────┘
-
- CLI ──────▶ 각 서비스에 명령 전달
-```
-
-| 서비스 | 역할 | 상시 실행 |
-|--------|------|-----------|
-| **data-collector** | Binance WebSocket/REST로 시세 수집, DB 저장 | Yes |
-| **strategy-engine** | 플러그인 전략 로드 및 시그널 생성 | 봇 실행 시 |
-| **order-executor** | 시그널 받아 실제 주문 실행 + 리스크 관리 | 봇 실행 시 |
-| **portfolio-manager** | 잔고, 손익, 포지션 추적 | Yes |
-| **backtester** | 과거 데이터로 전략 검증 | 요청 시 |
-| **shared** | 공통 모델, 이벤트 정의, 유틸리티 (라이브러리) | — |
-| **cli** | 사용자 인터페이스, 각 서비스 제어 | — |
-
-### 통신 흐름
-
-```
-[Binance WS]
- │
- ▼
-data-collector ──publish──▶ Redis Stream: "candles.{symbol}"
- │
- ┌───────────────┤
- ▼ ▼
- strategy-engine backtester (과거 데이터는 DB에서)
- │
- ▼
- Redis Stream: "signals"
- │
- ▼
- order-executor
- │
- ┌───────┴───────┐
- ▼ ▼
- [Binance API] Redis Stream: "orders"
- │
- ▼
- portfolio-manager
-```
-
----
-
-## Project Structure
-
-```
-trading/
-├── services/
-│ ├── data-collector/
-│ │ ├── src/
-│ │ │ ├── __init__.py
-│ │ │ ├── main.py # 서비스 진입점
-│ │ │ ├── binance_ws.py # WebSocket 실시간 시세
-│ │ │ ├── binance_rest.py # REST 과거 데이터 수집
-│ │ │ ├── storage.py # DB 저장 로직
-│ │ │ └── config.py
-│ │ ├── tests/
-│ │ ├── Dockerfile
-│ │ └── pyproject.toml
-│ │
-│ ├── strategy-engine/
-│ │ ├── src/
-│ │ │ ├── __init__.py
-│ │ │ ├── main.py
-│ │ │ ├── engine.py # 전략 로더 + 실행기
-│ │ │ ├── plugin_loader.py # 플러그인 동적 로드
-│ │ │ └── config.py
-│ │ ├── strategies/ # 플러그인 전략 디렉토리
-│ │ │ ├── base.py # 전략 추상 클래스
-│ │ │ ├── rsi_strategy.py # 예시: RSI 전략
-│ │ │ └── grid_strategy.py # 예시: 그리드 전략
-│ │ ├── tests/
-│ │ ├── Dockerfile
-│ │ └── pyproject.toml
-│ │
-│ ├── order-executor/
-│ │ ├── src/
-│ │ │ ├── __init__.py
-│ │ │ ├── main.py
-│ │ │ ├── executor.py # 주문 실행 로직
-│ │ │ ├── risk_manager.py # 리스크 관리 (손절/익절)
-│ │ │ └── config.py
-│ │ ├── tests/
-│ │ ├── Dockerfile
-│ │ └── pyproject.toml
-│ │
-│ ├── portfolio-manager/
-│ │ ├── src/
-│ │ │ ├── __init__.py
-│ │ │ ├── main.py
-│ │ │ ├── portfolio.py # 잔고/포지션 추적
-│ │ │ ├── pnl.py # 손익 계산
-│ │ │ └── config.py
-│ │ ├── tests/
-│ │ ├── Dockerfile
-│ │ └── pyproject.toml
-│ │
-│ └── backtester/
-│ ├── src/
-│ │ ├── __init__.py
-│ │ ├── main.py
-│ │ ├── engine.py # 백테스팅 엔진
-│ │ ├── simulator.py # 가상 주문 시뮬레이터
-│ │ ├── reporter.py # 결과 리포트 생성
-│ │ └── config.py
-│ ├── tests/
-│ ├── Dockerfile
-│ └── pyproject.toml
-│
-├── shared/
-│ ├── src/shared/
-│ │ ├── __init__.py
-│ │ ├── models.py # 공통 데이터 모델
-│ │ ├── events.py # 이벤트 타입 정의
-│ │ ├── broker.py # Redis Streams 클라이언트
-│ │ ├── db.py # DB 연결 (PostgreSQL)
-│ │ └── config.py # 공통 설정
-│ ├── tests/
-│ └── pyproject.toml
-│
-├── cli/
-│ ├── src/
-│ │ ├── __init__.py
-│ │ ├── main.py # Click 기반 CLI 진입점
-│ │ ├── commands/
-│ │ │ ├── data.py # 데이터 수집 명령
-│ │ │ ├── trade.py # 매매 시작/중지
-│ │ │ ├── backtest.py # 백테스팅 실행
-│ │ │ ├── portfolio.py # 포트폴리오 조회
-│ │ │ └── strategy.py # 전략 관리
-│ │ └── config.py
-│ ├── tests/
-│ └── pyproject.toml
-│
-├── docker-compose.yml # 전체 서비스 오케스트레이션
-├── .env.example # 환경변수 템플릿
-├── Makefile # 공통 명령어
-└── README.md
-```
-
----
-
-## Tech Stack
-
-| 용도 | 라이브러리 |
-|------|-----------|
-| 거래소 API | **ccxt** |
-| 메시지 브로커 | **Redis Streams** |
-| DB | **PostgreSQL** + **asyncpg** |
-| CLI | **Click** |
-| 데이터 분석 | **pandas**, **numpy** |
-| 기술 지표 | **pandas-ta** |
-| 비동기 처리 | **asyncio** + **aiohttp** |
-| 설정 관리 | **pydantic-settings** |
-| 컨테이너 | **Docker** + **docker-compose** |
-| 테스트 | **pytest** + **pytest-asyncio** |
-
----
-
-## Data Models
-
-### Core Models (shared/models.py)
-
-```python
-class Candle:
- symbol: str # "BTCUSDT"
- timeframe: str # "1m", "5m", "1h"
- open_time: datetime
- open: Decimal
- high: Decimal
- low: Decimal
- close: Decimal
- volume: Decimal
-
-class Signal:
- strategy: str # "rsi_strategy"
- symbol: str
- side: "BUY" | "SELL"
- price: Decimal
- quantity: Decimal
- reason: str # 시그널 발생 근거
-
-class Order:
- id: str
- signal_id: str # 추적용
- symbol: str
- side: "BUY" | "SELL"
- type: "MARKET" | "LIMIT"
- price: Decimal
- quantity: Decimal
- status: "PENDING" | "FILLED" | "CANCELLED" | "FAILED"
- created_at: datetime
- filled_at: datetime | None
-
-class Position:
- symbol: str
- quantity: Decimal
- avg_entry_price: Decimal
- current_price: Decimal
- unrealized_pnl: Decimal
-```
-
-### PostgreSQL Tables
-
-| 테이블 | 용도 |
-|--------|------|
-| `candles` | 시세 이력 (파티셔닝: symbol + timeframe) |
-| `signals` | 전략 시그널 이력 |
-| `orders` | 주문 이력 |
-| `trades` | 체결 이력 |
-| `positions` | 현재 포지션 |
-| `portfolio_snapshots` | 일별 포트폴리오 스냅샷 |
-
-### Storage Strategy
-
-- **실시간 시세:** Redis 캐싱 + PostgreSQL 영구 저장
-- **주문/체결:** PostgreSQL 즉시 기록
-- **백테스팅 데이터:** PostgreSQL에서 bulk read (pandas DataFrame)
-
----
-
-## Strategy Plugin System
-
-### Base Interface
-
-```python
-from abc import ABC, abstractmethod
-from shared.models import Candle, Signal
-
-class BaseStrategy(ABC):
- @abstractmethod
- def on_candle(self, candle: Candle) -> Signal | None:
- """캔들 데이터 수신 시 시그널 반환"""
- pass
-
- @abstractmethod
- def configure(self, params: dict) -> None:
- """전략 파라미터 설정"""
- pass
-```
-
-새 전략 추가 = `BaseStrategy` 상속 파일 하나 작성 후 `strategies/` 디렉토리에 배치.
-
-### 예시 전략
-
-- **RSI Strategy:** RSI 과매도 시 매수, 과매수 시 매도
-- **Grid Strategy:** 가격 구간을 나눠 자동 매수/매도 주문 배치
-
----
-
-## CLI Interface
-
-```bash
-# 데이터 수집
-trading data collect --symbol BTCUSDT --timeframe 1m
-trading data history --symbol BTCUSDT --from 2025-01-01
-trading data list
-
-# 자동매매
-trading trade start --strategy rsi --symbol BTCUSDT
-trading trade stop --strategy rsi
-trading trade status
-
-# 수동매매
-trading order buy --symbol BTCUSDT --quantity 0.01
-trading order sell --symbol BTCUSDT --price 70000
-trading order cancel --id abc123
-
-# 백테스팅
-trading backtest run --strategy rsi --symbol BTCUSDT \
- --from 2025-01-01 --to 2025-12-31
-trading backtest report --id latest
-
-# 포트폴리오
-trading portfolio show
-trading portfolio history --days 30
-
-# 전략 관리
-trading strategy list
-trading strategy info --name rsi
-
-# 서비스 관리
-trading service up
-trading service down
-trading service logs --name strategy-engine
-```
-
----
-
-## Risk Management
-
-### Risk Check Pipeline (order-executor)
-
-시그널 수신 시 다음 체크를 순서대로 통과해야 주문 실행:
-
-1. 최대 포지션 크기 초과 여부
-2. 일일 최대 손실 한도 도달 여부
-3. 동일 심볼 중복 주문 방지
-4. 주문 금액 < 가용 잔고 확인
-5. 가격 급변 감지 (슬리피지 보호)
-
-### Safety Mechanisms
-
-| 장치 | 설명 |
-|------|------|
-| **긴급 정지 (Kill Switch)** | `trading trade stop-all` — 모든 봇 중지, 미체결 주문 전량 취소 |
-| **일일 손실 한도** | 설정 비율 초과 시 자동 매매 중단 |
-| **최대 포지션 제한** | 총 자산 대비 단일 심볼 비율 제한 |
-| **연결 끊김 대응** | Binance 연결 끊기면 신규 주문 중단, 재연결 시도 |
-| **드라이런 모드** | 실제 주문 없이 시그널만 생성 — 전략 검증용 |
-
----
-
-## Configuration (.env)
-
-```
-BINANCE_API_KEY=
-BINANCE_API_SECRET=
-REDIS_URL=redis://localhost:6379
-DATABASE_URL=postgresql://user:pass@localhost:5432/trading
-LOG_LEVEL=INFO
-RISK_MAX_POSITION_SIZE=0.1
-RISK_STOP_LOSS_PCT=5
-RISK_DAILY_LOSS_LIMIT_PCT=10
-DRY_RUN=true
-```
-
----
-
-## Docker Compose Services
-
-```yaml
-services:
- redis: # 메시지 브로커 (항상 실행)
- postgres: # 데이터 저장소 (항상 실행)
- data-collector: # 시세 수집 (항상 실행)
- strategy-engine: # 전략 엔진 (봇 실행 시)
- order-executor: # 주문 실행 (봇 실행 시)
- portfolio-manager: # 포트폴리오 (항상 실행)
-```
diff --git a/docs/superpowers/specs/2026-04-01-operations-and-strategy-expansion-design.md b/docs/superpowers/specs/2026-04-01-operations-and-strategy-expansion-design.md
deleted file mode 100644
index e1aea74..0000000
--- a/docs/superpowers/specs/2026-04-01-operations-and-strategy-expansion-design.md
+++ /dev/null
@@ -1,458 +0,0 @@
-# Operations Infrastructure & Strategy Expansion — Design Spec
-
-## Overview
-
-기존 Binance 현물 암호화폐 자동매매 플랫폼의 두 가지 영역을 강화한다:
-
-1. **운영 인프라** — DB 마이그레이션, 구조화된 로깅, Telegram 알림, 에러 복구, 메트릭 수집
-2. **전략 확장** — 추세 추종/스캘핑 전략 추가, 백테스트 고도화
-
-접근 순서: 운영 인프라 먼저 완성 → 전략 추가. 안정적인 모니터링/알림 기반 위에서 새 전략을 검증할 수 있어야 한다.
-
----
-
-## Part 1: Operations Infrastructure
-
-### 1.1 DB Layer Migration (asyncpg → SQLAlchemy 2.0 Async + Alembic)
-
-**목표:** raw SQL과 asyncpg 직접 사용을 SQLAlchemy 2.0 async ORM으로 교체하고, Alembic으로 마이그레이션을 관리한다.
-
-**변경 사항:**
-
-- `shared/src/shared/db.py` — AsyncSession 기반으로 재작성
- - `create_async_engine()` + `async_sessionmaker()` 사용
- - asyncpg는 SQLAlchemy의 내부 드라이버로 유지 (`postgresql+asyncpg://`)
- - 기존 raw SQL 함수들을 ORM 쿼리로 전환
-
-- `shared/src/shared/sa_models.py` — SQLAlchemy ORM 모델 (신규)
- - 기존 Pydantic 모델(models.py)과 1:1 매핑되는 SA 테이블 정의
- - `Candle`, `Signal`, `Order`, `Trade`, `Position`, `PortfolioSnapshot` 테이블
- - Pydantic 모델은 이벤트 직렬화/API 전용으로 유지
-
-- `shared/alembic/` — Alembic 마이그레이션 환경 (신규)
- - `alembic.ini` — 설정 파일 (DATABASE_URL 참조)
- - `env.py` — async 엔진 설정, SA 모델 메타데이터 참조
- - `versions/` — 마이그레이션 파일들
- - 초기 마이그레이션: 기존 `db.py`의 CREATE TABLE 로직을 마이그레이션으로 이전
-
-- `Makefile` 타겟 추가:
- - `make migrate` — `alembic upgrade head`
- - `make migrate-down` — `alembic downgrade -1`
- - `make migrate-new MSG="description"` — `alembic revision --autogenerate -m "description"`
-
-- 각 서비스의 DB 접근 코드를 AsyncSession 기반으로 업데이트:
- - `data-collector/storage.py` — bulk insert 쿼리를 SA ORM으로
- - `order-executor/executor.py` — order CRUD를 SA ORM으로
- - `portfolio-manager/portfolio.py` — position/snapshot 쿼리를 SA ORM으로
- - `backtester/engine.py` — candle 조회를 SA ORM으로
-
-**의존성 추가:** `sqlalchemy[asyncio]>=2.0`, `alembic>=1.13`
-**의존성 제거:** `asyncpg` (직접 의존 → SQLAlchemy 내부 의존으로 변경)
-
----
-
-### 1.2 Structured Logging (structlog)
-
-**목표:** 전 서비스에 JSON 구조화 로깅을 적용하고, 에러 로그를 Telegram 알림과 연결한다.
-
-**변경 사항:**
-
-- `shared/src/shared/logging.py` (신규)
- - `setup_logging(service_name: str, log_level: str)` 함수
- - structlog 프로세서 체인: timestamp, log level, service_name 바인딩, JSON 렌더러
- - 개발 환경: 컬러 콘솔 출력 / 프로덕션: JSON stdout
- - `LOG_FORMAT` 환경변수로 전환 (`console` | `json`, 기본값: `json`)
-
-- 각 서비스 `main.py`에서 `setup_logging()` 호출
-- 기존 `logging.getLogger()` 호출을 `structlog.get_logger()` 로 교체
-- 컨텍스트 바인딩 예시:
- ```python
- log = structlog.get_logger().bind(service="strategy-engine", symbol="BTCUSDT")
- log.info("signal_generated", strategy="rsi", side="BUY", price=68500)
- ```
-
-- ERROR 이상 로그 → Telegram 알림 트리거 (1.3절 TelegramNotifier 연동)
- - structlog 커스텀 프로세서로 구현
- - 알림 전송 실패 시 로그만 남기고 서비스 중단하지 않음
-
-**의존성 추가:** `structlog>=24.0`
-
----
-
-### 1.3 Telegram Notification Service
-
-**목표:** 주요 이벤트(시그널, 주문, 에러, 일일 요약)를 Telegram으로 전송한다.
-
-**변경 사항:**
-
-- `shared/src/shared/notifier.py` (신규)
- - `TelegramNotifier` 클래스
- - `__init__(bot_token: str, chat_id: str)` — aiohttp 세션 관리
- - `send(message: str, parse_mode: str = "HTML")` — 메시지 전송
- - `send_signal(signal: Signal)` — 시그널 포맷팅 후 전송
- - `send_order(order: Order)` — 주문 체결/실패 알림
- - `send_error(error: str, service: str)` — 에러 알림
- - `send_daily_summary(positions: list, pnl: Decimal)` — 일일 요약
- - Rate limiting: 초당 최대 1건 (asyncio.Semaphore + 큐)
- - 연결 실패 시 최대 3회 재시도, 실패해도 서비스 중단하지 않음
-
-- `shared/src/shared/config.py` — 설정 추가:
- - `TELEGRAM_BOT_TOKEN: str = ""`
- - `TELEGRAM_CHAT_ID: str = ""`
- - `TELEGRAM_ENABLED: bool = False` (토큰 미설정 시 자동 비활성)
-
-- `.env.example` 업데이트:
- ```
- TELEGRAM_BOT_TOKEN=
- TELEGRAM_CHAT_ID=
- TELEGRAM_ENABLED=false
- ```
-
-- 연동 포인트:
- - `strategy-engine/engine.py` — 시그널 생성 시 `send_signal()`
- - `order-executor/executor.py` — 주문 체결/실패 시 `send_order()`
- - `shared/logging.py` — ERROR 로그 시 `send_error()`
- - `portfolio-manager/main.py` — 매일 자정(UTC) `send_daily_summary()`
-
-**의존성:** aiohttp (이미 존재)
-
----
-
-### 1.4 Error Recovery & Health Checks
-
-**목표:** 서비스 장애 시 자동 복구하고, 헬스체크 엔드포인트로 상태를 모니터링한다.
-
-**변경 사항:**
-
-- `shared/src/shared/resilience.py` (신규)
- - `retry_with_backoff(func, max_retries, base_delay)` — exponential backoff 데코레이터
- - 지터(jitter) 포함: `delay * (1 + random(0, 0.5))`
- - 최대 지연: 60초
- - `CircuitBreaker` 클래스:
- - 상태: CLOSED(정상) → OPEN(차단) → HALF_OPEN(시험)
- - `failure_threshold`: 연속 실패 N회 시 OPEN (기본: 5)
- - `recovery_timeout`: OPEN 후 N초 뒤 HALF_OPEN (기본: 60)
- - OPEN 전환 시 Telegram 알림 전송
-
-- `shared/src/shared/healthcheck.py` (신규)
- - `HealthCheckServer` — aiohttp 기반 경량 HTTP 서버
- - `GET /health` → `{"status": "ok", "service": "...", "uptime": ..., "checks": {...}}`
- - 체크 항목: Redis 연결, PostgreSQL 연결, Binance WS 연결(해당 서비스만)
- - 포트: `HEALTH_PORT` 환경변수 (서비스별 다르게 설정)
-
-- 각 서비스에 적용:
- - `data-collector` — Binance WS 재연결 (backoff), Redis/DB 재연결
- - `strategy-engine` — Redis 소비자 재연결
- - `order-executor` — 거래소 API 호출 재시도 (circuit breaker)
- - `portfolio-manager` — Redis/DB 재연결
-
-- `docker-compose.yml` — healthcheck를 `/health` 엔드포인트로 변경
-
-- `shared/src/shared/config.py` — 설정 추가:
- - `HEALTH_PORT: int = 8080`
- - `CIRCUIT_BREAKER_THRESHOLD: int = 5`
- - `CIRCUIT_BREAKER_TIMEOUT: int = 60`
-
----
-
-### 1.5 Prometheus Metrics
-
-**목표:** 각 서비스의 주요 지표를 Prometheus 포맷으로 노출한다.
-
-**변경 사항:**
-
-- `shared/src/shared/metrics.py` (신규)
- - `MetricsServer` — prometheus_client 기반
- - `/metrics` 엔드포인트 (healthcheck 서버에 통합)
- - 공통 메트릭:
- - `service_up` (Gauge) — 서비스 상태
- - `errors_total` (Counter) — 에러 횟수 (label: service, error_type)
- - `event_processing_seconds` (Histogram) — 이벤트 처리 시간
-
-- 서비스별 메트릭:
- - **data-collector:**
- - `candles_received_total` (Counter) — 수신 캔들 수
- - `ws_reconnections_total` (Counter) — WS 재연결 횟수
- - **strategy-engine:**
- - `signals_generated_total` (Counter, label: strategy, side)
- - `strategy_execution_seconds` (Histogram, label: strategy)
- - **order-executor:**
- - `orders_total` (Counter, label: status, side)
- - `risk_rejections_total` (Counter, label: reason)
- - **portfolio-manager:**
- - `portfolio_value` (Gauge) — 총 포트폴리오 가치
- - `unrealized_pnl` (Gauge, label: symbol)
-
-- `docker-compose.yml` — Prometheus + Grafana 서비스 추가 (선택적 프로필):
- ```yaml
- prometheus:
- image: prom/prometheus:latest
- profiles: ["monitoring"]
- volumes:
- - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- grafana:
- image: grafana/grafana:latest
- profiles: ["monitoring"]
- ports:
- - "3000:3000"
- ```
-
-- `monitoring/prometheus.yml` (신규) — 스크래핑 설정
-- `monitoring/grafana/` (신규) — 대시보드 프로비저닝 (선택적)
-
-**의존성 추가:** `prometheus-client>=0.20`
-
----
-
-## Part 2: Strategy Expansion
-
-### 2.1 Trend Following Strategies
-
-**MACD Strategy** (`strategies/macd_strategy.py`)
-- MACD line = EMA(12) - EMA(26), Signal line = EMA(9) of MACD
-- BUY: MACD가 Signal line 위로 교차 + 히스토그램 양전환
-- SELL: MACD가 Signal line 아래로 교차 + 히스토그램 음전환
-- 파라미터: `fast_period=12`, `slow_period=26`, `signal_period=9`, `quantity`
-- warmup_period: `slow_period + signal_period`
-
-**Bollinger Bands Strategy** (`strategies/bollinger_strategy.py`)
-- 중심선 = SMA(20), 상단 = 중심 + 2*std, 하단 = 중심 - 2*std
-- BUY: 가격이 하단 밴드 아래로 이탈 후 복귀
-- SELL: 가격이 상단 밴드 위로 이탈 후 복귀
-- 변동성 필터: 밴드 폭이 임계값 미만이면 시그널 무시 (횡보장 필터)
-- 파라미터: `period=20`, `num_std=2.0`, `min_bandwidth=0.02`, `quantity`
-- warmup_period: `period`
-
-**EMA Crossover Strategy** (`strategies/ema_crossover_strategy.py`)
-- 단기 EMA와 장기 EMA 교차
-- BUY: 단기 EMA가 장기 EMA 위로 교차 (Golden Cross)
-- SELL: 단기 EMA가 장기 EMA 아래로 교차 (Death Cross)
-- 파라미터: `short_period=9`, `long_period=21`, `quantity`
-- warmup_period: `long_period`
-
----
-
-### 2.2 Scalping Strategies
-
-**VWAP Strategy** (`strategies/vwap_strategy.py`)
-- VWAP = cumsum(price * volume) / cumsum(volume)
-- BUY: 가격이 VWAP 아래에서 VWAP으로 복귀 (평균 회귀)
-- SELL: 가격이 VWAP 위에서 VWAP으로 복귀
-- 일중 리셋: UTC 00:00에 VWAP 재계산
-- 파라미터: `deviation_threshold=0.002`, `quantity`
-- warmup_period: 최소 30 캔들
-
-**Volume Profile Strategy** (`strategies/volume_profile_strategy.py`)
-- 최근 N개 캔들의 가격대별 거래량 분포 계산
-- POC (Point of Control): 가장 거래량이 많은 가격대
-- Value Area: 전체 거래량 70%가 집중된 구간
-- BUY: 가격이 Value Area 하단 지지선에서 반등
-- SELL: 가격이 Value Area 상단 저항선에서 거부
-- 파라미터: `lookback_period=100`, `num_bins=50`, `value_area_pct=0.7`, `quantity`
-- warmup_period: `lookback_period`
-
----
-
-### 2.3 Strategy Common Improvements
-
-**BaseStrategy 확장:**
-```python
-class BaseStrategy(ABC):
- @property
- @abstractmethod
- def warmup_period(self) -> int:
- """지표 계산에 필요한 최소 캔들 수"""
- pass
-
- @abstractmethod
- def on_candle(self, candle: Candle) -> Signal | None:
- pass
-
- @abstractmethod
- def configure(self, params: dict) -> None:
- pass
-
- def reset(self) -> None:
- """전략 상태 초기화 (백테스트 간 재사용)"""
- pass
-```
-
-**전략 파라미터 외부화:**
-- `strategies/config/` 디렉토리에 YAML 설정 파일
-- 파일명: `{strategy_name}.yaml` (예: `rsi_strategy.yaml`)
-- 구조:
- ```yaml
- # rsi_strategy.yaml
- period: 14
- oversold: 30
- overbought: 70
- quantity: 0.001
- ```
-- `plugin_loader.py`가 전략 로드 시 자동으로 같은 이름의 YAML을 찾아 `configure()` 호출
-- CLI에서 `--param key=value`로 런타임 오버라이드 가능
-
-**기존 전략 업데이트:**
-- `RsiStrategy`, `GridStrategy`에 `warmup_period` 속성 추가
-- `reset()` 메서드 구현
-
-**의존성 추가:** `pyyaml>=6.0`
-
----
-
-### 2.4 Backtest Enhancement
-
-**DetailedMetrics 데이터클래스** (`backtester/src/backtester/metrics.py`, 신규):
-```python
-@dataclass
-class TradeRecord:
- entry_time: datetime
- exit_time: datetime
- symbol: str
- side: str
- entry_price: Decimal
- exit_price: Decimal
- quantity: Decimal
- pnl: Decimal
- pnl_pct: float
- holding_period: timedelta
-
-@dataclass
-class DetailedMetrics:
- # 기본
- total_return: float
- total_trades: int
- winning_trades: int
- losing_trades: int
- win_rate: float
- profit_factor: float
-
- # 리스크 메트릭
- sharpe_ratio: float
- sortino_ratio: float
- calmar_ratio: float
- max_drawdown: float
- max_drawdown_duration: timedelta
-
- # 수익률 분석
- monthly_returns: dict[str, float] # "2025-01": 0.05
- avg_win: float
- avg_loss: float
- largest_win: float
- largest_loss: float
- avg_holding_period: timedelta
-
- # 개별 거래
- trades: list[TradeRecord]
-```
-
-**BacktestEngine 확장:**
-- `engine.py`에 `DetailedMetrics` 계산 로직 추가
-- `simulator.py`에 `TradeRecord` 생성 로직 추가 (진입/청산 시점 기록)
-- Sharpe ratio = `mean(daily_returns) / std(daily_returns) * sqrt(365)` (crypto는 365일)
-- Sortino ratio = `mean(daily_returns) / downside_std * sqrt(365)`
-- Calmar ratio = `annualized_return / max_drawdown`
-- Max drawdown = `max(peak - trough) / peak`
-
-**Reporter 개선:**
-- `reporter.py` — rich 라이브러리로 테이블 출력
- - 요약 테이블: 핵심 메트릭
- - 월별 수익률 테이블
- - 최고/최악 거래 Top 5
-- CSV/JSON 내보내기: `--output csv` / `--output json` 플래그
-
-**CLI 확장:**
-- `trading backtest run` — 기존 출력에 상세 메트릭 추가
-- `trading backtest run --output csv --file result.csv` — 결과 내보내기
-
-**의존성 추가:** `rich>=13.0`
-
----
-
-## Updated Tech Stack
-
-| 용도 | 기존 | 변경 |
-|------|------|------|
-| DB ORM | asyncpg (raw SQL) | **SQLAlchemy 2.0 async** (asyncpg 드라이버) |
-| 마이그레이션 | 없음 | **Alembic** |
-| 로깅 | Python logging | **structlog** |
-| 알림 | 없음 | **Telegram Bot API** (aiohttp) |
-| 메트릭 | 없음 | **prometheus-client** |
-| 전략 설정 | 하드코딩 | **YAML** (pyyaml) |
-| 리포트 출력 | print | **rich** |
-
----
-
-## Updated .env.example
-
-```env
-# Exchange
-BINANCE_API_KEY=
-BINANCE_API_SECRET=
-
-# Infrastructure
-REDIS_URL=redis://localhost:6379
-DATABASE_URL=postgresql+asyncpg://trading:trading@localhost:5432/trading
-
-# Logging
-LOG_LEVEL=INFO
-LOG_FORMAT=json
-
-# Telegram
-TELEGRAM_BOT_TOKEN=
-TELEGRAM_CHAT_ID=
-TELEGRAM_ENABLED=false
-
-# Risk Management
-RISK_MAX_POSITION_SIZE=0.1
-RISK_STOP_LOSS_PCT=5
-RISK_DAILY_LOSS_LIMIT_PCT=10
-DRY_RUN=true
-
-# Health & Metrics
-HEALTH_PORT=8080
-CIRCUIT_BREAKER_THRESHOLD=5
-CIRCUIT_BREAKER_TIMEOUT=60
-```
-
----
-
-## New Files Summary
-
-| 파일 | 용도 |
-|------|------|
-| `shared/src/shared/sa_models.py` | SQLAlchemy ORM 모델 |
-| `shared/src/shared/logging.py` | structlog 설정 |
-| `shared/src/shared/notifier.py` | Telegram 알림 |
-| `shared/src/shared/resilience.py` | retry, circuit breaker |
-| `shared/src/shared/healthcheck.py` | 헬스체크 서버 |
-| `shared/src/shared/metrics.py` | Prometheus 메트릭 |
-| `shared/alembic/` | DB 마이그레이션 환경 |
-| `strategies/config/*.yaml` | 전략 파라미터 설정 |
-| `strategies/macd_strategy.py` | MACD 전략 |
-| `strategies/bollinger_strategy.py` | Bollinger Bands 전략 |
-| `strategies/ema_crossover_strategy.py` | EMA Crossover 전략 |
-| `strategies/vwap_strategy.py` | VWAP 전략 |
-| `strategies/volume_profile_strategy.py` | Volume Profile 전략 |
-| `backtester/src/backtester/metrics.py` | 상세 백테스트 메트릭 |
-| `monitoring/prometheus.yml` | Prometheus 설정 |
-
----
-
-## Scope Boundaries
-
-**포함:**
-- SQLAlchemy 2.0 async 전환 + Alembic 마이그레이션
-- structlog JSON 로깅
-- Telegram 알림 (시그널, 주문, 에러, 일일 요약)
-- 에러 복구 (retry, circuit breaker) + 헬스체크
-- Prometheus 메트릭 수집
-- 5개 신규 전략 (MACD, Bollinger, EMA Crossover, VWAP, Volume Profile)
-- BaseStrategy에 warmup_period, reset() 추가
-- YAML 기반 전략 파라미터
-- 백테스트 상세 메트릭 + rich 리포트
-
-**제외:**
-- Grafana 대시보드 프로비저닝 (Prometheus만 설정, 대시보드는 수동)
-- 멀티 거래소 지원
-- REST API / 웹 대시보드
-- 전략 조합 프레임워크 (향후 확장)
diff --git a/docs/superpowers/specs/2026-04-02-news-driven-stock-selector-design.md b/docs/superpowers/specs/2026-04-02-news-driven-stock-selector-design.md
new file mode 100644
index 0000000..d439154
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-02-news-driven-stock-selector-design.md
@@ -0,0 +1,418 @@
+# News-Driven Stock Selector Design
+
+**Date:** 2026-04-02
+**Goal:** Upgrade the MOC (Market on Close) strategy from fixed symbol lists to dynamic, news-driven stock selection. The system collects news/sentiment data continuously, then selects 2-3 optimal stocks daily before market close.
+
+---
+
+## Architecture Overview
+
+```
+[Continuous Collection] [Pre-Close Decision]
+Finnhub News ─┐
+RSS Feeds ─┤
+SEC EDGAR ─┤
+Truth Social ─┼→ DB (news_items) → Sentiment Aggregator → symbol_scores
+Reddit ─┤ + Redis "news" (every 15 min) market_sentiment
+Fear & Greed ─┤
+FOMC/Fed ─┘
+
+ 15:00 ET ─→ Candidate Pool (sentiment top + LLM picks)
+ 15:15 ET ─→ Technical Filter (RSI, EMA, volume)
+ 15:30 ET ─→ LLM Final Selection (2-3 stocks) → Telegram
+ 15:50 ET ─→ MOC Buy Execution
+ 09:35 ET ─→ Next-day Sell (existing MOC logic)
+```
+
+## 1. News Collector Service
+
+New service: `services/news-collector/`
+
+### Structure
+
+```
+services/news-collector/
+├── Dockerfile
+├── pyproject.toml
+├── src/news_collector/
+│ ├── __init__.py
+│ ├── main.py # Scheduler: runs each collector on its interval
+│ ├── config.py
+│ └── collectors/
+│ ├── __init__.py
+│ ├── base.py # BaseCollector ABC
+│ ├── finnhub.py # Finnhub market news (free, 60 req/min)
+│ ├── rss.py # Yahoo Finance, Google News, MarketWatch RSS
+│ ├── sec_edgar.py # SEC EDGAR 8-K/10-Q filings
+│ ├── truth_social.py # Truth Social scraping (Trump posts)
+│ ├── reddit.py # Reddit (r/wallstreetbets, r/stocks)
+│ ├── fear_greed.py # CNN Fear & Greed Index scraping
+│ └── fed.py # FOMC statements, Fed announcements
+└── tests/
+```
+
+### BaseCollector Interface
+
+```python
+class BaseCollector(ABC):
+ name: str
+ poll_interval: int # seconds
+
+ @abstractmethod
+ async def collect(self) -> list[NewsItem]:
+ """Collect and return list of NewsItem."""
+
+ @abstractmethod
+ async def is_available(self) -> bool:
+ """Check if this source is accessible (API key present, endpoint reachable)."""
+```
+
+### Poll Intervals
+
+| Collector | Interval | Notes |
+|-----------|----------|-------|
+| Finnhub | 5 min | Free tier: 60 calls/min |
+| RSS (Yahoo/Google/MarketWatch) | 10 min | Headlines only |
+| SEC EDGAR | 30 min | Focus on 8-K filings |
+| Truth Social | 15 min | Scraping |
+| Reddit | 15 min | Hot posts from relevant subs |
+| Fear & Greed | 1 hour | Updates once daily but check periodically |
+| FOMC/Fed | 1 hour | Infrequent events |
+
+### Provider Abstraction (for paid upgrade path)
+
+```python
+# config.yaml
+collectors:
+ news:
+ provider: "finnhub" # swap to "benzinga" for paid
+ api_key: ${FINNHUB_API_KEY}
+ social:
+ provider: "reddit" # swap to "stocktwits_pro" etc.
+ policy:
+ provider: "truth_social" # swap to "twitter_api" etc.
+
+# Factory
+COLLECTOR_REGISTRY = {
+ "finnhub": FinnhubCollector,
+ "rss": RSSCollector,
+ "benzinga": BenzingaCollector, # added later
+}
+```
+
+## 2. Shared Models (additions to shared/)
+
+### NewsItem (shared/models.py)
+
+```python
+class NewsCategory(str, Enum):
+ POLICY = "policy"
+ EARNINGS = "earnings"
+ MACRO = "macro"
+ SOCIAL = "social"
+ FILING = "filing"
+ FED = "fed"
+
+class NewsItem(BaseModel):
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
+ source: str # "finnhub", "rss", "sec_edgar", etc.
+ headline: str
+ summary: str | None = None
+ url: str | None = None
+ published_at: datetime
+ symbols: list[str] = [] # Related tickers (if identifiable)
+ sentiment: float # -1.0 to 1.0 (first-pass analysis at collection)
+ category: NewsCategory
+ raw_data: dict = {}
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
+```
+
+### SymbolScore (shared/sentiment_models.py — new file)
+
+```python
+class SymbolScore(BaseModel):
+ symbol: str
+ news_score: float # -1.0 to 1.0, weighted avg of news sentiment
+ news_count: int # Number of news items in last 24h
+ social_score: float # Reddit/social sentiment
+ policy_score: float # Policy-related impact
+ filing_score: float # SEC filing impact
+ composite: float # Weighted final score
+ updated_at: datetime
+
+class MarketSentiment(BaseModel):
+ fear_greed: int # 0-100
+ fear_greed_label: str # "Extreme Fear", "Fear", "Neutral", "Greed", "Extreme Greed"
+ vix: float | None = None
+ fed_stance: str # "hawkish", "neutral", "dovish"
+ market_regime: str # "risk_on", "neutral", "risk_off"
+ updated_at: datetime
+
+class SelectedStock(BaseModel):
+ symbol: str
+ side: OrderSide # BUY or SELL
+ conviction: float # 0.0 to 1.0
+ reason: str # Selection rationale
+ key_news: list[str] # Key news headlines
+
+class Candidate(BaseModel):
+ symbol: str
+ source: str # "sentiment" or "llm"
+ direction: OrderSide | None = None # Suggested direction (if known)
+ score: float # Relevance/priority score
+ reason: str # Why this candidate was selected
+```
+
+## 3. Sentiment Analysis Pipeline
+
+### Location
+
+Refactor existing `shared/src/shared/sentiment.py`.
+
+### Two-Stage Analysis
+
+**Stage 1: Per-news sentiment (at collection time)**
+- VADER (nltk.sentiment, free) for English headlines
+- Keyword rule engine for domain-specific terms (e.g., "tariff" → negative for importers, positive for domestic producers)
+- Score stored in `NewsItem.sentiment`
+
+**Stage 2: Per-symbol aggregation (every 15 minutes)**
+
+```
+composite = (
+ news_score * 0.3 +
+ social_score * 0.2 +
+ policy_score * 0.3 +
+ filing_score * 0.2
+) * freshness_decay
+```
+
+Freshness decay:
+- < 1 hour: 1.0
+- 1-6 hours: 0.7
+- 6-24 hours: 0.3
+- > 24 hours: excluded
+
+Policy score weighted high because US stock market is heavily influenced by policy events (tariffs, regulation, subsidies).
+
+### Market-Level Gating
+
+`MarketSentiment.market_regime` determination:
+- `risk_off`: Fear & Greed < 20 OR VIX > 30 → **block all trades**
+- `risk_on`: Fear & Greed > 60 AND VIX < 20
+- `neutral`: everything else
+
+This extends the existing `sentiment.py` `should_block()` logic.
+
+## 4. Stock Selector Engine
+
+### Location
+
+`services/strategy-engine/src/strategy_engine/stock_selector.py`
+
+### Three-Stage Selection Process
+
+**Stage 1: Candidate Pool (15:00 ET)**
+
+Two candidate sources, results merged (deduplicated):
+
+```python
+class CandidateSource(ABC):
+ @abstractmethod
+ async def get_candidates(self) -> list[Candidate]
+
+class SentimentCandidateSource(CandidateSource):
+ """Top N symbols by composite SymbolScore from DB."""
+
+class LLMCandidateSource(CandidateSource):
+ """Send today's top news summary to Claude, get related symbols + direction."""
+```
+
+- SentimentCandidateSource: top 20 by composite score
+- LLMCandidateSource: Claude analyzes today's major news and recommends affected symbols
+- Merged pool: typically 20-30 candidates
+
+**Stage 2: Technical Filter (15:15 ET)**
+
+Apply existing MOC screening criteria to candidates:
+- Fetch recent price data from Alpaca for all candidates
+- RSI 30-60
+- Price > 20-period EMA
+- Volume > average
+- Bullish candle pattern
+- Result: typically 5-10 survivors
+
+**Stage 3: LLM Final Selection (15:30 ET)**
+
+Send to Claude:
+- Filtered candidate list with technical indicators
+- Per-symbol sentiment scores and top news headlines
+- Market sentiment (Fear & Greed, VIX, Fed stance)
+- Prompt: "Select 2-3 stocks for MOC trading with rationale"
+
+Response parsed into `list[SelectedStock]`.
+
+### Integration with MOC Strategy
+
+Current: MOC strategy receives candles for fixed symbols and decides internally.
+
+New flow:
+1. `StockSelector` publishes `SelectedStock` list to Redis stream `selected_stocks` at 15:30 ET
+2. MOC strategy reads `selected_stocks` to get today's targets
+3. MOC still applies its own technical checks at 15:50-16:00 as a safety net
+4. If a selected stock fails the final technical check, it's skipped (no forced trades)
+
+## 5. Database Schema
+
+Four new tables via Alembic migration:
+
+```sql
+CREATE TABLE news_items (
+ id UUID PRIMARY KEY,
+ source VARCHAR(50) NOT NULL,
+ headline TEXT NOT NULL,
+ summary TEXT,
+ url TEXT,
+ published_at TIMESTAMPTZ NOT NULL,
+ symbols TEXT[],
+ sentiment FLOAT NOT NULL,
+ category VARCHAR(50) NOT NULL,
+ raw_data JSONB DEFAULT '{}',
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+CREATE INDEX idx_news_items_published ON news_items(published_at);
+CREATE INDEX idx_news_items_symbols ON news_items USING GIN(symbols);
+
+CREATE TABLE symbol_scores (
+ id UUID PRIMARY KEY,
+ symbol VARCHAR(10) NOT NULL,
+ news_score FLOAT NOT NULL DEFAULT 0,
+ news_count INT NOT NULL DEFAULT 0,
+ social_score FLOAT NOT NULL DEFAULT 0,
+ policy_score FLOAT NOT NULL DEFAULT 0,
+ filing_score FLOAT NOT NULL DEFAULT 0,
+ composite FLOAT NOT NULL DEFAULT 0,
+ updated_at TIMESTAMPTZ NOT NULL
+);
+CREATE UNIQUE INDEX idx_symbol_scores_symbol ON symbol_scores(symbol);
+
+CREATE TABLE market_sentiment (
+ id UUID PRIMARY KEY,
+ fear_greed INT NOT NULL,
+ fear_greed_label VARCHAR(30) NOT NULL,
+ vix FLOAT,
+ fed_stance VARCHAR(20) NOT NULL DEFAULT 'neutral',
+ market_regime VARCHAR(20) NOT NULL DEFAULT 'neutral',
+ updated_at TIMESTAMPTZ NOT NULL
+);
+
+CREATE TABLE stock_selections (
+ id UUID PRIMARY KEY,
+ trade_date DATE NOT NULL,
+ symbol VARCHAR(10) NOT NULL,
+ side VARCHAR(4) NOT NULL,
+ conviction FLOAT NOT NULL,
+ reason TEXT NOT NULL,
+ key_news JSONB DEFAULT '[]',
+ sentiment_snapshot JSONB DEFAULT '{}',
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+CREATE INDEX idx_stock_selections_date ON stock_selections(trade_date);
+```
+
+`stock_selections` stores an audit trail: why each stock was selected, enabling post-hoc analysis of selection quality.
+
+## 6. Redis Streams
+
+| Stream | Producer | Consumer | Payload |
+|--------|----------|----------|---------|
+| `news` | news-collector | strategy-engine (sentiment aggregator) | NewsItem |
+| `selected_stocks` | stock-selector | MOC strategy | SelectedStock |
+
+Existing streams (`candles`, `signals`, `orders`) unchanged.
+
+## 7. Docker Compose Addition
+
+```yaml
+news-collector:
+ build:
+ context: .
+ dockerfile: services/news-collector/Dockerfile
+ env_file: .env
+ ports:
+ - "8084:8084"
+ depends_on:
+ redis: { condition: service_healthy }
+ postgres: { condition: service_healthy }
+ healthcheck:
+ test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8084/health')"]
+ interval: 10s
+ timeout: 5s
+ retries: 3
+ restart: unless-stopped
+```
+
+## 8. Environment Variables
+
+```bash
+# News Collector
+FINNHUB_API_KEY= # Free key from finnhub.io
+NEWS_POLL_INTERVAL=300 # Default 5 min (overrides per-collector defaults)
+SENTIMENT_AGGREGATE_INTERVAL=900 # 15 min
+
+# Stock Selector
+SELECTOR_CANDIDATES_TIME=15:00 # ET, candidate pool generation
+SELECTOR_FILTER_TIME=15:15 # ET, technical filter
+SELECTOR_FINAL_TIME=15:30 # ET, LLM final pick
+SELECTOR_MAX_PICKS=3
+
+# LLM (for stock selector + screener)
+ANTHROPIC_API_KEY=
+ANTHROPIC_MODEL=claude-sonnet-4-20250514
+```
+
+## 9. Telegram Notifications
+
+Extend existing `shared/notifier.py` with:
+
+```python
+async def send_stock_selection(self, selections: list[SelectedStock], market: MarketSentiment):
+ """
+ 📊 오늘의 종목 선정 (2/3)
+
+ 1. NVDA 🟢 BUY (확신도: 0.85)
+ 근거: 트럼프 반도체 보조금 확대 발표, RSI 42
+ 핵심뉴스: "Trump signs CHIPS Act expansion..."
+
+ 2. XOM 🟢 BUY (확신도: 0.72)
+ 근거: 유가 상승 + 실적 서프라이즈, 볼륨 급증
+
+ 시장심리: Fear & Greed 55 (Neutral) | VIX 18.2
+ """
+```
+
+## 10. Testing Strategy
+
+**Unit tests:**
+- Each collector: mock HTTP responses → verify NewsItem parsing
+- Sentiment analysis: verify VADER + keyword scoring
+- Aggregator: mock news data → verify SymbolScore calculation and freshness decay
+- Stock selector: mock scores → verify candidate/filter/selection pipeline
+- LLM calls: mock Claude response → verify SelectedStock parsing
+
+**Integration tests:**
+- Full pipeline: news collection → DB → aggregation → selection
+- Market gating: verify `risk_off` blocks all trades
+- MOC integration: verify selected stocks flow to MOC strategy
+
+**Post-hoc analysis (future):**
+- Use `stock_selections` audit trail to measure selection accuracy
+- Historical news data replay for backtesting requires paid data (deferred)
+
+## 11. Out of Scope (Future)
+
+- Paid API integration (designed for, not implemented)
+- Historical news backtesting
+- WebSocket real-time news streaming
+- Multi-language sentiment analysis
+- Options/derivatives signals
diff --git a/docs/superpowers/specs/2026-04-02-platform-upgrade-design.md b/docs/superpowers/specs/2026-04-02-platform-upgrade-design.md
new file mode 100644
index 0000000..9c84e10
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-02-platform-upgrade-design.md
@@ -0,0 +1,257 @@
+# Platform Upgrade Design Spec
+
+**Date**: 2026-04-02
+**Approach**: Bottom-Up (shared library → infra → services → API security → operations)
+
+---
+
+## Phase 1: Shared Library Hardening
+
+### 1-1. Resilience Module (`shared/src/shared/resilience.py`)
+Currently empty. Implement:
+- **`retry_async()`** — tenacity-based exponential backoff + jitter decorator. Configurable max retries (default 3), base delay (1s), max delay (30s).
+- **`CircuitBreaker`** — Tracks consecutive failures. Opens after N failures (default 5), stays open for configurable cooldown (default 60s), transitions to half-open to test recovery.
+- **`timeout()`** — asyncio-based timeout wrapper. Raises `TimeoutError` after configurable duration.
+- All decorators composable: `@retry_async() @circuit_breaker() async def call_api():`
+
+### 1-2. DB Connection Pooling (`shared/src/shared/db.py`)
+Add to `create_async_engine()`:
+- `pool_size=20` (configurable via `DB_POOL_SIZE`)
+- `max_overflow=10` (configurable via `DB_MAX_OVERFLOW`)
+- `pool_pre_ping=True` (verify connections before use)
+- `pool_recycle=3600` (recycle stale connections)
+Add corresponding fields to `Settings`.
+
+### 1-3. Redis Resilience (`shared/src/shared/broker.py`)
+- Add to `redis.asyncio.from_url()`: `socket_keepalive=True`, `health_check_interval=30`, `retry_on_timeout=True`
+- Wrap `publish()`, `read_group()`, `ensure_group()` with `@retry_async()` from resilience module
+- Add `reconnect()` method for connection loss recovery
+
+### 1-4. Config Validation (`shared/src/shared/config.py`)
+- Add `field_validator` for business logic: `risk_max_position_size > 0`, `health_port` in 1024-65535, `log_level` in valid set
+- Change secret fields to `SecretStr`: `alpaca_api_key`, `alpaca_api_secret`, `database_url`, `redis_url`, `telegram_bot_token`, `anthropic_api_key`, `finnhub_api_key`
+- Update all consumers to call `.get_secret_value()` where needed
+
+### 1-5. Dependency Pinning
+All `pyproject.toml` files: add upper bounds.
+Examples:
+- `pydantic>=2.8,<3`
+- `redis>=5.0,<6`
+- `sqlalchemy[asyncio]>=2.0,<3`
+- `numpy>=1.26,<3`
+- `pandas>=2.1,<3`
+- `anthropic>=0.40,<1`
+Run `uv lock` to generate lock file.
+
+---
+
+## Phase 2: Infrastructure Hardening
+
+### 2-1. Docker Secrets & Environment
+- Remove hardcoded `POSTGRES_USER: trading` / `POSTGRES_PASSWORD: trading` from `docker-compose.yml`
+- Reference via `${POSTGRES_USER}` / `${POSTGRES_PASSWORD}` from `.env`
+- Add comments in `.env.example` marking secret vs config variables
+
+### 2-2. Dockerfile Optimization (all 7 services)
+Pattern for each Dockerfile:
+```dockerfile
+# Stage 1: builder
+FROM python:3.12-slim AS builder
+WORKDIR /app
+COPY shared/pyproject.toml shared/setup.cfg shared/
+COPY shared/src/ shared/src/
+RUN pip install --no-cache-dir ./shared
+COPY services/<name>/pyproject.toml services/<name>/
+COPY services/<name>/src/ services/<name>/src/
+RUN pip install --no-cache-dir ./services/<name>
+
+# Stage 2: runtime
+FROM python:3.12-slim
+RUN useradd -r -s /bin/false appuser
+WORKDIR /app
+COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
+COPY --from=builder /usr/local/bin /usr/local/bin
+USER appuser
+CMD ["python", "-m", "<module>.main"]
+```
+
+Create root `.dockerignore`:
+```
+__pycache__
+*.pyc
+.git
+.venv
+.env
+tests/
+docs/
+*.md
+.ruff_cache
+```
+
+### 2-3. Database Index Migration (`003_add_missing_indexes.py`)
+New Alembic migration adding:
+- `idx_signals_symbol_created` on `signals(symbol, created_at)`
+- `idx_orders_symbol_status_created` on `orders(symbol, status, created_at)`
+- `idx_trades_order_id` on `trades(order_id)`
+- `idx_trades_symbol_traded` on `trades(symbol, traded_at)`
+- `idx_portfolio_snapshots_at` on `portfolio_snapshots(snapshot_at)`
+- `idx_symbol_scores_symbol` unique on `symbol_scores(symbol)`
+
+### 2-4. Docker Compose Resource Limits
+Add to each service:
+```yaml
+deploy:
+ resources:
+ limits:
+ memory: 512M
+ cpus: '1.0'
+```
+Strategy-engine and backtester get `memory: 1G` (pandas/numpy usage).
+
+Add explicit networks:
+```yaml
+networks:
+ internal:
+ driver: bridge
+ monitoring:
+ driver: bridge
+```
+
+---
+
+## Phase 3: Service-Level Improvements
+
+### 3-1. Graceful Shutdown (all services)
+Add to each service's `main()`:
+```python
+shutdown_event = asyncio.Event()
+
+def _signal_handler():
+ log.info("shutdown_signal_received")
+ shutdown_event.set()
+
+loop = asyncio.get_event_loop()
+loop.add_signal_handler(signal.SIGTERM, _signal_handler)
+loop.add_signal_handler(signal.SIGINT, _signal_handler)
+```
+Main loops check `shutdown_event.is_set()` to exit gracefully.
+API service: add `--timeout-graceful-shutdown 30` to uvicorn CMD.
+
+### 3-2. Exception Specialization (all services)
+Replace broad `except Exception` with layered handling:
+- `ConnectionError`, `TimeoutError` → retry via resilience module
+- `ValueError`, `KeyError` → log warning, skip item, continue
+- `Exception` → top-level only, `exc_info=True` for full traceback + Telegram alert
+
+Target: reduce 63 broad catches to ~10 top-level safety nets.
+
+### 3-3. LLM Parsing Deduplication (`stock_selector.py`)
+Extract `_extract_json_from_text(text: str) -> list | dict | None`:
+- Tries ```` ```json ``` ```` code block extraction
+- Falls back to `re.search(r"\[.*\]", text, re.DOTALL)`
+- Falls back to raw `json.loads(text.strip())`
+Replace 3 duplicate parsing blocks with single call.
+
+### 3-4. aiohttp Session Reuse (`stock_selector.py`)
+- Add `_session: aiohttp.ClientSession | None = None` to `StockSelector`
+- Lazy-init in `_ensure_session()`, close in `close()`
+- Replace all `async with aiohttp.ClientSession()` with `self._session`
+
+---
+
+## Phase 4: API Security
+
+### 4-1. Bearer Token Authentication
+- Add `api_auth_token: SecretStr = ""` to `Settings`
+- Create `dependencies/auth.py` with `verify_token()` dependency
+- Apply to all `/api/v1/*` routes via router-level `dependencies=[Depends(verify_token)]`
+- If token is empty string → skip auth (dev mode), log warning on startup
+
+### 4-2. CORS Configuration
+```python
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=settings.cors_origins.split(","), # default: "http://localhost:3000"
+ allow_methods=["GET", "POST"],
+ allow_headers=["Authorization", "Content-Type"],
+)
+```
+Add `cors_origins: str = "http://localhost:3000"` to Settings.
+
+### 4-3. Rate Limiting
+- Add `slowapi` dependency
+- Global default: 60 req/min per IP
+- Order-related endpoints: 10 req/min per IP
+- Return `429 Too Many Requests` with `Retry-After` header
+
+### 4-4. Input Validation
+- All `limit` params: `Query(default=50, ge=1, le=1000)`
+- All `days` params: `Query(default=30, ge=1, le=365)`
+- Add Pydantic `response_model` to all endpoints (enables auto OpenAPI docs)
+- Add `symbol` param validation: uppercase, 1-5 chars, alphanumeric
+
+---
+
+## Phase 5: Operational Maturity
+
+### 5-1. GitHub Actions CI/CD
+File: `.github/workflows/ci.yml`
+
+**PR trigger** (`pull_request`):
+1. Install deps (`uv sync`)
+2. Ruff lint + format check
+3. pytest with coverage (`--cov --cov-report=xml`)
+4. Upload coverage to PR comment
+
+**Main push** (`push: branches: [master]`):
+1. Same lint + test
+2. `docker compose build`
+3. (Future: push to registry)
+
+### 5-2. Ruff Rules Enhancement
+```toml
+[tool.ruff.lint]
+select = ["E", "W", "F", "I", "B", "UP", "ASYNC", "PERF", "C4", "RUF"]
+ignore = ["E501"]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = ["F841"]
+
+[tool.ruff.lint.isort]
+known-first-party = ["shared"]
+```
+Run `ruff check --fix .` and `ruff format .` to fix existing violations, commit separately.
+
+### 5-3. Prometheus Alerting
+File: `monitoring/prometheus/alert_rules.yml`
+Rules:
+- `ServiceDown`: `service_up == 0` for 1 min → critical
+- `HighErrorRate`: `rate(errors_total[5m]) > 10` → warning
+- `HighLatency`: `histogram_quantile(0.95, processing_seconds) > 5` → warning
+
+Add Alertmanager config with Telegram webhook (reuse existing bot token).
+Reference alert rules in `monitoring/prometheus.yml`.
+
+### 5-4. Code Coverage
+Add to root `pyproject.toml`:
+```toml
+[tool.pytest.ini_options]
+addopts = "--cov=shared/src --cov=services --cov-report=term-missing"
+
+[tool.coverage.run]
+branch = true
+omit = ["tests/*", "*/alembic/*"]
+
+[tool.coverage.report]
+fail_under = 70
+```
+Add `pytest-cov` to dev dependencies.
+
+---
+
+## Out of Scope
+- Kubernetes/Helm charts (premature — Docker Compose sufficient for current scale)
+- External secrets manager (Vault, AWS SM — overkill for single-machine deployment)
+- OpenTelemetry distributed tracing (add when debugging cross-service issues)
+- API versioning beyond `/api/v1/` prefix
+- Data retention/partitioning (address when data volume becomes an issue)