diff options
Diffstat (limited to 'docs/superpowers/specs')
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) |
