Files
trading/CLAUDE.md
T

646 lines
28 KiB
Markdown
Raw Normal View History

# 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.