10 / 12 · AUTO TRADING WIKI

반복 실행과 중단 조건 만들기

하루 실행 순서와 중복 방지·오류 차단·실행 감사 기준을 정합니다.

발행
수정
문서 검토
2026-10-20
본문 버전
1.1.0-web.20261020

한 번 성공한 페이퍼 실행과 매일 반복해도 안전한 실행은 다르다. 반복 실행에서는 같은 날 두 번 시작되거나, 체결 기록을 저장하는 중에 프로그램이 끝나거나, 데이터 수집이 늦어지는 일이 생긴다. 이번 장은 그 상황을 기록하고 멈추는 방법을 다룬다.

예제는 auto-trading-lab의 로컬 paper 원장만 사용한다. 스케줄러를 켜는 것보다 중복 방지·완료 판정·복구 근거를 먼저 준비한다. 모든 시간과 로그는 가상 예시다.

1. 하루 실행을 단계별 상태로 남기기

실행 시작 때 run_id를 만들고 paper_runs에 기록한다. 단계가 끝날 때마다 마지막 확정 지점을 저장한다. 프로그램이 살아 있다는 사실과 해당 거래일의 처리가 끝났다는 사실은 별개다.

CREATED → VALIDATING → SIGNALS_READY → ORDERS_READY
        → SETTLING → RECONCILING → COMPLETED
어느 단계에서든 → BLOCKED 또는 FAILED
저장 상태 불명확 → RECOVERY_REQUIRED

BLOCKED는 데이터 지연이나 중단 파일처럼 정해진 조건에 걸린 상태다. FAILED는 예외로 정상 진행하지 못한 상태다. RECOVERY_REQUIRED는 DB 확정 여부를 다시 확인하기 전에는 처리 재개를 허용하지 않는 상태다. 이들을 모두 성공 종료 코드로 숨기지 않는다.

2. 거래일과 실행 시간을 구분하기

business_date는 처리할 거래 세션, started_at은 실제 실행을 시작한 시각이다. 6월 3일 데이터를 6월 4일 새벽에 처리할 수 있으므로 두 값을 같은 날짜로 가정하지 않는다. 시각에는 시간대를 보관하고 화면에는 한국 시간 기준임을 표시한다.

반복 실행의 입력은 daily_prices의 데이터 확정 상태와 거래 캘린더다. 컴퓨터의 오늘 날짜나 매주 월요일부터 금요일이라는 조건만으로 다음 세션을 정하지 않는다. 공식 거래일과 종목의 거래 가능 여부가 확인되지 않으면 CALENDAR_NOT_READY로 멈춘다.

일봉 기반 로컬 재생은 이미 확정된 과거 세션을 처리하는 작업이다. 실제 장 시작 시점의 주문 실행과 다르다. T일 종가 신호는 T+1 가상 OPEN 이벤트와 연결하고, 보고서에 가격의 관측 시각과 처리 시각을 나누어 적는다.

3. 중복 실행과 중복 주문을 각각 막기

스케줄러 설정에서 중복 실행을 제한해도 수동 실행이 겹칠 수 있다. 프로그램에도 계정·모드별 실행 잠금을 둔다. 동시에 두 실행이 시작되면 하나만 진행하고 나머지는 ALREADY_RUNNING으로 종료한다.

잠금은 실행을 겹치지 않게 하는 장치이며, 주문 중복을 영구적으로 막는 장치는 아니다. 주문에는 앞 장의 고유 의도키가 별도로 필요하다. 같은 (account_id, strategy_version, symbol, signal_date, action)은 다른 run_id에서 재처리해도 기존 주문을 찾아야 한다.

DB 연결이 끊기면 잠금도 사라질 수 있으므로 연결 상실 즉시 새 변경을 중단한다. 잠금을 다시 얻었다고 곧바로 주문 단계부터 반복하지 않는다. 먼저 이전 실행과 원장 상태를 대조한다. 계정 행 잠금과 원자적 변경은 MySQL 공식 잠금 읽기 문서를 따른다.

4. 중단 조건은 설정으로 고정하기

다음은 config/paper_runtime.json의 학습용 예시다. 실제 운영에서 사용할 지연 허용 시간은 데이터 출처와 수집 주기를 확인해 정해야 한다.

{
  "mode": "paper",
  "stop_file": "config/STOP_PAPER",
  "max_consecutive_failures": 2,
  "stop_on_unexplained_price_gap": true,
  "stop_on_reconciliation_difference": true,
  "stop_on_intent_conflict": true,
  "allow_external_order_calls": false,
  "resume_requires_reconciliation": true
}
중단 사유 처리
중단 파일 존재 새 의도·체결 생성 차단
데이터 미확정 또는 원인 불명 누락 BLOCKED/DATA_NOT_READY
현금·수량 대조 불일치 RECOVERY_REQUIRED
같은 의도키에 다른 내용 BLOCKED/INTENT_CONFLICT
외부 주문 기능이 연결됨 즉시 차단하고 설정·코드 검토
같은 기술 실패 2회 연속 자동 재시도 중단, 원인 기록

중단은 원장 삭제나 가상 포지션 강제 청산을 뜻하지 않는다. 이미 확정한 기록을 보존하고 새 변경을 멈추는 기능이다. 다시 실행할 때는 멈춘 이유가 해소되었는지 확인한다.

5. PowerShell 실행 스크립트의 종료 상태 전달하기

scripts/run_paper.ps1은 경로와 Python 실행 파일을 고정하고 실패 종료 코드를 호출자에게 전달한다. 다음 예시는 대응하는 src.paper_runner 모듈을 만든 뒤 사용할 실행 래퍼다.

$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $projectRoot
$pythonExe = Join-Path $projectRoot '.venv\Scripts\python.exe'
$stopPath = Join-Path $projectRoot 'config\STOP_PAPER'

if (Test-Path -LiteralPath $stopPath) {
    Write-Output 'state=BLOCKED reason=STOP_FILE'
    exit 20
}
if (-not (Test-Path -LiteralPath $pythonExe)) {
    throw '프로젝트 가상환경 Python을 찾지 못했습니다.'
}
& $pythonExe -m src.paper_runner --config 'config/paper_runtime.json'
$runnerExitCode = $LASTEXITCODE
exit $runnerExitCode

Python은 파일 로그를 UTF-8로 쓰고 run_id, business_date, stage, state, reason을 포함한다. 한글 로그는 Get-Content -Encoding UTF8으로 읽는다. PowerShell 버전별 기본 인코딩은 다를 수 있으므로 Microsoft 공식 인코딩 문서를 참고해 명시적으로 맞춘다.

Get-Content -Encoding UTF8 '.\logs\paper_runner.log' -Tail 40

6. 실패 지점 세 곳을 재현하기

자동 반복을 등록하기 전에 가상 데이터로 실패를 주입한다. 실제 계좌나 외부 주문 서버는 필요 없다. 실패 시험은 복사한 테스트 DB에서 수행하고 정상 학습 원장을 지우지 않는다.

실패 지점 기대 저장 상태 다시 시작할 때
주문 삽입 전 의도 없음 같은 키로 1회 생성
체결 트랜잭션 중, 확정 전 전체 변경 취소 미체결 주문만 재처리
DB 확정 후, 성공 로그 전 체결·원장 존재 기존 체결 확인 후 추가 정산 금지

마지막 사례가 중요하다. 로그에 성공이 없다는 이유로 체결을 다시 반영하면 현금과 수량이 두 번 바뀐다. 복구의 기준은 영속 저장소의 주문·체결·원장이다. MySQL의 트랜잭션 경계는 공식 COMMIT·ROLLBACK 문서를 참고한다.

DB에 접근할 수 없어 확정 여부를 모르면 RECOVERY_REQUIRED로 남긴다. 확인되지 않은 상태를 “실패했으니 아무 일도 없었다”로 가정하지 않는다. 로컬 시뮬레이터의 복구가 통과해도 실제 외부 주문의 응답 유실 문제가 해결된 것은 아니다.

7. 복구 기록을 남기고 재개하기

복구 기록에는 이전 run_id, 원인, 마지막 확정 단계, 대조한 주문·체결 건수, 변경 내용, 새 실행 식별자를 넣는다. 결과를 손으로 고쳤다면 수정 전후 값과 이유를 별도로 보관한다. 가능하면 원장 사건을 근거로 상태를 다시 계산한다.

recovery_id=demo-recovery-01 previous_run=demo-paper-09
reason=PROCESS_EXIT_AFTER_COMMIT
orders=1 fills=1 cash_entries=1 duplicate_entries=0
cash_difference=0.00 quantity_difference=0
action=KEEP_COMMITTED_FILL next_run=demo-paper-10
decision=RESUME_LOCAL_PAPER

중단 파일은 원인을 고친 뒤 담당자가 제거하는 절차로 둔다. 프로그램이 “몇 분 지났으니 괜찮다”고 자동 삭제하지 않는다. 재개 직전에도 설정 버전, 중단 사유 해소, 원장 대조를 확인한다.

8. 스케줄러는 마지막에 연결하기

수동 실행과 실패 복구가 통과하면 Windows 작업 스케줄러에 등록한다. 실행 프로그램은 PowerShell, 인수는 프로젝트의 실행 스크립트 절대 경로로 지정하고, 작업 시작 위치도 프로젝트 폴더로 고정한다. 조건은 외부 데이터 확정 시간에 맞추어 정한다.

로그인 여부, 절전·전원 설정, 실행 계정의 폴더 권한이 결과에 영향을 줄 수 있다. 작업이 등록되었다는 사실보다 지정 시각에 실행 기록과 종료 코드가 남는지 확인한다. 명령줄 관리가 필요하면 Microsoft 공식 schtasks 문서를 참고한다.

책의 예시를 읽었다고 자동 등록이 완료되는 것은 아니다. 이번 실습에서는 수동 실행 → 중복 시작 → 중단 파일 → 실패 복구 순서를 확인한 뒤 자신이 정한 관찰 일정에 연결한다.

9. Codex 요청과 완료 기준

로컬 paper_runner의 반복 실행과 복구 절차를 구현해줘.
단계별 상태·종료 코드·계정 실행 잠금·영구 고유 의도키를 구분해.
중단 파일·데이터 미확정·잔고 불일치·의도 충돌에서 새 변경을 차단해.
주문 전, 체결 확정 전, 확정 후 로그 전의 실패를 가상 DB에서 재현해.
재시작 후 주문·체결·정산이 한 번씩만 남는지 확인해.
스케줄러 등록은 수동 검증 결과와 등록할 명령을 먼저 보고해.
외부 주문 기능은 추가하지 말고 운영 로그에서 비밀값을 제외해.

완료 기준은 같은 거래일을 두 번 처리해도 주문과 자금이 늘지 않고, 중간 실패 후 마지막 확정 상태에서 재개하며, 중단 조건이 실제로 차단되는 것이다. 로그가 두 번 남았다는 사실과 주문이 두 번 발생했다는 사실도 구분해서 집계한다.

증상 먼저 확인할 것
스케줄러에서는 Python을 못 찾음 가상환경 절대 경로와 실행 계정 권한
중단 파일이 있는데 새 주문 생성 실행 전과 변경 직전의 중단 검사
실패 뒤 예약현금이 계속 남음 주문 상태와 예약 해제의 트랜잭션 경계
재실행이 기존 체결을 다시 정산 영속 원장 대조와 체결 고유키
종료 기록이 없는데 작업이 끝남 시작 기록과 비정상 종료 감지

다음 장에서는 페이퍼 성공을 실전 허가로 오해하지 않도록 별도의 차단 점검과 안전 기본값을 정리한다.

학습의 다음 단계를 남겨 두세요

공유 링크를 복사해 이어서 읽을 수 있습니다.