그런데 실제로 돌려서 결과를 받아 보니 이상했다. LLM이 뽑아준 미국 종목의 현재가가 그 시각 실시간 시세와 달랐다. 장이 열려 있는데도 어제 종가를 기준으로 판단하고 있었던 것이다.
원인을 찾다가, 한투 API에 해외주식 시세가 이미 있다는 걸 알게 됐다. 3편에서는 미국장을 yfinance로만 짜느라 아예 후보에 안 넣었던 부분이다.
쓰는 김에 내용을 조금 더 보완할 방법을 찾다 보니, 국내 쪽도 다시 보게 됐다. 한투 국내 API 74개 중에 내가 쓰고 있던 건 4개였다.
| 항목 | 3편 시점 | 지금 |
| 미국장 도구 | 4개 | 5개 |
| 한국장 도구 | 2개 | 4개 |
| 미국장 시세 | yfinance (당일 데이터 없음) | 한투 해외(무료실시간) + yfinance 폴백 |
| 쓰는 한투 API | 국내 4개 | 국내 16개 + 해외 11개 |
원칙은 그대로다.
"MCP 서버는 데이터만 수집하고, 판단·해석은 LLM에게 위임한다."
A. 미국장 — yfinance만으로는 당일이 안 보였다
실측부터
프리마켓이 돌고 있는 시각(ET 07:40)에 같은 종목을 두 소스로 동시에 조회해 봤다.
| 날짜 | 한투 해외 | yfinance |
| 당일 | 219.50 (거래량 520,439) | 데이터 없음 |
| 전일 | 217.50 | 217.50 |
3편 구조로는 장이 열려 있어도 어제 종가만 보고 있었다는 뜻이다.
여기서 세 가지가 더 나왔다.
① 응답에 "stat": "무료실시간"이 박혀 있다
15분 지연이 아니었다. 순위 API 응답의 메타 필드에 명시돼 있다. 3편 마무리에서 "yfinance는 15분 지연일 수 있다"고 한계로 적었던 부분이 그냥 해결됐다.
② 분봉이 stim: 040000 ~ etim: 200000
ET 04:00~20:00. 프리마켓·정규장·애프터마켓 전 구간을 준다. 미국 단타는 프리마켓 갭이 핵심인데 3편 버전으로는 그 구간이 아예 보이지 않았다.
③ 거래량 정밀도가 다르다
| 날짜 | 한투 | yfinance |
| 8/7 | 105,669,440 | 105,669,400 |
| 8/11 | 101,273,148 | 100,991,600 |
yfinance는 100 단위로 뭉갠다. 8/11은 28만 주 차이다. 거래량 급증 배율을 판단 근거로 쓰는 구조에서 무시하기 애매한 크기다.
하지만, 한투 해외 API에는 13F·공매도·애널리스트가 아예 없다. 그리고 일봉을 1회에 100건까지만 주기 때문에 MA200이나 1년 상대강도를 만들 수 없다.
그래서 항목별로 갈랐다.
| 데이터 | 소스 | 이유 |
| 실시간 체결가·당일 봉 | 한투 | yfinance 일봉엔 당일이 없음 |
| 프리·애프터 분봉 | 한투 | yfinance로는 안 보임 |
| 호가 10단계·잔량 | 한투 | yfinance에 없는 데이터 |
| 상승률·거래량·급등·시총 순위 | 한투 | 세레니티와 별개인 후보 발굴 채널 |
| 한글 뉴스 | 한투 | 재료 성격 빠르게 훑기 좋음 |
| 이평·볼린저·ATR·상대강도 | yfinance | 1년치 장기 시계열 필요 |
| 13F·공매도·애널리스트 | yfinance | 한투에 없음 |
| 영문 뉴스 | yfinance | 연관도(is_primary_subject) 판별 가능 |
3편의 "API 키 0개" 장점은 죽이고 싶지 않아서 이렇게 처리했다.
- 키가 없으면 전부 yfinance로 폴백 → 3편과 동일하게 동작
- 어느 소스를 썼는지 price_source(kis_realtime / yfinance)로 응답에 표시
- 한투 키는 mcp-market-data/.env에 이미 있으면 그대로 읽음 (중복 설정 안 함)
애매한 건 서버가 응답에 박아둔다는 2편·3편 방침의 연장이다.
신규 도구 get_us_market_movers
세레니티 X 스캔과 별개인 두 번째 후보 발굴 채널이다. 상승률 / 거래량 / 급등 / 시가총액 순위를 가져온다.
⚠️ min_volume 필터는 사실상 필수다. 안 걸면 $1짜리 잡주가 상위를 전부 먹는다.
잡은 버그: 당일 봉을 붙였더니 거래량 배율이 0.0배
이평이나 상대강도가 하루씩 늦는 게 싫어서, 한투에서 받은 당일 봉을 yfinance 시계열 뒤에 이어 붙였다. 그랬더니 이렇게 나왔다.
거래량 568,716 / 20일 평균 125,776,095 = 0.0배
프리마켓 부분 거래량을 완결된 20일 평균과 비교하고 있었다. 이 상태면 "거래량이 20일 평균 대비 1.5배 이상" 같은 필터는 장중에 무조건 탈락한다. 장 끝나야만 맞는 지표가 되는 것이다.
수정은 이렇게 했다.
- volume_ratio_vs_avg20은 직전 완결 세션 기준으로 고정
- 진행 중 거래량은 partial_session_volume으로 분리하고, "완결 데이터와 직접 비교하지 말 것" note를 붙임
- 결과: 0.0배 → 0.8배
⚠️ 일반화하면 진행 중인 데이터를 완결된 데이터와 같은 축에 놓으면 안 된다. 실시간 지표를 다루는 곳이면 어디든 걸리는 함정이다.
B. 한국장
3편을 다 쓰고 나서야 보였다. 미국장에 넣어준 공매도·상대강도·호가·애널리스트가 정작 한국장엔 없었다. 한국이 데이터가 훨씬 풍부한데도 그랬다.
한투 국내 API는 74개다. 2편에서 쓴 건 현재가 / 분봉 / 일봉 / 투자자동향 4개. 이번에 16개를 실호출로 검증해서 붙였다.
도구 2개 → 4개
| 도구 | 상태 | 내용 |
| get_kr_market_movers | 신규 | 거래량·등락률·체결강도·VI 순위 |
| get_current_price_and_chart | 확장 | + ATR14 · MA120/200 · 52주 고저 · 코스피 대비 상대강도 · 호가 · 예상체결 |
| get_institutional_buying | 대폭 확장 | + 가집계 · 창구 · 외국계 · 프로그램 · 공매도 · 융자잔고 |
| get_kr_news_and_opinion | 신규 | 한투 속보 + 증권사 투자의견·목표주가 + 실적 컨센서스 |
한국에만 있는 무기 — 창구(회원사) 분석
미국엔 아예 존재하지 않는 데이터다. 어느 증권사 창구에서 사고팔았는지가 실시간으로 나온다. 실제 삼성전자 응답:
매수 창구: UBS, KB증권, 골드만, BNK증권, 미래에셋증권
매도 창구: KB증권, 미래에셋증권, 키움증권, JP모간, 삼성증권
외국계 창구(UBS·골드만)가 매수 상위에 있으면 외국인 자금 유입으로 볼 여지가 있다. 물론 이것도 해석은 LLM에 넘긴다.
3편에서 "미국엔 수급 데이터가 없다"고 한탄하면서 상대강도·13F로 대체했던 것과 정확히 대칭되는 구도다. 한쪽은 없어서 우회했고, 한쪽은 있는데 안 쓰고 있었다.
수급 7종의 신선도가 전부 다르다
get_institutional_buying 하나가 성격이 다른 7가지를 돌려주게 됐다.
| 항목 | 성격 | 신선도 |
| daily_confirmed | 일별 확정 순매수 (금액, 원) | 장 마감 후 확정 |
| daily_detail | 일별 순매수 (수량). 외국인 등록/미등록 분리 | 장 마감 후 확정 |
| intraday_estimate | 장중 외인·기관 추정 가집계 | 추정치. 확정 아님 |
| brokers / foreign_brokers | 창구별 매매 | 장중 실시간 |
| program | 프로그램매매 순매수 | 장중 실시간 |
| short_sale / loan | 공매도 비중 / 신용융자 잔고 | 전 거래일까지 |
3편에서 도입한 data_freshness 패턴을 한국장에도 그대로 붙였다. 확정치와 추정치를 한 덩어리로 내려주면 LLM이 섞어서 판단한다.
상대강도가 없으면 판단이 안 된다
2편까지는 "외인이 3일 연속 순매수"만 봤다. 그런데 이건 시장이 올라서 같이 오른 건지, 종목 자체가 강한 건지 구분이 안 된다.
그래서 지수 일봉(코스피 0001 / 코스닥 1001)을 받아서 초과수익률을 계산하게 했다. 3편의 SPY/QQQ 대비 RS를 한국에 이식한 셈이다.
잡은 버그 4개 — 실데이터를 안 보면 못 잡는 것들
| 증상 | 원인 | 수정 후 |
| 거래량 1~5위가 전부 KODEX 인버스 | 대상제외 마스크 미설정 | 알트·삼성전자·삼기에너지 |
| 등락률 1위 +8.3%, 2위 +29.98% | 한투 응답 순서가 등락률 순이 아님 | 서버에서 재정렬 |
| 체결강도 1위가 거래량 702주 스팩 | 최소 거래량 미적용 | 솔본 149,288주 |
| 삼성전자 융자잔고가 16억 주 | MRKT_DIV_CLS_CODE=1(코스피). 3이 종목 | 8,420만 주 |
융자잔고 버그는 이렇게 왔다.
HTTP 200
rt_cd = 0
msg1 = "정상처리 되었습니다"
완벽하게 정상 응답이다. 응답 코드만 검사하는 코드로는 절대 못 잡는다. 그런데 내용을 열어 보면 이렇다.
{"bsop_date": "20260814", "stck_prpr": "6579.04", "rmnd_stcn": "1619264288", ...}
stck_prpr: 6579.04. 삼성전자 주가일 리가 없다. 코스피 지수값이었다. 구분 코드를 잘못 넣어서 종목이 아니라 시장 전체를 조회하고 있었던 것이다.
⚠️ "200 OK에 rt_cd=0"은 데이터가 맞다는 뜻이 아니다. 값을 눈으로 봐야 발견되는 종류의 버그고, 붙인 API 16개를 전부 실호출로 검증한 이유가 이거다.
첫 번째도 꽤 치명적이었다
필터를 안 걸면 거래량 순위가 ETF·인버스로 도배된다. 거래량 상위 5개가 전부 KODEX 시리즈면 후보 발굴 도구로서는 완전히 무용지물이다.
한투는 10자리 대상제외 마스크(1111111101)를 받는다. 자리별 의미는 투자위험 / 관리종목 / 정리매매 / 불성실공시 / 우선주 / 거래정지 / ETF / ETN / 신용주문불가 / SPAC 순이다.
페이징 없이는 MA200을 못 만든다
- 한투 국내 일봉: 1회 100건
- 지수 일봉: 1회 50건
MA200과 60일 상대강도를 계산하려면 조회 구간을 뒤로 밀면서 페이징해야 한다. 검증해 보니 250개, 2025-08-06 ~ 2026-08-14, 중복 0, 정렬 정상으로 나왔다.
토큰 발급 분당 1회 제한 (2편 보강)
2편에서 안 다룬 부분이다. token_manager가 토큰을 메모리에만 캐시하고 있었다.
- 프로세스 재시작마다 새로 발급 시도 → 403
- 한국장·미국장 서버가 같은 키를 쓰면 서로 발급을 밀어내면서 더 자주 터짐
- 개발 중에 실제로 겪었다 (HTTPStatusError: 403 Forbidden)
%LOCALAPPDATA%\kis-mcp\token.json에 파일 캐시를 두는 걸로 해결했다. 프로세스가 재시작돼도, 서버가 두 개여도 같은 토큰을 공유한다. 발급에 실패해도 만료 전 토큰이 남아 있으면 그걸 재사용하고, 네트워크 오류와 403은 구분해서 처리한다.
마무리
정리하면 이렇다.
- 미국장은 한투 실시간 + yfinance 장기 데이터의 하이브리드가 됐다. 키가 없으면 3편 그대로 동작한다.
- 한국장은 3편에서 미국장에 넣어준 것들(호가·공매도·상대강도·애널리스트)을 뒤늦게 따라잡았고, 창구 분석처럼 한국에만 있는 것도 붙었다.
- 버그 5개는 전부 응답 코드가 아니라 값을 눈으로 봐서 잡았다.
한계도 남아 있다.
- 한투 해외는 지원 거래소·종목 범위가 yfinance보다 좁다
- 창구 데이터는 어느 증권사를 거쳤는지일 뿐, 최종 주체를 증명하지 않는다
- 가집계는 말 그대로 추정치다
마지막으로, 현재 결과는 내 프롬프트·코드를 기반으로 한 참고 자료일 뿐이며 투자 판단과 책임은 본인에게 있다는 점을 유의해야 한다.
'Project > Hot stocks' 카테고리의 다른 글
| 주식 추천 MCP 프로젝트 - 주말 자동 복기와 규칙 갱신 루프 (0) | 2026.08.22 |
|---|---|
| 주식 추천 MCP 프로젝트 - GitHub 리포트 자동 발행 파이프라인 (0) | 2026.08.15 |
| 주식 추천 mcp 프로젝트 - 미국주식 시세/수급 데이터 수집 MCP 서버 구현 (0) | 2026.08.09 |
| 주식 추천 mcp 프로젝트 - 한국투자증권 API 기반 시세/수급 데이터 수집 MCP 서버 구현 (0) | 2026.03.01 |
| 주식 추천 mcp 프로젝트 - 데이터 수집 mcp 구현 (0) | 2026.02.28 |