본문 바로가기

Project/Hot stocks

주식 추천 mcp 프로젝트 - 미국장 하이브리드 + 국내 수급 확장

320x100

그런데 실제로 돌려서 결과를 받아 보니 이상했다. 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보다 좁다
  • 창구 데이터는 어느 증권사를 거쳤는지일 뿐, 최종 주체를 증명하지 않는다
  • 가집계는 말 그대로 추정치다

마지막으로, 현재 결과는 내 프롬프트·코드를 기반으로 한 참고 자료일 뿐이며 투자 판단과 책임은 본인에게 있다는 점을 유의해야 한다.

전체 코드: GitHub - SeongUk18/mcp_for_finding_stocks

728x90