📚 토스 자동매매 개발기 시리즈 (Phase 7 · 전체 19편)
- Phase 0 – 프로젝트 기반 설계
- Phase 0-1 – 통화 버그 수정
- Phase 1 – 설정 분리 및 재시도 로직
- Phase 1-2 – 설정/재시도 후속 작업
- Phase 1-3 – pytest 테스트 도입
- Phase 1-4 – 로그 로테이션 적용
- Phase 2-1 – 전략 클래스 설계
- Phase 2-2 – 다종목 모니터링
- Phase 3-1 – 데이터 영속성 설계
- Phase 3-2 – DB 모듈 구현
- Phase 3-3 – DB 통합
- Phase 2-3 – 전략 플러그인 인터페이스
- Phase 4 – 백테스트 엔진
- Phase 4 – 캔들 API 페이지네이션
- Phase 5 – 읽기전용 대시보드
- Phase 5 – 텔레그램 제어 로직
- Phase 5 – 텔레그램 제어 로직 2
- Phase 6 – 스케줄러/워치독
- Phase 7 – 1회 주문 한도 방어 로직 (현재 글)
현재 글: Phase 7 / 전체 19편
💡 이번 Phase 7 요약
- 목적: 버그나 설정 실수로 의도치 않게 큰 수량이 주문되는 것을 막는 방어 로직 구현
- 핵심 포인트: DRY_RUN보다 먼저 검사되는 “안전 0단계”로 삽입해, 시뮬레이션 상태에서도 한도 초과 주문을 즉시 거부하도록 설계
- 검증: 신규 테스트 4개 추가, 전체 pytest 122개 전부 통과 (기존 118개 + 신규 4개)
Phase 0~6을 지나 드디어 Phase 7, 실전 전환 준비
Phase 0부터 Phase 6까지, 설정 분리부터 전략 클래스, DB 영속성, 백테스트 엔진, 텔레그램 제어, 스케줄러/워치독까지 한 단계씩 쌓아왔다. 로드맵 마지막에 남은 건 Phase 7, 이름하여 “실전 전환 준비”다. 이번 글에서는 그 첫걸음으로 1회 주문 한도 방어 로직을 다룬다.
근데 이 단계는 이름부터 부담스럽다. 지금까지는 어디까지나 DRY_RUN이라는 안전망 안에서 놀았지만, Phase 7이 끝나면 진짜 내 계좌에 진짜 주문이 나갈 수도 있다는 얘기니까. 그래서 곧바로 세이프티 장치를 풀어버리는 대신, 먼저 준비해두면 좋을 것들부터 하나씩 짚기로 했다.
실전 전환 전 체크리스트
정리한 순서는 이랬다.
- 1회 주문 한도 방어 로직 (오늘 다룰 부분)
- 일일/누적 손실 한도 자동 정지
- 실제 목표가·전략으로 백테스트 검증
- (승인 후) 3중 안전장치 단계적 해제
사용량 한도도 고려해야 해서 하루에 하나씩 진행하기로 했다. 오늘은 1번, 1회 주문 한도 방어 로직부터.
문제: 큰 수량 주문을 막을 장치가 없었다
코드를 다시 훑어보니 허점이 하나 눈에 띄었다. 수량 계산 로직에 버그가 있거나, 설정값을 잘못 입력하는 실수를 하면 의도치 않게 큰 수량으로 주문이 나갈 수 있는 구조였다. 지금까지의 3중 안전장치(DRY_RUN, 목표가 1원, API 물리적 주석 처리)는 “거래 자체”를 막아주지만, 코드 로직의 실수까지 잡아주진 못한다.
그래서 config.py에 MAX_ORDER_QUANTITY_PER_TRADE라는 설정을 추가했다. 기본값은 10주. 한 번의 주문에서 이 수량을 넘기면 무조건 거부되도록 만드는 게 목표다. 이게 바로 이번 글의 핵심인 1회 주문 한도 방어 로직이다.
설계: DRY_RUN보다 먼저 검사하기
여기서 가장 중요한 설계 결정을 하나 했다. 이 1회 주문 한도 체크를 기존 3중 안전장치보다 앞서 검사되는 “안전 0단계”로 execute_order() 안에 삽입한 것이다.
왜 이 순서가 중요하냐면, DRY_RUN=True인 시뮬레이션 상태에서도 한도 초과 시 즉시 거부되게 만들면, 실전 전환 전에 “수량 계산 실수” 같은 코드 버그를 미리 잡아낼 수 있기 때문이다. 시뮬레이션 중에 몰래 숨어있던 버그를 실전 직전이 아니라 지금 발견하는 게 훨씬 안전하다.
가상 종목(삼성전자, 005930)을 예로 들면, 원래 5주를 사려던 주문이 계산 실수로 500주가 되어버려도, 이 0단계에서 바로 걸러진다.
참고: 한도를 초과한 주문은 ERROR 레벨 경고 알림만 발송하고, 거래 이력에는 전혀 남기지 않도록 처리했다. 실제로 체결되지 않은 거래가 통계에 섞여서 나중에 손익 분석을 왜곡하는 걸 막기 위해서다.
검증: 테스트 4개 추가, 122개 전부 통과
test_auto_trader.py에는 아래 3개 테스트를 추가했다.
- 한도 초과 시 주문이 거부되는지
- 한도와 정확히 같은 경계값(예: 10주)에서는 정상 체결되는지
- 한도 초과 주문이 거래 이력에 남지 않는지
여기에 test_config.py에는 한도 값 자체가 안전한 범위(1~100)를 벗어나지 않는지 확인하는 회귀 방지 테스트도 추가했다. 나중에 이 값을 실수로 너무 크게 바꿔버리면 방어선 자체가 무력화되니까, 그 실수를 미리 막기 위한 안전장치다.
전체 pytest를 재실행한 결과 122개 전부 통과(기존 118개 + 신규 4개)했고, 기존 DRY_RUN·TARGET_BUY_PRICE 안전장치 값은 전혀 건드리지 않았다는 것도 다시 확인했다.
1회 주문 한도 방어 로직은 DRY_RUN보다 먼저 검사되는 “안전 0단계”로 설계해, 시뮬레이션 상태에서도 수량 계산 버그를 실전 전환 전에 미리 발견할 수 있게 한다.
최종 결과: 4중 안전장치 체계
이번 작업으로 기존 3중 안전장치에 1회 주문 한도까지 더해져 아래처럼 4중 구조가 됐다.
| 단계 | 내용 |
|---|---|
| 0단계 (신규) | 1회 주문 한도 초과 시 즉시 거부 |
| 1단계 | DRY_RUN=True 시뮬레이션 모드 |
| 2단계 | 목표가 1원 (시장 체결 불가) |
| 3단계 | 실제 주문 API 물리적 주석 처리 |
로컬 실행 환경과 프로젝트 저장소 양쪽 반영 및 동기화까지 확인 완료했다.
다음 단계: 일일/누적 손실 한도 자동 정지
다음으로 준비할 건 일일/누적 손실 한도를 넘으면 감시 루프를 자동으로 일시정지시키는 로직이다. Phase 5에서 만든 engine_control.pause()를 재사용할 예정이다. 그 다음엔 실제 목표가·전략으로 백테스트 검증까지 마치고, 사용자가 명시적으로 승인할 때 3중 안전장치의 단계적 해제를 진행할 계획이다.
FAQ
Q1. 1회 주문 한도 방어 로직은 왜 DRY_RUN보다 먼저 검사하나요?
시뮬레이션 상태에서도 한도 초과를 즉시 거부하게 만들어야, 실전 전환 전에 수량 계산 버그 같은 코드 실수를 미리 발견할 수 있기 때문입니다.
Q2. 한도를 초과한 주문은 거래 이력에 남나요?
아니요, ERROR 레벨 경고 알림만 발송하고 거래 이력에는 전혀 남기지 않습니다. 실제로 체결되지 않은 거래가 통계에 섞이는 걸 막기 위해서입니다.
Q3. 기본 주문 한도는 몇 주인가요?
MAX_ORDER_QUANTITY_PER_TRADE의 기본값은 10주이며, 설정에서 자유롭게 조정할 수 있습니다.
Q4. 다음 Phase는 무엇인가요?
일일/누적 손실 한도를 넘으면 감시 루프를 자동으로 일시정지시키는 로직을 준비할 예정입니다.
“1회 주문 한도 방어 로직으로 안전장치 강화하기 | 토스 자동매매 개발기 #19”에 대한 2개의 생각