270 lines
13 KiB
Markdown
270 lines
13 KiB
Markdown
# web-ai
|
||
|
||
Windows AI 머신(AMD 9800X3D + RTX 5070 Ti 16GB)에서 동작하는 두 영역의 서비스:
|
||
|
||
1. **ai_trade** — Confidence Signal Pipeline V2. NAS stock 백엔드와 KIS Open API를 결합해 매수/매도 신호를 생성하는 FastAPI 워커.
|
||
2. **services** — NAS↔Windows 분산 워커: 렌더링(인스타 카드 / 음악 / 영상 / 이미지) + task-watcher + **trade-monitor**(실시간 매매 알람).
|
||
|
||
상위 워크스페이스 컨텍스트는 `../CLAUDE.md`, 본 디렉토리 상세는 `CLAUDE.md`, 운영 체크포인트는 `CHECK_POINT.md` 참조.
|
||
|
||
---
|
||
|
||
## 디렉토리 구조
|
||
|
||
| 경로 | 역할 | 포트 |
|
||
|------|------|------|
|
||
| `ai_trade/` | 자동매매 메인. Chronos-bolt(또는 Chronos-2) + 분봉 모멘텀 + KIS WebSocket 호가 + 매수/매도 신호 생성기. | `:8001` |
|
||
| `services/_shared/` | 4개 render worker 공통 모듈 (`ReliableQueue` — BLMOVE + ack/fail + recovery). | — |
|
||
| `services/insta-render/` | Instagram 카드 Playwright 렌더 워커. NAS Redis `queue:insta-render` 소비. | `:18710` |
|
||
| `services/music-render/` | Suno + MusicGen 음악 생성 워커. `queue:music-render` 소비. | `:18711` |
|
||
| `services/video-render/` | sora / veo / kling / seedance 4 provider 영상 생성 게이트웨이. `queue:video-render` 소비. | `:18712` |
|
||
| `services/image-render/` | gpt_image / nano_banana / flux(ComfyUI 로컬) 3 provider. `queue:image-render` 소비. | `:18714` |
|
||
| `services/task-watcher/` | 박재오 작업 시간대에 `queue:paused` 토글 → 워커 일시 정지. | `:18713` |
|
||
| `services/trade-monitor/` | 실시간 매매 알람. monitor-set pull → KIS 시세 + TA 조건(§6 8종) → report + heartbeat(kind=trader). 무상태. | `:18715` |
|
||
| `legacy/signal_v1/` | ⚠ **DEPRECATED** (2026-05-19). LSTM 봇. 자동 실행 차단됨. | OFF |
|
||
|
||
---
|
||
|
||
## ai_trade — Confidence Signal Pipeline V2
|
||
|
||
NAS stock 백엔드(`:18500`)에서 portfolio / news_sentiment / screener를 pull하고, KIS REST/WebSocket으로 분봉·호가를 보강한 뒤 Chronos 예측과 5분봉 모멘텀 분류로 매수/매도 신호를 생성한다.
|
||
|
||
### 매수 (screener Top-N + portfolio)
|
||
|
||
모두 충족 시 confidence 계산 → threshold 초과 시 emit:
|
||
|
||
1. `chronos.median > 0`
|
||
2. `chronos.q90 - chronos.q10 < 0.6` (absolute spread)
|
||
3. `minute_momentum == strong_up`
|
||
4. `asking_price.bid_ratio >= 0.6`
|
||
|
||
종합 confidence = `chronos_conf * 0.5 + minute_score * 0.3 + screener_norm * 0.2`. `> 0.7` 시 emit.
|
||
|
||
### 매도 (portfolio only, 우선순위 stop_loss → anomaly → take_profit)
|
||
|
||
- **stop_loss**: `pnl_pct < -7%` 즉시 (confidence=1.0)
|
||
- **anomaly**: `chronos.median < -1%` + `strong_down` + `bid_ratio < 0.4` + 종합 conf > 0.7
|
||
- **take_profit**: `pnl_pct > 15%` 검토 (confidence=0.6)
|
||
|
||
### 핵심 파일
|
||
|
||
| 파일 | 책임 |
|
||
|------|------|
|
||
| `main.py` | FastAPI app + lifespan (의존성 wiring) + poll_loop task 생성 |
|
||
| `config.py` | `Settings` dataclass — 환경변수 로드 |
|
||
| `state.py` | `PollState` (process-wide singleton) — portfolio·screener·signals 등 + `get_active_signals` / `purge_expired_signals` |
|
||
| `stock_client.py` | NAS stock 백엔드 pull (X-WebAI-Key + 메모리 캐시) |
|
||
| `kis_client.py` | KIS REST 분봉/호가 + asyncio.Lock 직렬화 + 지수 backoff |
|
||
| `kis_websocket.py` | KIS WebSocket 호가 + approval_key + 재연결 |
|
||
| `chronos_predictor.py` | HuggingFace Chronos zero-shot 분위수 예측 (FP32 강제) |
|
||
| `minute_momentum.py` | 5분봉 → strong_up / weak_up / neutral / weak_down / strong_down |
|
||
| `signal_generator.py` | 매수/매도 룰 엔진. cycle_id + expires_at 부착 |
|
||
| `pull_worker.py` | asyncio cron — 시간대별 분기 + post-close 트리거 + signal 생성 + expired purge |
|
||
| `scheduler.py` | 폴링 윈도우 판정 (KST 캘린더 + 휴장일) |
|
||
| `rate_limit.py` | 초당 N회 token bucket + `SignalDedup` SQLite WAL |
|
||
|
||
### 시작
|
||
|
||
```bat
|
||
cd ai_trade
|
||
start.bat
|
||
```
|
||
|
||
→ `Uvicorn running on http://0.0.0.0:8001`, `poll_loop started`.
|
||
|
||
휴장일/장 외 시간엔 poll_loop만 idle.
|
||
|
||
### 헬스 / 로그
|
||
|
||
```powershell
|
||
curl http://localhost:8001/health
|
||
Get-Content logs\ai_trade.log -Wait
|
||
nvidia-smi
|
||
```
|
||
|
||
---
|
||
|
||
## services — NAS↔Windows 분산 워커
|
||
|
||
NAS측 lab 서비스(insta-lab / music-lab / video-lab / image-render NAS측)가 `queue:<worker>-render` 에 LPUSH로 작업을 enqueue. Windows worker가 BLMOVE로 atomic dequeue 후 처리, 완료 시 NAS internal webhook으로 결과 통지.
|
||
|
||
### 신뢰성 패턴 (`_shared.ReliableQueue`)
|
||
|
||
- **dequeue**: `BLMOVE main → processing:<queue>:<worker_id>` (atomic).
|
||
- **ack**: `LREM processing 1 raw` (성공).
|
||
- **fail**: `LREM processing` → `attempts++` 후 main 재큐 또는 `max_attempts` 도달 시 `dead_letter:<queue>` 이동.
|
||
- **recover**: startup 시 자신의 processing list orphan을 main queue로 (attempts 증가).
|
||
|
||
### 시작 (NAS, WSL2 Docker)
|
||
|
||
```bash
|
||
cd services
|
||
docker compose up -d insta-render music-render video-render image-render task-watcher
|
||
```
|
||
|
||
build context는 `services/` 루트. 각 Dockerfile은 `_shared` 모듈을 함께 COPY하고 `PYTHONPATH=/app`.
|
||
|
||
### 운영 조작
|
||
|
||
```bash
|
||
# 워커 일시 정지 / 재개
|
||
redis-cli -h 192.168.45.54 SET queue:paused 1
|
||
redis-cli -h 192.168.45.54 DEL queue:paused
|
||
|
||
# 큐 / dead-letter 점검
|
||
redis-cli -h 192.168.45.54 LLEN queue:insta-render
|
||
redis-cli -h 192.168.45.54 LLEN dead_letter:queue:insta-render
|
||
redis-cli -h 192.168.45.54 KEYS 'processing:*'
|
||
```
|
||
|
||
### 환경 변수
|
||
|
||
| 변수 | 용도 |
|
||
|------|------|
|
||
| `REDIS_URL` | NAS Redis (`redis://192.168.45.54:6379`) |
|
||
| `NAS_BASE_URL` | NAS 대상 서비스 URL (insta-lab `:18700`, music-lab `:18600`, video-lab `:18801`, image-render NAS측 `:18802`) |
|
||
| `INTERNAL_API_KEY` | NAS internal webhook 인증 |
|
||
| `WORKER_ID` | (권장) `<service>-prod-1` 등 영속 ID. hostname 기반 default는 컨테이너 재기동 시 바뀌어 orphan 추적 불가 |
|
||
| `OPENAI_API_KEY` / `GEMINI_API_KEY` / `KLING_*` / `SEEDANCE_API_KEY` / `SUNO_API_KEY` | 각 provider 인증 |
|
||
| `COMFYUI_URL` | image-render FLUX 로컬 ComfyUI (`http://host.docker.internal:8188`) |
|
||
| `FLUX_BLOCK_TRADING_HOURS` | `1` 이면 장중(09:00~15:30) FLUX 차단 (Chronos GPU 보호) |
|
||
|
||
---
|
||
|
||
## trade-monitor — 실시간 매매 알람 워커 (신규 2026-07-03)
|
||
|
||
NAS stock 백엔드(`:18500`)에서 `monitor-set`을 60초마다 pull하고, KIS 실시간/일봉 시세로 TA 조건을 평가해 발화집합을 `report`로 전송. NAS가 edge diff로 신규 알림만 텔레그램/프론트에 노출. **워커 무상태**(dedup은 NAS 영속). 포트 `:18715`, WSL2 Docker. 상세 설계·조건 규칙은 `services/trade-monitor/DESIGN.md`.
|
||
|
||
### 루프 (1분)
|
||
|
||
1. `GET /api/webai/trade-alert/monitor-set` (X-WebAI-Key) → buy_targets(watch∪screener) + sell_targets(보유 avg_price/qty/holding_high) + buy_params/exit_params + session.
|
||
2. `session=="closed"`면 KIS 호출 0, idle. **비-KRX(알파벳) 티커 skip**(워커 책임).
|
||
3. KIS quote + 일봉 250봉 → 지표 계산 → 조건 평가(종목 단위 실패 격리).
|
||
4. `POST /api/webai/trade-alert/report {as_of, firing:[...]}` — 빈 배열도 전송(edge clear).
|
||
5. heartbeat `worker:trade-monitor:heartbeat` EX45 (kind=trader, state=market_open|market_closed|idle + last_alert_at). 60초 루프 > TTL45 만료갭 회피 위해 **15초 독립 태스크**.
|
||
|
||
### 조건 (§6 — `condition` 문자열이 FE 라벨/뱃지로 그대로 매핑됨)
|
||
|
||
- **매수**: `buy_ma20_pullback`(정배열 + ma20 근접 반등), `buy_breakout`(20봉 고점 돌파 + 거래량 배수), `buy_rsi_bounce`(RSI 과매도 반등, 무상태).
|
||
- **매도**: `sell_stop_loss`, `sell_take_profit`, `sell_trailing_stop`, `sell_ma_break`(ma50/ma200 severity), `sell_climax`(거래량 급증 + 윗꼬리 — holdings_intel 정합 예정).
|
||
|
||
### 핵심 파일
|
||
|
||
| 파일 | 책임 |
|
||
|------|------|
|
||
| `config.py` | Settings (`TM_` 접두사, ai_trade와 분리) |
|
||
| `indicators.py` | 순수: `sma` / `rsi_series`(Wilder) / `highest_high` |
|
||
| `conditions.py` | 순수 §6: `evaluate_buy` / `evaluate_sell` |
|
||
| `kis_client.py` | KIS **자체 OAuth 토큰** + `get_quote` + `get_daily_ohlcv` + 0.5s throttle |
|
||
| `nas_client.py` | monitor-set / report (X-WebAI-Key + retry) |
|
||
| `monitor.py` | `run_cycle` / `monitor_loop` / `make_state_fn` |
|
||
| `main.py` | FastAPI lifespan + `_shared.heartbeat_loop` 배선 + `/health` |
|
||
|
||
### 환경 변수
|
||
|
||
| 변수 | 기본 | 설명 |
|
||
|------|------|------|
|
||
| `NAS_BASE_URL` | `http://192.168.45.54:18500` | stock 백엔드 |
|
||
| `WEBAI_API_KEY` | (필수) | X-WebAI-Key |
|
||
| `REDIS_URL` | `redis://192.168.45.54:6379` | heartbeat |
|
||
| `TM_KIS_APP_KEY` / `TM_KIS_APP_SECRET` / `TM_KIS_ACCOUNT` | (필수) | KIS **전용** 자체 토큰(ai_trade와 분리 발급 → 토큰 상호 무효화·EGW00201 회피) |
|
||
| `TM_KIS_IS_VIRTUAL` | `0` | 실전/모의 |
|
||
| `TM_LOOP_INTERVAL` | `60` | 루프 주기(초) |
|
||
| `TM_CLIMAX_VOL_MULT` | `3.0` | sell_climax 거래량 배수 (→ monitor-set `exit_params.climax_vol_x`로 중앙화 예정) |
|
||
|
||
### 상태
|
||
|
||
⏳ 구현·머지 완료(테스트 34/34), **미배포**. 배포 전: ① 전용 KIS 앱키 발급·주입(박재오 진행 중) ② `sell_climax` holdings_intel 정합(`price < day_high × 0.97` + `exit_params` 파라미터화) ③ 첫 운영 KIS 필드 검증. BE가 `node_monitor.WORKER_REGISTRY`에 등재 완료 → 배포 시 `/api/agent-office/nodes`·web-ui `/infra`에 trader 노드 자동 노출(미배포 동안 down, 무경보).
|
||
|
||
### 시작 (NAS, WSL2 Docker)
|
||
|
||
```bash
|
||
cd services
|
||
docker compose up -d trade-monitor
|
||
```
|
||
|
||
---
|
||
|
||
## 환경 변수 (ai_trade)
|
||
|
||
| 변수 | 기본 | 설명 |
|
||
|------|------|------|
|
||
| `STOCK_API_URL` | (필수) | NAS stock 백엔드 base URL |
|
||
| `WEBAI_API_KEY` | (필수) | stock 백엔드 호출 시 X-WebAI-Key |
|
||
| `SIGNAL_V2_PORT` | `8001` | uvicorn 포트 |
|
||
| `KIS_ENV_TYPE` | `virtual` | `virtual` / `real` |
|
||
| `KIS_REAL_APP_KEY` / `KIS_REAL_APP_SECRET` / `KIS_REAL_ACCOUNT` | — | KIS 실계좌 |
|
||
| `KIS_VIRTUAL_APP_KEY` / `KIS_VIRTUAL_APP_SECRET` / `KIS_VIRTUAL_ACCOUNT` | — | KIS 모의계좌 |
|
||
| `V1_TOKEN_PATH` | `legacy/signal_v1/data/kis_token.json` | KIS 토큰 파일 (V1 토큰 read-only 공유) |
|
||
| `CHRONOS_MODEL` | `amazon/chronos-2` | Chronos 모델 ID |
|
||
| `STOP_LOSS_PCT` | `-0.07` | 손절 임계 |
|
||
| `TAKE_PROFIT_PCT` | `0.15` | 익절 임계 |
|
||
| `CHRONOS_SPREAD_THRESHOLD` | `0.6` | 매수 hard gate spread 상한 |
|
||
| `ASKING_BID_RATIO_THRESHOLD` | `0.6` | 매수 hard gate 호가 비율 |
|
||
| `CONFIDENCE_THRESHOLD` | `0.7` | 매수 종합 confidence 하한 |
|
||
| `MIN_MOMENTUM_FOR_BUY` | `strong_up` | 매수 hard gate 모멘텀 단계 |
|
||
| `SIGNAL_TTL_SECONDS` | `300` | emit signal expires_at TTL |
|
||
|
||
`.env` 는 web-ai 루트 (이 디렉토리)에 둔다. **절대 커밋 금지.**
|
||
|
||
---
|
||
|
||
## 테스트
|
||
|
||
```bash
|
||
# ai_trade
|
||
python -m pytest ai_trade/tests -q
|
||
|
||
# services/_shared 공통 모듈
|
||
cd services/_shared && python -m pytest tests/ -q
|
||
|
||
# 각 worker
|
||
cd services/insta-render && python -m pytest tests/ -q
|
||
cd services/music-render && python -m pytest tests/ -q
|
||
cd services/video-render && python -m pytest tests/ -q
|
||
cd services/image-render && python -m pytest tests/ -q
|
||
cd services/trade-monitor && python -m pytest tests/ -q # 34 tests
|
||
```
|
||
|
||
**`.venv` 한글 사용자 경로 깨짐**으로 시스템 Python(`C:\Users\jaeoh\AppData\Local\Programs\Python\Python312\python.exe`) 사용 권장. 또는 `py -3.12 -m pytest …`.
|
||
|
||
---
|
||
|
||
## 알려진 함정
|
||
|
||
1. **KIS rate limit (EGW00201)** — V1+V2 동시 실행 시 충돌. V1은 `legacy/`로 격리. ai_trade는 `asyncio.Lock`으로 throttle 직렬화 (`kis_client.py`).
|
||
2. **`.venv` 한글 경로** — 시스템 Python 사용.
|
||
3. **Chronos FP16 overflow** — 한국 주가 5만원+ 시 inf. FP32 강제됨.
|
||
4. **post-close 트리거** — 상태기반(`last_post_close_date`)으로 변경됨. 16:00 이후 + 오늘 미실행이면 trigger.
|
||
5. **services worker_id** — env로 명시 권장. hostname 기반 default는 컨테이너 재기동 시 바뀌어 orphan 분실 위험.
|
||
6. **dead-letter 누적** — `redis-cli LLEN dead_letter:*` 정기 점검 필요.
|
||
7. **Dockerfile build context** — `services/` 루트 (각 worker 디렉토리 아님). compose 변경 동반.
|
||
8. **분산 워커 /infra 관측 필수 (팀 규칙)** — 모든 WSL docker 워커는 heartbeat(`worker:<name>:heartbeat` EX45) + BE `node_monitor.WORKER_REGISTRY` 등재 + `/infra` 노출이 필수. trade-monitor는 kind=trader로 등재됨.
|
||
9. **trade-monitor KIS 앱키 분리** — ai_trade와 **다른 전용 app_key**(`TM_KIS_*`) 사용. 같은 app_key 공유 시 토큰 상호 무효화 + EGW00201. 실전 최대 89앱 발급 가능.
|
||
|
||
---
|
||
|
||
## Phase 진행 상태 (Confidence Signal Pipeline V2)
|
||
|
||
| Phase | 내용 | 상태 |
|
||
|-------|------|------|
|
||
| 0 | Architecture & contract spec | ✅ |
|
||
| 1 | stock 백엔드 WebAI API 보강 (NAS) | ✅ |
|
||
| 1.5 | V1 → `signal_v1/` rename → `legacy/` 격리 | ✅ |
|
||
| 2 | ai_trade pull worker + signal API client + scheduler | ✅ |
|
||
| 3a | KIS REST 분봉 + WebSocket 호가 + NXT 스케줄 | ✅ |
|
||
| 3b | Chronos-bolt-base 추론 + 5분봉 모멘텀 분류기 | ✅ |
|
||
| 4 | Signal Generator + 로깅 | ✅ |
|
||
| 4.5 | 코드 리뷰 F1-F6 hotfix (토큰 경로 / throttle Lock / post-close 상태기반 / Chronos abs / state.signals lifecycle / render queue 신뢰성) | ✅ |
|
||
| 5 | agent-office `/signal` + Ollama Qwen3 14B + 이중 텔레그램 | ⏳ |
|
||
| 6 | signal_v1 deprecation (legacy 완료, 아카이브만 남음) | 일부 ✅ |
|
||
| 7 | 운영 모니터링 + 4주 IC 검증 | ⏳ |
|
||
|
||
상세 spec/plan은 `../web-ui/docs/superpowers/specs/` / `../web-ui/docs/superpowers/plans/` (별도 repo).
|
||
|
||
---
|
||
|
||
## 라이선스 / 사용
|
||
|
||
비공개. 박재오 개인 웹 플랫폼.
|