Files
kyu 97cf5aecb0
trading/pipeline/head This commit looks good
chore: 누적 미커밋 작업분 일괄 커밋
이번 세션 외 그간 쌓인 변경 일괄 저장:
- bareunaapi: finance_dict 금융용어 / stock_loader 종목 로더 보강
- kis-api: 키움 토큰·수집 로직
- us-market / dart-collector: 수집 보강
- docker-compose: GEMINI_API_KEY 등 환경변수 추가
- score-engine/news-collector requirements, CLAUDE.md
- 신규: PROJECT.md, news-collector/sentiment_rules.py

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 01:24:16 +09:00

646 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Trading AI — Claude Code 프로젝트 가이드
> **역할**: 워렌 버핏 스타일 한국 주식 AI 투자 분석 전문가로 행동할 것.
> 가치투자 관점(ROE·영업이익률·부채비율·FCF)을 최우선으로 판단한다.
> 이 파일은 매 대화 시작 시 자동 로드됨 — 파일 탐색 없이 이 내용만으로 작업 시작.
---
## Claude 행동 원칙
### 1. 먼저 생각, 그 다음 코딩
- 가정은 명시적으로 밝힐 것. 불확실하면 질문.
- 해석이 여러 개면 나열하고 고를 것 — 혼자 결정 금지.
- 더 단순한 방법이 있으면 말할 것. 불필요한 복잡성에 반론.
### 2. 단순함 우선
- 요청한 것만 구현. 추측성 기능·추상화·유연성 추가 금지.
- "나중에 필요할 수도" 코드 금지. 200줄이 50줄로 가능하면 다시 짤 것.
- 불가능한 시나리오를 위한 에러 핸들링 금지.
### 3. 정밀한 변경
- 요청한 부분만 수정. 인접 코드 "개선" 금지.
- 기존 스타일 유지 (내 방식이 달라도).
- 내 변경으로 생긴 불필요한 import/변수/함수만 제거. 기존 dead code는 언급만.
### 4. 검증 기준 명시
- 다단계 작업 시 각 단계 완료 조건을 먼저 정의.
- Docker 서비스 변경 → 로그 확인 필수.
- DB 변경 → 쿼리로 확인 필수.
### 이 프로젝트 특이사항
- sudo 비밀번호 입력 불가 → sudo 필요 작업은 `! sudo <cmd>` 형태로 유저에게 요청.
- n8n 마이그레이션 충돌 이력 있음 → n8n 스키마 변경 시 반드시 백업 먼저.
- NAS fstab은 한 줄 유지 필수 (nfsvers=3).
- 상장폐지 필터(`is_active=true`) 빠지면 추천 결과 오염됨.
---
## 시스템 개요
뉴스 수집 → 형태소분석 → 임베딩 → 중복제거 → AI분석 → 재무스코어링 → 기술분석 → 매수/매도 추천 → 텔레그램 알림
---
## 인프라
| 항목 | 값 |
|------|-----|
| 서버 OS | Ubuntu 22.04 |
| GPU | GPU0: RTX 3060 12GB (EXAONE 추론) / GPU1: RTX 3070 8GB (bge-m3 임베딩) |
| NAS | `192.168.0.36:/volume1/trading → /mnt/nas` (nfsvers=3 필수) |
| DB 연결 | `postgres:5432`, DB명 `trading_ai`, user `kyu` |
| 환경변수 | `/home/kyu/trading/.env` |
| Docker | `docker compose` (파일: `/home/kyu/trading/docker-compose.yml`) |
| PostgreSQL 데이터 | `/mnt/nas/postgresql/data` (NAS 저장) |
---
## 서비스 목록
| 서비스 | 컨테이너 | 포트 | 내부IP | 역할 |
|--------|---------|------|--------|------|
| news-collector | trading-news-collector | 8787 | 172.30.0.16 | 뉴스수집+형태소+임베딩+Ollama분석 |
| bareunaapi | trading-bareunaapi | 5757 | 172.30.0.12 | 한국어 형태소분석 FastAPI 래퍼 |
| bareun | trading-bareun | 5656/9902 | 172.30.0.15 | 바른 NLP gRPC 서버 |
| dart-collector | trading-dart-collector | 8888 | 172.30.0.17 | DART 공시/재무제표 수집 |
| score-engine | trading-score-engine | 8686 | 172.30.0.19 | 종합 투자 스코어 계산+텔레그램 |
| ta-engine | trading-ta-engine | 8484 | 172.30.0.23 | 기술적분석 (MA/RSI/MACD/볼밴/스토캐스틱) |
| dashboard-api | trading-dashboard-api | 8989 | 172.30.0.22 | 대시보드 REST API |
| kis-api | trading-kis-api | 8585 | 172.30.0.18 | 네이버모바일 주가수집 + 매매시그널 |
| ollama | trading-ollama | 11434 | 172.30.0.13 | EXAONE 3.5 7.8B 추론(GPU0) + bge-m3 임베딩(GPU1) |
| n8n | trading-n8n | 5678 | 172.30.0.20 | 워크플로우 자동화 |
| n8n-worker | trading-n8n-worker | - | 172.30.0.21 | n8n Queue Worker |
| postgres | trading-postgres | 5432 | - | 메인 DB |
| redis | trading-redis | 6379 | 172.30.0.10 | 캐시/중복제거 |
| qdrant | trading-qdrant | 6333 | 172.30.0.11 | 뉴스 벡터 유사도 필터 |
| aux-signal | trading-aux-signal | 8282 | 172.30.0.25 | 보조 데이터: 네이버 컨센서스·기관/외국인 수급 + ECOS 매크로(USD/KRW, 국고채10년) |
| us-market | trading-us-market | 8383 | 172.30.0.24 | 미증시→한국 동조 시그널 (섹터ETF 14개 ±5점, 개별 페어 회귀 ±10점, 자동 페어발굴) |
| graph-engine | trading-graph-engine | 9090 | 172.30.0.27 | GAT 그래프 신경망 (12피처 노드 × 가격상관/섹터/뉴스공기 엣지) → graph_score |
| telegram-bot | trading-telegram-bot | - | 172.30.0.26 | 텔레그램 명령 처리 (/buy /sell /stock /deep /market) |
---
## 파일 구조 & 핵심 함수
### `/home/kyu/trading/news-collector/main.py` (21KB)
뉴스 수집 + AI 분석 파이프라인. 포트 8787.
| 함수/클래스 | 역할 |
|------------|------|
| `class S` | 전역 상태 (DB pool, Redis, Qdrant client) |
| `nhash(title, url)` | SHA256[:16] 뉴스 중복 해시 |
| `is_korean(text)` | 한글 포함 여부 체크 |
| `parse_rss_date(date_str)` | RSS pubDate → ISO timestamp |
| `@app.post("/collect/market")` | 네이버 금융 메인 뉴스 수집 |
| `@app.post("/collect/stocks")` | 종목별 네이버 뉴스 수집 |
| `@app.post("/collect/rss")` | 18개 RSS 피드 수집 |
| `@app.get("/sources")` | RSS 소스 목록 |
**파이프라인 순서** (함수 내부):
1. 바른API `/analyze` → 형태소분석 + 종목감지
2. Ollama `bge-m3` → 1024차원 임베딩
3. Qdrant → 코사인 유사도 ≥0.92 중복 제거
4. Ollama `exaone3.5:7.8b` (GPU0) → 호재/악재/중립 + intensity(1~5) + catalyst분류
5. PostgreSQL `news_analysis` 저장
**EXAONE 프롬프트**: 버핏 관점 (실적/수주/배당/리스크/모멘텀/기타 catalyst 분류)
**RSS 소스 (28개)**: 한국경제, 매일경제, 머니투데이, 이데일리, 연합뉴스, 조선비즈, 헤럴드경제, 아시아경제, 파이낸셜뉴스, SBS Biz, 뉴스1, 뉴시스 등 + 네이버금융 직접 크롤링
**스케줄러**:
- 평일 RSS: 8-18시 5분마다 (`rss_weekday`)
- 주말 RSS: 8-22시 15분마다 (`rss_weekend`) — 누적 학습용
- 평일 마켓: 9-17시 10분마다 (`market`, 네이버 금융 메인)
- raw 분석: 24시간 30분마다 200건 batch (`process_raw`) — 백로그 소화용으로 확장
- 주간 raw 백필: 일요일 02:00 전체 종목 × 50페이지 (`historical_raw_weekly`)
**수집/분석 분리** (대규모 백필용):
- `POST /collect/historical-raw?count=0&max_pages=100` — raw만 빠르게 수집 (LLM 스킵)
- `POST /process/raw?batch_size=200` — news_raw → news_analysis 점진 분석
- `GET /raw/stats` — 백필 진행률 (total/unprocessed/processed)
---
### `/home/kyu/trading/bareunaapi/main.py` (9.5KB)
한국어 형태소분석 FastAPI 래퍼. 포트 5757.
| 함수/클래스 | 역할 |
|------------|------|
| `class AppState` | bareun gRPC stub + Redis + 종목사전 |
| `class AnalyzeRequest / AnalyzeResponse` | 분석 요청/응답 모델 |
| `news_hash(title, url)` | Redis 중복제거용 해시 |
| `extract_morphemes(text)` | 바른API → 명사 추출 |
| `extract_stocks(text)` | 텍스트에서 종목코드 감지 |
| `build_filtered(nouns, stocks)` | 필터링된 텍스트 구성 |
| `scan_finance_terms(text)` | finance_dict.py 금융용어 매칭 |
| `_analyze(req)` | 핵심 분석 로직 |
| `@app.post("/analyze")` | 단건 분석 |
| `@app.post("/analyze/batch")` | 배치 분석 |
| `@app.get("/stocks")` | 로드된 종목 목록 |
| `@app.post("/stocks/refresh")` | KRX→Naver 종목 새로고침 |
| `@app.delete("/dedup/flush")` | Redis 중복제거 캐시 초기화 |
**관련 파일**:
- `bareunaapi/finance_dict.py` — 주식/금융 전문 용어 사전
- `bareunaapi/stock_loader.py` — KRX→Naver 폴백, ~3768 종목 로드
---
### `/home/kyu/trading/dart-collector/main.py` (34KB)
DART 공시/재무제표 수집. 포트 8888.
| 함수/클래스 | 역할 |
|------------|------|
| `class Stats` | 수집 통계 |
| `get_corp_code(stock_code)` | 종목코드 → DART corp_code 변환 |
| `get_corp_name(stock_code)` | 종목코드 → 기업명 |
| `calc_financial_ratios(key_items, prev_revenue)` | ROE/영업이익률/부채비율/FCF/매출성장률 계산 |
| `@app.post("/collect/disclosures")` | 공시 수집 |
| `@app.post("/collect/financials")` | 재무제표 수집 (300개 종목) |
| `@app.post("/collect/major")` | 주요 대형주 수집 |
| `@app.post("/collect/corps")` | DART 기업목록 갱신 |
| `@app.get("/corps")` | 기업목록 조회 |
| `@app.get("/corps/{stock_code}")` | 특정 기업 조회 |
| `@app.get("/financials/{stock_code}")` | 종목 재무데이터 |
| `@app.get("/stats")` | 수집 통계 |
---
### `/home/kyu/trading/score-engine/main.py` (27KB)
종합 투자 스코어 계산 + 텔레그램 알림. 포트 8686.
| 함수/클래스 | 역할 |
|------------|------|
| `get_recommendation(score)` | 점수→등급 변환 |
| `calc_fundamental_score(fin, per, pbr)` | 재무점수 산출 (-100~100) |
| `calc_magic_formula(fin, market_cap)` | 그린블라트 매직 포뮬러 (ROC + EY) 0~30점 |
| `calc_piotroski_score(curr, prev)` | 피오트로스키 F-Score 7신호 → -15~+15 |
| `calc_altman_z(fin, market_cap)` | 알트만 Z-Score 단순화 → 매수/매도/관망 신호 |
| `calc_peg(curr, prev, per)` | 린치 GARP — PER/이익성장률 → 신호 |
| `calc_momentum(conn, code)` | AQR 12-1개월 가격 모멘텀 → 신호 |
| `calc_beneish_simplified(curr, prev)` | Beneish M-Score 단순화 (분식 의심도) |
| `aggregate_signals(signals)` | 6공식 보팅 다수결 → (요약, 카운트) |
| `calc_trend_score(conn, code)` | 5년 ROE 일관성·추세 -30~+30 |
| `calc_dcf(fin, market_cap)` | 간이 DCF 내재가치 + 안전마진 |
| `calc_earnings_quality(fin)` | CFO/영업이익 비율 (분식의심 패널티) |
| `is_value_investable(fin, per, pbr, market_cap)` | 버핏 필터 통과 여부 |
| `@app.post("/score/calculate")` | 종목 점수 계산 실행 |
| `@app.post("/briefing/send")` | 텔레그램 브리핑 전송 |
| `@app.get("/ranking")` | 전체 종목 랭킹 |
| `@app.get("/recommendations")` | 추천 종목 목록 |
| `@app.get("/stock/{code}")` | 특정 종목 점수 상세 |
| `@app.get("/backtest")` | 추천 성과 백테스트 (수익률/승률/샤프/MDD/알파) |
| `@app.post("/learn-weights")` | 공식별 신호 → 7d 수익률 회귀로 가중치 학습 |
| `@app.get("/learn-weights")` | 현재 적용 중인 공식 가중치 조회 |
| `@app.post("/learn-pricing")` | D+E: 점수→30d 수익률 선형회귀 + Random Forest 학습 |
| `@app.get("/predict-price/{code}")` | 학습된 모델로 종목 30일 후 예상 수익률·가격 |
| `@app.get("/sector/concentration")` | 섹터 집중도 + 30% 초과 경고 |
**스코어링 공식 (실제 가중치)**:
```
종합점수 = 펀더멘털통합×0.24 + 뉴스(catalyst가중)×0.18 + 기술×0.15
+ DART공시×0.10 + 외국인수급×0.14 + 공매도×0.06
+ 가격모멘텀×0.03 + DCF안전마진×0.10
+ 시장레짐 보정(±5~10)
+ 앙상블 보팅(±18) ← 6공식 매수/매도 합 × 학습가중치
```
**10공식 앙상블 보팅** — 각 공식이 독립 신호 발신, 학습 가중치로 결합:
| 공식 | 출처/논문 | 데이터 | 매수 신호 |
|------|----------|--------|----------|
| 매직포뮬러 | Greenblatt | ROC + EY | magic_score ≥ 20 |
| F-Score | Piotroski 2000 | 7신호 (전년 대비) | f_score ≥ 6 |
| 알트만 Z | Altman 1968 | 6.72×ROA + 1.05×(시총/부채) | Z ≥ 2.6 |
| PEG | Lynch GARP | PER / 이익성장률 | PEG ≤ 1.5 |
| 12-1 모멘텀 | AQR (Carhart 1997) | (P_-21 / P_-252) - 1 | ≥ 10% |
| Beneish | Beneish 1999 | TATA·SGI·CFO/NI | 의심도 < 50 + CFO/NI > 1 |
| **GP/A** | Novy-Marx 2013 | 영업이익/총자산 (대체) | ≥ 15% |
| **G-Score** | Mohanram 2005 | 5신호 vs 섹터 중앙값 | ≥ 4 |
| **Amihud** | Amihud 2002 | avg(\|return\|/거래대금) | ≥ 100 (소형 알파) |
| **베타(BAB)** | Frazzini-Pedersen 2014 | 종목 vs KOSPI 60일 회귀 | β < 0.7 (저베타 알파) |
**학습 가중치** (`weight_config` 테이블):
- `POST /learn-weights?days=90` — 백테스트로 공식별 매수vs매도 그룹 7d 수익률 차이(edge) 측정
- edge가 큰 공식일수록 가중치 ↑ (정규화 후 합 = 6, 균등 시 각 1.0)
- 다음 점수 계산부터 자동 적용. 표본 부족 시 default(균등 1.0)
**펀더멘털통합** = `clip(buffett_score + trend_score + earnings_quality + magic_score + f_score_adj, -100, 100)`
- `buffett_score`: ROE / 영업이익률 / 부채비율 / 매출성장 / PER / PBR / FCF / 배당
- `trend_score`: 5년 ROE 일관성·추세 (-30~+30)
- `earnings_quality`: CFO/영업이익 ≥1 가산, <0.7 분식의심 패널티
- `magic_score`: ROC(영업이익/총자산) + EY(영업이익/EV) 임계값 합산 (0~30)
- `f_score_adj`: F-Score ≥6 +15, 5 +8, 4 +3, ≤2 -15 (가치함정 회피)
**매직 포뮬러 임계값** (한국 시장 보정):
- ROC: ≥25% +15 / ≥15% +10 / ≥8% +5
- EY: ≥15% +15 / ≥10% +10 / ≥6% +5
**피오트로스키 F-Score 7신호** (현재/전년 사업보고서 비교, 9중 2개는 데이터 부재로 생략):
1. ROA(NI/총자산) > 0 2. CFO > 0 3. ΔROA > 0 4. CFO > NI
5. Δ부채비율 < 0 6. Δ영업이익률 > 0 7. Δ자산회전율 > 0
**추천 등급** (점수 + 다수공식 동의 강제):
- 강력매수: 점수 ≥70 AND 매수보팅 ≥3
- 매수관심: 점수 ≥40 AND 매수보팅 ≥1 AND 매도보팅 <2
- 매도관심: 점수 ≤-30 OR 매도보팅 ≥3
- 강력매도: 점수 ≤-60 OR 매도보팅 ≥4
- 관망: 그 외
**버핏 가치투자 필터** (`is_value_investable`):
- `operating_profit > 0` 영업적자 제외
- ROE ≥ 10%
- 부채비율 ≤ 200%
- PER ≤ 60
- 시총 ≥ 100억
- `dart_corps.is_active=true` (상장폐지 제외)
---
### `/home/kyu/trading/ta-engine/main.py` (37KB)
기술적 분석 엔진. 포트 8484.
| 함수/클래스 | 역할 |
|------------|------|
| `_ema_series(values, period)` | EMA 계산 |
| `_ma(closes, n)` | 단순이동평균 |
| `_rsi(closes, period=14)` | RSI |
| `_macd(closes)` | MACD (12/26/9) |
| `_bollinger(closes, period=20)` | 볼린저밴드 |
| `_stochastic(highs, lows, closes, period=14)` | 스토캐스틱 K/D |
| `_vol_ratio(volumes, period=20)` | 거래량비율 |
| `calc_indicators(ohlcv)` | 전체 지표 계산 |
| `calc_tech_score(ind)` | 기술점수 (-100~100) |
| `calc_price_targets(price, ind, sig)` | 목표가/손절가 계산 |
| `analyze_position(price, buy_price, qty, ...)` | 포지션 분석 |
| `@app.get("/technical/{code}")` | 종목 기술분석 |
| `@app.get("/ranking")` | 기술점수 랭킹 |
| `@app.get("/buy-candidates")` | 매수 후보 |
| `@app.post("/analyze/all")` | 전체 종목 분석 |
| `@app.post("/analyze/{code}")` | 특정 종목 분석 |
| `@app.post("/position")` | 포지션 분석 |
| `@app.get("/report/{code}")` | 종합 리포트 |
**목표가 계산**:
- 진입가: `min(현재가, MA20×0.99)`
- T1: `max(볼밴상단, 현재가×1.05)`
- T2: `max((현재가+52주고가)/2, 현재가×1.10)`
- T3: `max(52주고가×0.97, 현재가×1.20)`
- 손절가: `min(MA60×0.98, 현재가×0.95)`
---
### `/home/kyu/trading/dashboard-api/main.py` (19KB)
대시보드 REST API. 포트 8989.
| 엔드포인트 | 역할 |
|-----------|------|
| `GET /api/summary` | 전체 요약 통계 |
| `GET /api/recent` | 최근 뉴스 분석 |
| `GET /api/ranking` | 종합 랭킹 |
| `GET /api/recommendations` | 추천 종목 |
| `GET /api/avoid` | 회피 종목 |
| `GET /api/signals` | 매매 시그널 |
| `GET /api/technical/{code}` | 기술분석 데이터 |
| `GET /api/buy-candidates` | 매수 후보 |
| `GET /api/alerts` | 알림 목록 |
| `GET /api/timeline` | 타임라인 |
| `GET /api/stock/{code}` | 종목 상세 |
| `GET /api/search` | 종목 검색 |
| `POST /api/position` | 포지션 등록 |
| `GET /api/report/{code}` | 종목 리포트 |
| `GET /api/fundamentals` | 전체 재무데이터 |
| `GET /api/fundamentals/{code}` | 종목 재무데이터 |
| `GET /api/name/{code}` | 종목명 조회 |
| `GET /` | 대시보드 HTML |
---
### `/home/kyu/trading/kis-api/main.py` (11KB)
네이버 모바일 주가수집 + 매매시그널. 포트 8585.
| 함수/엔드포인트 | 역할 |
|---------------|------|
| `class Stats` | 수집 통계 |
| `calc_signal(p, news_sc, dart_sc)` | 뉴스+공시점수 → 매매시그널 |
| `GET /price/{code}` | 단일 종목 주가 |
| `GET /prices` | 전체 주가 목록 |
| `GET /signals` | 전체 시그널 |
| `GET /signals/{code}` | 종목별 시그널 |
| `POST /collect` | 주가 수집 실행 |
**Redis DB 할당**:
- db=0: n8n Queue
- db=1: bareunaapi 중복제거
- db=3: kis-api/score-engine (`price:{code}`, `prices:last_update`)
- db=4: news-collector (`news:naver:{hash}`)
- db=5: ta-engine (`ta:{code}`)
---
### `/home/kyu/trading/aux-signal/main.py`
보조 데이터 수집. 포트 8282.
| 엔드포인트 | 역할 |
|-----------|------|
| `POST /collect/naver` | 네이버 integration API → 컨센서스 + 기관/외국인 일별 수급 |
| `POST /collect/macro` | 한국은행 ECOS → USD/KRW 환율, 국고채 10년 |
| `GET /consensus/{code}` | 종목 컨센서스 (목표가/투자의견) |
| `GET /flow/{code}` | 기관/외국인 일별 순매수 |
| `GET /macro/latest` | 최신 매크로 지표 |
| `GET /macro/{indicator}` | 지표별 시계열 |
스케줄: 영업일 18:00 네이버 수급/컨센서스, 08:30 ECOS 매크로.
---
### `/home/kyu/trading/us-market/main.py`
미국증시 동조 시그널. 포트 8383.
| 엔드포인트 | 역할 |
|-----------|------|
| `POST /collect` | Finnhub/AlphaVantage → 미국 ETF·개별주 가격 |
| `POST /collect/backfill` | 신규 페어용 과거 데이터 백필 |
| `POST /collect/yfinance-backfill` | yfinance 폴백 백필 |
| `POST /signal/calculate` | 페어 회귀로 한국 종목별 미증시 시그널 산출 |
| `GET /signal/{kr_code}` | 한국 종목 KR 코드별 시그널 |
| `GET /signal/latest` | 전체 종목 최신 시그널 |
| `GET /pairs` | 등록된 KR↔US 페어 |
| `POST /pairs/recalc-beta` | 60일 회귀 베타 재계산 |
| `POST /pairs/discover` | KOSPI200 × S&P500 상관 자동 발굴 |
| `GET /etfs` / `GET /etfs/{etf}/latest` | 섹터 ETF 현황 |
**기여 점수**:
- 섹터 ETF 동조 (SOXX/XBI/LIT 등 14개) → 같은 한국 섹터 ±5점
- 개별 페어 60일 회귀 베타 (NVDA↔SK하이닉스 등) → ±10점
스케줄: 매일 KST 07:30 수집, 08:00 시그널 계산.
---
### `/home/kyu/trading/graph-engine/main.py`
GAT 그래프 신경망 (PyTorch). 포트 9090.
| 엔드포인트 | 역할 |
|-----------|------|
| `POST /graph/build` | 노드/엣지 재구성 |
| `POST /train` | 학습 (6mo rolling) |
| `POST /predict` | 전체 추론 → `stock_scores.graph_score` |
| `GET /predict/{code}` | 종목별 예측 값 |
| `GET /status` | 모델/학습 상태 |
- **노드**: 활성 종목, 12피처 (1d/5d/20d 수익률, vol_ratio, RSI, tech_score, ROE, 영업이익률, 부채비율, 7d 뉴스, 미증시 overnight, log_mcap)
- **엣지**: ① 60일 가격 상관 |corr|≥0.4 ② 동일 섹터 ③ 뉴스 공기 ≥3회
- **스케줄**: 일요 06:00 학습 / 매일 08:30 추론
- **모델 저장**: `/mnt/nas/models/graph/`
---
### `/home/kyu/trading/telegram-bot/main.py`
텔레그램 명령 처리 봇 (python-telegram-bot).
| 명령 | 역할 |
|------|------|
| `/start /help` | 도움말 |
| `/buy /buys` | 강력매수/매수관심 톱 N |
| `/sell /sells` | 매도관심/강력매도 톱 N |
| `/stock <코드>` | 종목 점수·신호 요약 |
| `/deep <코드>` | RAG+EXAONE 심층분석 결과 |
| `/market` | 시장 요약(섹터·매크로) |
권한: `TELEGRAM_CHAT_ID` 일치 채팅만 응답.
---
## 데이터베이스 스키마 (PostgreSQL)
### `news_analysis`
```
id, title, url, source, published_at, hash(16자 UNIQUE)
sentiment(호재/악재/중립), intensity(1~5), primary_stock
affected_stocks(JSONB), reason, investment_action(매수관심/매도관심/관망)
keywords(JSONB), stock_names(JSONB), stock_codes(JSONB)
similar_count, catalyst, analyzed_at, created_at
```
### `stock_technical`
```
stock_code(UNIQUE), stock_name, price
ma5, ma20, ma60, ma120, rsi, macd, macd_signal, macd_hist
bb_upper, bb_mid, bb_lower, pct_b, stoch_k, stoch_d
vol_ratio, tech_score(-100~100), signal(매수/매도/관망)
obv, obv_trend, vwap20, ichimoku(JSONB) ← 새 지표
signals(JSONB), targets(JSONB), analyzed_at
```
### `dart_corps`
```
stock_code(PK), corp_code, corp_name, modify_date, is_active
※ is_active=true 필터 필수 (상장폐지 제외)
```
### `dart_financials`
```
stock_code, corp_code, corp_name, bsns_year, reprt_code
revenue, operating_profit, net_income
total_assets, total_liabilities, total_equity, operating_cashflow
roe, operating_margin, net_margin, debt_ratio, revenue_growth, fcf_ratio
UNIQUE(stock_code, bsns_year, reprt_code)
※ F-Score는 전년 11011 사업보고서 필요 → years≥2 백필
```
### `stock_scores` (주요 컬럼)
```
stock_code, score_date, total_score, recommendation
news_score, dart_score, technical_score, foreign_score, short_score, price_score
trend_score, intrinsic_value, margin_of_safety, earnings_quality
magic_score, f_score, roc_pct, earnings_yield_pct
altman_z, peg, momentum_pct, beneish_score
gpa_pct, g_score, amihud_illiq, market_beta ← 학술 논문 기반 4개
signals(JSONB), buy_votes, sell_votes
position_size_pct, volatility_60d, market_regime_adj, sector
top_reasons, UNIQUE(stock_code, score_date)
```
### `weight_config`
```
config_date(PK), weights(JSONB), period_days, sample_size
※ 최신 row가 calculate_daily_scores에서 자동 로드 → ensemble_bonus 가중
```
### `pricing_model_v2` (가격예측 모델 학습 결과 저장)
```
id, model_date, segment, model_type(linear/rf/xgb), target(return_7d/30d/alpha_30d)
feature_names(JSONB), feature_importance(JSONB), coef(JSONB), intercept
r2_oos, ic_spearman, hit_ratio, sample_size, period_days, model_blob(bytea), created_at
※ /learn-pricing 호출 시 모델별 row 갱신, /predict-price가 model_blob 역직렬화로 추론
```
### `news_raw` (수집/분석 분리용 임시 저장소)
```
id, stock_code, title, url, url_hash(UNIQUE), source,
published_at_text, collected_at, processed(BOOL), processed_at
※ 크롤러는 raw만 빠르게 저장 → 야간 cron(process_raw)이 batch로 LLM 분석
```
### 뷰
- `v_recent_signals` — 최근 24시간 호재/악재 (intensity DESC)
- `v_stock_news_count` — 종목별 7일 뉴스 감성 카운트
---
## 환경변수 키 목록 (.env)
```
POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_DB=trading_ai POSTGRES_USER=kyu
REDIS_MAX_MEMORY=2gb REDIS_MAXMEMORY_POLICY=allkeys-lru
OLLAMA_NUM_PARALLEL=4 (EXAONE→GPU0, bge-m3→GPU1 자동 분배)
QDRANT_COLLECTION=news_vectors QDRANT_VECTOR_SIZE=1024
TELEGRAM_CHAT_ID=8690666445
KIS_IS_PAPER=true (모의투자 모드)
ECOS_API_KEY=... (한국은행 매크로 — ecos.bok.or.kr/api 무료)
```
---
## 자주 쓰는 명령어
```bash
# 전체 상태
docker compose ps
# 특정 서비스 로그 (실시간)
docker logs trading-news-collector --tail 50 -f
docker logs trading-score-engine --tail 50 -f
docker logs trading-n8n --tail 30
# 재빌드+재배포
docker compose build <service> && docker compose up -d <service>
# PostgreSQL 쿼리 (DB명: trading_ai, user: kyu)
docker exec trading-postgres psql -U kyu -d trading_ai -c "SELECT ..."
# Redis
source /home/kyu/trading/.env && docker exec trading-redis redis-cli -a $REDIS_PASSWORD
# n8n 마이그레이션 확인
docker exec trading-postgres psql -U kyu -d trading_ai -c "SELECT COUNT(*) FROM n8n.migrations;"
# 추천 종목 조회
docker exec trading-postgres psql -U kyu -d trading_ai -c "
SELECT s.stock_code, d.corp_name, s.total_score, s.recommendation,
s.magic_score, s.f_score, s.roc_pct, s.earnings_yield_pct
FROM stock_scores s JOIN dart_corps d ON d.stock_code=s.stock_code
WHERE d.is_active=true AND s.score_date=CURRENT_DATE
ORDER BY s.total_score DESC LIMIT 10;"
# 다년치 사업보고서 백필 (F-Score / PEG용)
curl -X POST 'http://localhost:8888/collect/financials?count=3000&years=10&annual_only=true'
# 섹터 정보 채우기 (dart_corps.sector)
curl -X POST http://localhost:8888/collect/sectors
# 점수 수동 재계산 (전체 활성종목 대상)
curl -X POST http://localhost:8686/score/calculate
# 백테스트 기반 공식 가중치 학습
curl -X POST 'http://localhost:8686/learn-weights?days=90'
# 현재 가중치 조회
curl -s http://localhost:8686/learn-weights
# 종목별 6공식 신호 확인
docker exec trading-postgres psql -U kyu -d trading_ai -c "
SELECT stock_code, stock_name, total_score, signals
FROM stock_scores WHERE score_date=CURRENT_DATE
ORDER BY total_score DESC LIMIT 10;"
```
---
## 주의사항
- **상장폐지 필터**: 모든 추천 쿼리에 `JOIN dart_corps d ON d.stock_code=... WHERE d.is_active=true` 필수
- **fstab**: NAS 마운트는 반드시 한 줄로 작성, `nfsvers=3` 필수
- **GPU**: Ollama 단일 컨테이너가 GPU0(EXAONE 추론)·GPU1(bge-m3 임베딩) 동시 사용. `runtime: nvidia` 필요
- **KIS**: 현재 모의투자 모드 (`KIS_IS_PAPER=true`)
- **바른API**: KRX→Naver 폴백으로 ~3768 종목 로드. 형태소 실패 시 공백분리 폴백
---
## 현재 이슈 / TODO
- [ ] fstab NAS 항목이 두 줄로 분리됨 — sudo로 직접 수정 필요
- [x] n8n 워크플로우 재구성 완료 (워크플로우 16개 / 활성 14개)
- [x] vLLM 제거 → Ollama 단일 운영으로 전환 (EXAONE 3.5 7.8B + bge-m3)
- [x] n8n DB 마이그레이션 오류 수정 완료 (51개 마이그레이션 등록)
- [x] SSL 인증서 자동 갱신 설정 완료 (certbot.timer + deploy hook → nginx reload)
도메인: al/cla/n8/pns/tr.kyleyang.co.kr, 훅: /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
---
## Behavioral Guidelines (LLM 공통 실수 감축)
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
### 1. Think Before Coding
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
### 2. Simplicity First
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
### 3. Surgical Changes
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
### 4. Goal-Driven Execution
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.