업데이트(2025-10-16): 브래킷 주문(손절/익절) 자동화, exec-date 모드, 갭 가드(최대 허용 시가), 당일 매수 옵션(MOC/ATC), 정정·취소(kt10002/kt10003), 에러 트러블슈팅 표 추가.
안녕하세요, TechOracle입니다. 이 글은 키움증권 REST API로 주문→체결→브래킷 자동화까지 한 번에 실전 구축하는 서술형 가이드입니다. 지난 글(분봉 수집)과 달리 여기서는 “실전 실패를 줄이는 운영 설계”에 초점을 둡니다. 토큰/레이트/재시도/감사로그 같은 비기능 요구사항이 바로 수익률을 결정합니다.
#요약 (바로 적용)
- TR 코드 표준: 매수 kt10000, 매도 kt10001, 정정 kt10002, 취소 kt10003 (프로젝트 표준 기억값 기준)
- 리스크 가드: gap_guard.max_open_px 초과 시 자동 스킵, 대안은 당일 MOC/ATC 진입
- 브래킷: 체결 직후 손절/익절 동시 지정 + ATR/PSAR 트레일링 지원
- idempotency: 주문 중복 방지 키(client_order_id) 필수
- 운영 스케줄(Asia/Seoul): PreMarket 08:55 / Monitor 09:00 / Close 16:00 / EOD 20:00
원칙: 주문 엔진과 데이터(분석) 레이어를 분리 — 장애가 나도 손쉽게 복구됩니다.
1) 아키텍처(이야기로 이해하기)
- 신호 수집: v4_today 등에서 오늘의 후보 리스트를 가져옵니다.
- 리스크 필터: risk_config.yaml의 갭 가드·최소 유동성·허용 시장·단일 종목 최대 수량 등 체크.
- 주문 생성: kt10000/kt10001로 진입. client_order_id는 날짜+심볼+전략 해시로 생성.
- 체결 모니터링: monitor_fills_and_brackets.py가 체결 이벤트를 듣고 브래킷을 자동 제출.
- 정정·취소: 슬리피지/체결 지연 시 kt10002/kt10003로 관리.
- 감사로그: 모든 요청/응답/예외를 파일+DB로 남겨, 다음 날 리플레이/리컨실리에이션.
장점: 신호/주문/체결/브래킷이 루즈 커플링이라, 규칙을 바꿔도 전체가 망가지지 않습니다.
2) 사전 준비물
- 파이썬 가상환경: .venv64 (없으면 .venv32)
- 베이스: D:\stock
- 토큰 캐시: D:\stock\secrets\kiwoom_token_prod.json (운영) / ..._mock.json (모의)
- 엔드포인트/헤더: D:\stock\cfg\kiwoom_endpoints.json (운영/모의 도메인, TR 코드, 공통 헤더 api-id, Authorization 등)
- 로그: D:\stock\logs\send_open_orders.log
3) risk_config.yaml (핵심 규칙)
# D:\stock\cfg\risk_config.yaml
max_positions: 12
max_qty_per_order: 0.02 # 계좌 평가액 대비 비율
allow_markets: ["KOSPI","KOSDAQ"]
liquidity:
min_daily_amount: 500000000 # 5억 이상 권장
min_price: 1000
max_price: 300000
entry:
mode: "signal_day_moc" # signal_day_moc | next_day_open | next_day_moc
gap_guard:
enabled: true
max_open_premium_pct: 2.5 # 다음날 시가가 +2.5% 초과면 스킵
bracket:
enabled: true
stop_k: 0.5 # ATR k
take_r: 1.5 # R 단위 이익실현
trailing:
enabled: true
method: "ATR" # ATR | PSAR
atr_window: 14
갭 가드와 당일 매수(MOC/ATC) 옵션은 실전 손실을 줄이는 가장 현실적인 레버입니다.
4) 주문 생성기 — send_open_orders.py
실패를 계획한 코드가 좋은 코드입니다. 토큰 만료/429/5xx/일시적 네트워크를 idempotent하게 흡수하세요.
# D:\stock\app\execution\send_open_orders.py
import json, time, uuid, logging, requests
from datetime import datetime
API_BASE = "https://api.kiwoom.com"
TR_BUY = "kt10000"
TR_SELL = "kt10001"
log = logging.getLogger("send_orders")
def idemp_key(symbol, side, strategy):
d = datetime.now().strftime("%Y%m%d")
return f"{d}-{symbol}-{side}-{strategy}-{uuid.uuid4().hex[:8]}"
def place_order(token, symbol, side, qty, price=None, order_type="MKT", strategy="v4"):
url = f"{API_BASE}/uapi/domestic-stock/v1/trading/order"
headers = {"Authorization": token, "api-id": TR_BUY if side=="BUY" else TR_SELL,
"Content-Type": "application/json",
"X-Idempotency-Key": idemp_key(symbol, side, strategy)}
payload = {
"symbol": symbol,
"side": side, # BUY | SELL
"order_type": order_type, # MKT | LMT
"qty": int(qty),
"limit_price": price,
"account": "YOUR_ACCOUNT_ID"
}
retry = 0
while True:
r = requests.post(url, headers=headers, json=payload, timeout=20)
if r.status_code in (429, 500, 502, 503, 504) and retry < 3:
retry += 1; time.sleep(2 ** retry); continue
r.raise_for_status()
return r.json()
Idempotency 키가 없으면 네트워크 재시도 때 중복 체결 악몽이 생깁니다.
5) 체결 감시 & 브래킷 제출 — monitor_fills_and_brackets.py
체결을 감지하면 손절/익절을 바로 걸고, 트레일링은 주기적으로 업데이트합니다.
# D:\stock\app\execution\monitor_fills_and_brackets.py
# 의사코드 수준 (핵심 로직만)
def on_fill(fill):
# 1) 손절/익절 가격 계산 (R = entry - stop)
entry = fill.avg_price
atr = get_atr(fill.symbol) # 분석 레이어에서 조회
stop = entry - atr * CFG.bracket.stop_k
take = entry + (entry - stop) * CFG.bracket.take_r
submit_oco_bracket(fill.symbol, stop, take)
def submit_oco_bracket(symbol, stop, take):
# OCO(One Cancels the Other) 의사 구현:
# 익절 체결되면 손절 자동 취소, 반대도 동일
create_stop_order(symbol, stop)
create_takeprofit_order(symbol, take)
def on_tick(symbol, price):
if CFG.bracket.trailing.enabled:
new_stop = trail_by_atr(symbol, price)
if new_stop > current_stop(symbol):
amend_stop_order(symbol, new_stop) # kt10002 정정
실제 API 호출부는 프로젝트의 공통 래퍼에 묶어 테스트 더블(모의)로 쉽게 교체하세요.
6) 정정·취소 — kt10002/kt10003
- 정정(kt10002): 지정가 슬리피지·트레일링 업데이트에 사용
- 취소(kt10003): 미체결·중복 제출 방지
# amend_cancel.py - 요약
def amend_order(order_id, new_price):
headers = {"Authorization": token, "api-id": "kt10002"}
payload = {"order_id": order_id, "new_price": new_price}
return post_json(URL, headers, payload)
def cancel_order(order_id):
headers = {"Authorization": token, "api-id": "kt10003"}
payload = {"order_id": order_id}
return post_json(URL, headers, payload)
7) exec-date와 재현성
백테스트/리플레이·운영 점검용으로 실행 기준일을 강제합니다.
# D:\stock\ops\run_close.ps1
$py = "D:\stock\.venv64\Scripts\python.exe"
& $py D:\stock\app\execution\send_open_orders.py --exec-date 2025-10-16 --risk D:\stock\cfg\risk_config.yaml --dry-run
- dry-run: 실제 주문 대신 요청 페이로드만 로그로 남김
- replay: 과거 신호·시세와 함께 주문 의사결정을 재현해 알고리즘 회귀 테스트 수행
8) 트러블슈팅 표
증상 원인 해결
| 중복 체결 | 재시도 시 idempotency 미적용 | X-Idempotency-Key 필수, 키 스코프/만료 정책 수립 |
| 401/403 | 토큰 만료/IP 미등록 | 토큰 여유 5분 갱신, 화이트리스트 점검 |
| 429/5xx | 레이트 제한·간헐 장애 | 지수 백오프, 서킷브레이커, 큐잉(간격 벌리기) |
| 갭 과열 체결 | gap_guard 미적용 | max_open_premium_pct 활성화, MOC/ATC 플랜 병행 |
| 브래킷 누락 | 체결 이벤트 레이스컨디션 | 체결 감지 후 지연 200–400ms 후 일괄 제출, 재시도 루틴 |
| 장내·반일장 오동작 | 캘린더 미반영 | krx_calendar.py 기반 거래일/반일장 체크, 타임존 고정(KST) |
9) 운영 체크리스트
- 거래일·반일장 검증 후 실행
- 토큰 캐시 파일 정상·만료 5분 전 갱신
- 리스크 가드 로드 확인(갭 가드/최대 수량)
- 주문 전 사전 추정 체결가와 슬리피지 한도 계산
- 감사로그·주문 응답 저장(에러 포함)
해시태그
#키움증권 #키움REST #주문시스템 #브래킷주문 #정정주문 #취소주문 #execdate #자동매매 #리스크가드 #갭가드 #TechOracle
'IT와 과학 > 주식자동매매기술' 카테고리의 다른 글
| 주식투자 지표보기 - 거래대금 급증률 — 상승의 '연료'를 먼저 본다 (0) | 2025.10.04 |
|---|---|
| VWAP 완벽 마스터 가이드: 거래량으로 시장을 읽는 법 (0) | 2025.09.23 |
| 키움증권 REST API로 주식 분봉차트 조회하기 (Python) (0) | 2025.09.05 |
| 키움증권 연동을 위한 가상환경 활성화 (1) | 2025.08.31 |
| 💰 2020년 코로나 때 이거 알았으면 10배 수익! VIX 지수 완벽 가이드 (0) | 2025.03.03 |