Files
web-page-backend/docs/superpowers/specs/2026-05-31-stock-holdings-intelligence-design.md
gahusb 2996cf16d1 docs(spec): 주식 보유종목 인텔리전스 설계
스크리너 엔진을 보유종목에 restrict 적용 + 신규 매도/리스크 룰 +
이슈 감지(급변·거래량·외인·뉴스 LLM) + 포트 건강 → 매일 advisory 브리핑.
EOD 일봉 + 장중 경량 가드, KIS 실주문 미사용. 기존 screener/snapshot/
news_sentiment/portfolio 재활용, 신규 데이터소스 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:25:21 +09:00

123 lines
9.4 KiB
Markdown
Raw 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.
# 주식 보유종목 인텔리전스 — 설계 Spec
- **작성일**: 2026-05-31
- **상태**: 설계 승인 (구현 plan 대기)
- **대상 서비스**: `stock` + `agent-office`(StockAgent) + `web-ui`(stock/포트폴리오 페이지)
- **사이클**: 스마트 에이전트 고도화 3종 중 **2번 주식**. (1번 로또 완료, 3번 인스타 후속)
---
## 1. 배경 & 목표
현재 StockAgent는 아침 뉴스 요약(07:30) · KRX 강세주 스크리너(16:30) · AI 뉴스 sentiment(08:00)를 브리핑한다. CEO는 여기서 더 나아가 **내 보유종목을 집중 분석**해 ①종목별 매수/매도 자세 ②이슈 정리 ③포트폴리오 건강을 매일 advisory로 브리핑받길 원한다.
### 핵심 결정 (2026-05-31 brainstorming)
1. **실행 수준 = 브리핑 전용(advisory)**. `/api/trade/order`(KIS 실주문) 미사용. 매수/매도는 "제안"만, 실제 주문은 사용자 수동. (로또와 동일한 정직·관찰 철학)
2. **분석 주기 = 일봉 EOD + 장중 경량 가드**. 장마감 후 일봉으로 기술분석 → 다음날 아침 브리핑. 장중엔 현재가로 손절·급변(±N%)만 경도 알림. 인트라데이 분봉 파이프라인 신설 안 함.
3. **브리핑 범위 = 보유종목 + 포트 레벨**. 종목별 액션 + 포트폴리오 건강(집중도·비중·현금·손익).
4. **이슈 소스 = 기존 뉴스+감성+LLM 요약 + 급변·거래량·외인수급 이벤트**. 신규 스크래핑 0 (DART·실적 일정 제외).
### 기존 자산 (100% 재활용, 신규 ML/데이터소스 없음)
- `stock/app/screener/snapshot.py``krx_daily_prices`(일봉 OHLCV) + `krx_master`(listing) + naver 외인 flow. 스크리너 잡(평일 16:30)이 갱신.
- `stock/app/screener/engine.py` + `nodes/`(ma_alignment·momentum·rs_rating·vcp_lite·volume_surge·foreign_buy·high52w·hygiene). **`ScreenContext.restrict(tickers)`** + `latest_close()`/`latest_high()`로 보유종목 한정 분석 가능.
- `portfolio` 테이블(broker·ticker·name·quantity·avg_price·purchase_price) + `/api/portfolio`(현재가·손익 계산) + `broker_cash`(예수금).
- `price_fetcher`(현재가 3분 TTL) · `news_sentiment` 테이블(종목별 감성) · `ai_summarizer`(Claude Haiku).
### 알려진 제약 (설계 반영)
- **섹터 필드 없음**: `portfolio`·`krx_master`에 sector 없음 → 섹터 편중은 best-effort(FDR `StockListing`의 Sector/Industry가 있으면 사용, 없으면 생략)이고, **시장(KOSPI/KOSDAQ)·종목 비중 집중도**를 기본 지표로 사용.
- **KRX 외 종목**(미국주 등): krx_daily_prices 밖 → 기술분석 불가, **뉴스·현재가·손익만** graceful 처리.
- **snapshot 히스토리 의존**: MA200·52주 고점 노드는 ~1년 일봉 필요. 스크리너가 이미 이 노드들을 쓰므로 윈도우는 충족 가정(plan에서 lookback 확인 단계 포함).
---
## 2. 데이터 모델 & 컴포넌트
### 신규 테이블 `holdings_signals` (stock.db, 일별 종목 시그널 이력)
```
date TEXT NOT NULL -- KST 거래일
ticker TEXT NOT NULL
name TEXT
action TEXT NOT NULL -- 'add' | 'hold' | 'trim' | 'sell'
tech_score REAL -- 매수강도(score 노드 가중합, 0~1 정규화)
exit_flags TEXT NOT NULL DEFAULT '{}' -- JSON {stop_loss,ma50_break,ma200_break,momentum_loss,take_profit,climax}
issues TEXT NOT NULL DEFAULT '[]' -- JSON [{type, severity, summary}]
close INTEGER
pnl_rate REAL -- 평단 대비 % (스냅샷 시점)
reasons TEXT -- 액션 근거 텍스트
created_at TEXT NOT NULL DEFAULT (datetime('now'))
PRIMARY KEY(date, ticker) -- 멱등 upsert
```
> 추세/이력은 이 테이블에서 조회. 포트 레벨 요약은 on-the-fly 계산(별도 테이블 불필요).
### 신규 `stock/app/holdings_intel.py` (순수연산 중심, FastAPI 의존성 최소)
- `get_holdings() -> list[dict]``portfolio` 행 + 현재가(price_fetcher) + pnl_rate. KRX 여부 플래그(`is_krx`).
- `technical_posture(ctx_restricted, tickers) -> dict[ticker, score]``ScreenContext.restrict(tickers)`에 score 노드 실행 → 매수강도.
- `exit_rules(holding, prices_df, params) -> dict`**신규**: 손절·MA이탈·모멘텀소멸·익절·클라이맥스 flag 산출 (§3).
- `decide_action(tech_score, exit_flags, pnl) -> (action, reasons)`**신규**: 매수강도+exit 조합 → add/hold/trim/sell + 근거.
- `market_events(prices_df, flow, params) -> dict[ticker, list]` — 급변(±N%)·거래량 Z-score·외인 순매도.
- `news_issues(tickers) -> dict[ticker, list]` — news+news_sentiment 필터 → Claude Haiku 악재·심각도 요약(악재 있는 종목만).
- `portfolio_health(holdings, cash) -> dict` — 종목 비중 집중도(HHI/최대비중)·시장 mix·현금 비중·총 손익.
- `compute_and_store(asof) -> dict` — 위를 조합해 holdings_signals upsert (멱등).
- `build_holdings_brief(asof) -> dict` — 브리핑/UI payload 조립(종목별 action+issues + portfolio_health + 추세).
### API (stock)
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/stock/holdings/intel` | 최신 브리핑 payload |
| GET | `/api/stock/holdings/intel/history?ticker=&days=` | 종목 시그널 추세 |
| POST | `/api/stock/holdings/intel/run` | 수동 계산 트리거(BackgroundTask) |
---
## 3. 매도/리스크 룰 & 이슈 (설정 가능 임계값 — 기본값 제시)
### exit_flags (각 boolean + 값)
- **stop_loss**: `current < avg_price × (1 STOP_PCT)` (기본 STOP_PCT=0.08, Minervini식)
- **ma50_break / ma200_break**: 종가 < MA50 / MA200
- **momentum_loss**: momentum/RS 노드 점수가 직전 대비 임계 하락 (or 음전환)
- **take_profit**: `pnl_rate ≥ TAKE_PCT` (기본 25%) — 부분 익절 후보
- **climax**: 거래량 급증(vol > avg×CLIMAX_VOL_X) + 종가 상단 꼬리 (분산 의심)
### decide_action 매트릭스
- tech_score 高 + exit_flags 無 → **add**(추가매수 후보)
- exit_flags 無 (강건) → **hold**
- ma50_break 또는 momentum_loss 또는 take_profit → **trim**(일부 축소)
- stop_loss 또는 ma200_break → **sell**(청산 후보)
- 각 결정에 trigger된 flag를 근거 텍스트로 동봉. (advisory — "제안")
### issues
- **시장이벤트** (기존 데이터): 일봉 ±EVENT_PCT% 급변 / 거래량 Z-score>임계 / naver flow 외인 순매도 N일 연속.
- **뉴스이슈**: 보유종목 최근 뉴스 + news_sentiment 음수 → Claude Haiku로 `{type, severity(low/med/high), summary}` 요약. 악재 있는 종목만 호출(비용 bounded).
---
## 4. 플로우 · 에이전트 · UI
1. **EOD 계산 (평일 16:40)**: 기존 스크리너/뉴스 잡과 동일하게 **agent-office cron이 orchestrate**`_run_stock_holdings_eod()``StockAgent.run_holdings_eod()` → stock `POST /api/stock/holdings/intel/run``holdings_intel.compute_and_store(today)` → holdings_signals upsert. 스크리너 snapshot 갱신(16:30) 직후라 일봉 준비됨.
2. **아침 브리핑 (평일 08:30, agent-office StockAgent.run_holdings_brief)**: 저장된 최신 시그널 + 야간 갭(현재가) → 텔레그램 1통(종목별 액션 + 포트 건강 + 상위 이슈). AI 뉴스(08:00) 다음 슬롯.
3. **장중 경량 가드 (평일 09:00~15:30, 30분 간격)**: 현재가로 손절선 이탈·급변(±N%)만 점검 → 발생 시 텔레그램 alert. throttle(종목·유형별 재발화 억제) + daily cap (로또 시그널 패턴 재활용).
4. **agent-office**: `service_proxy`에 holdings intel 호출 추가 + StockAgent 메서드(run_holdings_brief / intraday_guard) + scheduler cron.
5. **UI (web-ui)**: stock/포트폴리오 페이지에 **"보유종목 인텔리전스" 탭/섹션 통합** — 종목별 액션 카드(자세·exit flags·근거) + 포트 건강 위젯 + 이슈 피드 + 종목 시그널 추세(history).
---
## 5. 에러·성능·테스트·리스크
- **멱등성**: holdings_signals PRIMARY KEY(date,ticker) upsert → 재계산 안전.
- **성능 (NAS Celeron)**: 보유종목만 restrict(소수 종목)이라 전체 스크리너 대비 매우 가벼움. LLM 이슈 요약은 악재 종목만(bounded). EOD 1회 + 장중 가드는 현재가만(경량).
- **graceful degrade**: price_fetcher/KIS/news 실패 시 부분 데이터로 진행 + 경고 로그. KRX 외 종목은 기술분석 skip(뉴스·손익만). 텔레그램 실패는 로그만(job 성공 유지).
- **테스트**: exit_rules 각 flag, decide_action 매트릭스 전 분기, market_events 검출, portfolio_health 계산, holdings_signals 멱등, KRX 외 종목 graceful, 뉴스 0건 경로.
- **리스크**: ①기술적 시그널은 휴리스틱이지 보장 아님 → advisory 프레이밍·자동매매 없음 ②섹터 데이터 갭 → 시장·비중 집중도로 대체 ③snapshot 히스토리 의존 → plan에 lookback 확인 ④보유종목 출처는 portfolio 테이블(사용자/KIS 동기화) — 누락 시 빈 브리핑 graceful.
---
## 6. 결정 로그 (2026-05-31)
1. 실행 수준 = **advisory 전용** (KIS 실주문 미사용)
2. 주기 = **일봉 EOD + 장중 경량 가드**
3. 범위 = **보유종목 + 포트 레벨**
4. 이슈 소스 = **기존 뉴스+감성+LLM + 급변·거래량·외인 이벤트**
## 7. 스코프 밖 / 향후
- 자동매매(승인후/완전자동), 인트라데이 분봉, DART 공시·실적 일정, 신규 매수후보 발굴(기존 16:30 스크리너가 담당), 교체(rotation) 제안 — 향후 사이클.
- 인스타 에이전트(자율 카드 발급) — 다음 사이클.