본문 바로가기

IT와 과학/주식자동매매기술

키움 REST API | 주문 시스템 구현(브래킷·정정·취소, exec-date) — Python 실전 (2025-10 최신)

728x90
반응형

업데이트(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) 아키텍처(이야기로 이해하기)

  1. 신호 수집: v4_today 등에서 오늘의 후보 리스트를 가져옵니다.
  2. 리스크 필터: risk_config.yaml의 갭 가드·최소 유동성·허용 시장·단일 종목 최대 수량 등 체크.
  3. 주문 생성: kt10000/kt10001로 진입. client_order_id는 날짜+심볼+전략 해시로 생성.
  4. 체결 모니터링: monitor_fills_and_brackets.py가 체결 이벤트를 듣고 브래킷을 자동 제출.
  5. 정정·취소: 슬리피지/체결 지연 시 kt10002/kt10003로 관리.
  6. 감사로그: 모든 요청/응답/예외를 파일+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

728x90
반응형