Commit Graph

31 Commits

Author SHA1 Message Date
claude-owner
e0edc8f1e3 feat: 예측 실패 원인 노출 + /health/models 진단 + restart-ci.bat
사금향님이 만난 409 'both chronos & lgbm failed' 에러가 원인을 안 보여줘서
디버깅 어려웠음. 세 군데 보강:

1. ensemble.py: 두 모델 다 실패 시 chronos/lgbm 각각의 실제 에러 원문
   (type:message) 을 RuntimeError 메시지에 포함. predict.py 가 409 detail
   로 그대로 노출하므로 브라우저에서 바로 원인 확인 가능. LGBM 가 None
   반환 (체크포인트 없음) 인 경우도 'model checkpoint not found' 로 명시.

2. /health/models 엔드포인트 추가:
   - chronos.ping() — lazy load 시도 + 디바이스/모델명 반환
   - LGBM_MODEL_DIR 의 *.pkl 개수와 샘플 8개 파일명 반환. cold start
     (체크포인트 0개) 면 'no_checkpoints' 상태로 알림.

3. restart-ci.bat 추가 — restart.bat 에서 pause 빼고 종료 코드로만 알리는
   SSH 비대화형 친화 버전. 일반 사용은 그대로 restart.bat.
2026-05-23 15:42:44 +09:00
claude-owner
44873ddb39 fix(chart): "오늘" 표시를 차트 본체 마커 → 시간축 아래 캡션으로 이동
lightweight-charts 의 timeScale tick 라벨에 직접 텍스트를 끼우는 공식 API 가
없어서, 차트 컨테이너 바로 아래에 작은 캡션 (• 오늘 · YYYY-MM-DD (요일)) 으로
표시. 사용자 요청 ("차트에 표시 말고 아래 날짜 표시하는 곳에").
2026-05-23 02:49:36 +09:00
claude-owner
e610599879 fix(chart): 10분봉 페이지네이션, 장외 캐시, 예측 horizon 캡 일치
reviewer 지적사항 반영:

1. KIS 분봉이 한 번에 30개만 와서 10분봉이 최대 3개만 나오던 문제 →
   fetch_minute_range() 추가. FID_INPUT_HOUR_1 을 30분씩 후퇴시키며
   페이지네이션, 중복 ts 자연 dedupe, max_pages=20 으로 무한루프 방지.
   _ensure_intraday_fresh 는 last_ts+1m ~ now 빈 구간만 채우므로 평소엔
   1~2 페이지로 끝남.

2. 장외/주말에 매번 KIS 를 때리던 문제 →
   - 주말: 'weekend' 반환, KIS 안 부름 (분봉 endpoint 는 당일만 지원)
   - 평일 장외 + 오늘 데이터 있음: 'cached_closed' 반환
   - 장중 + 10분 이내: 'fresh' 반환
   토큰/조회 제한 다시 밟지 않음.

3. 프론트 horizon 입력 60 vs 백엔드 30 불일치 →
   PredictionPanel 의 cap 을 30 으로 맞춤. 백엔드 predict.py 의 학습/검증
   범위와 일치. placeholder 도 '1~30' 으로 명시.
2026-05-23 01:41:43 +09:00
claude-owner
0a5c634680 feat(chart): 10m 실시간 / 일·주·월 토글 / 오늘 마커 / 예측 거래일 선택
- backend/app/api/chart.py: interval=10m|1d|1w|1mo. 10m 은 ohlcv_1m 을
  time_bucket(10min) 으로 집계, stale(>10분) 이면 KIS 분봉 fetch 후 재조회.
  1w/1mo 는 ohlcv_daily 를 date_trunc 로 집계. today 필드 추가.
- backend/app/fetch/kis.py: fetch_minute_price() 추가 (tr_id FHKST03010200).
  KIS 응답 KST 시각을 tz-aware datetime 으로 변환, 오름차순 정렬.
- web/lib/api.ts: ChartInterval 타입, getChart(interval), predict(horizons[]).
- web/components/StockChart.tsx: 10m 이면 timeVisible. 일·주·월에서 오늘
  화살표 마커 표시. ISO datetime 도 파싱.
- web/components/PredictionPanel.tsx: 단기/중기/장기 프리셋 + 사용자 직접
  지정 (예: 1,2,3,7). API 에 horizons 배열 전달.
- web/app/[code]/page.tsx: interval 칩 (10분/일/주/월). 10m 일 때 60초마다
  폴링. interval 별 기본 lookback (10m=1, 1d=180, 1w=730, 1mo=1825).
2026-05-23 01:34:29 +09:00
claude-owner
928c2160f9 feat(kis): 토큰 디스크 캐시 + restart.bat env 재로드
토큰 발급 1분/1일 제한 (EGW00133 등 403) 회피:
- _load_disk_cache / _save_disk_cache 로 /app/.cache/kis_token.json 영속화
  · ./backend:/app 바인드 마운트로 호스트 backend/.cache/ 에 저장
  · backend/.cache/ 는 .gitignore 에 이미 포함됨 (secret 비커밋)
  · app_key prefix 캐시 무효화 키 (.env 갱신 시 자동 폐기)
  · atomic write (tmp → rename) + 0600 권한
- get_token: 메모리 → 디스크 → 신규 발급 순으로 fallback
- 컨테이너 재기동해도 24시간 유효 토큰 재사용 → 발급 폭주 방지

restart.bat:
- restart → up -d --force-recreate --no-deps backend web
  · restart 는 env_file 재로드 안 함 (.env 의 KIS_APP_KEY 변경이 무시됨)
  · up -d 는 새 인스턴스 생성하며 env_file 다시 읽음
  · --no-deps 로 db 는 절대 건드리지 않음 (postgres_data 영속)
2026-05-23 01:22:32 +09:00
tkrmagid
78388d347e fix(web): API base 를 페이지 host 로 동적 해석 (LAN 접속 대응)
리뷰어 지적: docker-compose 가 NEXT_PUBLIC_API_BASE=http://localhost:8000 을
주입해서 클라이언트 번들에 localhost 가 inline 됨. 사금향 게임컴 (192.168.10.13)
브라우저에선 동작하지만, 같은 내부망의 다른 PC 또는 외부 검증자가
http://192.168.10.13:3000 에 접속하면 fetch 가 그 PC 의 localhost:8000 으로 가서
연결 실패. 백엔드는 정상인데도 '검색 결과 없음' 으로 보임.

해석 우선순위:
  1) NEXT_PUBLIC_API_BASE 가 비 localhost 값 → 그대로 (프로덕션 도메인 케이스)
  2) 브라우저 → window.location.hostname:8000 (LAN/localhost 자동 대응)
  3) SSR 폴백 → localhost:8000

localhost/127.0.0.1 판별은 //(localhost|127.0.0.1)(?::|$) 정규식 — 'localhost.evil.com'
같은 서브도메인 우회는 매치 안 됨. node 로 7케이스 검증 완료.

web/app 코드만이라 사금향 PC 는 restart.bat 으로 반영 (next dev hot-reload).
2026-05-21 21:52:09 +09:00
tkrmagid
659871118f fix(compose-gpu): MODEL_DEVICE 를 .env 로 덮어쓸 수 있게 변경
이전: MODEL_DEVICE: cuda (하드코딩) → .env 에 MODEL_DEVICE=cpu 두어도
compose environment 가 env_file 보다 우선이라 무시됨.

수정: MODEL_DEVICE: ${MODEL_DEVICE:-cuda} — 셸 env 또는 .env 의 값이
있으면 그 값, 없으면 기본 cuda.

용도: FinBERT/Chronos 가 GPU 에서 'no kernel image is available for
execution on the device' (PyTorch cu121 / RTX 3070 Ti 호환 이슈) 가 날 때
.env 에 MODEL_DEVICE=cpu 두고 backend 만 recreate 하면 CPU 폴백.

  echo MODEL_DEVICE=cpu>> .env
  docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d backend
2026-05-21 21:32:54 +09:00
tkrmagid
bd47198088 fix(backend): setuptools<80 핀 (pykrx 가 pkg_resources 필요)
증상 — 사금향 게임컴 (192.168.10.13) 백엔드 진단으로 확정:
  POST /api/refresh/005930 → pykrx_ohlcv: failed
    error: 'No module named pkg_resources'

root cause: setuptools 80.0.0 (2025-04) 이 pkg_resources 모듈을 제거함.
Dockerfile 의 'pip install --upgrade ... setuptools ...' 가 최신 (80+) 을
설치 → pykrx 의 'import pkg_resources' 가 import 실패.

해결:
  1) bootstrap 단계에서 'pip install setuptools<80' 명시 핀
  2) reqs.txt 설치 후에도 동일 핀 반복 (transitive upgrade 차단)
  3) 빌드 단계에서 'import pkg_resources' 확인 — 깨지면 빌드 실패시켜
     런타임에서 발견되는 일 없도록 함

이 패치 후 KIS (이미 ok 였음) 와 함께 pykrx ohlcv/trading_value 가 정상
동작 → chart 자동 충전 (fa817b3) 이 의도대로 데이터 채워 넣음.

별개 이슈 (이 커밋 범위 밖): FinBERT/Chronos 가 RTX 3070 Ti 에서
'no kernel image is available for execution on the device' 발생.
임시 회피: .env 에 MODEL_DEVICE=cpu 추가.
2026-05-21 21:30:01 +09:00
tkrmagid
fa817b31e4 feat(chart): ohlcv_daily 비어있으면 자동 refresh
문제: symbols 시드 후 첫 방문 시 ohlcv_daily 가 비어있어서
  - 차트가 빈 캔버스로 렌더링됨
  - 예상차트 보기 → 409 'no ohlcv_daily for {code}; refresh first'
daily_batch (16:00 KST) 또는 수동 POST /api/refresh/{code} 만이 OHLCV 를
채우는 구조였는데, 첫 방문 사용자는 둘 다 트리거 안 됨.

수정: GET /api/chart/{code} 가 ohlcv 0행 + symbol 존재 케이스에서
refresh_code 를 동기 호출 (pykrx OHLCV + trading_value + news + finbert)
후 재조회. idempotent 라 다음 방문부터는 캐시 히트.

lookback_days = max(days, 365) — 차트 요청 범위 + Chronos/LightGBM
학습용 과거 시계열 마진. 첫 호출 ~10-30s, 이후는 즉시.

KRX 가 도커망에서 막힌 환경이면 refresh 가 실패 status 로 끝나고
ohlcv 는 여전히 비어있음 — 기존 동작과 동일 (graceful).
2026-05-21 21:10:44 +09:00
tkrmagid
96b7afd443 fix(restart.bat): backend/web 만 재시작 (db 제외)
리뷰어 지적: docker compose restart 는 서비스명 생략시 db 까지 같이 재시작한다.
db 까지 같이 내려가면 backend lifespan 부팅 시드가 db healthcheck 와 무관하게
실행돼서 race 로 seed 가 다시 실패할 수 있다 (restart 는 depends_on.condition:
service_healthy 를 지키지 않음). db 는 postgres_data 볼륨에 상태가 영속이라
재시작할 이유도 없음.

추가: 실행중 컨테이너 체크도 backend/web 기준으로 좁힘 — db 만 떠있고
backend 가 down 인 경우 통과되던 버그 수정.
2026-05-21 21:05:57 +09:00
tkrmagid
89651251a4 feat: restart.bat 추가 (재빌드 없이 컨테이너만 재시작)
build.bat 은 이미지 재빌드 포함 — Dockerfile / pyproject.toml / package*.json /
compose 설정 같은 의존성/이미지 구성이 바뀌었을 때 사용.

restart.bat 은 docker compose restart 만 호출 — backend/app/, web/app/ 안의
코드만 바뀐 경우 사용. 바인드 마운트 (./backend:/app, ./web:/app) 덕에 새 코드가
즉시 컨테이너에서 보이고, 재시작으로 lifespan 부팅 시드도 재실행된다.

사용 가이드:
  git pull 후 변경 파일이
    - pyproject.toml / Dockerfile / package*.json → build.bat
    - app/ 코드만                                  → restart.bat
2026-05-21 21:03:16 +09:00
tkrmagid
296bd6dccd fix(seed): SEED 10종목 항상 보장 + KRX 실패 graceful degrade + 수동 reseed API
문제: 빌드/기동은 성공했는데 종목 검색 결과 빈 화면. 원인은 시드 트랜잭션
구조 — `_fetch_market_listing(KRX)` 가 `with engine.begin()` 블록 *밖*에서
호출되고, 컨테이너에서 KRX 서버 접근이 실패하거나 단일 ticker 처리 중
한 곳이라도 예외가 나면 전체 트랜잭션이 롤백되어 SEED_TICKERS 10개조차
들어가지 않음.

수정:
- `_upsert_seed_tickers()` 분리. SEED 10종목 전용 트랜잭션. 네트워크 무관.
- `seed_symbols()` 가 (1) SEED 먼저 → (2) KRX 시장별 fetch (try-per-market)
  → (3) 전 종목 bulk upsert (별도 트랜잭션) 순서로 동작. KRX fetch 실패해도
  SEED 는 살아남음.
- `main.py` lifespan bootstrap 이 SEED upsert 를 매 부팅마다 무조건 실행
  (10회, ms 단위, idempotent). count <= 10 일 때만 KRX 전 종목 fetch.
- 새 admin 엔드포인트 `POST /api/refresh/seed/symbols` — 컨테이너 재기동
  없이 시드 재시도 가능.

이번 사례 (사금향 PC) 의 복구 절차:
    curl -X POST http://localhost:8000/api/refresh/seed/symbols
이후 검색에 SEED 10 종목은 즉시 떠야 함. KRX 가 막힌 환경이면 markets={0,0}
가 응답에 찍히고, 그래도 SEED 만으로 핵심 동작은 가능.
2026-05-21 02:31:49 +09:00
tkrmagid
2c42c1151c fix(backend): accelerate 0.30.1 → 0.34.2 (chronos-forecasting 호환)
chronos-forecasting 1.4.1 의 의존성: `accelerate>=0.32,<1`.
기존 핀 `accelerate==0.30.1` 이 해당 범위 밖이라 pip 가
ResolutionImpossible 로 실패했음.

검증: python:3.11-slim 컨테이너에서
  pip install --dry-run -r reqs.txt (torch==2.3.1 포함, 30개 deps 전체)
exit 0. resolve 결과:
  chronos-forecasting 1.4.1
  transformers 4.41.2
  tokenizers 0.19.1
  accelerate 0.34.2
  torch 2.3.1
2026-05-21 01:48:13 +09:00
tkrmagid
9c7c02703a fix(backend): pip/uvicorn 을 python3.11 인터프리터에 강제 바인딩
Ubuntu 22.04 의 python3-pip 패키지는 python3.10 용이라, /usr/local/bin/python
만 3.11 로 심볼릭 링크해도 `pip`/`pip3` 명령은 여전히 3.10 의 site-packages
에 의존성을 설치한다. pyproject.toml 이 >=3.11 을 선언했으므로 런타임
인터프리터 불일치 위험.

수정:
- apt 에서 python3-pip 제거. get-pip.py 로 python3.11 전용 pip 를 부트스트랩.
- 모든 pip 호출을 `python -m pip` 로 통일.
- 직후 `RUN python -V && python -m pip -V` sanity check 추가 — 빌드 로그에
  인터프리터 버전이 박혀서 추후 불일치 의심 시 즉시 확인 가능.
- CMD 도 `python -m uvicorn` 으로 변경 — 콘솔 스크립트 shebang 이 아니라
  명시적으로 3.11 인터프리터에서 실행됨을 보장.

검증 (별도 컨테이너에서 nvidia/cuda:12.1.1 베이스로 동일 레이어 실행):
  Python 3.11.0rc1
  pip 26.1.1 from /usr/local/lib/python3.11/dist-packages/pip (python 3.11)
2026-05-20 23:53:25 +09:00
tkrmagid
e08f3b0765 fix(backend): Dockerfile editable install 제거 + PYTHONPATH
기존 `pip install -e .` 두 줄은 두 가지 이유로 실패:
1. `app/` 가 COPY 되기 전 단계라 setuptools packages.find 결과가
   비어 editable 빌드가 무의미.
2. Ubuntu 22.04 의 기본 pip 가 PEP 660 build_editable hook 을
   요구하지만, build isolation 환경의 setuptools 가 노출 안 함
   ("build backend is missing the 'build_editable' hook").

수정:
- editable install 자체를 제거. 런타임은 PYTHONPATH=/app 으로
  `app.*` import 가 동작하므로 프로젝트를 wheel 로 설치할 필요 없음.
- 의존성은 tomllib 로 pyproject.toml 에서 직접 추출해 단일 `pip
  install -r` 로 설치. ML 휠 (chronos/lgbm/transformers ...) 레이어
  캐싱이 pyproject.toml 단위로 유지됨 → app 코드 변경시 재빌드 회피.
- COPY app ./app 은 deps 설치 뒤로 유지 (캐시 분리).
2026-05-20 23:42:58 +09:00
tkrmagid
eb56025d9c chore(web): package-lock.json + next-env.d.ts 동기화
`web/package.json` 의 next/eslint-config-next ^14.2.33 상향 (6c79230) 에
맞춰 lockfile 갱신. npm 이 resolve 한 실제 버전은 14.2.35 (14.2.x 최신
patched). 자동 생성되는 next-env.d.ts 의 문서 URL 한 줄도 함께 동기화.

검증:
- `npm install --package-lock-only` 통과
- `npm run typecheck` 통과
- `npm run build` 통과 (Next.js 14.2.35, 4 페이지 정상 생성)
2026-05-20 23:37:54 +09:00
tkrmagid
6c792305a9 fix(web): next + eslint-config-next ^14.2.33 보안 패치
Next.js 2025-12-11 보안 권고에 따라 14.2.x release line 의
최신 patched 버전으로 상향. 14.2.3 → ^14.2.33.

ref: https://nextjs.org/blog/security-update-2025-12-11
2026-05-20 23:33:28 +09:00
tkrmagid
5e6ce11491 feat(weights): shadow chronos/lgbm 예측 + ensemble_weights 자동 보정
- ensemble.predict() 가 chronos_raw / lgbm_raw 를 함께 반환
- predict_and_store() 가 매 호출마다 3종 행 적재:
    model='ensemble' (user_triggered=인자)
    model='chronos'  (user_triggered=FALSE, shadow)
    model='lgbm'     (user_triggered=FALSE, shadow)
- retrain_weekly.adjust_weights(): 최근 30일 prediction_outcomes 의
  chronos vs lgbm hit_rate 로 ensemble_weights upsert
    w_chronos = clamp(0.1, hr_c/(hr_c+hr_l), 0.9), w_lgbm = 1 - w_chronos
  모델별 표본 < 10 이면 기본값(0.6/0.4) 유지
- API 응답에 saved_shadow_ids 추가 (TS 타입도 동기화)
- README: 동작 모델 메모 섹션을 실제 구현과 일치하도록 갱신

리뷰어 지적 3번 (ensemble_weights 가 영원히 갱신 안됨, upsert_weights 미호출) 해결.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:27:21 +09:00
tkrmagid
0af556396e fix(match): 주말/공휴일 이월 매칭 (target_date <= today + 최초 거래일 종가)
- match_for_date(d) → match_up_to(today) 로 시맨틱 변경: target_date == d
  대신 target_date <= today AND outcomes 미존재 전부 후보로
- 각 후보마다 ohlcv_daily 에서 target_date 이상 today 이하 범위의 최초
  거래일 행을 actual_close 로 매칭 → 주말/공휴일 자동 이월
- user_triggered 필터 제거: chronos/lgbm shadow 행도 함께 매칭됨
- prediction_outcomes.target_date 에는 실제 매칭된 거래일을 기록
- 하위 호환: match_for_date(d) 는 match_up_to(d) 별칭으로 유지

리뷰어 지적 2번 (공휴일/주말이면 target_date 일치 행이 영원히 미매칭) 해결.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:27:09 +09:00
tkrmagid
f84b460e54 fix(bootstrap): backend lifespan 에서 DB migrate + symbols 시드 자동화
- main.py 의 lifespan 시작 시 idempotent migration 적용 + symbols 비어있으면 pykrx 로 전 종목 시드
- BOOTSTRAP_DISABLED=1 / SCHEDULER_DISABLED=1 env 로 비활성 가능 (테스트 용)
- 실패해도 서버는 뜨고 /health/db 가 진단 제공

리뷰어 지적 1번 (cold-start 시 /api/refresh 404) 해결.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:26:59 +09:00
tkrmagid
bc016ab76d docs: Phase 1a~6 완료 체크 + API 엔드포인트 요약 표
진행 계획에 체크박스로 완료 상태 표시. Phase 7(백테스트 + shadow 예측)만 남음.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:10:58 +09:00
tkrmagid
4fb6cec383 feat(phase-6): Next.js UI + TypeScript strict + 백엔드 mypy 설정
UI:
- web/lib/api.ts: 백엔드 모든 엔드포인트의 클라이언트 + 타입 (Symbol,
  ChartPayload, PredictResponse, LatestPredictionResponse, MetricsResponse,
  NewsResponse). NEXT_PUBLIC_API_BASE 자동 정규화.
- web/components/SearchBox: 디바운스 검색, seed_only 토글, trigram + prefix.
- web/components/StockChart: lightweight-charts 캔들 + 예측 overlay
  (median dashed + q10/q90 점선). base_date 에서 target_date 까지 이어 붙임.
- web/components/PredictionPanel: "예상차트 보기" 버튼 → POST /api/predict
  → user_triggered=TRUE 저장 → onResult 콜백으로 StockChart 에 반영.
  표로 +1/+3/+5거래일 direction, prob_up/flat/down, expected_return,
  ci_low~ci_high 표시.
- web/components/MetricsPanel: 최근 30일 hit_rate / mae.
- web/components/NewsList: 최근 뉴스 + 감성 라벨/점수.
- web/app/page.tsx: 검색 페이지.
- web/app/[code]/page.tsx: 종목 상세 (차트 + 패널 + 메트릭 + 뉴스).

TypeScript 보강 (사용자 요청 "typescript도 추가해서 나중에 수정하기 쉽게"):
- tsconfig.json: strict 외에 forceConsistentCasingInFileNames,
  noFallthroughCasesInSwitch, noImplicitOverride 추가.
- package.json: typecheck (tsc --noEmit), check (typecheck + lint) 스크립트,
  eslint + eslint-config-next 14.2.3.
- .eslintrc.json: next/core-web-vitals.
- package-lock.json 커밋 (재현 가능한 dep).

백엔드:
- pyproject.toml: [tool.mypy] 추가. strict_optional, no_implicit_optional,
  check_untyped_defs. 3rd-party stub 없는 pykrx/chronos 등은 ignore.

검증: `npx tsc --noEmit` 통과 (exit=0).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:10:24 +09:00
tkrmagid
41ee9d5bb0 feat(phase-5): FastAPI 엔드포인트 (검색/차트/예측/메트릭/뉴스)
- GET  /api/symbols/search?q=...&seed_only=  : trigram + prefix + ILIKE 합산 정렬
- GET  /api/symbols/{code}                    : 메타
- GET  /api/chart/{code}?days=N&include_*     : OHLCV + 일별 감성 + 외인기관거래대금
- POST /api/predict/{code}?horizons=1,3,5     : on-demand 앙상블 예측 + DB 적재
                                                (user_triggered=TRUE)
- GET  /api/predict/{code}/latest             : 최신 base_date 의 예측 묶음 + base_close
                                                (UI 가 차트 마지막 점에 이어 붙임)
- GET  /api/metrics/{code}?window_days=N      : 종목 단위 hit_rate / mae (model, horizon 별)
- GET  /api/metrics?window_days=N             : 전체 누적
- GET  /api/news/{code}?source=&limit=        : 최신순 뉴스/공시 목록 (감성 점수 포함)

main.py 에 6개 라우터 모두 include.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:05:08 +09:00
tkrmagid
bf4fb01146 feat(phase-4): LGBM 모델 + 앙상블 + 매칭/재학습 잡
- backend/app/models/lgbm.py: 종목 × horizon 별 LightGBM 회귀(y_ret_h)
  + 다중분류(y_dir_h, 3-class). joblib 으로 backend/data/models/{code}_h{H}_*.pkl
  저장. early_stopping(30). predict_one() 으로 최신 영업일 피처에 추론.
- backend/app/models/weights.py: ensemble_weights 테이블 IO,
  default w_chronos=0.6 / w_lgbm=0.4 (DB 행 없으면 fallback).
- backend/app/models/ensemble.py: Chronos sample 분포 + LGBM regression+cls
  결합. point/q10/q90 + prob_up/flat/down + direction 라벨. 한쪽 모델
  실패 시 다른 쪽 단독 fallback (cold start: chronos 단독).
- backend/app/pipelines/predict_one.py: predict_and_store(). 결과를
  predictions 테이블에 UPSERT, user_triggered 누적 OR. base_date = 마지막
  ohlcv 거래일, target_date = base_date + H 영업일(주말 스킵, 공휴일은
  매칭잡에서 자연 보정).
- backend/app/pipelines/match_outcomes.py: target_date == d 인
  user_triggered=TRUE 예측을 d 의 실제 종가와 매칭 → prediction_outcomes
  적재. direction_hit(±0.3% flat band) + abs_error. 실제 종가 없으면
  자연 skip.
- backend/app/pipelines/retrain_weekly.py: 시드 10종목 × H 재학습 +
  최근 30일 model_performance 적재.
- backend/app/db/migrations/003_ensemble_weights.sql: (code, horizon) →
  (w_chronos, w_lgbm, hit_rate_*, sample_count).
- backend/app/pipelines/scheduler.py:
    daily_batch    : 평일 16:00 KST
    match_outcomes : 평일 16:30 KST   ← 사용자가 확정한 매칭 시점
    retrain_weekly : 일요일 02:00 KST

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:03:01 +09:00
tkrmagid
b1ca6ab5d3 feat(phase-3): Chronos zero-shot 예측 + 피처 빌더
- backend/app/models/chronos.py: amazon/chronos-t5-small (env CHRONOS_MODEL
  override 가능). lazy singleton, cuda + bf16 자동, q10/median/q90 + raw
  samples 반환 (앙상블 가중평균용).
- backend/app/models/features.py: 종목별 학습/추론 피처 DataFrame.
  OHLCV + TA(rsi/macd/atr/bb/sma/ema/vol_z) + 외인기관거래대금 + macro
  (kospi/kosdaq/usdkrw/us10y + r1) + sentiment(v_sentiment_daily, 3d rolling).
  학습용은 with_targets=True 로 y_close_h{1,3,5}, y_ret_h*, y_dir_h*
  (±0.3% flat band) 추가.
- pyproject.toml: chronos-forecasting 1.4.1, accelerate 0.30.1, joblib 1.4.2.

이 단계까지는 코드만. 실제 모델 다운로드는 첫 ping/predict 호출 시점에.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 15:59:14 +09:00
tkrmagid
edda01adbf feat(phase-2): KR-FinBERT 감성 스코어링 + 일별 집계 뷰
- backend/app/nlp/finbert.py: snunlp/KR-FinBert-SC 어댑터.
  - score = P(pos) - P(neg) ∈ [-1, +1], label = argmax (neg/neu/pos)
  - 768d mean-pooled last hidden state → news.embedding (VECTOR) 저장
  - settings.huggingface_token 인증, lazy singleton, cuda/cpu auto
- backend/app/nlp/score_news.py: news 테이블에서 sentiment_score IS NULL
  행을 배치 스코어 → UPDATE (... embedding=(:e)::vector). 종목 필터 + limit 옵션.
- backend/app/db/migrations/002_sentiment_view.sql: v_sentiment_daily 뷰.
  종목·KST 일별 n_articles, mean_score, pos/neg/neu_ratio, weighted_score
  (naver_finance 1.0 / google_rss 0.7 / dart 0.5).
- backend/app/db/migrate.py: 이미 실행 중인 DB 에 새 SQL 마이그레이션 적용용
  CLI. 모든 SQL 파일은 idempotent.
- refresh_one.py: refresh 끝에 종목당 200건까지 finbert 스코어, finbert
  SourceStatus 를 RefreshReport 에 추가.
- daily_batch.py: 모든 종목 처리 후 score_pending_news(limit=2000) 로 mop-up.

모델 캐시는 docker-compose hf_cache 볼륨(/root/.cache/huggingface).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 15:57:34 +09:00
tkrmagid
239b104a2b docs(phase-1a): 예측-실제 매칭 정책을 "다음 거래일 장 종료 후"로 확정
사용자 확정: 예측 차트와 실제 차트 매칭은 다음 거래일 장이 끝나는 시점으로.

- README.md: 매칭 배치 시각을 KRX 정규장 마감(15:30) 후 종가 확정 시점
  (16:00 ~ 16:30 KST 사이) 으로 명시. 주말/공휴일은 다음 거래일로 이월.
- scheduler.py docstring: Phase 4 슬롯을 "16:30 KST 평일 prediction_outcomes
  매칭 배치" 로 구체화. 추론은 on-demand 만 사용한다는 점도 명시.

코드 동작 변화 없음(스케줄러는 아직 daily_batch 1개 잡만 등록).
Phase 4 진입 시 이 정책대로 매칭 잡을 추가.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 15:46:23 +09:00
tkrmagid
56f73a1f12 feat(phase-1a): external data fetchers + refresh pipeline + scheduler
10종목 시드 + pykrx OHLCV / 외인·기관 거래대금, KIS read-only EOD, OpenDART
공시, 네이버 금융 뉴스 스크레이퍼, 구글 뉴스 RSS, yfinance 거시(KOSPI/KOSDAQ/
USDKRW/US10Y) fetcher 를 추가하고 refresh_one / daily_batch / backfill /
APScheduler(16:00 KST) 파이프라인으로 묶음.

- backend/app/seed: 10종목 시드 (대형/고변동/테마/플랫폼/방어)
- backend/app/fetch: pykrx, kis, dart, news, macro, symbols_seed
- backend/app/pipelines: refresh_one, daily_batch, backfill(CLI), scheduler
- backend/app/api/refresh.py: POST /api/refresh/{code}?lookback_days=N
- backend/app/main.py: lifespan 으로 스케줄러 기동/종료, /health/keys 추가
- README: .env 복사 안내 보강

스모크 테스트 (실제 키 사용) 결과:
  KIS token  : ok (token 346자 발급)
  KIS daily  : 005930 11rows
  DART list  : 005930 30일 10건
  Naver news : 005930 12건
  Google RSS : "삼성전자" 92건

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 15:43:18 +09:00
tkrmagid
cacddf5adf feat(phase-0): scaffold backend + web + docker + DB schema
- docker-compose.yml: timescaledb-ha (timescaledb 2.27 + vectorscale + pgvector + pgai)
  + backend (FastAPI, CUDA 12.1) + web (Next.js 14)
- docker-compose.gpu.yml: GPU profile overlay for RTX 3070 Ti
- build.bat: Windows bootstrap, auto-detects nvidia-smi and selects GPU/CPU compose
- backend: Dockerfile, pyproject.toml, FastAPI skeleton with /health and /health/db
- DB migration 001_init.sql: symbols (with trigram search), ohlcv_daily/1m (hypertables),
  macro_daily, trading_value_daily, news (vector embedding), predictions
  (with user_triggered flag for on-demand UX), prediction_outcomes, model_performance
- web: Next.js 14 + Tailwind + lightweight-charts placeholder
- README: KIS/DART/HuggingFace token issuance guides + 10 seed tickers + run instructions
2026-05-20 14:37:35 +09:00
tkrmagid
619dc7811b docs: add README with project title 2026-05-20 13:28:20 +09:00
ejclaw
bc301a8374 init stock_chart_site workspace 2026-05-20 13:25:44 +09:00