트레이딩 실행 레이어 설계: “결정”을 “주문”으로 바꾸는 마지막 1단계
최종 수정: 작성자: Finyul
백테스트는 잘 돌아가는데 실거래 연결이 무서운 이유는 단 하나입니다. “결정(Decision)”과 “주문(Order)” 사이에 현실이 끼어들기 때문입니다. 이 글은 Account State(계정/포지션 상태)를 입력으로 받아 제약·검증·주문 생성·실행 결과 반영까지 담당하는 트레이딩 실행 레이어(Execution Layer) 설계 청사진을 제공합니다.
전체 폐루프(분석→예측→결정→실행)가 먼저 필요하면 LLM 멀티에이전트 투자 시스템(MAS) 완전 가이드를 참고하세요.

실행 레이어가 없는 자동매매는 왜 위험한가
결정은 “의도”이고, 주문은 “계약”이다
- 결정: “+20% 늘리자” 같은 의도/정책
- 주문: 거래소/브로커에 제출되는 정확한 계약(수량·가격·유효시간·조건)
실거래에선 아래가 항상 개입합니다: 포지션 상한, 증거금/현금 부족, 중복 주문, 거래정지, 부분체결, 슬리피지, 수수료, 지연. 즉 실행 레이어가 없으면 좋은 결정도 ‘나쁜 주문’으로 번역됩니다.
| 책임(Owner) | 입력 | 출력 | 실패 원인 | 필요한 로그 |
|---|---|---|---|---|
| Decision | 예측·제약 상태 | 의도(포지션 조절)·action(예: increase_20) | 스키마/도메인 위반 | decision_id, rationale, constraints_applied |
| Order | Account State·결정·시장 상태 | 계약(수량·가격·유효시간) | 상한·스텝·중복·체결·슬리피지·세션·현금 부족·거래정지 | gate 결과, Order, Fill, 거부 사유 |
Account State란 무엇인가: 실행 레이어의 “유일한 진실(SSOT)”
Account State(계정 상태)는 “지금 계정이 어떤 상태인지”를 한 번에 담은 스냅샷입니다. 실행 레이어는 결정 JSON만 보지 않고 Account State를 반드시 함께 봅니다.
최소 Account State 스키마(추천)
- 현금/가용현금, 통화
- 보유 포지션(수량, 평균단가, 평가손익, 현재 비중)
- 미체결 주문(open orders)
- 리스크 제약(limit set): 최대 비중, 스텝, 일일/주간 거래 횟수, 손실 트리거
- 시장/계정 플래그: 거래 가능 여부, 쿨다운(cooldown), 위험 상태(risk_flag)
- 버전/추적: as_of, account_id, state_hash

샘플(Account State JSON)
{
"account_id": "A-001",
"as_of": "2026-03-04T10:00:00+09:00",
"cash": {"available": 10000000, "currency": "KRW"},
"positions": [
{"symbol": "REIT_X", "qty": 100, "avg_price": 101.2, "market_price": 103.0, "weight": 0.20}
],
"open_orders": [
{"symbol": "REIT_X", "side": "BUY", "qty": 50, "status": "NEW"}
],
"limits": {
"max_weight": 0.60,
"step_weight": 0.20,
"max_trades_per_day": 3,
"cooldown_minutes": 30
},
"flags": {"tradable": true, "cooldown": false, "risk_flag": []},
"state_hash": "sha256:..."
}참고
- Account State는 계산값·추정이 아니라 브로커/거래소 실제 스냅샷이어야 한다.
- 미체결 주문까지 포함해 동기화해야 한다.
- 동기화가 어긋나면 실행 레이어가 잘못된 주문을 내보내 사고로 이어진다.
실행 레이어의 목표: “안전하게 번역하고, 안전하게 거부하고, 반드시 기록”하기
실행 레이어는 세 가지를 해야 합니다.
- 결정→주문 번역(Translate): “increase_20”을 수량/주문 타입으로 바꾸기
- 검증/제약(Validate & Constrain): 위반이면 주문 생성 금지 + HOLD(또는 축소)
- 결과 반영(Update): 체결/부분체결/실패를 Account State와 로그에 반영
1) 결정→주문 번역(Translate)
"increase_20"을 수량/주문 타입으로 바꾸기
Order Model 문서화 필수
2) 검증/제약(Validate & Constrain)
위반이면 주문 생성 금지 + HOLD(또는 축소)
5개 게이트로 사고 방지
3) 결과 반영(Update)
체결/부분체결/실패를 Account State와 로그에 반영
주문/체결/거부 사유 기록
안전하게 번역하고, 안전하게 거부하고, 반드시 기록
실패 시 기본값: HOLD + 사유 로그
실행 레이어 파이프라인(권장): 5개의 게이트로 사고를 막아라
여기서 “게이트”는 통과하지 못하면 주문이 절대 나가지 않는 방화벽입니다.
- Gate 1) 스키마 검증(JSON 파싱/필수 필드/허용 enum)
결정 출력이 흔들리면 실행이 터집니다. 결정 JSON은 반드시 스키마로 고정하세요. LLM 에이전트 출력 표준화: JSON 스키마 템플릿으로 분석→예측→결정 연결하기를 참고하세요. - Gate 2) 리스크 제약 검증(포지션 상한/스텝/빈도/쿨다운)
이미 max_weight에 근접하면 “increase_20”은 거부 또는 축소. 하루 거래 횟수 초과면 HOLD. 쿨다운 시간 내면 HOLD. - Gate 3) 계정 검증(현금·증거금·미체결·중복 주문)
같은 방향의 미체결 주문이 있으면 중복 방지. 현금 부족이면 주문 수량 자동 축소(또는 거부). - Gate 4) 시장 검증(거래정지·호가 공백·세션)
거래정지/서킷브레이커/세션 외 시간이면 주문 금지. 유동성 부족(호가 공백) 시 지정가/분할로 강제(또는 HOLD). - Gate 5) 주문 모델 검증(타입·유효시간·슬리피지/수수료 반영)
시장가/지정가 선택 규칙. 예상 거래비용 반영(예: 연구 백테스트에서 사용한 0.03% = 0.0003 같은 형태로 가정값도 로그에 남김).

“결정→주문” 번역 규칙: 주문 모델(Order Model)을 먼저 고정하라
결정이 “increase_20”이면 주문은 무엇이 될까요? 이게 사람 머릿속에 있으면 시스템이 흔들립니다. Order Model을 문서로 고정하세요.
번역에 필요한 최소 입력
- 결정 action(예: increase_20 / reduce_40 / hold)
- Account State(현금, 현재 비중, open_orders)
- 제약(limit set)
- 시장 상태(세션, 유동성, 변동성 플래그)
번역 출력 예시
- order_type: MKT / LMT / VWAP(분할) / IOC 등
- qty 또는 target_weight
- time_in_force: DAY/GTC
- price (지정가일 때)
| 액션(action) | 의도 | 권장 주문 타입 | 수량(또는 목표비중) 산식 | 예외 처리(거부·축소·분할) |
|---|---|---|---|---|
| close | 전량 청산 | MKT / LMT | 현재 보유 수량 전량 | 부분체결 대비 로그. 상한·스텝·중복 시 축소/거부. |
| reduce_40 | 비중 40%p 감소 | MKT / LMT | 현재 qty의 40% 또는 target 차이 | 미체결 매도 있으면 중복 방지. 유동성 낮으면 분할(VWAP/지정가). |
| reduce_20 | 비중 20%p 감소 | MKT / LMT | 현재 qty의 20% 또는 target 차이 | 상한·스텝·중복 주문 시 축소/거부. 유동성 낮으면 분할. |
| hold | 유지 | — | 주문 없음 | — |
| increase_20 | 비중 20%p 증가 | MKT / LMT | target_weight·계정규모 → qty | 상한 초과 시 축소 또는 거부. 중복 주문 시 거부. 유동성 낮으면 분할(VWAP/지정가). |
| increase_40 | 비중 40%p 증가 | MKT / LMT | target_weight·계정규모 → qty | 상한·스텝·중복 주문 시 축소/거부. 유동성 낮으면 분할. |
| increase_upper_limit | 상한까지 증액 | MKT / LMT | max_weight와 현재 비중 차이 → qty | 이미 상한이면 주문 없음. 중복·유동성 시 축소/분할. |
실행 레이어가 “실거래 공포”를 줄이는 전형적 Before/After
Before(실행 레이어 없음)
- LLM 결정: “increase_40”
- 실제 계정: 이미 비중 0.58, 상한 0.60, 미체결 BUY가 존재
- → 결과: 중복 주문 + 상한 위반 시도 + 체결 꼬임
- → 나중에 “왜 이렇게 됐지?”를 추적할 근거가 없음
After(실행 레이어 있음)
- Gate 2에서 상한/스텝 검증 → increase_40 거부 또는 increase_20로 자동 축소
- Gate 3에서 중복 주문 감지 → 새 주문 생성 금지(또는 기존 주문 수정)
- 모든 과정이 로그에 남아 리플레이 가능
- → “결정이 문제였나? 번역이 문제였나? 실행이 문제였나?”가 분리됩니다.
실행 로그는 선택이 아니라 필수: 주문/체결/거부 사유가 남아야 개선된다
실행 레이어는 “좋은 주문을 내는 것”만큼 “왜 주문을 안 냈는지”가 중요합니다.
필수 로그(권장)
- decision_id / run_id / inputs_hash
- gate 통과/실패 결과 + failed_rule
- 생성된 주문(Order)과 응답(status)
- 체결(Fill) 정보(부분체결 포함)
- 비용(수수료/슬리피지 추정)과 반영 방식
운영 전체 루틴(로그·리플레이·리뷰) 설계는 AI 트레이딩 운영의 핵심: 로그·리플레이·리뷰로 “왜 그 결정을 했는지” 남기는 법에서 이어집니다.
FAQ
자주 묻는 질문
- 백테스트는 되는데 실거래에서만 망가지는 이유는 뭔가요?
- 대부분 미체결/부분체결/슬리피지/세션/거래정지/중복 주문 같은 실행 변수가 백테스트에 없거나 단순화되어서입니다. 실행 레이어는 이 현실 변수를 "게이트"로 강제합니다.
- 실행 레이어는 LLM으로 만들면 안 되나요?
- 권장하지 않습니다. 실행은 검증 가능한 규칙 엔진이 맡아야 합니다(예외 처리/컴플라이언스/안전). LLM은 "결정/설명"까지가 적합합니다.
- 가장 먼저 넣어야 할 제약 1순위는?
- 포지션 상한 + 스텝 제한 + 중복 주문 방지 3개가 1순위입니다. 이것만 있어도 사고 확률이 크게 줄어듭니다.
- HOLD(거부)도 로그에 남겨야 하나요?
- 반드시 남겨야 합니다. HOLD는 "아무것도 안 함"이 아니라 리스크를 선택한 결정이고, 다음 개선의 가장 좋은 단서입니다.
결론: 실행 레이어는 “마지막 단계”가 아니라 “생존 장치”다
- Account State를 SSOT로 고정하고
- 결정→주문 번역 규칙을 문서화하고
- 5개 게이트로 안전하게 거부하고
- 주문/체결/거부 사유를 로그로 남기면
- 실거래 연결의 공포는 크게 줄어듭니다.