From 4cb9dc6a7c33a1e4694536070ca1b3966860260a Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 2 Jul 2026 15:19:00 +0900 Subject: [PATCH] =?UTF-8?q?docs(stock):=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EB=A7=A4=EB=A7=A4=20=EC=95=8C=EB=9E=8C=20=EC=84=A4=EA=B3=84=20?= =?UTF-8?q?=EC=8A=A4=ED=8E=99=20(watchlist=E2=88=AAscreener=20buy=20+=20ex?= =?UTF-8?q?it+trailing=20sell,=201=EB=B6=84=20Windows=20=EC=9B=8C=EC=BB=A4?= =?UTF-8?q?,=20NAS=20edge=20dedup)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 브레인스토밍 확정 요구사항 6종 + 아키텍처 A(신규 Windows docker 워커). TA/조건판정은 Windows, edge 중복판정 상태는 NAS 영속(재시작 스팸 방지). cross-repo 계약(webai monitor-set/report, agent-office notify, watchlist CRUD, heartbeat) 정의. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01EqCYBhvTcdeCTUDX3RhWx9 --- ...2026-07-02-realtime-trade-alerts-design.md | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-02-realtime-trade-alerts-design.md diff --git a/docs/superpowers/specs/2026-07-02-realtime-trade-alerts-design.md b/docs/superpowers/specs/2026-07-02-realtime-trade-alerts-design.md new file mode 100644 index 0000000..1ae92a4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-02-realtime-trade-alerts-design.md @@ -0,0 +1,220 @@ +# 실시간 매매 알람 (Real-time Trade Alerts) — 설계 스펙 + +- 작성일: 2026-07-02 +- 상태: 설계 승인됨 (사용자 리뷰 대기) +- 관련 세션: BE(web-backend, 본 스펙 주도) · AI(web-ai 워커) · FE(web-ui 탭) + +## 1. 목표 + +장이 열려 있는 동안(**시간외 포함**) 실시간으로 주가 기준치를 분석해, 조건 충족 시 **매수/매도 알람**을 텔레그램으로 **사용자 + 아내** 둘 다에게 전송한다. 기술적 분석(TA) 계산은 **Windows PC의 docker 워커**에서 수행한다. + +기존에는 이 판단들이 EOD(하루 1회)로만 돌았다: +- 매수 후보 = 스크리너(평일 16:30) · 매도/보유 advisory = holdings_intel(08:30/16:50). + +이번 작업의 핵심 = **동일 판단을 장중(+시간외) 1분 주기 실시간으로 전환 + 조건 충족 즉시 알람**. + +## 2. 확정된 요구사항 (사용자 결정) + +| 항목 | 결정 | +|------|------| +| 매수 유니버스 | **watchlist(사용자 관리) ∪ 당일 스크리너 후보** | +| 매수 트리거 | **TA 자동 시그널**(수동 목표가 없음) | +| 매도 트리거 | **기존 exit 룰 + 트레일링 스톱** | +| 감시 주기/세션 | **1분 폴링** · 장전 시간외 08:30–09:00 · 정규장 09:00–15:30 · 시간외 단일가 16:00–18:00 | +| 중복 방지 | **상태 전이(edge-triggered)** — 거짓→참 전이 시만 알림, 참 유지 중 무알림, 재무장 | +| watchlist 관리 | **텔레그램 봇 명령 + web-ui 탭 둘 다** | +| 수신자 | **사용자 + 아내 둘 다**(매수·매도 모두) | +| TA 연산 위치 | **Windows WSL2 docker 신규 워커** | +| 트레일링 스톱 기본값 | 보유기간 고점 대비 **−10%**(파라미터화) | +| 매수 신호 | 지지선 되돌림(MA20/50) · 돌파(전고점/52주) · RSI 과매도 반등 | + +## 3. 아키텍처 + +``` +[Windows WSL2 docker] trade-monitor 워커 (web-ai · AI세션) + 1분 루프 (KST 세션 게이팅) + ① GET NAS /api/webai/trade-alert/monitor-set (X-WebAI-Key) + ② KIS 실시간/시간외 시세 + 분봉/일봉 → TA 계산 + ③ 조건 평가 → 현재 발화집합 F = {(ticker, kind, condition)} + ④ POST NAS /api/webai/trade-alert/report {firing: F} (X-WebAI-Key) + ⑤ heartbeat: worker:trade-monitor:heartbeat (EX45, 관측 편입) + │ + ▼ +[NAS] stock (:18500 · web-backend · BE) + • watchlist·alert_state(edge dedup, 영속)·alert_history·holding high-water + • monitor-set 조립(watchlist ∪ screener 후보 ∪ 보유) + 세션/휴장 게이팅 + • report 수신 → edge diff(F vs 직전 발화) → 신규 edge를 agent-office로 push + │ (텔레그램 전송 성공 시에만 alert_state 갱신) + ▼ +[NAS] agent-office (:18900 · web-backend · BE) + • POST /api/agent-office/stock/trade-alert → 텔레그램(너+아내) + • 봇 명령 /watch /unwatch /watchlist → stock watchlist CRUD + • 알람 activity feed 편입 + +[web-ui] 관심종목 탭 (FE세션) — watchlist CRUD + 알람 이력 뷰 +``` + +**설계 원칙** +- TA/조건판정 = Windows(요구사항). **edge 중복판정 상태 = NAS 영속** → 워커 재시작해도 재알림 스팸 없음(youtube_publisher 교훈 재적용). +- 워커는 dedup 상태를 **안 가진다**. 매 사이클 "현재 발화집합 전체"만 보고 → NAS가 diff(단일 진실원천). +- 워커의 대외 채널은 **NAS stock 한 곳**(기존 ai_trade↔stock의 `X-WebAI-Key` 재사용). 텔레그램 발송은 stock→agent-office push(기존 realestate→agent-office/notify 패턴). + +## 4. DB 스키마 (stock.db) + +```sql +-- 매수 감시 관심종목 (사용자 관리) +CREATE TABLE IF NOT EXISTS watchlist ( + ticker TEXT PRIMARY KEY, + name TEXT, + note TEXT, + params_json TEXT NOT NULL DEFAULT '{}', -- 종목별 조건 오버라이드(선택) + added_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) +); + +-- edge 중복판정 상태 (영속 — 재시작 스팸 방지의 핵심) +CREATE TABLE IF NOT EXISTS trade_alert_state ( + ticker TEXT NOT NULL, + kind TEXT NOT NULL, -- 'buy' | 'sell' + condition TEXT NOT NULL, -- ex) buy_ma20_pullback, sell_trailing_stop + currently_firing INTEGER NOT NULL DEFAULT 0, + first_fired_at TEXT, + last_fired_at TEXT, + last_seen_at TEXT, + PRIMARY KEY (ticker, kind, condition) +); + +-- 알람 이력 +CREATE TABLE IF NOT EXISTS trade_alert_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ticker TEXT NOT NULL, + name TEXT, + kind TEXT NOT NULL, + condition TEXT NOT NULL, + price REAL, + detail_json TEXT, + fired_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) +); +CREATE INDEX IF NOT EXISTS idx_tah_fired ON trade_alert_history(fired_at DESC); +``` + +보유기간 고점(트레일링 스톱용) high-water는 `krx_daily_prices`(기존)에서 lookback max로 계산하거나 별도 컬럼으로 관리 — 구현 계획에서 확정(v1: 포지션 최초 관측 이후 일봉 고가 max, 없으면 최근 N일). + +## 5. 계약 (Contracts) — cross-repo 잠금 대상 + +### 5.1 NAS stock ↔ Windows 워커 (X-WebAI-Key) + +`GET /api/webai/trade-alert/monitor-set` +```json +{ + "session": "pre | regular | after | closed", + "as_of": "2026-07-02T09:01:00+09:00", + "buy_targets": [{"ticker":"005930","name":"삼성전자","source":"watch|screener","params":{}}], + "sell_targets": [{"ticker":"000660","name":"SK하이닉스","avg_price":180000,"qty":10, + "holding_high":210000,"params":{}}], + "buy_params": {"rsi_oversold":30,"breakout_vol_mult":1.5,"pullback_pct":0.02}, + "exit_params": {"stop_pct":0.08,"take_pct":0.25,"trailing_pct":0.10} +} +``` +- `session=closed`면 워커는 KIS 호출 없이 sleep. + +`POST /api/webai/trade-alert/report` +```json +{ "as_of":"2026-07-02T09:01:00+09:00", + "firing":[ {"ticker":"005930","kind":"buy","condition":"buy_ma20_pullback", + "price":71500,"detail":{"ma20":71200,"rsi":34}} ] } +``` +응답: `{ "new_alerts": , "cleared": }` +- NAS가 `firing` vs `trade_alert_state[firing=1]` diff → 신규 edge만 텔레그램. + +### 5.2 stock → agent-office (내부) + +`POST /api/agent-office/stock/trade-alert` +```json +{ "alerts":[ {"ticker":"005930","name":"삼성전자","kind":"buy", + "condition":"buy_ma20_pullback","price":71500, + "detail":{...},"fired_at":"..."} ] } +``` +→ agent-office가 너+아내에게 텔레그램. (realestate/notify 패턴) + +### 5.3 stock watchlist CRUD (web-ui + agent-office 봇) +- `GET /api/stock/watchlist` +- `POST /api/stock/watchlist` `{ticker, note?}` +- `DELETE /api/stock/watchlist/{ticker}` +- `GET /api/stock/trade-alerts?days=N` (이력, web-ui용) + +### 5.4 워커 heartbeat (관측 편입) +`worker:trade-monitor:heartbeat` EX45, 값 JSON `{name:"trade-monitor",kind:"trader",state:"market_open|market_closed|idle",ts,last_alert_at,...}`. `/api/agent-office/nodes` workers[]에 추가. + +## 6. 알람 조건 (Windows 워커가 계산) + +**매수** (buy_targets): +- `buy_ma20_pullback` — MA20>MA50>MA200 정렬 + 저가가 MA20/50에 `pullback_pct` 이내 접근 후 종가 반등 +- `buy_breakout` — 종가 > (전 N일 고점 또는 52주 신고가) + 거래량 > `breakout_vol_mult`×20일평균 +- `buy_rsi_bounce` — RSI(14)가 `rsi_oversold` 아래로 내려갔다가 **봉 시리즈 내에서** 다시 상향 돌파(최근 봉에서 30 상향 크로스). 워커는 무상태 — 매 사이클 봉 데이터로 크로스를 계산(cross-cycle 메모리 불필요) + +**매도** (sell_targets): +- `sell_stop_loss` — (price−avg)/avg ≤ −`stop_pct` +- `sell_ma_break` — 종가 < MA50 (심각: < MA200) +- `sell_take_profit` — (price−avg)/avg ≥ `take_pct` +- `sell_climax` — 급등 소진(holdings_intel climax 로직 이식) +- `sell_trailing_stop` — price ≤ holding_high × (1 − `trailing_pct`) + +## 7. 데이터 흐름 — edge dedup (NAS) + +``` +매 1분 report 수신 시: + F = report.firing 집합 + prev = SELECT (ticker,kind,condition) FROM trade_alert_state WHERE currently_firing=1 + new_edge = F − prev + cleared = prev − F + for e in new_edge: + ok = agent_office.send_trade_alert(e) # 텔레그램 + if ok: + INSERT trade_alert_history(e) + UPSERT trade_alert_state(e, firing=1, fired/last=now) + # 실패 시 상태 미갱신 → 다음 사이클 재시도 + for c in cleared: + UPDATE trade_alert_state SET firing=0 WHERE key=c # 재무장 + UPDATE last_seen_at for all F +``` +- 영속 `trade_alert_state` → 워커·NAS 재시작에도 재알림 스팸 없음. +- 텔레그램 실패 시 firing 미표시 → 재시도 보장(node_monitor "성공 시만 갱신" 관용). + +## 8. 세션/휴장 게이팅 + +NAS `monitor-set.session` 필드가 KST 시각 + `holidays.json`(`is_market_open`)으로 판정: +- pre 08:30–09:00 / regular 09:00–15:30 / after 16:00–18:00 → 그 외/휴장 = closed. +- 워커는 `closed`면 sleep. (불필요 KIS 호출·알람 차단) + +## 9. 에러 처리 + +- 워커: KIS 실패 → 해당 사이클 skip + 다음 분 재시도, 종목별 실패 격리. heartbeat로 생사 노출. +- NAS: 워커 인증 `X-WebAI-Key`. 텔레그램 실패 → 상태 미갱신. `report`는 멱등(같은 F 재전송 무해). +- 워커 다운 시 알람 정지 → node_monitor 경보(기존 관측)로 감지. + +## 10. 테스트 전략 (BE, TDD) + +- watchlist CRUD (추가/중복/삭제/조회) +- monitor-set 조립 (watchlist ∪ screener ∪ 보유, 세션 게이팅, 휴장) +- **edge diff 로직**: 신규 edge만 알림 / 참 유지 무알림 / 해제 후 재발화 재알림 / 재시작 지속성(영속 상태) +- 텔레그램 전송 실패 시 상태 미갱신(재시도) +- alert_history 기록 / trade-alerts 조회 +- agent-office: /watch·/unwatch·/watchlist 봇 명령 → stock CRUD, trade-alert notify → 텔레그램 포맷(너+아내) +- webai 계약 엔드포인트(monitor-set/report) 스키마·인증 + +## 11. 작업 분담 + +| repo | 세션 | 산출물 | 상태 | +|------|------|--------|------| +| **web-backend** (stock + agent-office) | **BE(본 세션)** | DB·watchlist·edge·webai 계약·텔레그램·봇 | 이번에 구현 | +| **web-ai** (`services/trade-monitor/` WSL2 docker) | AI세션 | 1분 루프·KIS·TA·조건평가·report·heartbeat | 계약 넘김 | +| **web-ui** (관심종목 탭) | FE세션 | watchlist CRUD·조건·이력 뷰 | 계약 넘김 | + +- 계약(§5)은 co-gahusb로 잠근 뒤 3세션 병렬. +- 워커 재빌드는 로컬 docker(사용자): `wsl -d Ubuntu-24.04 -- docker compose up -d --build trade-monitor`. + +## 12. 범위 밖 (YAGNI / 후속) +- 실주문 자동 집행(알람 전용, KIS 주문 X). +- KIS 웹소켓 실시간 틱(1분 폴링으로 충분). +- 종목별 수동 목표가(이번은 TA 자동만). +- 백테스트/성과 추적(후속 슬라이스).