diff --git a/docs/superpowers/specs/2026-05-31-stock-holdings-intelligence-design.md b/docs/superpowers/specs/2026-05-31-stock-holdings-intelligence-design.md new file mode 100644 index 0000000..ea7afcc --- /dev/null +++ b/docs/superpowers/specs/2026-05-31-stock-holdings-intelligence-design.md @@ -0,0 +1,122 @@ +# 주식 보유종목 인텔리전스 — 설계 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) 제안 — 향후 사이클. +- 인스타 에이전트(자율 카드 발급) — 다음 사이클.