AlgoLab Blog · 한국투자증권 공식 도구 점검 · 2026

KIS 전략 빌더 자동매매 — 코드로 확인한 한계 5가지

KIS · strategy_builder 2026-11-08 · 약 7분 읽기 · 알고랩 AlgoLab
한 줄 요약

한국투자증권 공식 저장소의 strategy_builder는 코딩 없이 전략을 만들고 신호를 보는 데는 좋지만, 그대로는 자동매매가 아닙니다. 2026-11-08 기준 main 3a9be59 코드를 확인한 결과, 정해진 시간에 혼자 돌아가는 루프가 없고 주문은 사람이 확인 버튼을 눌러야 나갑니다. 빌더에서 넣은 손절·익절은 실주문 경로에서 쓰이지 않습니다. 장기 이동평균 100일 이상은 일봉 조회가 100건에서 끊겨 늘 HOLD가 나옵니다. 그리고 실전 모드에서는 주문 헤더에 모의투자 TR인 VTTC0802U가 들어갑니다.

KIS 공식 GitHub 샘플을 받아 보면 strategy_builder 폴더가 있습니다. README는 "비주얼 전략 빌더 + 실시간 주문 실행"이라고 소개하고 80개 지표, 57개 캔들 패턴, 10개 프리셋 전략을 내세웁니다. 상담에서도 "한투가 만든 무료 도구가 있던데 그걸로 충분하지 않나요?"라는 질문이 나옵니다.

그래서 화면이 아니라 코드가 실제로 무엇을 하는지 확인했습니다. API 키 없이 돌리려고 kis_auth를 가짜 모듈로 바꾼 하네스를 만들었고, 주문은 한 건도 나가지 않습니다. 하네스는 /downloads/kis_strategy_builder_check.py에 있습니다. 같은 저장소의 백테스터는 KIS 공식 백테스터 점검에서 따로 다뤘습니다.

목차

  1. 구조 — 버튼을 눌러야 한 번 돈다
  2. 손절·익절 설정이 실주문에 안 들어간다
  3. 실전 모드에서 모의 TR이 나간다
  4. 장기 이평 100일 이상은 늘 HOLD
  5. 매도 조건을 비우면 '음봉이면 매도'
  6. 무인으로 돌리려면 무엇을 더해야 하나

1. 구조 — 버튼을 눌러야 한 번 돈다

백엔드는 FastAPI(포트 8000), 화면은 Next.js(포트 3000)입니다. ./start.sh로 둘을 띄우고 http://localhost:3000에 접속합니다. 흐름은 이렇습니다.

단계화면호출무엇이 일어나나
1/builder없음지표·조건·리스크를 고르고 브라우저 localStorage에 저장
2/executePOST /api/strategies/execute종목마다 generate_signal() 1회 → BUY/SELL/HOLD + 강도
3주문 확인 창POST /api/orders/executeOrderConfirmModal에서 수량·가격 확인 후 1건 주문

코드 전체에서 while·setInterval을 찾아보면 호가 웹소켓 폴링, 인증 상태 확인, 미리보기 문법 강조에만 있습니다. 전략을 몇 분마다 다시 돌리거나 장 시작에 맞춰 실행하는 스케줄러는 없습니다. examples/run_all_strategies.py도 10개 전략의 신호를 한 번 출력하고 끝나며 주문은 내지 않습니다.

즉 이 도구는 "신호를 보여 주고, 사람이 확인하면 주문해 주는 화면"입니다. 화면을 꺼 두면 아무 일도 일어나지 않습니다. 이것 자체는 결함이 아니라 설계입니다. 다만 "자동매매"를 기대하고 설치했다면 출발점이 다릅니다.

2. 손절·익절 설정이 실주문에 안 들어간다

빌더 4단계에서 손절·익절·트레일링 스톱을 퍼센트로 넣을 수 있고, 프리셋 golden_cross도 stopLoss 5%가 켜져 있습니다. 이 값이 어디로 가는지 따라가 봤습니다.

# strategy_core/preset/golden_cross.py
'risk': {
    'stopLoss':     {'enabled': True,  'percent': 5},
    'takeProfit':   {'enabled': False, 'percent': 10},
    'trailingStop': {'enabled': False, 'percent': 3},
},

# core/order_executor.py
self.risk_manager = RiskManager()     # 인자 없음 → 기본값 -5.0 / 10.0

백테스트 결과와 실제 계좌가 갈라지는 지점입니다. 백테스트는 손절을 적용한 성과를 보여 주는데, 실행 화면에서는 같은 손절이 지켜지지 않습니다. 손절은 사람이 직접 보고 매도하거나, 별도의 감시 코드를 붙여야 합니다. 봇에 넣어야 할 안전장치 목록은 자동매매 안전장치 가이드에 정리해 두었습니다.

3. 실전 모드에서 모의 TR이 나간다

가장 중요한 발견입니다. 백엔드의 모드 값과 주문 코드의 모드 값이 서로 다른 문자열을 씁니다.

# backend/state.py
current_mode: 현재 모드 ("vps" 또는 "prod")

# backend/routers/orders.py
env_dv = get_current_mode()               # "vps" 또는 "prod"
executor = OrderExecutor(env_dv=env_dv)

# core/order_executor.py
if self.env_dv == "real":                 # "prod"는 여기 걸리지 않는다
    tr_id = "TTTC0802U" if signal.action == Action.BUY else "TTTC0801U"
else:
    tr_id = "VTTC0802U" if signal.action == Action.BUY else "VTTC0801U"

kis_auth._url_fetch()는 모의투자일 때 T로 시작하는 TR을 V로 바꿔 주기만 하고, 반대 방향 변환은 하지 않습니다. 하네스에서 실전 상태(isPaperTrading() = False)로 두고 돌린 결과입니다.

env_dv='prod'  -> header tr_id sent: VTTC0802U
env_dv='real'  -> header tr_id sent: TTTC0802U

같은 저장소의 data_fetcher.py는 is_real = env_dv in ("real", "prod")로 두 값을 모두 받기 때문에 잔고조회 TTTC8434R와 취소 TTTC0013U는 실전에서 맞게 나갑니다. 주문만 어긋납니다. 실서버로 보내 보지는 않았으므로 어떤 에러가 오는지는 확인하지 못했습니다. 다만 실전 도메인에 모의 TR을 보내는 조합이라 정상 체결을 기대하기는 어렵습니다.

설령 이 부분을 고쳐도 TTTC0802U는 구 TR입니다. 현재 공식 예제 order_cash.py는 매수 TTTC0012U, 매도 TTTC0011U에 EXCG_ID_DVSN_CD(KRX·NXT·SOR)를 넣습니다. 신·구 대조는 KIS 주문 tr_id 신·구 대조에 있습니다. 직접 고친다면 이렇게 바꿉니다.

# core/order_executor.py 수정 예 (직접 검증 후 사용)
is_real = self.env_dv in ("real", "prod")
if is_real:
    tr_id = "TTTC0012U" if signal.action == Action.BUY else "TTTC0011U"
else:
    tr_id = "VTTC0012U" if signal.action == Action.BUY else "VTTC0011U"

params = {
    "CANO": trenv.my_acct, "ACNT_PRDT_CD": trenv.my_prod,
    "PDNO": signal.stock_code, "ORD_DVSN": ord_dvsn,
    "ORD_QTY": str(ord_qty), "ORD_UNPR": ord_unpr,
    "EXCG_ID_DVSN_CD": "KRX",
}

모의에서 잘 되다가 실전으로 바꾸는 순간 막히는 패턴은 이 도구만의 문제가 아닙니다. 전환 체크리스트는 KIS 모의→실전 전환을 보십시오. 주문이 거부될 때 응답 코드 읽는 법은 KIS API 에러코드 11가지에 있습니다.

4. 장기 이평 100일 이상은 늘 HOLD

신호 계산에 쓰는 일봉은 data_fetcher.get_daily_prices()가 FHKST03010100(국내주식기간별시세)을 한 번만 불러 받습니다. 공식 API 설명에 따르면 이 TR은 "한 번의 호출에 최대 100건"입니다. 연속조회가 없으니 100봉보다 긴 기간이 필요하면 데이터가 모자랍니다.

# core/data_fetcher.py
start_date = (datetime.now() - timedelta(days=days + 50)).strftime("%Y%m%d")
params = {
    "FID_COND_MRKT_DIV_CODE": "J",
    "FID_INPUT_ISCD": stock_code,
    "FID_INPUT_DATE_1": start_date,
    "FID_INPUT_DATE_2": end_date,
    "FID_PERIOD_DIV_CODE": "D",
    "FID_ORG_ADJ_PRC": "0",
}

골든크로스 프리셋은 장기 MA를 화면에서 최대 200까지 고를 수 있습니다. 가짜 일봉(한 번에 최대 100건만 돌려주게 만든 합성 데이터)으로 단기 5일을 고정하고 장기만 바꿔 돌린 결과입니다.

long= 20 required_days= 30 rows= 30 -> HOLD / 크로스 조건 미충족
long= 60 required_days= 70 rows= 70 -> HOLD / 크로스 조건 미충족
long= 90 required_days=100 rows=100 -> SELL / 데드크로스 발생 (MA5 < MA90)
long= 95 required_days=105 rows=100 -> SELL / 데드크로스 발생 (MA5 < MA95)
long=100 required_days=110 rows=100 -> HOLD / 데이터 부족
long=120 required_days=130 rows=100 -> HOLD / 데이터 부족
long=200 required_days=210 rows=100 -> HOLD / 데이터 부족
MA 20 · 60 · 90 · 95 — 계산됨 MA 100 · 120 · 200 — 늘 HOLD FHKST03010100 1회 = 최대 100봉 에러 없이 HOLD "데이터 부족"만 찍히므로 화면에서는 '조건이 안 맞는 날'처럼 보인다
골든크로스 프리셋, 단기 MA 5 고정. 합성 데이터라 휴장일은 넣지 않았습니다.

위험한 이유는 에러가 아니라는 점입니다. 화면에는 HOLD와 "데이터 부족"이 떠서 "오늘은 신호가 없구나"로 읽힙니다. 빌더에서 만든 전략도 같습니다. SMA(5)가 SMA(120)를 상향 돌파하는 전략을 넣었더니 HOLD 데이터 부족이 나왔습니다. 실제 시장 데이터는 휴장일만큼 행이 더 줄어들므로 경계는 하네스보다 조금 더 앞쪽일 수 있습니다. 100건을 넘겨 받는 방법은 KIS 일봉 FHKST03010100 100건 제한에 있습니다.

5. 매도 조건을 비우면 '음봉이면 매도'

빌더에서 청산 조건을 비워 두면 어떻게 될까요. 실행기 코드에 기본값이 있습니다.

# strategy_core/executor.py — execute_from_builder_state()
strategy_def = parse_strategy(
    name=name_snake,
    name_ko=strategy_name,
    buy_condition=buy_condition,
    sell_condition=sell_condition or "close < open",
)

청산을 정하지 않은 전략은 당일 종가가 시가보다 낮으면 매도 신호를 냅니다. 화면 로그에는 "매수 조건"만 찍히고 이 기본값은 표시되지 않습니다. 주문 단계의 다른 기본 동작도 알아 둘 만합니다. 하네스로 모의 모드에서 주문 함수를 불렀을 때 KIS로 갈 바디입니다.

ORDER VTTC0802U {"CANO": "00000000", "ACNT_PRDT_CD": "01", "PDNO": "005930",
                 "ORD_DVSN": "01", "ORD_QTY": "3", "ORD_UNPR": "0"}
ORDER VTTC0801U {"CANO": "00000000", "ACNT_PRDT_CD": "01", "PDNO": "005930",
                 "ORD_DVSN": "00", "ORD_QTY": "10", "ORD_UNPR": "50000"}

6. 무인으로 돌리려면 무엇을 더해야 하나

정리하면 이 도구는 전략 아이디어를 빠르게 조립하고, 그날 신호를 눈으로 확인하는 데 쓸모가 있습니다. 80개 지표와 .kis.yaml 내보내기, 생성되는 Python 코드는 공부용으로도 좋습니다. 무인 자동매매로 쓰려면 최소한 아래가 더 필요합니다.

필요한 것strategy_builder 현재더할 내용
정해진 시각 실행버튼 클릭 1회스케줄러 또는 상주 루프, 휴장일 확인 CTCA0903R
손절·익절설정만 저장보유 종목 감시 → 조건 충족 시 매도
주문 TRprod에서 VTTC0802U모드 판정 수정, 신 TR TTTC0012U·EXCG_ID_DVSN_CD
과거 데이터1회 최대 100봉구간 나눠 반복 조회 또는 로컬 저장
주문 수량매수 1주·매도 전량예수금·비중 기준 수량 계산
체결 확인·기록미체결 캐시체결 조회 TTTC8001R, 매매 일지, 알림

저장소 README도 외부 접속이 필요하면 인증된 역방향 프록시 뒤에 두라고 권합니다. 기본값은 127.0.0.1에서만 열립니다. 이 목록을 직접 채울지 맡길지 고민된다면 직접 만들기 vs 외주의 판단 기준이 도움이 됩니다.

확인 범위. 2026-11-08에 GitHub API로 받은 koreainvestment/open-trading-api main 3a9be59(2026-10-27 커밋)의 코드입니다. 저장소는 Python 3.11 이상을 요구하는데, 이 PC는 3.10이라 하네스에 ExceptionGroup 대체 한 줄을 넣어 돌렸습니다. 실서버·모의 서버에 주문은 보내지 않았고, 웹 화면은 띄우지 않고 코드로 확인했습니다. 저장소는 예고 없이 바뀌므로 설치 전에 같은 부분을 다시 확인하십시오.

알고랩은 프로그램 제작 도구를 제공하며 투자자문업·투자일임업을 영위하지 않습니다. 이 글의 전략 이름과 신호는 도구 동작을 설명하기 위한 예시이며 특정 종목이나 매매를 권하지 않습니다. 과거 데이터로 본 결과는 미래 수익을 보장하지 않습니다.

자주 묻는 질문

KIS strategy_builder만 설치하면 자동매매가 되나요?

그대로는 아닙니다. 2026-11-08 기준 코드에는 정해진 시각에 전략을 다시 돌리는 스케줄러가 없고, 주문은 실행 화면에서 확인 버튼을 눌러야 1건씩 나갑니다. 신호 확인과 반자동 주문 도구에 가깝습니다.

빌더에서 설정한 손절은 실제로 작동하나요?

실주문 경로에서는 작동하지 않습니다. 손절·익절 값은 .kis.yaml로 내보내 백테스터에서 쓰이고, 실행 화면의 주문 코드는 리스크 관리 함수를 부르지 않습니다. 실계좌 손절은 직접 관리하거나 감시 코드를 붙여야 합니다.

실전투자 모드로 바꾸면 주문이 정상적으로 나가나요?

코드상 실전 모드 값은 prod인데 주문 코드는 real만 실전으로 판정해, 주문 헤더에 모의투자 TR VTTC0802U가 들어갑니다. 실서버 응답은 확인하지 않았지만, 실전에 쓰기 전에 모드 판정과 신 TR TTTC0012U로 수정하고 소액으로 검증하는 것이 안전합니다.

120일 이동평균 전략이 계속 HOLD만 나옵니다. 왜 그런가요?

일봉을 FHKST03010100으로 한 번만 조회하는데, 이 TR은 한 번에 최대 100건입니다. 장기 이동평균이 100일 이상이면 필요한 봉 수를 못 채워 '데이터 부족' HOLD가 나옵니다. 구간을 나눠 반복 조회하도록 고쳐야 합니다.

공식 도구로 만든 전략을 무인으로 돌리고 싶다면

strategy_builder에서 조립한 조건을 스케줄 실행, 손절 감시, 신 TR 주문, 체결 기록까지 갖춘 프로그램으로 맞춤 제작합니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기