Skip to content

Repository files navigation

LLM 기반 주식 투자 신호 생성 시스템

LLM을 활용해 한국 주식 종목의 매수/매도 신호를 생성하고, 백테스팅으로 그 유효성을 검증하는 시스템.
백테스팅 실험 외에 오늘 날짜 기준 실시간 신호를 생성하는 Forward TestStreamlit 대시보드를 포함한다.

목차: 개요 · 실험 설계 · 프로젝트 구조 · 실행 방법


개요

연구 질문

메인: LLM이 유효한 주식 투자 신호(Buy/Sell)를 생성할 수 있는가?
서브: 어떤 재무 컨텍스트 조합을 제공할 때 신호 품질이 최적화되는가?

전체 흐름

데이터 수집 → LLM 백테스팅 (4모델 × 5조건) → 성과 비교·유의성 검정 (compare / significance / breakdown)
                                             ↓
              오늘 기준 실시간 신호 (Forward Test) → Streamlit 대시보드 (탭 3개)

실험 설계

LLM에 제공하는 재무 컨텍스트 조합을 달리하며 최적 구성을 탐색한다. 동일 LLM에 서로 다른 컨텍스트를 제공하고 성과를 비교하는 Ablation Study 방식으로 설계됐다.

조건 추가 컨텍스트 세부 항목
cond1 없음 종목명 + 현재가만 제공 (No Context)
cond2 재무 + 기술지표 PER / PBR / ROE / 시가총액 / 52주 위치 / 1개월 수익률 / 거래량 변화율
cond3 + 애널리스트 리포트 리포트 제목 / 목표주가 (최근 30일, 최대 5건)
cond4 + DART 분기 실적 매출 / 영업이익 / 영업이익률 / 순이익 (전년동기比) / 부채비율 / 영업현금흐름 (최근 정기보고서 = 단일분기, 4~5월만 연간)
cond4_no_reports cond4에서 리포트 제거 (LOO ablation) 재무지표 + DART 실적. 리포트의 marginal effect 측정용

~~cond4_blind (종목명 익명화)~~는 미실행. 종목명을 가려도 시가총액·현재가로 부분 식별이 가능해 완전한 blind가 성립하지 않는다. 사전학습 편향 검증은 대신 cond1 신호의 종목 편중 분석으로 수행했다 (experiments_log.md "cond1 사전학습 편향 검증").

공통 조건은 2023-01~2025-12(36개월) 매월 첫 거래일, KOSPI/KOSDAQ 대형주 20종목, 4모델 (temperature=0.0), 20거래일 후 절대·초과 수익률 평가다. 프롬프트 설계와 전체 변수 목록은 EXPERIMENT_VARS.md 참고.

결과

백테스트 14,203건(4모델 × 5조건) 완료. cond4 Buy는 애널리스트 컨센서스를 4모델 중 3모델에서 유의하게 상회하고, 리포트 추가는 4모델 전부 비유의하며, cond1의 종목 편중은 재무 데이터를 주면 무작위 기대값으로 수렴한다. 포트폴리오로 운용하면 무기술 벤치마크를 넘지는 못한다.

수치·검정 결과는 experiments_log.md, 나머지 문서는 아래 프로젝트 구조의 docs/ 참고.


프로젝트 구조

stock_analysis/
├── app.py                           # Streamlit 진입점 (page_config·탭 배치만)
├── app_ui/                          # 대시보드 구현 (탭별 분리)
│   ├── __init__.py                  # 부트스트랩 (sys.path·.env·ROOT_DIR)
│   ├── shared.py                    # 탭 공용 상수·로더 (둘 이상의 탭이 쓰는 것만)
│   ├── tab_analyze.py               # ① 개별 종목 분석 (실시간 신호 생성, 유일하게 API 사용)
│   ├── tab_data.py                  # ② 종목 데이터 조회·내려받기 (LLM 호출 없음)
│   ├── tab_portfolio.py             # ③ 포트폴리오 백테스트 (누적 곡선·MDD)
│   │
│   │                                # 아래 3개는 app.py 마운트에서 제외 (파일은 보존).
│   │                                # 발표 자료용 도표를 로컬에서 캡처할 때 쓴다.
│   │                                # 판단 근거는 docs/app_scope.md
│   ├── tab_matrix.py                # 백테스트 신호 매트릭스
│   ├── tab_report.py                # 백테스트 성과·모델 비교
│   └── tab_flip.py                  # 조건 간 신호 전이 (짝 비교 검정)
├── src/
│   ├── utils.py                     # 공통 유틸 (TICKERS, 경로, 주가 캐시, 수익률 계산)
│   ├── experiments.py               # 실험 조건 정의 (cond1~cond4 + ablation)
│   ├── context_builders.py          # LLM 프롬프트용 컨텍스트 섹션 빌더
│   ├── prompt.py                    # LLM 프롬프트 구성 요소 (역할·판단기준·confidence)
│   │
│   ├── collect/                     # 데이터 수집
│   │   ├── crawl.py                 # 네이버금융 애널리스트 리포트 크롤링 (증분)
│   │   ├── collect_financials.py    # DART + FDR 재무/기술지표 수집
│   │   ├── collect_dart_fundamentals.py  # DART 정기보고서 분기 실적 수집 (최근 공시)
│   │   └── update.py               # Forward Test용 실시간 데이터 수집
│   │
│   └── experiment/                  # 실험 실행 및 분석
│       ├── baseline_consensus.py    # 대조군 A: 컨센서스 추종 전략
│       ├── baseline_golden.py       # 대조군 B: 골든크로스 전략
│       ├── llm_experiment.py        # LLM 백테스팅 (체크포인트 재개 지원)
│       ├── compare.py               # 기술통계 비교 (평균·Hit·Sharpe, 섹터·종목)
│       ├── significance.py          # 추론통계 (유의성 검정: Mann-Whitney·Welch·effect size)
│       ├── breakdown.py             # 다축 분해 (연도별·시장국면별, mean+median 병기)
│       ├── forward_test.py          # Forward Test (오늘 기준 단일 종목 신호 생성)
│       ├── forward_run_all.py       # Forward 일괄 실행 (전 종목 × 5조건, 주간 반복)
│       ├── forward_verify.py        # Forward 입력 정보 신선도·정합성 검증
│       └── forward_eval.py          # Forward 성숙 신호 평가 (신호일 이후 실제 수익률·적중)
│
├── data/                            # 수집 데이터
│   ├── financials/                  # 재무 + 기술지표 CSV
│   ├── price/                       # 주가 캐시 CSV
│   ├── reports/                     # 애널리스트 리포트 CSV
│   └── dart_fundamentals/           # DART 분기 실적 CSV (최근 정기보고서)
├── results/                         # 실험 결과
│   ├── baseline/                    # 대조군 수익률
│   ├── experiment/cond{1-4}/        # LLM 실험 결과 (체크포인트 포함)
│   ├── analysis/                    # 비교 분석 CSV
│   ├── forward/                     # Forward Test 결과 JSON + evaluation.csv
│   └── forward_demo/                # 앱 시연 신호 (평가 표본에서 제외, gitignore)
├── docs/
│   ├── experiments_log.md           # 실험 일지 (백테스트)
│   ├── prove.md                     # 데이터 정확성 검증 (증권사 대조)
│   ├── forward_log.md               # Forward Test 운영 일지
│   └── TODO.md                      # 남은 작업·한계
├── docs_cache/                      # DART API 법인코드 캐시 (gitignore)
├── EXPERIMENT_VARS.md               # 실험 변수·프롬프트 설계
├── pyproject.toml                   # 의존성 정의 (uv)
├── uv.lock                          # 정확한 버전 고정 파일
└── .env                             # 환경변수 (gitignore)

실행 방법

1. 환경 설정

uv 설치 후 의존성을 한 번에 설치한다.

# uv 설치 (최초 1회)
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# 가상환경 생성 + 의존성 설치
uv sync

.env 파일을 생성하고 API 키를 입력한다.

DARTS_API_KEY=your_dart_api_key           # DART OpenAPI (https://opendart.fss.or.kr)
GEMINI_API_KEY=your_gemini_api_key        # Google Gemini/Gemma (앵커 모델)
OPENAI_API_KEY=your_openai_api_key        # (선택) gpt-* 사용 시
ANTHROPIC_API_KEY=your_anthropic_api_key  # (선택) claude-* 사용 시

모델명 접두어로 provider가 정해진다 — gemini-*/gemma-*→Google, gpt-*→OpenAI, claude-*→Anthropic. 그 외 접두어는 지원하지 않는다(즉시 에러). 키는 실제 쓰는 provider의 것만 있으면 된다.

2. 데이터 수집

# 애널리스트 리포트 크롤링 (네이버금융, 증분 업데이트)
python src/collect/crawl.py

# 재무/기술지표 수집 (DART + FinanceDataReader)
python src/collect/collect_financials.py

# DART 분기 실적 수집 (cond4용, 최근 정기보고서)
python src/collect/collect_dart_fundamentals.py

3. 베이스라인 실험 (대조군)

python src/experiment/baseline_consensus.py   # 컨센서스 추종
python src/experiment/baseline_golden.py      # 골든크로스 (MA5×MA20)

4. LLM 백테스팅

python src/experiment/llm_experiment.py --cond cond1
python src/experiment/llm_experiment.py --cond cond2
python src/experiment/llm_experiment.py --cond cond3
python src/experiment/llm_experiment.py --cond cond4
python src/experiment/llm_experiment.py --cond cond4_no_reports
  • 중단 후 재개 가능: 완료된 (ticker, signal_date) 쌍은 자동 스킵
  • 단일 종목 테스트 (프롬프트 출력 확인): --test 플래그 추가
python src/experiment/llm_experiment.py --cond cond4 --test

5. 성과 비교 분석

기술통계는 compare.py, 추론통계(유의성 검정)는 significance.py, 다축 분해(연도·국면)는 breakdown.py로 분리돼 있다. 3년 총합은 국면 효과를 가릴 수 있어 breakdown으로 보완한다.

# 기술통계 — cond1~4 전체 + 섹터·종목별 분석
python src/experiment/compare.py --all

# 특정 조건까지만 비교
python src/experiment/compare.py --cond cond3

# 섹터·종목 분석 포함
python src/experiment/compare.py --cond cond4 --sector

# 추론통계 — 조건 간 차이의 유의성 검정 (별도 실행)
python src/experiment/significance.py --all

# 다축 분해 — 연도별 / 시장 국면별 (mean+median 병기)
python src/experiment/breakdown.py

멀티모델: 백테스팅·분석 모두 --model로 모델을 지정한다(기본값 앵커 gemini-2.5-flash-lite). 예: python src/experiment/llm_experiment.py --cond cond4 --model claude-haiku-4-5.

결과는 모델별로 분리 저장된다:

  • 실험: results/experiment/{cond}/{model}/{latest|날짜}/
  • 분석: results/analysis/{model}/{latest|날짜}/
파일 생성 내용
all_comparison.csv compare.py 신호별(Buy/Neutral/Sell/전체) × 조건별 수익률·Hit Rate·Sharpe
full_comparison.csv compare.py 전략별 한 줄 요약 (대조군 포함)
all_sector.csv compare.py 섹터별 × 조건별 성과
all_stock_buy.csv compare.py 종목별 × 조건별 Buy 신호 성과
all_significance.csv significance.py 통계적 유의성 검정 결과 (Mann-Whitney, Welch's t-test, effect size)
breakdown_yearly.csv breakdown.py 연도별(2023~25) × 조건별 Buy/Sell 성과 (mean+median)
breakdown_regime.csv breakdown.py 시장 국면별(상승/하락) × 조건별 Buy/Sell 성과

6. Forward Test

백테스팅과 동일 모델·프롬프트로 오늘 날짜 기준 신호를 생성하고 20거래일 뒤 실제 수익률로 검증한다 (앵커: gemini-2.5-flash-lite).

신호 생성은 2026-08-02 배치로 종료했다. 아래 ①~③은 더 이상 실행하지 않는다. 남은 작업은 이미 생성된 4배치(07-12 / 07-19 / 07-26 / 08-02)를 한 주 간격으로 평가하는 ④뿐이며, forward_eval.py는 LLM을 호출하지 않고 주가로 수익률만 계산하므로 API 비용이 0이고 사전 수집도 필요 없다.

python src/collect/crawl.py                # ① 애널리스트 리포트 최신화 (증분) — cond3/4에 필요
python src/experiment/forward_run_all.py   # ② 전 종목 × 5조건 신호 생성 (DART는 자동 갱신)
python src/experiment/forward_verify.py    # ③ 넣은 정보 신선도·정합성 점검 (현재가·ROE·리포트·DART)
python src/experiment/forward_eval.py      # ④ 성숙분 실제 수익률·적중 평가 — 현재 유일하게 실행

단일 종목 테스트: python src/experiment/forward_test.py --ticker 005930 --cond cond3

  • 신호 저장: results/forward/{날짜}/{model}/{ticker}_{cond}.json (당일 동일 ticker+cond+model은 캐시 반환)
  • 평가 저장: results/forward/evaluation.csv — 미성숙(20거래일 미경과) 신호는 pending
  • 주간 반복 시 20거래일 보유구간이 겹쳐 표본이 독립이 아니므로 실전 참고용 (유의성 검정은 백테스트가 담당)
  • 백테스트 데이터 비오염: forward는 재무지표·DART를 모두 인메모리로 계산하며 data/financials/·data/dart_fundamentals/에 쓰지 않는다 → 백테스트 데이터(2023-2025)가 순수하게 유지됨. 리포트만 crawl.py로 증분 갱신(백테스트는 신호일 30일 창으로 필터하여 무영향).

7. Streamlit 대시보드

streamlit run app.py

탭 3개로 구성된다. API를 호출하는 것은 탭①의 "분석하기" 버튼 하나뿐이며, 나머지 두 탭은 LLM을 쓰지 않는다.

내용 LLM API
① 개별 종목 분석 KRX 상장 보통주 전 종목(약 2,760개) 중 하나를 골라 오늘 기준 신호 생성. 신호 배지·투자 근거·재무지표(cond2+)·리포트(cond3+)·DART 실적(cond4)·해당 종목 백테스트 성과 호출
② 종목 데이터 조회 선택 종목의 시세·기술지표·재무지표·DART 실적을 현재 시점 기준으로 수집해 표시하고 CSV/JSON/Markdown으로 내려받기 없음
③ 포트폴리오 백테스트 신호대로 운용했을 때의 누적 곡선 + MDD + 연환산 변동성. 거래비용 왕복 0.25% 토글 없음

①②가 시스템, ③이 검증이다. 백테스트 신호 매트릭스·성과 비교·신호 전이 화면은 앱 마운트에서 제외했다 — 인터랙션이 값을 더하지 않아 정지 화면 한 장으로 대체 가능하고, 그 내용은 포스터와 발표 영상이 맡기 때문이다. 모듈 파일은 app_ui/에 남겨 도표 캡처용으로 쓴다. 범위 판단의 근거는 app_scope.md에 정리했다.

탭①의 대상은 백테스트 20종목이 아니라 KRX 상장 보통주 전 종목이다. 백테스트와 forward는 방법을 검증하는 통제 실험이고 분석 자체는 임의 종목에 적용되어야 하기 때문이다. 20종목 밖은 과거 성과 이력이 없고 리포트 커버리지도 낮은데, 둘 다 화면에서 명시한다. 우선주는 DART 재무제표가 보통주 기준 하나뿐이라 PER·시가총액이 어긋나므로 목록에서 제외한다(근거는 prove.md "각도 4").

탭①에서 생성된 신호는 results/forward_demo/로 격리해 정식 평가 표본(forward_eval.py)에 섞이지 않게 한다.

탭②는 get_today_context()를 직접 호출하므로 LLM 없이 파이프라인이 수집하는 값을 손실 없이 받는다. 애널리스트 리포트는 제외한다ensure_reports가 백테스트 20종목을 건드리지 않아 (그 CSV는 crawl.py가 관리하는 실험 입력) 종목에 따라 갱신 기준이 갈리기 때문이다. 화면 전체를 "지금 수집한 값"으로 통일하기 위한 선택이다.

About

graduate project- stock analysis

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages