증권사 API, 어디로 갈 것인가
자동매매 봇을 만들겠다고 마음먹은 순간, 가장 먼저 맞닥뜨린 관문이 증권사 API였습니다. 마침 제가 쓰는 증권사가 두 곳이었고, 둘 다 Open API를 제공하고 있었죠. 비교해본 끝에, 한국투자증권의 KIS API로 방향을 잡았습니다.
이유는 명확했습니다. REST 기반이라 호출이 편리하고, 문서화도 비교적 잘 되어있었습니다.
(GIT https://github.com/koreainvestment/open-trading-api.git) 저장소가 있을 정도로 관리가 되고 있습니다
무엇보다 실제 돈이 별로 없는데 이 계좌에 돈이 더 적게 있어서 선택했습니다
인증 구조: OAuth 토큰과의 첫 만남
KIS API는 OAuth 토큰 기반 인증을 씁니다. 흐름은 단순합니다. 앱 키와 시크릿으로 접근 토큰을 발급받고, 이후 모든 API 호출 헤더에 이 토큰을 실어 보내면 됩니다. 유효 시간은 약 24시간.
그런데 여기서 첫 번째 삽질이 찾아왔습니다. 봇이 새벽에 재시작되면 토큰을 매번 새로 발급받아야 했는데, 발급에 시간도 걸리고 호출 제한도 있었습니다. 고민 끝에 토큰을 파일에 캐싱하는 방식을 택했습니다. 시작할 때 캐시가 살아 있으면 그대로 쓰고, 만료됐으면 새로 받는 구조로 구성했습니다.
구현한 주요 API 기능들
KIS API 클라이언트에서 실제로 쓰고 있는 기능들을 갈래별로 정리해봅니다.
시세 조회 — 종목별 현재가, 등락률, 거래량. 과거 100일치 일봉(기술적 지표용), 1분봉(VWAP 계산용), 그리고 매수·매도 호가 10단계 데이터까지 끌어옵니다.
주문 실행 — 매수는 언제나 지정가입니다. 이유는 아래 삽질기에서 말씀드리겠습니다. 매도는 상황에 따라 지정가와 시장가를 동적으로 전환합니다.
계좌 조회 — 보유 종목, 수량, 평균 매수가, 주문 가능 금액, 당일 체결 내역. 시장 정보 — KOSPI/KOSDAQ 등락률, 10개 주요 업종 등락률, 외국인·기관 순매수 동향. 이 정도면 봇이 판단을 내리기에 충분한 재료입니다.
삽질 1: 시장가 매수의 뜻밖의 함정
초기 버전에서는 체결 확률을 높이겠다는 생각에 시장가 매수를 썼습니다. 그랬더니 실전에서 이런 메시지가 돌아왔습니다.
잔고는 분명히 충분한데. 한참을 헤맸습니다.
원인은 이랬습니다. KIS API의 시장가 주문은 상한가 기준으로 증거금을 산정합니다. 코스닥 종목의 상한가는 +30%이니, 1만 원짜리를 시장가로 넣으면 13,000원 기준으로 증거금이 잡히는 겁니다. 소액 계좌에서는 이 차이가 치명적이었습니다.
결론은 간단했습니다. 매수는 무조건 지정가. 이후로 이 에러는 완전히 사라졌습니다. 매도는 사정이 다릅니다 — 손절처럼 속도가 생명인 경우엔 시장가를, 여유 있는 익절에는 지정가를 씁니다.
삽질 2: 쉬지 않는 봇의 부작용
봇은 24시간 돌고 있는데, 주식시장은 그렇지 않습니다. 장이 닫힌 시간에도 시세 API를 꾸준히 두드리고 있었던 거죠. 의미 없는 데이터를 받아오며 API 호출 횟수만 소모하고 있었습니다.
처방은 어렵지 않았습니다. 장 운영 시간을 확인해서 장외에는 API 호출 자체를 건너뛰고, 잔고 동기화는 장중에만, 일일 리포트는 마감 이후에만 보내도록 정리했습니다. 사소해 보이지만, 이걸 방치하면 에러 로그가 산처럼 쌓이고 API 제한에 걸립니다. 작은 구멍이 배를 가라앉히는 법이니까요.
삽질 3: API 응답이 건네는 깜짝 선물들
KIS API와 지내다 보면, 응답값에서 예상 밖의 것들을 만나게 됩니다.
숫자여야 할 데이터가 문자열(string)로 오는 일이 잦습니다. 빈 값도 null이 아니라 빈 문자열 ""로 도착합니다. 형변환할 때 예외 처리를 빠뜨리면 그 자리에서 봇이 멈춥니다.
더 무서운 건 현재가가 0원으로 오는 경우입니다. 장외 시간이나 거래 정지 종목에서 발생하는데, 이걸 그대로 수익률 계산에 넣으면 -100% 손절 판정이 내려집니다. 조용히, 그러나 확실하게 보유 종목을 날려버리는 버그입니다. 현재가가 0 이하일 때는 매매 로직 자체를 건너뛰도록 가드를 세워두어야 합니다.
초당 API 호출 제한도 늘 의식해야 합니다. 짧은 시간에 요청을 쏟아부으면 차단당합니다. 캐싱으로 중복 호출을 줄이는 것, 이게 가장 현실적인 답이었습니다.
API 호출, 줄일 수 있는 만큼 줄이기
실전에서 API 호출을 줄이기 위해 두 가지를 적용했습니다.
캐싱 — 일봉 데이터, 시장 지수, 업종별 지수는 사이클당 한 번만 조회하고 캐시합니다. 같은 사이클 안에서 종목마다 같은 데이터를 반복 요청하는 낭비를 막는 거죠.
조기 필터링 — 1,000원 미만 초저가주는 시세 조회 직후 바로 걸러냅니다. 그러면 이후 단계인 체결강도 조회, 일봉 조회 같은 API 호출이 통째로 사라집니다. 주당 가격이 매수 가능 금액을 넘는 종목도 마찬가지. 일찍 자르는 게 결국 전체를 가볍게 만듭니다.
수수료라는 조용한 적
자동매매에서 의외로 간과하기 쉬운 존재가 수수료입니다. 한 번의 매매에서는 티가 안 나지만, 매매 횟수가 쌓이면 수익을 조금씩, 그러나 꾸준히 깎아먹습니다.
* 위 수수료는 작성 시점 기준이며, 증권사 이벤트나 정책에 따라 달라질 수 있습니다.
이 수치를 코드에 직접 박아두지 않고 설정 파일에서 관리합니다. 증권사 수수료가 바뀌면 설정 한 줄만 고치면 되니까요. 익절 기준가를 계산할 때도 수수료를 자동 보정해서, "분명 익절인데 수수료 빼니 손해"라는 허무한 상황을 사전에 차단합니다.
실전과 모의투자, 같은 듯 다른 세계
KIS API는 실전과 모의투자, 두 개의 세계를 제공합니다. 도메인부터 체결 방식까지, 생각보다 많은 것이 다릅니다.
원래대로라면 모의투자로 충분히 검증을 거친 뒤에 실전으로 넘어가는 게 맞습니다. 모의에서 멀쩡하던 코드가 실전에서 에러를 뱉는 일은, 생각보다 자주 일어나니까요.
하지만 저는 그러지 않았습니다. 투자금이 엄~청 적은 금액이었거든요(5만원도 안되는 금액). 잃어도 수업료라 생각할 수 있는 수준이었기에, 고민 없이 실전으로 바로 들어갔습니다. 덕분에 모의투자 기능을 정교하게 다듬는 데까지는 손이 닿지 않았습니다.
대신, 시뮬레이션은 다른 방식으로 했습니다. 장이 마감되고 나면 AI를 이용해서 그날의 로그를 분석하고, 거기서 발견된 문제를 수정합니다. 수정된 로직이 제대로 동작하는지는 실제 로그 데이터를 기반으로 시뮬레이션해서 확인했습니다. 모의투자 환경 대신, 실전 로그가 곧 저의 테스트베드였던 셈입니다.
이번 파트에서 얻은 교훈들
KIS API와 씨름하면서 몸으로 배운 것들입니다.
소액 계좌에서는 상한가 기준 마진이 발목을 잡습니다. 지정가만 써도 체결에 문제는 없었습니다.
의미 없는 호출이 쌓이면 에러 로그와 API 제한이라는 이중고가 찾아옵니다.
이 한 줄의 방어 코드가 없으면, 멀쩡한 보유 종목이 가짜 손절로 사라집니다.
같은 데이터를 매번 새로 요청하는 건, API 제한과 속도 양쪽 모두에서 손해입니다.
하드코딩된 수수료는 언젠가 반드시 틀어집니다. 그리고 그 '언젠가'는 생각보다 빨리 옵니다.
다음 편에서는 GPT를 종목 선정에 투입한 이야기를 꺼내보겠습니다. 여기서부터가 이 프로젝트의 진짜 재미입니다.