[잔고 관리] - _today_buy_total 인스턴스 변수로 당일 누적 매수 추적 (KIS T+2 미차감 보완) - MAX_BUY_PER_CYCLE, MAX_DAILY_BUY_RATIO 설정 추가 - available_deposit = max_daily_buy - effective_today_buy 계산 [앙상블 & 포지션 사이징] - AdaptiveEnsemble 실제 연동 (하드코딩 가중치 제거) - Kelly Criterion Half-Kelly 포지션 비중 계산 - SignalWeights.normalize() Water-Filling 알고리즘으로 경계 위반 해결 - _accuracy_weighted() 크기 가중 정확도로 통일 - ensemble_weights.json → ensemble_history.json 통합 [LLM 클라이언트] - GeminiLLMClient 추가 (Gemini → Ollama 폴백 체인) - _class_last_call_ts 클래스 변수로 워커 재시작 후에도 스로틀 유지 - Ollama 미실행 조기 감지 및 명확한 오류 메시지 [KIS API] - 모든 requests.get/post에 timeout=Config.HTTP_TIMEOUT 적용 - get_balance()에 today_buy_amt 필드 추가 [장중 전용 운영] - KRXCalendar: exchange_calendars 기반, 2024~2026 공휴일 하드코딩 폴백 - EOD 셧다운: 15:35에 전체 상태 저장 후 서버 자동 종료 - Watchdog: .eod_date 마커로 EOD 후 재시작 차단 - daily_launcher.py: 매일 08:30 실행, 휴장일 감지 후 봇 미시작 - Windows 작업 스케줄러 WebAI_DailyLauncher 등록 [텔레그램 스킬 수정] - PYTHONIOENCODING=utf-8 서브프로세스 환경 설정 (cp949 이모지 오류 해결) - /regime: IPC macro_indices 파싱 구현, --json 모드 input() 블로킹 제거 - /weights: ensemble_history.json 형식 파싱 업데이트 - /model_health: glob 패턴 *_v3.pt 수정 - /postmortem: 거래 없을 때 빈 JSON 출력으로 Telegram 오류 해결 - /macro: price=0 시 prev_close 폴백 표시 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
697 lines
31 KiB
Markdown
697 lines
31 KiB
Markdown
# 🤖 AI Trading Bot — 프로젝트 설계 문서 (CLAUDE.md)
|
||
|
||
> **최종 갱신**: 2026-03-19
|
||
> **런타임**: Windows (Python 3.x, PyTorch CUDA, FastAPI, Ollama)
|
||
> **하드웨어**: AMD 9800X3D + RTX 5070 Ti (16 GB VRAM)
|
||
|
||
---
|
||
|
||
## 1. 시스템 아키텍처 개요
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ main_server.py │
|
||
│ FastAPI (uvicorn, port 8000) — 프로세스 매니저 & REST API 서버 │
|
||
│ ┌──────────────┐ ┌─────────────────┐ ┌──────────────────────┐ │
|
||
│ │ Trading Bot │ │ Telegram Bot │ │ ProcessWatchdog │ │
|
||
│ │ (Process #1) │ │ (Process #2) │ │ (Daemon Thread) │ │
|
||
│ └──────┬───────┘ └────────┬────────┘ └──────────┬───────────┘ │
|
||
│ │ │ │ │
|
||
│ └─── Shared Memory (IPC) ───┘ Health Check / Restart │
|
||
│ + Command Queue │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 1.1 멀티 프로세스 구성
|
||
|
||
| 프로세스 | 역할 | 진입점 |
|
||
|---------|------|--------|
|
||
| **Main Server (Uvicorn)** | FastAPI REST API 서버, 프로세스 오케스트레이터 | `main_server.py` |
|
||
| **Trading Bot** | 자동매매 메인 루프 (스케줄러, 분석, 주문) | `modules/bot.py` → `AutoTradingBot.loop()` |
|
||
| **Telegram Bot** | 사용자 인터랙션 (명령어 처리, 알림) | `modules/services/telegram_bot/runner.py` |
|
||
| **ProcessWatchdog** | 자식 프로세스 헬스체크 & 자동 재시작 (30초 간격) | `modules/utils/process_tracker.py` |
|
||
|
||
### 1.2 프로세스 간 통신 (IPC)
|
||
|
||
```
|
||
┌─────────────┐ SharedMemory (128KB) ┌──────────────┐
|
||
│ Trading Bot │ ─── write_status() ───────► │ Telegram Bot │
|
||
│ │ ◄── read_status() ──────── │ │
|
||
│ │ │ │
|
||
│ │ multiprocessing.Queue │ │
|
||
│ │ ◄── send_command() ──────── │ │
|
||
│ │ (텔레그램 → 봇 명령) │ │
|
||
└─────────────┘ └──────────────┘
|
||
```
|
||
|
||
- **SharedMemory** (`web_ai_bot_ipc`, 128KB): 메인 봇이 상태 데이터(잔고, GPU, 매크로 지표 등)를 JSON으로 기록, 텔레그램 봇이 읽기
|
||
- **Command Queue** (`multiprocessing.Queue`): 텔레그램 → 메인 봇 양방향 명령 채널 (`restart`, `evaluate` 등)
|
||
- **Lock** (`multiprocessing.Lock`): SharedMemory 동시 접근 보호
|
||
- **IPC Staleness**: 600초 (10분 이상 오래된 데이터는 무시)
|
||
|
||
### 1.3 서버 생명주기 (Lifespan)
|
||
|
||
```python
|
||
# main_server.py > lifespan()
|
||
1. Config.validate() # 환경변수 검증
|
||
2. ProcessTracker.check_and_kill_zombies() # 좀비 프로세스 정리
|
||
3. 전역 객체 초기화 (OllamaManager, KISClient, NewsCollector)
|
||
4. Shared Resources 생성 (Lock, Queue, Event)
|
||
5. Trading Bot 프로세스 생성 & 시작
|
||
6. Telegram Bot 프로세스 생성 & 시작
|
||
7. ProcessWatchdog 시작 (30초 간격 헬스체크)
|
||
8. → yield (서버 정상 운영)
|
||
9. [종료] shutdown_event 설정 → 자식 종료 → SharedMemory 해제
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 디렉토리 구조
|
||
|
||
```
|
||
web-ai/
|
||
├── main_server.py # [Entry Point] FastAPI + 프로세스 매니저
|
||
├── warmup_and_restart.py # LSTM 사전학습 + 봇 자동 시작 스크립트
|
||
├── watchlist_manager.py # 뉴스 기반 일일 Watchlist 자동 업데이트
|
||
├── backtester.py # 전략 백테스팅 CLI
|
||
├── theme_manager.py # 종목별 테마/섹터 관리
|
||
├── .env # 환경변수 (KIS, Telegram, Ollama 등)
|
||
│
|
||
├── modules/
|
||
│ ├── __init__.py
|
||
│ ├── config.py # [Config] 환경변수 & 상수 정의
|
||
│ ├── bot.py # [Core] AutoTradingBot (상태 머신 & 스케줄러)
|
||
│ │
|
||
│ ├── analysis/ # [AI Brain] 분석 엔진
|
||
│ │ ├── deep_learning.py # Attention-LSTM (7D 피처, PyTorch GPU)
|
||
│ │ ├── technical.py # 기술적 지표 (RSI, MACD, BB, ADX, OBV...)
|
||
│ │ ├── macro.py # 거시경제 분석 (KOSPI/KOSDAQ/MSI)
|
||
│ │ ├── ensemble.py # 적응형 앙상블 (3신호 가중치 자동조정)
|
||
│ │ ├── evaluator.py # 주간 성과 평가 + LLM 전문가 패널
|
||
│ │ └── backtest.py # 백테스팅 프레임워크 (Sharpe, MDD 등)
|
||
│ │
|
||
│ ├── strategy/ # [Decision] 매매 의사결정
|
||
│ │ └── process.py # 워커 프로세스용 분석 함수 (병렬 처리)
|
||
│ │
|
||
│ ├── services/ # [I/O] 외부 서비스 연동
|
||
│ │ ├── kis.py # 한국투자증권 REST API (동기 + 비동기)
|
||
│ │ ├── ollama.py # Ollama LLM 인터페이스 (GPU 충돌 방지)
|
||
│ │ ├── news.py # Google News RSS 크롤링 (동기 + 비동기)
|
||
│ │ ├── telegram.py # 텔레그램 메시지 발송 (Fire-and-forget)
|
||
│ │ └── telegram_bot/
|
||
│ │ ├── server.py # 텔레그램 봇 서버 (명령어 핸들러)
|
||
│ │ └── runner.py # 텔레그램 봇 독립 프로세스 실행기
|
||
│ │
|
||
│ └── utils/ # [Util] 유틸리티
|
||
│ ├── ipc.py # SharedMemory + Command Queue IPC
|
||
│ ├── process_tracker.py # PID 추적 & 좀비 정리 & Watchdog
|
||
│ ├── monitor.py # CPU/GPU/RAM 서킷 브레이커
|
||
│ └── performance_db.py # 일별 스냅샷 & 매매 기록 영구 저장
|
||
│
|
||
├── data/ # [Runtime Data]
|
||
│ ├── watchlist.json # 현재 감시 종목 리스트
|
||
│ ├── daily_trade_history.json # 일일 매매 기록
|
||
│ ├── kis_token.json # KIS OAuth 토큰 캐시
|
||
│ ├── peak_prices.json # 트레일링 스탑용 최고가
|
||
│ ├── ensemble_history.json # AdaptiveEnsemble 가중치 + 매매 히스토리 (종목별)
|
||
│ ├── models/ # LSTM 체크포인트 (종목별 .pt 파일)
|
||
│ └── performance/ # 성과 데이터 (daily_snapshots, trade_records)
|
||
│
|
||
└── tests/ # 테스트
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 핵심 모듈 상세
|
||
|
||
### 3.1 AutoTradingBot (`modules/bot.py`)
|
||
|
||
**메인 트레이딩 루프** — 장 시작(09:00) ~ 장 마감(15:30) 사이에 자동 실행
|
||
|
||
```
|
||
[v3.1 주요 기능]
|
||
├── ATR 기반 동적 손절/익절 + 트레일링 스탑
|
||
├── Kelly Criterion 포지션 사이징 (실전 승률·손익비 기반, Half-Kelly)
|
||
├── AdaptiveEnsemble 연동 (매도 후 가중치 자동 학습)
|
||
├── 당일 누적 매수 추적 (_today_buy_total) - KIS T+2 미차감 보완
|
||
├── 사이클당 최대 매수 종목 수 제한 (MAX_BUY_PER_CYCLE)
|
||
├── ProcessPoolExecutor 병렬 분석 (워커 1개, OOM 대응 자동 재시작)
|
||
├── 일별 자산 스냅샷 (09:05~09:15)
|
||
├── 주간 성과 평가 (월요일 아침)
|
||
├── CPU 서킷 브레이커 연동
|
||
└── IPC Command Queue 폴링 (텔레그램 명령 처리)
|
||
```
|
||
|
||
**잔고 추적 로직 (v3.1 — 과매수 방지)**:
|
||
```
|
||
KIS get_balance() → raw_deposit (dnca_tot_amt)
|
||
↓
|
||
max_daily_buy = raw_deposit × MAX_DAILY_BUY_RATIO (80%)
|
||
tracking_deposit = max_daily_buy - effective_today_buy
|
||
↑
|
||
max(kis_today_buy, self._today_buy_total)
|
||
(KIS thdt_buy_amt vs 로컬 누적 중 큰 값)
|
||
```
|
||
- `_today_buy_total`: 인스턴스 변수, 사이클 간 유지 (09:00 리셋)
|
||
- `_buy_scores`: BUY 시 신호 점수 저장 → SELL 시 `record_trade()` 전달
|
||
|
||
**run_cycle() 흐름**:
|
||
1. 시스템 헬스 체크 (CPU/GPU/RAM)
|
||
2. 거시경제 분석 (KOSPI/KOSDAQ/MSI)
|
||
3. 위험 상태별 분기 (SAFE/CAUTION/DANGER)
|
||
4. Watchlist 종목 OHLCV 수집 (KIS 비동기 배치)
|
||
5. 잔고 조회 + 당일 누적 매수 차감 → 실제 가용 예수금 계산
|
||
6. `ProcessPoolExecutor`로 종목 병렬 분석 (Kelly Criterion + Ensemble 가중치)
|
||
7. 앙상블 점수 기반 매수/매도 판단 (사이클당 MAX_BUY_PER_CYCLE 제한)
|
||
8. 주문 실행 & 결과 텔레그램 알림
|
||
9. 매도 시 `record_trade()` → Ensemble 가중치 학습
|
||
10. IPC 상태 갱신
|
||
|
||
### 3.2 AI 분석 파이프라인
|
||
|
||
```
|
||
┌─────────────────────┐
|
||
│ analyze_stock_ │
|
||
│ process() │
|
||
│ (strategy/process)│
|
||
└─────────┬───────────┘
|
||
│
|
||
┌─────────────────────┼────────────────────┐
|
||
▼ ▼ ▼
|
||
┌───────────────┐ ┌────────────────┐ ┌─────────────────┐
|
||
│ Technical │ │ Deep Learning │ │ LLM (Ollama) │
|
||
│ Analyzer │ │ LSTM │ │ Sentiment │
|
||
│ (기술적 지표) │ │ (주가 예측) │ │ (뉴스 감성분석) │
|
||
├───────────────┤ ├────────────────┤ ├─────────────────┤
|
||
│ RSI 25% │ │ Attention-LSTM │ │ qwen2.5:7b │
|
||
│ 이격도 15% │ │ 4L×512H │ │ JSON 포맷 요청 │
|
||
│ MACD 15% │ │ 7차원 피처 │ │ 뉴스+지표 통합 │
|
||
│ Stochastic 5% │ │ 60일 시퀀스 │ │ 감성+신뢰도 │
|
||
│ BB 15% │ │ GPU 가속 │ │ │
|
||
│ ADX 15% │ │ 종목별 모델 │ │ │
|
||
│ MTF 10% │ │ (ModelRegistry)│ │ │
|
||
│ OBV ±보너스 │ │ │ │ │
|
||
└───────┬───────┘ └───────┬────────┘ └───────┬─────────┘
|
||
│ │ │
|
||
└──────────┬────────┘ │
|
||
▼ │
|
||
┌─────────────────┐ │
|
||
│ AdaptiveEnsemble│ ◄───────────────────┘
|
||
│ (학습형 가중치) │
|
||
├─────────────────┤
|
||
│ get_weights() │ ← 과거 매매 결과 반영
|
||
│ (ADX+macro+conf)│ 크기 가중 정확도 기준
|
||
│ 경계: 0.10~0.65 │ Water-Filling 정규화
|
||
│ Kelly Fraction │ ← 승률·손익비 기반
|
||
└────────┬────────┘
|
||
▼
|
||
┌────────────────┐
|
||
│ 매수/매도/홀드 │
|
||
│ 최종 판단 │
|
||
└────────────────┘
|
||
```
|
||
|
||
#### 3.2.1 Deep Learning — Attention-LSTM (`analysis/deep_learning.py`)
|
||
|
||
| 항목 | 값 |
|
||
|------|-----|
|
||
| **아키텍처** | 4-Layer Stacked LSTM + Attention + FC |
|
||
| **Hidden Size** | 512 |
|
||
| **Input Features** | 7 (close, open, high, low, volume_norm, rsi_14, macd_hist) |
|
||
| **시퀀스 길이** | 60일 |
|
||
| **학습 에포크** | 최대 200 (Early Stopping patience=15) |
|
||
| **빠른 재학습** | 30 에포크 (체크포인트 존재 시) |
|
||
| **쿨다운** | 1200초 (20분, 동일 종목 재학습 방지) |
|
||
| **ModelRegistry** | LRU 방식, 최대 5개 모델 동시 적재 |
|
||
| **체크포인트** | `data/models/{ticker}_v3.pt` |
|
||
| **GPU 관리** | LSTM 학습 시 Ollama 자동 언로드/리로드 |
|
||
|
||
#### 3.2.2 기술적 분석 (`analysis/technical.py`)
|
||
|
||
`TechnicalAnalyzer.get_technical_score()` → 0.0 ~ 1.0 통합 점수
|
||
|
||
| 지표 | 비중 | 설명 |
|
||
|------|------|------|
|
||
| RSI (14일) | 25% | Wilder 방식, 30 이하 과매도/70 이상 과매수 |
|
||
| 이동평균 이격도 | 15% | 20일 MA 대비 현재가 위치 |
|
||
| MACD | 15% | 12/26/9, 히스토그램 방향 |
|
||
| Stochastic | 5% | Fast %K/%D (14/3/3) |
|
||
| Bollinger Bands | 15% | 20일/2σ, %B 위치 + 밴드폭 |
|
||
| ADX | 15% | 추세 강도 (>25 강한 추세) |
|
||
| Multi-Timeframe | 10% | 5일/20일/60일 추세 일관성 |
|
||
| OBV | ±0.1 보너스 | 거래량 기반 매집/분산 감지 |
|
||
|
||
추가 기능:
|
||
- `calculate_atr()` → ATR 기반 동적 손절/익절
|
||
- `calculate_dynamic_sl_tp()` → 변동성 적응형 SL/TP
|
||
- `calculate_obv()` → 스마트 머니 다이버전스 감지
|
||
|
||
#### 3.2.3 거시경제 분석 (`analysis/macro.py`)
|
||
|
||
```python
|
||
MacroAnalyzer.get_macro_status(kis_client) → {
|
||
"status": "SAFE" | "CAUTION" | "DANGER",
|
||
"risk_score": int,
|
||
"indicators": {
|
||
"KOSPI": {"price", "change", "high", "low", "prev_close", "volume"},
|
||
"KOSDAQ": {"price", "change", ...},
|
||
"KOSPI200":{"price", "change", ...},
|
||
"MSI": float # Market Stress Index (0~100)
|
||
}
|
||
}
|
||
```
|
||
|
||
- **SAFE** (risk_score < 1): 정상 매매
|
||
- **CAUTION** (1 ≤ risk_score < 3): 매수 규모 축소
|
||
- **DANGER** (risk_score ≥ 3): 매수 중단, 보유분만 관리
|
||
|
||
#### 3.2.4 앙상블 (`analysis/ensemble.py`)
|
||
|
||
`AdaptiveEnsemble` — 과거 매매 결과 기반 가중치 자동 조정 + Kelly Criterion:
|
||
|
||
**가중치 학습 흐름**:
|
||
```
|
||
BUY 체결 → bot._buy_scores[ticker] = {tech, sentiment, lstm} 저장
|
||
SELL 체결 → ensemble.record_trade(ticker, ..., outcome_pct=yld)
|
||
→ _update_weights() → EMA(alpha=0.10) 가중치 점진 조정
|
||
→ _save() → data/ensemble_history.json
|
||
워커 프로세스 → reload_if_stale() → 파일 mtime 감지 시 재로드
|
||
```
|
||
|
||
**주요 메서드**:
|
||
- `get_weights(ticker, adx, macro_state, ai_confidence)` → `SignalWeights`
|
||
- 시장 컨텍스트 (strong_trend/sideways/danger/default) 별 기본 가중치
|
||
- 종목별 최근 10거래 크기 가중 정확도 반영
|
||
- ai_confidence >= 0.75 → LSTM 가중치 +25% (confidence 상한 0.80 반영)
|
||
- `get_kelly_fraction(ticker, half_kelly=True)` → 0.03~0.25 범위 투자 비중
|
||
- f* = (p·b - q) / b (p=승률, b=손익비)
|
||
- 거래 데이터 < 10건 → 보수적 기본값 8%
|
||
- Half-Kelly 적용으로 변동성 과대추정 보완
|
||
- `compute_ensemble_score(tech, sentiment, lstm, investor, weights)` → 통합 점수
|
||
- `reload_if_stale()` → 파일 mtime 기반 cross-process 동기화
|
||
|
||
**`SignalWeights.normalize()` — Water-Filling 알고리즘**:
|
||
- 경계(0.10~0.65) 위반 시 해당 값을 경계에 고정, 나머지에 잔여 비중 비례 배분
|
||
- 2차 정규화(합=1 보장)와 경계 클램핑이 상충하는 문제 해결
|
||
- 영구 저장: `data/ensemble_history.json` (가중치 + 매매 히스토리 통합)
|
||
|
||
#### 3.2.5 성과 평가 (`analysis/evaluator.py`)
|
||
|
||
`PerformanceEvaluator.generate_weekly_report()`:
|
||
- 핵심 지표: 총수익률, Sharpe Ratio, MDD, 승률, 평균손익비, KOSPI 상관도
|
||
- S/A/B/C/D/F 등급 산출
|
||
- **5명 전문가 LLM 패널** (Ollama): 각각 다른 관점으로 평가
|
||
- HTML 포맷 텔레그램 주간 보고서 자동 생성
|
||
|
||
---
|
||
|
||
## 4. 외부 서비스 연동
|
||
|
||
### 4.1 한국투자증권 KIS API (`services/kis.py`)
|
||
|
||
#### 인증
|
||
|
||
```python
|
||
KISClient.ensure_token()
|
||
# OAuth 2.0 → access_token 발급 → data/kis_token.json에 캐시
|
||
# 토큰 만료 시 자동 갱신 (_request_api에서 처리)
|
||
```
|
||
|
||
| 설정 | 모의투자 | 실전투자 |
|
||
|------|---------|---------|
|
||
| Base URL | `openapivts.koreainvestment.com:29443` | `openapi.koreainvestment.com:9443` |
|
||
| 환경변수 | `KIS_VIRTUAL_APP_KEY/SECRET/ACCOUNT` | `KIS_REAL_APP_KEY/SECRET/ACCOUNT` |
|
||
| 전환 | `.env` → `KIS_ENV_TYPE=virtual` | `.env` → `KIS_ENV_TYPE=real` |
|
||
|
||
#### API 스로틀링
|
||
|
||
- 초당 2회 제한 (`_throttle()` — 0.5초 딜레이)
|
||
- 토큰 만료 시 자동 갱신 (403 → retry with new token)
|
||
|
||
#### 주요 API 엔드포인트 매핑
|
||
|
||
| 기능 | KISClient 메서드 | KIS TR_ID |
|
||
|------|-----------------|-----------|
|
||
| 잔고 조회 | `get_balance()` → `{holdings, total_eval, deposit, today_buy_amt}` | `VTTC8434R` (모의) / `TTTC8434R` (실전) |
|
||
| 주문 (매수/매도) | `order()` | `VTTC0802U` / `VTTC0801U` (모의) |
|
||
| 현재가 조회 | `get_current_price()` | `FHKST01010100` |
|
||
| 일봉 OHLCV | `get_daily_ohlcv()` → `_get_daily_ohlcv_by_range()` | `FHKST03010100` |
|
||
| 일봉 종가 | `get_daily_price()` → `_get_daily_price_by_range()` | `FHKST03010100` |
|
||
| 거래량 순위 | `get_volume_rank()` | `FHPST01710000` |
|
||
| 지수 현재가 | `get_current_index()` | `FHPUP02100000` |
|
||
| 지수 일봉 | `get_daily_index_price()` | `FHKUP03500100` |
|
||
| 투자자 동향 | `get_investor_trend()` | `FHKST01010900` |
|
||
| Hash Key | `get_hash_key()` | - |
|
||
|
||
#### 비동기 클라이언트 (`KISAsyncClient`)
|
||
|
||
`aiohttp` 기반 — 다중 종목 동시 수집용:
|
||
- `get_daily_price_batch()` — 여러 종목 일봉 병렬 수집
|
||
- `get_daily_ohlcv_batch()` — 여러 종목 OHLCV 병렬 수집
|
||
- `get_investor_trends_batch()` — 여러 종목 투자자 동향 병렬 수집
|
||
|
||
---
|
||
|
||
### 4.2 Ollama LLM (`services/ollama.py`)
|
||
|
||
| 설정 | 값 |
|
||
|------|-----|
|
||
| **모델** | `qwen2.5:7b-instruct-q4_K_M` (VRAM ~4GB) |
|
||
| **API URL** | `http://localhost:11434` |
|
||
| **Context Window** | 4096 토큰 |
|
||
| **Max Output** | 200 토큰 |
|
||
| **Temperature** | 0.1 (결정론적, JSON 안정성) |
|
||
| **Keep Alive** | 5분 (비활성 시 자동 언로드) |
|
||
| **Timeout** | 90초 |
|
||
| **CPU Threads** | 8 (9800X3D 최적화) |
|
||
| **응답 포맷** | JSON (format: "json") |
|
||
|
||
**GPU 충돌 방지**:
|
||
- LSTM 학습 중 → Ollama 추론 최대 60초 대기
|
||
- VRAM > 12GB → 모델 즉시 언로드 (`keep_alive=0`)
|
||
- LSTM 학습 전 → Ollama 자동 언로드, 학습 후 → 자동 리로드
|
||
|
||
---
|
||
|
||
### 4.3 뉴스 수집 (`services/news.py`)
|
||
|
||
- **소스**: Google News RSS (`news.google.com/rss/search`)
|
||
- **동기**: `NewsCollector.get_market_news()` — 시장 일반 뉴스 5건
|
||
- **비동기**: `AsyncNewsCollector`
|
||
- `get_market_news_async()` — 시장 뉴스 (5분 캐시)
|
||
- `get_stock_news_async()` — 종목별 뉴스 (5분 캐시)
|
||
|
||
---
|
||
|
||
## 5. 웹 백엔드 서버 API (FastAPI)
|
||
|
||
### 5.1 서버 정보
|
||
|
||
| 항목 | 값 |
|
||
|------|-----|
|
||
| **프레임워크** | FastAPI + Uvicorn |
|
||
| **호스트** | `0.0.0.0:8000` |
|
||
| **NAS 백엔드** | `http://192.168.45.54:18500` (웹 프론트엔드 서버) |
|
||
|
||
### 5.2 API 엔드포인트
|
||
|
||
#### `GET /` — 서버 상태
|
||
|
||
```json
|
||
{
|
||
"status": "online",
|
||
"gpu_vram": 4.2,
|
||
"service": "Windows AI Server (Refactored)"
|
||
}
|
||
```
|
||
|
||
#### `GET /trade/balance` | `GET /api/trade/balance` — 잔고 조회
|
||
|
||
KIS API를 통해 현재 계좌 잔고(예수금, 보유종목, 평가금액) 조회.
|
||
|
||
```json
|
||
{
|
||
"total_eval": 10500000,
|
||
"deposit": 5000000,
|
||
"holdings": [
|
||
{
|
||
"ticker": "005930",
|
||
"name": "삼성전자",
|
||
"qty": 10,
|
||
"avg_price": 72000,
|
||
"current_price": 73500,
|
||
"profit_rate": 2.08
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### `POST /trade/order` | `POST /api/trade/order` — 수동 주문
|
||
|
||
```json
|
||
// Request Body
|
||
{
|
||
"ticker": "005930",
|
||
"action": "BUY", // "BUY" | "SELL"
|
||
"quantity": 10
|
||
}
|
||
|
||
// Response
|
||
{
|
||
"status": "executed",
|
||
"kis_result": { ... }
|
||
}
|
||
```
|
||
|
||
#### `POST /analyze/portfolio` | `POST /api/analyze/portfolio` — AI 포트폴리오 분석
|
||
|
||
현재 잔고 + 최신 뉴스를 종합하여 Ollama LLM으로 포트폴리오 분석.
|
||
|
||
```json
|
||
{
|
||
"analysis": "... AI 분석 결과 (한국어) ..."
|
||
}
|
||
```
|
||
|
||
### 5.3 NAS 서버와의 통신 흐름
|
||
|
||
```
|
||
┌──────────────┐ HTTP Request ┌────────────────────┐
|
||
│ NAS Backend │ ─────────────────────► │ Windows AI Server │
|
||
│ (웹 프론트) │ │ (FastAPI:8000) │
|
||
│ :18500 │ ◄──────────────────── │ │
|
||
│ │ JSON Response │ │
|
||
└──────────────┘ └────────────────────┘
|
||
|
||
[통신 시나리오]
|
||
1. 웹 → /api/trade/balance → 잔고 데이터 표시
|
||
2. 웹 → /api/trade/order → 수동 매수/매도 실행
|
||
3. 웹 → /api/analyze/portfolio → AI 분석 결과 표시
|
||
4. 웹 → / → 서버 상태 및 GPU 정보
|
||
```
|
||
|
||
- **NAS 서버** (`192.168.45.54:18500`): 웹 프론트엔드 호스팅, 사용자 인터페이스 제공
|
||
- **Windows AI 서버** (`0.0.0.0:8000`): GPU 연산, KIS API 통신, AI 분석 처리
|
||
- 내부 네트워크 (LAN) 통신, 외부 노출 없음
|
||
|
||
---
|
||
|
||
## 6. 텔레그램 봇 설정 & 명령어
|
||
|
||
### 6.1 환경변수
|
||
|
||
```env
|
||
TELEGRAM_BOT_TOKEN=8546032918:AAF5GJcP92DrtpSoQdaimMIZe7bz_xtGGPo
|
||
TELEGRAM_CHAT_ID=7388056964
|
||
```
|
||
|
||
### 6.2 봇 프로세스 아키텍처
|
||
|
||
```
|
||
runner.py
|
||
└── run_telegram_bot_standalone()
|
||
├── SharedIPC 초기화 (lock, queue, shutdown_event)
|
||
├── TelegramBotServer 생성
|
||
├── IPC에서 초기 데이터 로드
|
||
├── bot_server.run() (python-telegram-bot polling)
|
||
└── Conflict 감지 시 백오프 재시도 (최대 10회)
|
||
```
|
||
|
||
- **라이브러리**: `python-telegram-bot` (Application, CommandHandler)
|
||
- **메시지 포맷**: HTML (`parse_mode="HTML"`)
|
||
- **동시 업데이트**: `concurrent_updates=True`
|
||
- **로깅**: `telegram_bot.log` (파일 + 콘솔)
|
||
|
||
### 6.3 명령어 목록
|
||
|
||
| 명령어 | 설명 | 데이터 소스 |
|
||
|--------|------|------------|
|
||
| `/start` | 봇 시작 & 전체 명령어 안내 | - |
|
||
| `/status` | 봇 상태, 시장 지수, AI 모델 상태 | IPC (SharedMemory) |
|
||
| `/portfolio` | 보유 종목 & 수익률 조회 | IPC → FakeKIS.get_balance() |
|
||
| `/watchlist` | 현재 감시 종목 리스트 | IPC → watchlist 데이터 |
|
||
| `/update_watchlist` | Watchlist 즉시 업데이트 요청 | Command Queue → 메인 봇 |
|
||
| `/macro` | 거시경제 분석 (KOSPI/KOSDAQ/MSI) | IPC → macro_indices |
|
||
| `/system` | CPU/GPU/RAM 시스템 상태 | IPC → gpu_status + psutil |
|
||
| `/ai` | AI 모델 상태 (VRAM, 학습 여부) | IPC → gpu_status |
|
||
| `/restart` | 메인 봇 재시작 명령 | Command Queue |
|
||
| `/stop` | 봇 종료 | shutdown_event.set() |
|
||
| `/exec <cmd>` | 서버 쉘 명령어 직접 실행 | subprocess (10초 타임아웃) |
|
||
| `/evaluate` | 즉시 성과 평가 보고서 생성 | PerformanceEvaluator |
|
||
|
||
### 6.4 TelegramMessenger (`services/telegram.py`)
|
||
|
||
단방향 알림 전용 (메인 봇 → 사용자):
|
||
- **비동기 전송**: `threading.Thread(daemon=True)` — Fire-and-forget
|
||
- **HTML 파싱**: 마크다운 에러 방지
|
||
- 매매 실행, 서버 시작/종료, 에러 알림 등에 사용
|
||
|
||
### 6.5 Conflict 처리
|
||
|
||
텔레그램 봇 API는 동시에 하나의 polling 인스턴스만 허용:
|
||
- `Conflict` 에러 감지 시 지수 백오프 (5s → 10s → ... → 30s)
|
||
- 최대 10회 재시도 후 프로세스 종료
|
||
- Watchdog가 감지하여 자동 재시작
|
||
|
||
---
|
||
|
||
## 7. 환경 설정 (`modules/config.py`)
|
||
|
||
### 7.1 주요 설정 상수
|
||
|
||
| 그룹 | 키 | 값 | 설명 |
|
||
|------|-----|-----|------|
|
||
| **매매** | `MAX_INVESTMENT_PER_STOCK` | 3,000,000원 | 종목당 최대 투자금 |
|
||
| **매매** | `MAX_BUY_PER_CYCLE` | 2 | 사이클당 최대 매수 종목 수 (env: `MAX_BUY_PER_CYCLE`) |
|
||
| **매매** | `MAX_DAILY_BUY_RATIO` | 0.80 | 예수금 대비 일일 최대 매수 비율 (env: `MAX_DAILY_BUY_RATIO`) |
|
||
| **IPC** | `SHM_NAME` | `web_ai_bot_ipc` | SharedMemory 이름 |
|
||
| **IPC** | `SHM_SIZE` | 131,072 (128KB) | SharedMemory 크기 |
|
||
| **IPC** | `IPC_STALENESS` | 600초 | 데이터 유효 기간 |
|
||
| **GPU** | `VRAM_WARNING_THRESHOLD` | 12.0 GB | VRAM 경고 임계값 |
|
||
| **프로세스** | `WATCHDOG_INTERVAL` | 30초 | 헬스체크 간격 |
|
||
| **프로세스** | `MAX_RESTART_COUNT` | 3 | 최대 자동 재시작 횟수 |
|
||
| **LSTM** | `LSTM_COOLDOWN` | 1,200초 | 동일 종목 재학습 방지 |
|
||
| **LSTM** | `LSTM_FAST_EPOCHS` | 30 | 빠른 재학습 에포크 |
|
||
| **CPU** | `CPU_CIRCUIT_BREAKER_THRESHOLD` | 92% | 서킷 브레이커 임계값 |
|
||
| **CPU** | `CPU_CIRCUIT_BREAKER_CONSECUTIVE` | 2회 | 연속 초과 시 발동 |
|
||
| **Ollama** | `OLLAMA_NUM_CTX` | 4,096 | 컨텍스트 윈도우 |
|
||
| **Ollama** | `OLLAMA_NUM_PREDICT` | 200 | 최대 출력 토큰 |
|
||
| **Ollama** | `OLLAMA_NUM_THREAD` | 8 | CPU 스레드 수 |
|
||
| **Network** | `HTTP_TIMEOUT` | 10초 | 기본 HTTP 요청 타임아웃 |
|
||
|
||
### 7.2 .env 파일 구조
|
||
|
||
```env
|
||
# NAS Backend (웹 프론트엔드 서버)
|
||
NAS_API_URL=http://192.168.45.54:18500
|
||
|
||
# Ollama LLM
|
||
OLLAMA_API_URL=http://localhost:11434
|
||
OLLAMA_MODEL=qwen2.5:7b-instruct-q4_K_M
|
||
|
||
# KIS API (virtual/real 전환)
|
||
KIS_ENV_TYPE=virtual
|
||
KIS_REAL_APP_KEY=...
|
||
KIS_REAL_APP_SECRET=...
|
||
KIS_REAL_ACCOUNT=XXXXXXXX-XX
|
||
KIS_VIRTUAL_APP_KEY=...
|
||
KIS_VIRTUAL_APP_SECRET=...
|
||
KIS_VIRTUAL_ACCOUNT=XXXXXXXX-XX
|
||
|
||
# Telegram Bot
|
||
TELEGRAM_BOT_TOKEN=...
|
||
TELEGRAM_CHAT_ID=...
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 운영 가이드
|
||
|
||
### 8.1 시작 방법
|
||
|
||
```bash
|
||
# 일반 시작
|
||
python main_server.py
|
||
|
||
# LSTM 사전학습 후 자동 시작
|
||
python warmup_and_restart.py
|
||
|
||
# 텔레그램 봇만 단독 실행 (디버깅용)
|
||
python -m modules.services.telegram_bot.runner
|
||
```
|
||
|
||
### 8.2 좀비 프로세스 관리
|
||
|
||
- `main_server.py` 실행 시 자동으로 이전 좀비 프로세스 정리
|
||
- `pids.txt` 기반 → 메모리 기반 PID 추적으로 전환 완료
|
||
- 수동 확인: `Get-Process python` (PowerShell)
|
||
|
||
### 8.3 로그 파일
|
||
|
||
| 파일 | 용도 |
|
||
|------|------|
|
||
| `server.log` | Uvicorn 서버 로그 |
|
||
| `telegram_bot.log` | 텔레그램 봇 로그 |
|
||
| `warmup.log` | LSTM 사전학습 진행 로그 |
|
||
| `bot_output.log` | 트레이딩 봇 출력 로그 |
|
||
|
||
### 8.4 트러블슈팅
|
||
|
||
| 증상 | 원인 | 해결 |
|
||
|------|------|------|
|
||
| KIS 403 Forbidden | 토큰 만료 또는 Rate Limit | `data/kis_token.json` 삭제 후 재시작 |
|
||
| Telegram Conflict | 이전 봇 프로세스 미종료 | `main_server.py` 재시작 (자동 정리) |
|
||
| GPU OOM | LSTM + Ollama 동시 적재 | `VRAM_WARNING_THRESHOLD` 낮추기 |
|
||
| CPU 100% 고정 | 좀비 워커 프로세스 | `main_server.py` 재시작 |
|
||
| IPC 데이터 오래됨 | 메인 봇 크래시 | Watchdog 자동 재시작 확인, 수동 재시작 |
|
||
| 예수금 초과 매수 | KIS 모의투자 T+2 미차감 | `MAX_DAILY_BUY_RATIO` / `MAX_BUY_PER_CYCLE` 조정 |
|
||
| Kelly 비중이 너무 낮음 | 거래 기록 부족 (< 10건) | 초기에는 기본값 8% 사용, 거래 누적 후 자동 조정 |
|
||
| 앙상블 가중치 갱신 안 됨 | 매도 체결 없음 또는 `_buy_scores` 누락 | 봇 재시작 전 매도 완료 확인; `data/ensemble_history.json` 확인 |
|
||
|
||
---
|
||
|
||
## 9. 데이터 흐름 요약
|
||
|
||
```
|
||
[시장 개장 전]
|
||
WatchlistManager → 뉴스 분석 → Watchlist 갱신
|
||
|
||
[장중 사이클 (≈5분 간격)]
|
||
1. SystemMonitor.check_health() → CPU/GPU 확인
|
||
2. MacroAnalyzer.get_macro_status() → 시장 상태 판단
|
||
3. KIS → get_balance() → raw_deposit - today_buy_total = 가용 예수금
|
||
4. KIS → get_daily_ohlcv_batch() → OHLCV 수집
|
||
5. ProcessPool → analyze_stock_process() × N종목
|
||
├── ensemble.reload_if_stale() → 파일 mtime 감지 시 가중치 재로드
|
||
├── TechnicalAnalyzer → 기술적 점수
|
||
├── PricePredictor → LSTM 예측
|
||
├── OllamaManager → LLM 감성 분석
|
||
├── AdaptiveEnsemble.get_weights() → 학습된 동적 가중치
|
||
└── calculate_position_size() → Kelly Criterion 수량 산출
|
||
6. 매수 판단 → 예수금 확인 → KIS 주문
|
||
├── _buy_scores[ticker] 저장 (앙상블 학습용)
|
||
├── _today_buy_total += 매수금액
|
||
└── buys_this_cycle++ (MAX_BUY_PER_CYCLE 제한)
|
||
7. 매도 판단 → KIS 주문
|
||
└── ensemble.record_trade() → 가중치 학습 + ensemble_history.json 저장
|
||
8. SharedIPC.write_status() → 텔레그램 봇에 공유
|
||
9. TelegramMessenger → 결과 알림
|
||
|
||
[장 마감 후]
|
||
PerformanceDB.save_daily_snapshot() → 일별 자산 기록
|
||
Evaluator → 주간 보고서 (월요일)
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 버전 변경 이력
|
||
|
||
### v3.1 (2026-03-19) — 잔고 관리 & 앙상블 학습 완성
|
||
|
||
**버그 수정**:
|
||
- `tracking_deposit` 사이클 간 초기화 문제 → `_today_buy_total` 인스턴스 변수로 누적 추적
|
||
- KIS 모의투자 T+2 미차감으로 인한 예수금 초과 매수 방지
|
||
- `ai_confidence >= 0.85` 임계값 버그 (LSTM confidence 상한 0.80 미반영) → 0.75로 수정
|
||
- OHLCV 피처 누락 시 silent fallback → 경고 로그 출력
|
||
|
||
**신규 기능**:
|
||
- `MAX_BUY_PER_CYCLE`: 사이클당 최대 매수 종목 수 제한 (기본 2)
|
||
- `MAX_DAILY_BUY_RATIO`: 예수금 대비 일일 최대 매수 비율 (기본 80%)
|
||
- `kis.get_balance()` → `today_buy_amt` 필드 추가 (`thdt_buy_amt`)
|
||
|
||
**앙상블 (`analysis/ensemble.py`)**:
|
||
- `AdaptiveEnsemble`을 `process.py`에 실제 연동 (하드코딩 가중치 제거)
|
||
- `get_kelly_fraction()`: Half-Kelly Criterion 포지션 비중 계산 추가
|
||
- `SignalWeights.normalize()`: Water-Filling 알고리즘으로 경계 위반 문제 해결
|
||
- `_accuracy()` 이진 지표 제거 → `_accuracy_weighted()` (크기 가중) 통일
|
||
- `reload_if_stale()`: 파일 mtime 기반 cross-process 동기화
|
||
|
||
**포지션 사이징 (`strategy/process.py`)**:
|
||
- `calculate_position_size()`: 하드코딩 10% → Kelly Criterion (과거 승률·손익비 기반)
|
||
- `bot.py` 중복 계산 제거 → 워커의 `suggested_qty` 직접 사용
|
||
|
||
**앙상블 학습 루프 (`bot.py`)**:
|
||
- BUY 체결 시 `_buy_scores[ticker]` 신호 점수 저장
|
||
- SELL 체결 시 `ensemble.record_trade()` → `ensemble_history.json` 갱신
|
||
- 워커 프로세스는 `reload_if_stale()`로 자동 반영
|