📚 연재 — 클로드코드로 주식투자 에이전트 만들기 ① 목적과 설계 · ② 한투 API 연결 · ③ 분석가 두뇌 · ④ 매매와 안전장치 · ⑤ 운영 자동화
토대부터
분석가가 아무리 똑똑해도 먹일 데이터가 없으면 소용없다. 그래서 가장 먼저 한국투자증권(KIS) Open API 연결부터 만들었다. 시세, 잔고, 일봉, 그리고 나중엔 주문까지 — 봇이 시장과 대화하는 모든 통로가 여기를 지난다.
구조는 봇 본체(stockbot)와 분리해 kis-core라는 공용 모듈로 뒀다. 인증·통신을 한곳에 모아두면 나중에 다른 프로그램에서도 재사용할 수 있기 때문이다.
~/projects/kis-core/
├── config.py # 모의/실전 도메인·TR_ID 분기
├── token_manager.py # 토큰 발급·캐싱
├── client.py # 시세·잔고·일봉·주문
└── .env # 앱키 (git 제외)
시작 전 준비 — 키 발급과 설치 (how-to)
따라 만들려면 먼저 열쇠 두 개가 필요하다. 증권사 API 키와 LLM API 키다. 내가 한 순서 그대로 정리하면 이렇다.
1) 한국투자증권 Open API 신청
- KIS 개발자 포털(KIS Developers)에서 Open API 사용을 신청하고 **앱키(App Key)·앱시크릿(App Secret)**을 발급받는다.
- 처음부터 실계좌로 가면 위험하니 모의투자 계좌를 함께 신청한다. 이 시리즈의 모든 검증은 모의투자(가상자금 1억)로 했다.
2) Gemini API 키 발급
- 분석가 3명의 두뇌로 쓸 LLM 키다. Google AI Studio에서 발급받는다. (모델 선택·비용은 3편에서 자세히)
3) 파이썬 환경과 패키지
- 파이썬 3.x에 HTTP 통신용
requests면 일단 시작할 수 있다. macOS는 뒤에 나올 SSL 문제 때문에certifi도 챙긴다.
pip install requests certifi
4) 키는 코드가 아니라 .env에 분리
발급받은 키는 소스에 박지 말고 .env에 넣어 git에서 제외한다(.gitignore에 .env 추가).
KIS_APP_KEY=발급받은_앱키
KIS_APP_SECRET=발급받은_앱시크릿
KIS_ACCOUNT=50193178-01 # 모의투자 계좌번호
KIS_ENV=vps # vps=모의, prod=실전
GEMINI_API_KEY=발급받은_제미나이_키
이렇게 분리해두면 실수로 키를 공개 저장소에 올리는 사고를 막는다. 준비가 끝났으니, 이제 실제로 부딪힌 함정들을 보자.
Photo by Chris Ried on Unsplash
첫 번째 함정 — 토큰 1분 제한
KIS API는 OAuth 토큰으로 인증한다. 그런데 발급에 제약이 있다. 토큰 재발급은 1분에 한 번만 가능하다. 처음엔 이걸 모르고 테스트하느라 연달아 호출했더니 이런 에러가 떴다.
{"error_code":"EGW00133","error_description":"접근토큰 발급 잠시 후 다시 시도하세요(1분당 1회)"}
해결책은 토큰을 한 번 발급받으면 파일에 캐싱해서 재사용하는 것이다. 토큰은 24시간 유효하므로, 매 호출마다 발급하지 않고 만료 직전에만 갱신하면 된다. 게다가 여러 프로세스가 동시에 발급을 시도하면 충돌하므로, 파일 락(file lock)으로 한 번에 하나만 발급하게 막았다.
def get_token():
tok = _cached_valid() # 유효한 캐시가 있으면 재사용
if tok:
return tok
with open(LOCK_FILE, "w") as lock:
fcntl.flock(lock, fcntl.LOCK_EX) # 동시 발급 방지
tok = _cached_valid() # 락 잡은 사이 다른 프로세스가 갱신했을 수도
if tok:
return tok
# 실제 발급 + 캐시 저장
나중엔 1분 제한에 걸렸을 때 봇이 죽지 않고 62초 기다렸다가 자동으로 재시도하는 로직까지 더했다. 무인으로 돌릴 거라면 이런 자가 복구가 필수다.
두 번째 함정 — 모의투자 서버의 변덕
KIS는 실전 계좌와 모의투자 계좌의 도메인·포트가 다르다. 모의는 openapivts.koreainvestment.com:29443, 실전은 :9443이다. 주문 TR_ID도 모의는 앞에 V가 붙는다(예: 매수 VTTC0802U). 그래서 설정 한 줄로 둘을 전환할 수 있게 만들었다.
문제는 모의투자 서버가 간헐적으로 500 에러를 뱉는다는 것이었다. 일봉 조회에서 처음 발견했는데, 같은 요청인데 어떤 때는 되고 어떤 때는 안 됐다. 데이터 기간이 길수록 더 자주 실패했다.
이건 임시방편으로 넘길 문제가 아니라 구조적으로 막아야 했다. 그래서 조회 함수에 재시도 + 기간 자동 축소 로직을 넣었다.
def inquire_daily(symbol, days=90, retries=3):
for d in [days, 70, 50, 30]: # 기간을 단계적으로 줄여가며
for attempt in range(retries): # 각 기간마다 재시도
try:
res = requests.get(...)
if res.status_code == 500:
time.sleep(0.8); continue # 일시 오류 → 재시도
...
return data
except RequestException:
time.sleep(0.8)
raise RuntimeError("기간 축소·재시도 모두 실패")
서버가 가끔 토라져도 봇이 알아서 버티고, 최악의 경우에도 30일치는 확보한다. 나중에 이 재시도 로직을 잔고조회·현재가 조회에도 공통으로 적용했다.
세 번째 함정 — SSL 인증서
맥에서 첫 외부 호출을 했더니 이런 에러가 났다.
[SSL: CERTIFICATE_VERIFY_FAILED] unable to get local issuer certificate
이건 코드 버그가 아니라 macOS 파이썬이 SSL 인증서 묶음을 못 찾아서 생기는 아주 흔한 문제다. 파이썬에 같이 설치된 인증서 설치 스크립트를 실행하면 해결된다.
/Applications/Python*/Install\ Certificates.command
# 또는
python3 -m pip install --upgrade certifi
Photo by Florian Olivo on Unsplash
첫 성공 — 잔고 조회
함정들을 하나씩 넘고 나서 드디어 잔고 조회가 됐다.
[모의투자(vps)] 계좌 50193178-01
----------------------------------------
예수금: 99,985,092원
평가금액: 99,985,092원
총손익: +0원
----------------------------------------
보유 종목 없음
모의투자 신청 금액 1억이 예수금에 찍혔다. 토큰 발급 → 잔고 조회까지 전 과정이 처음으로 끝까지 돌아간 순간이었다. 별것 아닌 숫자 같지만, 봇이 실제 계좌와 통신하는 토대가 섰다는 뜻이라 꽤 든든했다.
여기에 현재가·밸류에이션(PER·PBR·EPS) 조회와 일봉 조회까지 붙이니, 분석가들이 먹을 데이터가 다 준비됐다. 실제 데이터를 처음 받아봤을 때 예상과 다른 형식(예: EPS가 '6564.00' 같은 소수점 문자열)에 또 한 번 막혔지만, 이런 잔버그는 모의투자로 미리 돌려보며 하나씩 잡는 게 정석이다.
배운 것
이 단계에서 가장 크게 배운 건 분석 로직이 아니라 방어 코드의 중요성이었다. 외부 API는 언제든 느려지고, 틀린 형식을 주고, 일시적으로 죽는다고 가정해야 한다. 재시도, 캐싱, 폴백, 타임아웃 — 화려하진 않지만 무인 시스템의 생존을 좌우하는 건 결국 이런 기본기였다.
조금 더 풀어 쓰면, 세 함정이 서로 다른 얼굴을 하고 있지만 뿌리는 같았다. 내 코드가 “요청을 보내면 답이 온다”고 믿었다는 것이다.
토큰 1분 제한은 “발급받으면 성공한다”는 믿음이 깨진 경우다. 모의투자 서버의 500 응답은 “서버가 살아 있다”는 믿음이 깨진 경우고, SSL 문제는 “연결이 된다”는 가장 밑바닥 믿음이 깨진 경우다. 세 층위 모두에서 실패가 가능하다는 걸 코드가 알고 있어야 밤새 도는 프로그램이 된다.
여기서 실제로 도움이 된 습관이 하나 있다. API를 부르는 코드를 한 겹 감싸서, 그 안에서만 재시도·타임아웃·로깅을 처리하는 것이다. 분석 로직 쪽에서는 그냥 get_price(code)를 부르고, 실패하면 예외가 아니라 None을 받는다. 이렇게 나눠 두면 나중에 증권사를 바꾸거나 캐싱을 넣을 때 손댈 곳이 한 군데뿐이다. 반대로 분석 코드 곳곳에 requests.get이 흩어져 있으면, 재시도 로직을 넣는 순간 열 군데를 고쳐야 한다.
또 하나 배운 건 실패를 조용히 삼키면 안 된다는 점이다. 재시도 끝에 실패했는데 빈 값을 그냥 넘기면, 봇은 “데이터가 없다”와 “가져오지 못했다”를 구분하지 못한다. 전자는 정상적인 상황(거래 정지 종목 등)이고 후자는 장애다. 이 둘을 같게 처리하면, 어느 날 API가 종일 죽어 있었는데도 로그상으로는 아무 일 없었던 것처럼 보인다. 실패는 반드시 흔적을 남기게 해야 한다.
이 두 가지 — 호출부를 한 겹 감싸는 것, 실패에 흔적을 남기는 것 — 만 지켜도 초기 개발 속도는 거의 안 떨어지면서 운영 안정성은 크게 달라진다.
다음 편에서는 드디어 봇의 “두뇌”를 만든다. 분석가 3명의 프롬프트를 설계하고, LLM을 실제로 호출해 첫 분석 결과를 받아보는 과정이다.
따라 하기 — KIS API 키 발급 5단계
한국투자증권(KIS) 오픈 API를 직접 붙이려면 다음 순서로 준비한다.
- 계좌 개설: 한국투자증권 계좌를 만든다(비대면 앱). 자동매매 검증용 모의투자 계좌도 함께 신청한다.
- 개발자 등록: KIS Developers(apiportal.koreainvestment.com)에서 개발자 등록을 한다.
- 앱키·앱시크릿 발급: 앱을 등록하면 APP KEY와 APP SECRET이 나온다. 이 두 값이 인증의 핵심이다.
- 계좌 정보 확인: 계좌번호 앞 8자리(CANO)와 상품코드(ACNT_PRDT_CD, 보통
01)를 확인한다. - 환경변수(.env)에 저장: 발급값을 코드에 직접 넣지 말고
.env로 관리한다.
KIS_ENV=vps # 모의투자(vps) / 실전(prod)
KIS_APP_KEY=...
KIS_APP_SECRET=...
KIS_CANO=00000000
KIS_ACNT_PRDT_CD=01
흔한 함정 (미리 알아두기)
- 접근토큰은 발급 후 재발급에 약 1분 제한이 있다 → 토큰을 캐싱해 재사용해야 막히지 않는다.
- 모의투자 서버는 간헐적으로 500·타임아웃을 낸다 → 재시도 로직이 필수다.
- 실전과 모의투자는 도메인·TR_ID가 다르다 →
KIS_ENV값으로 분기해 실수로 실계좌에 주문 나가는 걸 막는다.
📚 ← ① 목적과 설계 · 다음 편 → ③ 분석가 3명의 두뇌
※ 이 글은 개인 프로젝트 제작기로, 특정 종목의 매수·매도 권유나 투자 조언이 아닙니다. 자동매매는 손실 위험이 있으며 모든 투자 판단과 책임은 본인에게 있습니다.
댓글
✍️ 편집자 모드 — 이 댓글은 공개되지 않고 편집자에게만 전달됩니다.