안 죽고 안 터지게 — 자동매매 안전장치와 운영
드라이런·수량 대조·다단계 드로다운 알림·긴급 정지·텔레그램. 자동매매의 진짜 난이도는 운영에 있다.
자동매매의 진짜 난이도는 "돈 버는 로직"이 아니다. 실계좌에서 안 죽고, 안 터지게 운영하는 것이다. 이 마지막 편이 그 안전망을 다룬다.
1) 드라이런 — 항상 여기서 시작
5편에서 봤듯, dry_run=True면 실제 주문 없이 로직이 뭘 하려는지 로그로만 남긴다. 새 전략·새 종목은 무조건 드라이런부터. 며칠 돌려보며 이상한 주문이 없는지 확인한 뒤 실전으로 넘어간다.
2) 수량 대조 (Reconciliation) — 가장 중요한 안전장치
내 DB가 생각하는 보유수량과 브로커의 실제 보유수량이 어긋나면 모든 계산이 무너진다. (내가 손으로 팔았거나, 체결을 놓쳤거나, 부분체결이 꼬였을 때 생긴다.)
그래서 주기적으로 "영웅문 실제 수량 vs 내 DB 수량"을 대조하고, 불일치하면 자동 정지 + 긴급 알림을 보낸다.
if 브로커_실수량 != DB_수량:
자동매매_정지()
텔레그램("🚨 수량 불일치 — 수동 확인 필요")
틀린 상태로 계속 주문을 내는 것보다, 멈추고 사람을 부르는 게 항상 낫다.
3) 다단계 드로다운 알림 (L1~L4)
그리드는 하락장에서 계속 사들이며 자금이 물릴 수 있다. 그래서 하락 깊이·자금 소진 정도에 따라 경고를 단계적으로 올린다.
| 단계 | 의미 |
|---|---|
| L1 | 자금 소진 임박 경고 |
| L2~L3 | 하락 심화 — 주의 |
| L4 | 깊은 하락 — 수동 개입(추가 매수/정지) 판단 |
돈이 걸린 시스템은 조용히 망가지면 안 된다. 위험이 커질수록 더 크게 알린다.
4) 긴급 정지 (킬 스위치)
무슨 일이 생기면 한 번에 멈출 수단이 반드시 있어야 한다. 메뉴의 "🚨 긴급 정지" 버튼 하나로 모든 자동 주문을 중단한다. 자동화에서 수동 정지 권한은 타협 대상이 아니다.
5) 재시작해도 살아남기
- 대기 주문·상태를 SQLite에 영속화 → 앱이 꺼졌다 켜져도 그 자리에서 복원.
- catch-up 조회(4편) → 봇이 밤사이 멈춰 놓친 체결을 재시작 후 따라잡는다.
자동매매는 몇 달을 돌린다. 재부팅·정전·업데이트를 견디게 만드는 게 핵심이다.
6) 알림 — 텔레그램
체결·오류·안전 경고를 텔레그램 봇으로 실시간 전송한다. 네트워크가 잠깐 끊길 수 있으니 재시도 로직을 넣는다. (봇 토큰·chat_id는 2편의 settings.json에서.)
체결됨 → 텔레그램("✅ QLD 매수 4주 @ $594 체결")
오류/정지 → 텔레그램("🚨 ...")
7) 현황을 웹으로 — 대시보드 (선택)
폰에서 언제든 현황을 보고 싶으면, 보유·수익·상태를 정적 HTML로 만들어 웹에 퍼블리시하면 된다. (이 프로젝트는 Cloudflare Worker로 올린다.) 서버를 상시 켜둘 필요 없이 읽기 전용 현황판을 갖는 셈이다.
📦 코드로 — 수량 대조와 드로다운 판정
수량 대조는 놀랍도록 단순하다. 브로커 실수량과 내 체결 합계를 빼기만 하면 된다.
from dataclasses import dataclass
@dataclass
class ReconcileResult:
broker_quantity: int
system_quantity: int
delta: int # broker - system
matched: bool
def reconcile(repo, code, broker_quantity):
system_qty = repo.sum_trade_quantities(code) # DB에 기록된 체결 합계
delta = broker_quantity - system_qty
return ReconcileResult(broker_quantity, system_qty, delta, delta == 0)
# 사용
r = reconcile(repo, "QLD", broker_quantity=controller.get_position_quantity("QLD"))
if not r.matched:
grid_manager.stop()
telegram(f"🚨 QLD 수량 불일치: 브로커 {r.broker_quantity} vs DB {r.system_quantity}")
다단계 드로다운 판정 — 가격이 얼마나 깊이 빠졌는지에 따라 레벨을 올린다.
from enum import IntEnum
class AlertLevel(IntEnum):
NONE = 0; L1_NEAR_EXHAUST = 1; L2_EXHAUSTED = 2
L3_MANUAL_BUY = 3; L4_DEEP_MANUAL_BUY = 4
def evaluate_drawdown(current_tier, total_tiers,
lowest_tier_price, current_price,
l3_pct=10.0, l4_pct=30.0):
# 높은 레벨 우선
if current_price <= lowest_tier_price * (1 - l4_pct/100):
return AlertLevel.L4_DEEP_MANUAL_BUY # 최저가 -30% 이하
if current_price <= lowest_tier_price * (1 - l3_pct/100):
return AlertLevel.L3_MANUAL_BUY # 최저가 -10% 이하
if current_tier >= total_tiers and current_price <= lowest_tier_price:
return AlertLevel.L2_EXHAUSTED # 그리드 탄약 소진
if current_tier >= total_tiers - 1:
return AlertLevel.L1_NEAR_EXHAUST # 소진 임박
return AlertLevel.NONE
같은 레벨 알림이 반복되지 않게 한 번 보낸 레벨은 기록(중복 방지) 한다. 위험이 커질수록(L1→L4) 더 크게, 하지만 딱 한 번씩 알린다.
✅ 확인
reconcile을 주기적으로 돌려matched=False면 즉시 정지 + 알림이 오는지(가짜 불일치를 넣어) 테스트.evaluate_drawdown에 여러 가격을 넣어 L1~L4 경계가 의도대로 나오는지 확인.- 이 둘이 동작하면, "조용히 망가지는" 최악은 막은 것이다.
🔧 부품을 하나로 — 최소 봇
지금까지 만든 부품(시세·주문·체결·알림)을 묶으면, 실제로 도는 봇이 된다. 핵심은 놀랍도록 짧다 — 주기적으로 보고 → 판단 → 주문 → 체결 알림의 반복.
# bot.py — 부품을 묶은 최소 자동 봇
import time, json, pathlib
from hero.kiwoom_rest import KiwoomRestClient
cfg = json.loads(pathlib.Path("config/settings.json").read_text("utf-8"))["kiwoom_rest"]
client = KiwoomRestClient(cfg["appkey"], cfg["secretkey"], mock=cfg.get("mock", True))
TICKER, EXCH = "QLD", "NY"
seen = set()
while True:
price = client.us_current_price(TICKER, EXCH)
if price is not None:
# 1) 규칙 판단 (6편의 그리드가 여기 들어간다)
# if 매수조건(price): client.us_order("buy", TICKER, EXCH, qty, price=...)
# if 매도조건(price): client.us_order("sell", TICKER, EXCH, qty, price=...)
# 2) 새 체결이면 알림
for f in client.us_executions():
oid = f.get("ord_no")
if oid and oid not in seen:
seen.add(oid)
print(f"✅ 체결: {f['stk_cd']} {f['slby_tp_nm']} "
f"{f['cntr_qty']}주 @ {f['cntr_uv']}") # → 텔레그램 발송
time.sleep(10) # rate limit 여유 + CPU 절약
이게 자동매매 봇의 심장이다. 여기 # 1) 규칙 판단에 6편 그리드를, # 2) 알림에 텔레그램을, 그리고 앞서 본 수량 대조·드로다운·긴급 정지를 두르면 — 처음부터 끝까지 당신이 만든 봇이 완성된다.
⚠️ 실전에서는
while True안에 장 시간 체크(미국장 열렸을 때만), 예외 처리(조회 실패해도 안 멈춤), 긴급 정지 플래그를 반드시 넣어야 한다. 위는 뼈대다.
마무리 — 여기까지가 '작동하는 기초'
7편에 걸쳐, 우리는:
- 왜 REST로 만드는가 → 2. 키 발급 → 3. 토큰·인증 → 4. 데이터 조회 → 5. 주문 → 6. 그리드 로직 → 7. 안전장치
까지 자동매매의 뼈대 전체를 훑었다. 이건 작동하는 기초형이다. 실전에서 오래 굴리려면 여기에 더 많은 것이 필요하다 — 백테스트, 엣지케이스(휴장·조기폐장·섬머타임), 세금·수수료 정밀 반영, 장기 하락 대응, 수백 개의 테스트.
가장 중요한 원칙 하나: 실전 투입 전 모의·소액·드라이런으로 충분히 검증하라. 자동매매는 잘 만들면 강력하지만, 잘못 만들면 잘못을 빠르게 반복한다. 잃어도 되는 여윳돈으로만.
이 강좌가 "나도 만들 수 있다"의 출발점이 되면 좋겠다. 막히는 부분은 각 편 댓글로 편하게 물어봐 주세요 — 함께 만들어가요.
※ 이 강좌는 프로그래밍 교육입니다. 투자 권유나 특정 상품·종목·시점 추천이 아니며, 투자에는 원금 손실 위험이 있습니다. 증권사 API 약관과 관련 규정을 반드시 확인하세요.
아래 자료에서 본인에게 적용되는 최신 조건을 다시 확인하세요. 링크는 특정 상품 가입을 권하는 제휴 링크가 아닙니다.
- 키움증권 REST API 포털 ↗토큰 발급·TR 호출·미국주식 주문/조회 스펙
- Python 표준 라이브러리 · urllib.request ↗외부 의존성 없는 HTTP 호출
이 글은 교육·정보 제공 목적이며 개인별 투자자문이나 특정 금융상품의 매수·매도 권유가 아닙니다.
이 글이 포함된 외벌이 재무 워크북(PDF)을 이메일로 무료로 받아보세요. 글과 도구는 계속 무료입니다.
함께 생각해요
이 글을 읽고 생긴 질문이나 직접 실행한 경험을 나눠주세요.
댓글을 불러오는 중입니다…