스크리너 엔진을 보유종목에 restrict 적용 + 신규 매도/리스크 룰 + 이슈 감지(급변·거래량·외인·뉴스 LLM) + 포트 건강 → 매일 advisory 브리핑. EOD 일봉 + 장중 경량 가드, KIS 실주문 미사용. 기존 screener/snapshot/ news_sentiment/portfolio 재활용, 신규 데이터소스 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.4 KiB
9.4 KiB
주식 보유종목 인텔리전스 — 설계 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)
- 실행 수준 = 브리핑 전용(advisory).
/api/trade/order(KIS 실주문) 미사용. 매수/매도는 "제안"만, 실제 주문은 사용자 수동. (로또와 동일한 정직·관찰 철학) - 분석 주기 = 일봉 EOD + 장중 경량 가드. 장마감 후 일봉으로 기술분석 → 다음날 아침 브리핑. 장중엔 현재가로 손절·급변(±N%)만 경도 알림. 인트라데이 분봉 파이프라인 신설 안 함.
- 브리핑 범위 = 보유종목 + 포트 레벨. 종목별 액션 + 포트폴리오 건강(집중도·비중·현금·손익).
- 이슈 소스 = 기존 뉴스+감성+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(FDRStockListing의 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
- EOD 계산 (평일 16:40): 기존 스크리너/뉴스 잡과 동일하게 agent-office cron이 orchestrate —
_run_stock_holdings_eod()→StockAgent.run_holdings_eod()→ stockPOST /api/stock/holdings/intel/run→holdings_intel.compute_and_store(today)→ holdings_signals upsert. 스크리너 snapshot 갱신(16:30) 직후라 일봉 준비됨. - 아침 브리핑 (평일 08:30, agent-office StockAgent.run_holdings_brief): 저장된 최신 시그널 + 야간 갭(현재가) → 텔레그램 1통(종목별 액션 + 포트 건강 + 상위 이슈). AI 뉴스(08:00) 다음 슬롯.
- 장중 경량 가드 (평일 09:00~15:30, 30분 간격): 현재가로 손절선 이탈·급변(±N%)만 점검 → 발생 시 텔레그램 alert. throttle(종목·유형별 재발화 억제) + daily cap (로또 시그널 패턴 재활용).
- agent-office:
service_proxy에 holdings intel 호출 추가 + StockAgent 메서드(run_holdings_brief / intraday_guard) + scheduler cron. - 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)
- 실행 수준 = advisory 전용 (KIS 실주문 미사용)
- 주기 = 일봉 EOD + 장중 경량 가드
- 범위 = 보유종목 + 포트 레벨
- 이슈 소스 = 기존 뉴스+감성+LLM + 급변·거래량·외인 이벤트
7. 스코프 밖 / 향후
- 자동매매(승인후/완전자동), 인트라데이 분봉, DART 공시·실적 일정, 신규 매수후보 발굴(기존 16:30 스크리너가 담당), 교체(rotation) 제안 — 향후 사이클.
- 인스타 에이전트(자율 카드 발급) — 다음 사이클.