KAIST 디지털금융MBA · 클라우드컴퓨팅실습
유튜버가 오른다고 한 종목,
정말 올랐을까?
HINDSIGHT
- 김지환
- 김현준
- 오선호
- 조인성
- 채재희
영상이 맞았는지는 늘 지나고 나서야 안다.
그 "지나고 난 뒤"를 1년치 모았다.
목차
오늘 이야기할 순서
- 쇼릴1분 16초로 보는 HINDSIGHT
- 문제와 답왜 만들었나 · 무엇을 세나
- 데이터 · API공개 API 6개 · 채널 선정 · 파이프라인
- 아키텍처구조 · 요청 한 번의 흐름 · 막혔던 것과 고친 것
- 분석 결과정말 올랐을까 · 발견 · 한계
- 활용한 문장 · 누가 어떻게 쓰나
- 마무리부록(DB · API 문서) · 주소 3개
왜 만들었나
유튜브의 "오른다"는 전망,
적중률을 아무도 재지 않았다
애널리스트 리포트
있다적중률을 보여주는 서비스
주식 · 코인 유튜브
없다자극적인 제목 · 조회수 수십만
그래서 공개된 데이터를 모아 정제해서, 오늘 그 결과를 보여 드립니다

우리의 답
"오른다"고 한 영상 하나하나를 사건으로 보고,
그 뒤 주가를 전부 센다
(업종 언급 4,060건은 따로 · 최근 1개월은 아직)
데이터 · API ① 출처
공개 API 6개, 각각 다른 질문에 답한다
누가 언제 무슨 종목을 말했나
채널 · 제목 · 게시일 · 조회수"삼전"이 어느 종목인가
상장사 3,998개 → 종목 사전그 뒤 주가는
국내 종가 · 거래량시장이 오른 덕인가
미국 시세 · 코스피 · S&P500 · 업종 ETF 20코인은
KRW 마켓 294 · 일봉공시를 따라간 건가
공시 49,099건데이터 · API ② 채널 선정
채널은 사람이 고르지 않았다
A · 대형 영향력
100만구독자 이상 · 언급률 15% 이상
B · 종목 집중
30%언급률 이상 · 구독자 주식 5만 · 코인 10만 이상
언급률 = 최근 50편 제목 중 종목이 나온 비율 · 둘 중 하나만 넘으면 채택 · 30% 하나로 자르면 삼프로TV·김작가TV가 빠진다
데이터 · API ③ 파이프라인 · 매일 07:10 GitHub Actions
제목 한 줄이 통계가 되기까지
아키텍처 ① 구조
Vercel · Render · Supabase · GitHub Actions
아키텍처 ② 동작 원리
읽기는 GET, 쓰기는 배치만 — 외부 API 는 요청 때 부르지 않는다
GET /stats/v1/summary?scope=overall&horizon=20&stance=bull← JSON · 0.6KB · 캐시 10분bull:overall:20SELECT value FROM stats.summaries WHERE key = …← 1 rowPOST /{service}/internal/syncX-Internal-Token외부 API 6개 읽기 → INSERT쓰기는 여기뿐아키텍처 ③ 막혔던 것과 고친 것 · 실패한 실험의 원인과 개선 과정
세 번 막혔고, 세 번 고쳤다
그 밖에 Supabase 연결 15개 한도 · 공공데이터포털 "미등록 키"(옛 V1 주소) · 뉴스 크롤링을 넣었다 뺀 것 — 질문 주시면 설명
분석 결과 ① 스냅샷
"오른다"고 한 뒤, 실제로는
낙관 언급 뒤 20거래일 수익률 분포 · 파랑 하락 · 빨강 상승
분석 결과 ② 시장별 · 채널별
시장은 모두 같은 방향, 채널은 다르다
| 20거래일 뒤 | 표본 | 평균 | 상승 | 시장 대비 | 거래량 |
|---|
시장 대비 TOP 3 · BOTTOM 3 (낙관 30건 · 10종목 이상)
분석 결과 ③ 발견
숫자가 말해 준 다섯 가지
—
—
—
—
—
학습용 통계 · 투자 권유 아님 · 1년 · 채널 51개 · 제목만 본 범위 안의 결과
분석 결과 ④ 한계
이 숫자가 말하지 않는 것, 두 가지
자막·설명은 안 봤고, 낙관·비관은 모델이 아니라 단어 사전으로 가른다. 그래서 낙관 언급을 완벽하게 가려내지는 못한다 — 근거 단어를 화면에 그대로 보여 준다
다른 해, 다른 채널이면 숫자는 달라질 수 있다. 더 많은 채널, 더 긴 기간으로 넓힐 필요가 있다
그리고 이 결과는 상관이지 인과가 아니다 — 유튜브가 내리게 한 게 아니라 이미 오른 뒤에 언급이 몰리는 구조 · 종가 기준 · 거래 비용 없음
데이터가 말하는 한 문장
유튜브가 "오른다"고 할 때는
이미 늦었다
그래서 — 영상이 말한 종목을 사기 전에, 그 종목이 전에 떴을 때 어땠는지 먼저 본다
활용 · 비즈니스 모델
누가, 어떻게, 무엇으로 지속하나
누가
- 개인투자자 — 유튜브로 정보를 얻는 사람
- 리서치 · 콘텐츠팀 — 출연자 · 인용 채널 고를 때
어떻게 · 사기 전 두 번 클릭
- 종목 — 전에 떴을 때 어땠나
- 채널 — 이 채널 말은 맞았나
수익 · 운영
- 언급 급증 알림 구독 · 채널 적중 리포트 · 데이터 API
- 운영비 ≈ 0 — 무료 티어 + 하루 1회 배치 (YouTube 쿼터 300유닛)
부록 ① DB 테이블 문서 · 전체 docs/db.md
스키마 4개 · 테이블 9개 · 영상 1 → 언급 n → 수익률 1
| 스키마.테이블 | 주요 컬럼 | 키 |
|---|---|---|
| yt.channels | channel_id, title, handle, category(stock·crypto), subscriber_count, uploads_playlist_id | PK channel_id |
| yt.videos | video_id, channel_id, title, published_at, view_count | PK video_id · FK channel_id → channels |
| market.assets | asset_id(시장:심볼), market, symbol, name, aliases(JSON), asset_type(stock·etf·crypto·index·theme), benchmark_id, tracked, corp_code | PK asset_id |
| market.prices | asset_id, trade_date, close, volume | PK id · UQ(asset_id, trade_date) |
| market.disclosures | rcept_no, asset_id, rcept_dt, report_nm, kind(실적·계약·자금조달·주요사항·지분·기타) | PK rcept_no |
| mentions.mentions | video_id, channel_id, asset_id, matched_text, confidence, stance(bull·bear·neutral), stance_words, published_at | PK id · UQ(video_id, asset_id) |
| mentions.unmatched | video_id, channel_id, title, published_at | PK video_id |
| stats.event_returns | mention_id, asset_id, channel_id, market, kind, stance, benchmark_id, t0_date, t0_close, r5·r20·r60, x5·x20·x60, vol_ratio, near_disclosure | PK id · UQ mention_id |
| stats.summaries | key(bull:overall:20 · channel:<id>:20 …), value(JSON: n, mean, win_rate, excess_mean, histogram …) | PK key |
부록 ② API 설명 문서 · 전체 docs/api.md · 실행
GET 으로 읽고, 쓰기는 배치만 · 엔드포인트 36개(읽기 24 · 내부·상태 12) 중 주요 8개
| 기능 | 요청 | 입력값 | 응답(JSON) |
|---|---|---|---|
| 수익률 요약 | GET /stats/v1/summary | scope(overall·market:KRX·channel:<id>·asset:<id>) · horizon(5·20·60) · stance(bull) | { key, value:{ n, mean, median, win_rate, excess_mean, vol_ratio_median, histogram[] } } |
| 채널 랭킹 | GET /stats/v1/channels/ranking | horizon · metric(excess_mean·win_rate·asset_win_rate) · min_n=30 · min_assets=10 | { top[], bottom[], all[] } 각 { channel_id, n, mean, excess_mean, win_rate } |
| 종목 언급별 수익률 | GET /stats/v1/assets/{asset_id}/events | asset_id (예 KRX:005930) | { items:[{ mention_id, channel_id, stance, published_at, t0_date, r5, r20, r60, x20, vol_ratio, near_disclosure }] } |
| 급증 종목 | GET /mentions/v1/mentions/trending | days=7 · limit · stance | { items:[{ asset_id, mentions, prev_mentions, channels }] } |
| 언급 목록 | GET /mentions/v1/mentions | asset_id · channel_id · stance · after_id(커서) | { items:[{ video_id, asset_id, matched_text, stance, stance_words, published_at }], next_cursor } |
| 종가·거래량 | GET /market-data/v1/assets/{asset_id}/prices | from · to · limit | { items:[{ trade_date, close, volume }] } |
| 채널 목록 | GET /youtube/v1/channels | — | { items:[{ channel_id, title, handle, category, subscriber_count }] } |
| 수집 실행(배치 전용) | POST /{service}/internal/sync | 헤더 X-Internal-Token · days · background | { added, updated, skipped[] } · 상태는 /internal/sync-status |
공통: JSON · ISO 날짜 · 수익률은 소수(0.024 = 2.4%) · 오류는 RFC 9457 problem+json · 응답마다 X-Request-ID · GET 은 Cache-Control 10분 · 명세 contracts/*.yaml(Spectral · oasdiff CI)
마무리
감사합니다
공개 API 6개 → 하루 한 번 가공 → 한 질문에 답하는 숫자
5조 · 김지환 · 김현준 · 오선호 · 조인성 · 채재희