내가 만든 AI 에이전트 (3/7) — 주식 정보 관리 디렉터: 시그널 수집에서 포트폴리오 스코어링
지표견(Jipyogyeon) Discord 채널 자동 수집, 시그널 파싱, 포트폴리오 기반 알림 분류까지의 전체 구조
핵심 요약
- 지표견(Jipyogyeon) Discord 채널에서 퀀트 트레이딩 시그널을 자동 수집하는 파이프라인을 구축했다
- Discord embed 메시지를 구조화 데이터로 파싱하고, 포트폴리오 보유 종목과 매칭하여 알림 우선순위를 분류한다
- 홈서버 대시보드와 연동하여 critical/high 알림을 강조 표시하고, 일일 브리핑을 요약 카드로 전달한다
배경
퀀트 시그널 서비스를 구독하면 데이터는 쏟아지지만, "내 포트폴리오에 해당하는 시그널"만 빠르게 필터링하는 레이어가 없습니다. Discord 채널에 올라오는 시그널을 수동으로 확인하는 방식은 확장되지 않습니다.
지표견(Jipyogyeon)은 하루에도 수십 개의 시그널을 여러 채널에 배포합니다. 진입, 청산, PEG, 모멘텀, Top5 리포트 등. 이 중에서 포트폴리오 보유 종목에 해당하는 시그널을 즉시 알림으로 분리하고, 나머지는 거래 종료 후 요약으로 받는 구조가 필요했습니다.
주식 정보 관리 디렉터는 이 수집-분류-전달 파이프라인을 자동화합니다.
본문
1. 전체 아키텍처
Discord 채널 (지표견)
↓ 60초 폴링
discord_listener.py → signal_parser.py
↓ 구조화 데이터
포트폴리오 매칭 → 알림 분류
↓
├─ 즉시 알림 → Discord Webhook + 홈서버 /api/webhooks
└─ 로그 전용 → daily_raw.json에 저장
↓ (15:50 KST)
일일 브리핑 → 홈서버 /api/webhooks
2. discord_listener.py — 채널 폴링
Discord HTTP API를 직접 사용합니다. discord.py 같은 라이브러리가 아니라, HTTP GET 요청으로 메시지를 읽어옵니다.
핵심 설계 원칙: 읽기 전용. 메시지 전송, 리액션, 타이핑 표시 등 쓰기 동작을 일절 하지 않습니다. Discord ToS 위반을 방지하기 위한 의도적 제한입니다.
폴링 구조: - 60초 간격으로 전체 채널을 순회 - 채널 간 1~1.5초 랜덤 딜레이 (rate limit 방지) - 마지막으로 읽은 메시지 ID를 기억해서 새 메시지만 처리 - 오늘 날짜(KST) 메시지만 처리 — 이전 날짜 메시지는 스킵
거래 시간 필터: 한국 주식 시장 기준 09:00~16:00 KST, 평일만 동작합니다. 장 외 시간에는 다음 거래 시작 시간까지 대기합니다. 매일 새 거래일이 시작되면 메시지 상태를 초기화합니다.
채널 구성: channels.json으로 관리합니다. 현재 10개 채널을 모니터링합니다:
| 채널 | 내용 |
|---|---|
| 오늘의시그널 | 일일 시장 요약 |
| 셋업형성 | VCP/셋업 포착 |
| 진입 | 정석/돌파/공격 진입 시그널 |
| 분할매수 | 피라미딩 추매 시그널 |
| 청산 | 최종/돌파 청산 |
| 분할매도 | 분할 익절/청산 |
| 모멘텀 | 모멘텀 BUY/SELL |
| peg | PEG 기반 시그널 |
| 종합알림_4h | 4시간 주기 종합 |
| top5 | Top 5 리더 종목 리포트 |
3. signal_parser.py — Discord Embed 파싱
지표견의 시그널은 Discord Rich Embed 형식으로 옵니다. 일반 텍스트가 아닙니다.
Embed 구조:
embed.title: 확신도 + 시그널 타입 (예: "🟡 B 💰 정석 진입")
embed.description: 종목명(티커) + 가격/SL/RR
embed.fields: 상태(점수, 배열), 시그널 태그, AI 평가, 설명, 정보(시장환경, 거래소)
파서는 이 구조를 Signal 데이터 클래스로 변환합니다:
| 필드 | 출처 | 예시 |
|---|---|---|
| conviction | title의 이모지 | 🟣→S, 🟢→A, 🟡→B, 🟠→C, 🔴→D |
| signal_type | title의 시그널명 | "💰 정석 진입", "💸 최종 청산" |
| action | signal_type 매핑 | BUY / SELL / CHECK |
| ticker | description 파싱 | "090430" |
| price / stop_loss / risk_reward | description 파싱 | 142600 / 122851 / 2.1 |
| score | fields["상태"] | 60 |
| ema_alignment | fields["상태"] | "정배열" / "역배열" / "꼬임" |
| ai_summary | fields["AI 평가"] | "꿀통 눌림목 21 EMA 지지" |
| exchange | fields["정보"] | "KRX" / "NASDAQ" |
Top5 리포트는 별도 파서(parse_top5_embed)가 처리합니다. 순위, 확신도, 종목, 섹터, 시그널 타입, 가격, 손절가, 목표가까지 추출합니다.
4. 포트폴리오 기반 알림 분류
파싱된 시그널은 portfolio.json의 보유 종목/관심 종목과 매칭하여 알림 우선순위를 결정합니다.
알림 규칙:
| 우선순위 | 조건 | 알림 |
|---|---|---|
| critical | 보유 종목에 SELL 시그널 | 즉시 알림 (청산 경고) |
| high | 보유 종목에 아무 시그널 | 즉시 알림 (포트폴리오 히트) |
| high | S/A등급 + 60점 이상 + BUY | 즉시 알림 (고확신 진입) |
| low | 그 외 모든 시그널 | 로그만 (알림 없음) |
즉시 알림이 필요한 시그널만 Discord Webhook과 홈서버로 전달됩니다. 나머지는 daily_raw.json에 저장되어 일일 브리핑에 사용됩니다.
투자 전략 반영: 한국 주식은 단기/중기/장기 투자, 미국 주식은 장기 투자만. 암호화폐는 처리하지 않습니다.
5. 홈서버 연동
시그널 데이터는 홈서버의 웹훅 API로 전달됩니다.
POST /api/webhooks
{
"content": "{시그널 JSON}",
"tags": ["stock", "signal", "buy", "kr", "alert"],
"source": "stock-director"
}
태그는 시그널 속성에 따라 자동 생성됩니다: 시장(kr/us), 액션(buy/sell/check), 긴급도(alert/urgent), 확신도(high-conviction).
일일 브리핑: 거래 종료 직전(15:50 KST)에 당일 수집한 전체 시그널을 요약합니다.
{
"date": "2026-04-06",
"total": 42,
"buy_count": 18,
"sell_count": 8,
"check_count": 16,
"portfolio_hits": [...],
"high_conviction_entries": [...]
}
홈서버 대시보드에서 이 브리핑을 요약 카드로 표시합니다. critical/high 알림은 시각적으로 강조됩니다.
6. 상시 실행 구조
discord_listener.py는 macOS의 launchd로 상시 실행됩니다. 시스템 시작 시 자동으로 기동되고, 크래시 시 자동 재시작됩니다.
외장드라이브 TCC 제한 이슈: macOS는 외장드라이브의 파일에 대해 TCC(Transparency, Consent, and Control) 권한 제한을 적용합니다. launchd에서 외장드라이브의 스크립트를 직접 실행하면 권한 문제가 발생할 수 있습니다. 해결 방법으로 내부 스토리지에 실행용 복사본을 두고, 외장드라이브의 원본과 동기화하는 프로토콜을 사용합니다.
설계 과정에서 마주친 문제들
Discord embed 파싱의 복잡성: 지표견의 메시지 형식이 한 가지가 아닙니다. 일반 시그널, PEG 시그널, Top5 리포트가 각각 다른 구조입니다. 단일 파서로 처리하려다 실패했고, 시그널 타입별로 파서를 분리한 후 안정되었습니다.
rate limit 관리: 10개 채널을 빠르게 순회하면 Discord API rate limit에 걸립니다. 채널 간 랜덤 딜레이(1~1.5초)를 넣어서 해결했습니다. 429 응답이 오면 retry_after 값만큼 대기합니다.
한국어 종목명 매핑: 지표견은 영문 종목명을 사용합니다(예: "SK Hynix Inc."). 포트폴리오 알림에서는 한국어 이름("SK하이닉스")을 표시하는 것이 자연스럽습니다. portfolio.json에 한국어 이름을 추가하고, 알림 생성 시 참조하도록 매핑을 추가했습니다.
장 외 시간 리소스 낭비: 초기에는 24시간 폴링을 했습니다. 장 외 시간에는 시그널이 없으므로 불필요한 API 호출이었습니다. 거래 시간 필터를 추가해서 평일 09:00~16:00에만 동작하도록 했습니다.
마무리
주식 정보 관리 디렉터의 핵심 가치는 소음에서 신호를 분리하는 것입니다. 하루에 수십 개의 시그널이 올라오지만, 실제로 즉시 확인해야 하는 것은 몇 개뿐입니다.
구조는 단순합니다: 수집(listener) → 파싱(parser) → 분류(classify) → 전달(webhook). 각 단계가 하나의 역할만 담당합니다.
투자 정보 관리에서 중요한 것은 데이터의 양이 아닙니다. 포트폴리오에 영향을 주는 시그널을 얼마나 빠르게, 정확하게 식별하느냐입니다. 이 파이프라인은 그 필터링 레이어를 자동화합니다.
시리즈 전체 안내: 시리즈 목차
댓글
댓글 쓰기