한국투자증권 Open API × n8n 주식 모니터링 자동화
이 강의는 무엇을 만드나요?
매일 장 마감(오후 4시) 후 자동으로 삼성전자(또는 원하는 종목)의 시세·거래량을 조회하고, 거래량이 평균 대비 1.5배 이상 급증하면 디스코드로 알림을 보내며, 결과를 구글 시트에 자동 기록하는 시스템을 만듭니다.
학습 목표
이 과정을 마치면 수강생은 다음을 할 수 있습니다.
- 한국투자증권 KIS Developers에서 Open API를 신청하고 App Key·Secret을 발급받을 수 있다.
- n8n 워크플로에서 OAuth 2.0 토큰을 자동으로 발급·갱신할 수 있다.
- HTTP Request 노드로 시세·기간별 시세 API를 호출하고 응답을 해석할 수 있다.
- JavaScript 노드와 Edit Fields 노드로 응답 데이터를 가공하여 평균 거래량·거래량 비율 같은 파생 지표를 만들 수 있다.
- IF 노드로 조건 분기를 만들고, 디스코드 웹훅과 구글 시트 노드로 결과를 외부로 내보낼 수 있다.
전체 시스템 한눈에 보기
flowchart TD
A[Schedule Trigger<br/>매일 16:00] --> B[토큰 발급<br/>POST]
B --> C[현재가 조회<br/>GET]
C --> D[Edit Fields<br/>오늘 시세 정제]
D --> E[기간별 시세<br/>GET 35일]
E --> F[JavaScript<br/>평균거래량 계산]
F --> G[Edit Fields1<br/>거래량비율]
G --> H{IF<br/>비율>1.5}
H -->|true| I[Discord 알림<br/>+ Sheet 기록]
H -->|false| J[Sheet 기록만]
모듈 구성
| # |
모듈명 |
핵심 산출물 |
소요 시간 |
| 0 |
사전 준비 |
한투증권 계좌, 디스코드 계정, 구글 계정 |
30분 |
| 1 |
한투증권 API 신청과 인증 이해 |
App Key·App Secret 확보 |
45분 |
| 2 |
OAuth 토큰 자동 발급 워크플로 |
Schedule + HTTP(POST) 노드 |
45분 |
| 3 |
주식 현재가 시세 조회 |
HTTP(GET) + Edit Fields |
60분 |
| 4 |
기간별 시세 + 평균거래량 계산 |
HTTP(GET) + JavaScript |
60분 |
| 5 |
거래량 비율 조건 분기 |
Edit Fields + IF |
30분 |
| 6 |
디스코드 알림 + 구글 시트 연동 |
Discord Webhook + Google Sheets |
60분 |
모듈 0 — 사전 준비
0.1 무엇을 미리 준비해야 하나요?
이 강의는 실제로 동작하는 시스템을 만드는 실습 중심이라, 시작 전에 다음 3가지가 반드시 준비되어 있어야 합니다.
| 준비물 |
용도 |
비고 |
| 한국투자증권 계좌 |
API 신청 |
실전·모의 모두 가능 |
| 디스코드 계정 |
알림 수신 |
무료. 서버 1개 새로 만들 예정 |
| 구글 계정 |
데이터 적재 |
구글 시트 사용 |
| n8n 환경 |
워크플로 실행 |
Cloud / Self-hosted 모두 가능 |
🔀 실전·모의 모두 지원
본 강의는 두 환경 모두를 지원합니다. 모듈 2·3·4의 핵심 단계에서 [실전 / 모의] URL을 양쪽 병기하므로 본인 환경에 맞는 줄만 따라가면 됩니다. 워크플로 구조와 매개변수는 두 환경이 동일합니다.
- 🟢 실전: 만료 걱정 없이 장기 운영 가능, 신분증 인증 필요
- 🟡 모의: 신분증 없이 빠른 시작, 일정 기간 후 재신청 필요
0.2 왜 n8n인가?
flowchart TD
A[직접 코딩<br/>Python] --> X[유연하지만<br/>유지보수 부담]
B[n8n 워크플로] --> Y[노드 조합으로<br/>빠른 프로토타입]
C[유료 SaaS<br/>Zapier 등] --> Z[비용 부담<br/>커스터마이즈 한계]
Y --> R[입문자 최적]
n8n은 노드를 연결하는 시각적 자동화 도구입니다. 코드 없이도 API 호출·조건 분기·메시지 발송을 조합할 수 있어, 금융 데이터 자동화의 첫 걸음으로 적합합니다.
모듈 1 — 한국투자증권 API 신청과 인증 이해
1.1 학습 포인트
- KIS Developers 서비스 신청 절차
- App Key / App Secret의 의미와 보관 원칙
- OAuth 2.0 Client Credentials (2-legged) 방식 이해
- 토큰 유효기간(24시간)과 갱신 주기
1.2 신청 절차 흐름
flowchart TD
A[한투 홈페이지<br/>로그인] --> B[트레이딩<br/>Open API]
B --> C[KIS Developers<br/>신청·조회]
C --> D[휴대폰 인증]
D --> E[약관 동의]
E --> F[계좌 선택<br/>실전·모의]
F --> G[App Key·Secret<br/>발급 완료]
1.3 OAuth 2-legged 방식이란?
| 구분 |
2-legged (개인/일반법인) |
3-legged (제휴법인) |
| 인증 단계 |
App Key·Secret만으로 토큰 발급 |
최종 사용자 동의 코드 필요 |
| 토큰 유효기간 |
24시간 |
Access 3개월 / Refresh 1년 |
| 재발급 |
6시간 이내 동일 토큰 반환 |
갱신 절차 별도 |
| 본 강의 사용 |
✅ |
❌ |
💡 6시간 룰: 토큰 발급 후 6시간 이내에 다시 요청하면 같은 토큰이 반환됩니다. 6시간 지나면 새 토큰으로 갱신됩니다. 매일 1회 발급 워크플로면 충분합니다.
1.4 보안 체크리스트
모듈 2 — OAuth 토큰 자동 발급 워크플로
2.1 학습 포인트
- Schedule Trigger 설정 (매일 16:00 자동 실행)
- HTTP Request 노드의 POST 메서드와 Body 구성
- 응답에서
access_token 추출 → 다음 노드에서 참조
2.2 워크플로 구조
flowchart TD
A[Schedule Trigger<br/>Days · 1 · 4pm] --> B[HTTP Request<br/>POST tokenP]
B --> C[output access_token<br/>access_token_expired<br/>token_type Bearer]
2.3 핵심 매개변수 정리
| 항목 |
값 |
| Method |
POST |
| URL (실전) |
https://openapi.koreainvestment.com:9443/oauth2/tokenP |
| URL (모의) |
https://openapivts.koreainvestment.com:29443/oauth2/tokenP |
| Authentication |
None |
| Body Content Type |
JSON |
| Body fields |
grant_type, appkey, appsecret |
2.4 Body 필드 값
| Name |
Value |
| grant_type |
client_credentials |
| appkey |
(발급받은 App Key) |
| appsecret |
(발급받은 App Secret) |
⚠️ 함정 주의: API 호출 시 토큰 앞에 반드시 Bearer (공백 포함) 를 붙여야 합니다. Bearer eyJ0eXAi... 형태로 사용하세요.
모듈 3 — 주식 현재가 시세 조회
3.1 학습 포인트
- KIS API 문서 읽는 법 (Method·URL·TR ID·Header·Body)
- Header에 토큰을 동적으로 끼워 넣는 표현식
{{ $json.access_token }}
- 응답 JSON 구조 해석 (
output.stck_prpr = 현재가)
- Edit Fields 노드로 필요한 필드만 추리기
3.2 워크플로 위치
flowchart TD
A[Schedule] --> B[Token 발급]
B --> C[HTTP Request1<br/>현재가 GET]
C --> D[Edit Fields<br/>5개 필드 정제]
D --> E[다음 모듈로]
3.3 HTTP Request1 매개변수
| 항목 |
값 |
| Method |
GET |
| URL (실전) |
https://openapi.koreainvestment.com:9443/uapi/domestic-stock/v1/quotations/inquire-price |
| URL (모의) |
https://openapivts.koreainvestment.com:29443/uapi/domestic-stock/v1/quotations/inquire-price |
| Authentication |
None |
| Send Query Parameters |
ON · Using Fields Below |
| Send Headers |
ON · Using Fields Below |
Query Parameters
| Name |
Value |
의미 |
| FID_COND_MRKT_DIV_CODE |
J |
KRX(한국거래소) — 일반 종목은 항상 J |
| FID_INPUT_ISCD |
005930 |
종목코드 (삼성전자 예시) |
Header Parameters
| Name |
Value |
| content-type |
application/json; charset=utf-8 |
| authorization |
Bearer {{ $json.access_token }} |
| appkey |
(App Key) |
| appsecret |
(App Secret) |
| tr_id |
FHKST01010100 (실전·모의 공통) |
| custtype |
P (개인) |
3.4 시장 코드 J·NX·UN 비교
| 코드 |
의미 |
사용 빈도 |
| J |
KRX (한국거래소·코스피/코스닥) |
대부분 사용 |
| NX |
NXT (넥스트레이드·ATS) |
일부 종목만 |
| UN |
KRX + NXT 통합 |
시세 조회만 가능 |
3.5 응답에서 우리가 쓸 필드
| n8n 필드명 |
API 응답 키 |
의미 |
| 종목코드 |
output.stck_shrn_iscd |
종목 단축코드 |
| 현재가 |
output.stck_prpr |
현재가(원) |
| 등락률 |
output.prdy_ctrt |
전일 대비 등락률(%) |
| 오늘거래량 |
output.acml_vol |
누적 거래량 |
| 날짜 |
new Date().toISOString().slice(0,10) |
YYYY-MM-DD |
💡 종목코드와 날짜는 String, 나머지 3개는 Number 타입으로 설정해야 다음 단계 계산이 정상 동작합니다.
모듈 4 — 기간별 시세 + 평균거래량 계산
4.1 학습 포인트
- 두 번째 시세 API(
inquire-daily-itemchartprice) 호출
- 동적 날짜 계산 (오늘 기준 35일 전)
- output1 / output2 구조 이해 (output2는 일자별 배열)
- JavaScript 노드로 최근 20일 평균거래량 산출
4.2 왜 35일 전인가?
flowchart TD
A[목표: 영업일 20개] --> B[주말 8일 제외<br/>4주 기준]
B --> C[공휴일 여유<br/>3-5일]
C --> D[총 35일이면<br/>20영업일 확보 안전]
4.3 HTTP Request2 매개변수
| 항목 |
값 |
| Method |
GET |
| URL (실전) |
https://openapi.koreainvestment.com:9443/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice |
| URL (모의) |
https://openapivts.koreainvestment.com:29443/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice |
| tr_id |
FHKST03010100 (실전·모의 공통) |
Query Parameters (총 6개)
| Name |
Value |
의미 |
| FID_COND_MRKT_DIV_CODE |
J |
시장 분류 |
| FID_INPUT_ISCD |
005930 |
종목코드 |
| FID_INPUT_DATE_1 |
(35일 전 동적 계산) |
시작일자 |
| FID_INPUT_DATE_2 |
(오늘 동적 계산) |
종료일자 |
| FID_PERIOD_DIV_CODE |
D |
일/주/월/년 — D=일봉 |
| FID_ORG_ADJ_PRC |
0 |
0=수정주가, 1=원주가 |
FID_INPUT_DATE_1 표현식 (n8n)
{{ (() => {
const d = new Date();
d.setDate(d.getDate() - 35);
return d.toISOString().slice(0,10).replaceAll('-', '');
})() }}
FID_INPUT_DATE_2 표현식
{{ new Date().toISOString().slice(0,10).replaceAll('-', '') }}
4.4 JavaScript 노드 — 평균거래량 계산
// 1. output2를 배열로 변환
const rows = Object.values($json.output2 || {});
// 2. 데이터 없으면 null 반환
if (rows.length === 0) {
return {
...$json,
평균거래량: null,
};
}
// 3. 최근 20개만 추출
const recent = rows.slice(0, 20);
// 4. 평균 거래량 계산
const avgVolume =
recent.reduce((sum, item) => sum + Number(item.acml_vol), 0) /
recent.length;
// 5. 결과 반환
return {
...$json,
평균거래량: Math.round(avgVolume),
};
💡 Mode는 Run Once for All Items로 설정합니다.
4.5 산출 결과 확인 포인트
flowchart TD
A[output1<br/>요약 정보] --> Z[그대로 유지]
B[output2<br/>일자별 배열] --> Y[평균거래량으로<br/>축약]
Z --> R[최종 출력]
Y --> R
모듈 5 — 거래량 비율 조건 분기
5.1 학습 포인트
- 두 노드의 데이터를 식 안에서 동시에 참조 (
$('Edit Fields').item.json.오늘거래량)
- 표현식의 노드 이름 의존성 (이름 변경 시 식도 변경 필요)
- IF 노드의
Numbers > is greater than 비교
5.2 거래량 비율 표현식
{{
$('Edit Fields').item.json['오늘거래량']
/
Number($json['평균거래량'])
}}
| 좌측 |
우측 |
의미 |
| 오늘거래량 |
평균거래량 |
비율 (1.0 = 평균과 같음) |
5.3 분기 임계값 가이드
| 임계값 |
의미 |
알림 빈도 |
| 1.2배 |
약한 신호 |
자주 발생 |
| 1.5배 |
중간 신호 |
권장 — 본 강의 기본값 |
| 2.0배 |
강한 신호 |
드물게 발생 |
| 3.0배 |
이상 거래 |
매우 드물게 |
5.4 IF 노드 설정
| 항목 |
값 |
| 좌측 (value 1) |
{{ $json['거래량비율'] }} |
| 비교 |
Numbers → is greater than |
| 우측 (value 2) |
1.5 |
모듈 6 — 디스코드 알림 + 구글 시트 연동
6.1 학습 포인트
- 디스코드 서버 생성 → 채널 웹훅 URL 확보
- Webhook 방식의 장점 (OAuth 인증 불필요, URL만으로 연동)
- n8n Credentials에 Discord Webhook 등록
- 구글 시트 헤더 설계와 자동 적재
6.2 디스코드 연동 흐름
flowchart TD
A[discord.com<br/>로그인·가입] --> B[서버 추가<br/>n8n 테스트용]
B --> C[일반 채널<br/>설정 톱니]
C --> D[연동 메뉴<br/>웹후크 만들기]
D --> E[웹후크 URL<br/>복사]
E --> F[n8n Credentials<br/>Discord Webhook]
6.3 구글 시트 헤더 설계
| 컬럼 |
의미 |
데이터 타입 |
| 종목명 |
회사 이름 |
문자열 |
| 종목코드 |
6자리 코드 |
문자열 |
| 현재가 |
종가(원) |
숫자 |
| 오늘거래량 |
누적 거래량 |
숫자 |
| 거래량비율 |
평균 대비 배수 |
숫자 |
| 등락률 |
전일 대비(%) |
숫자 |
| 날짜 |
YYYY-MM-DD |
문자열 |
6.4 두 갈래 분기 후 처리
flowchart TD
A[IF 노드] -->|true 거래량 급증| B[Discord 메시지]
A -->|false 평범| C[Sheet 추가만]
B --> D[Sheet 추가]
C --> E[종료]
D --> E
실습 챌린지 — 마지막 과제
수강생이 강의를 완주한 뒤 도전할 수 있는 단계별 과제입니다.
| 난이도 |
과제 |
핵심 학습 |
| ⭐ |
종목을 SK하이닉스(000660)로 변경 |
종목코드 변경 지점 파악 |
| ⭐⭐ |
임계값을 2.0배로 상향 후 1주일 모니터링 |
임계값 민감도 체험 |
| ⭐⭐ |
종목 3개를 동시에 모니터링 |
Loop / Split In Batches 노드 |
| ⭐⭐⭐ |
DART 공시 API와 결합해 알림에 최근 공시 포함 |
API 다중 결합 |
| ⭐⭐⭐ |
등락률 ±5% 이상도 함께 알림 조건 추가 |
IF 노드의 OR/AND |
자주 발생하는 오류와 해결
| 증상 |
원인 |
해결 |
| 401 Unauthorized |
토큰 만료 또는 Bearer 누락 |
Bearer 접두사 확인, 토큰 재발급 |
| 401 또는 도메인 오류 |
URL과 키의 환경 불일치 (실전 URL + 모의 키 등) |
두 환경의 URL과 키를 같은 환경으로 일치 |
| 평균거래량 = null |
output2가 비어있음 |
시작일자가 너무 가까운지 확인 |
| 표현식 에러 |
노드 이름이 바뀜 |
$('노드명') 안의 이름 동기화 |
| Discord 메시지 미수신 |
웹후크 URL 오타 |
Credentials 재등록 |
참고 자료
- KIS Developers: https://apiportal.koreainvestment.com
- n8n 공식 문서 — HTTP Request 노드, Schedule Trigger, IF 노드
- Discord Developer 문서 — Webhooks
- DART Open API (확장 과제용)
다음 단계
이 과정을 마친 수강생은 다음 후속 과정으로 확장할 수 있습니다.
- 다종목 포트폴리오 모니터링 — Loop 노드 + 종목 리스트 시트
- 알림에 차트 이미지 첨부 — QuickChart 또는 Chart.js 렌더링
- 이상 신호 분류 자동화 — Claude/GPT API 연동으로 알림에 해석 추가
- 백테스트 연동 — 누적된 시트 데이터로 거래량 급증 시그널의 사후 수익률 검증