페이퍼 전략을 거래 도중 재시작했더니 재시작 전에 내보냈던 것과 다른 순서로 주문이 나왔습니다. 시장 데이터도 같고, 전략 버전도 같고, 계좌 잔고도 같았습니다. 신호 계산 결과는 일치했습니다. 하지만 포지션과 대기 중인 주문에 대한 전략의 기억은 달랐습니다.
하루 동안 재생 작업을 하며 불일치의 원인을 추적했습니다. 여기서 얻은 교훈은 간단합니다. 소프트웨어에는 재시작도 시장 이벤트와 같습니다. 전략이 상태를 어떻게 다시 구성하는지 테스트하지 않으면, 깔끔한 백테스트 뒤에 보유 자산을 잊어버리는 페이퍼 시스템이 숨어 있을 수 있습니다.
09:10 — 단순한 포지션을 골랐습니다
테스트 전략은 유동성이 높은 무기한 계약을 거래했습니다. 단기 이동평균이 장기 이동평균을 상향 돌파하면 롱에 진입하고, 반대 교차가 나오면 청산하는 방식입니다. 이미 소규모 포지션을 보유한 상태에서 재생을 시작했고, 포지션을 줄이기 위한 reduce-only 지정가 주문도 대기 중이었습니다. 덕분에 복구 과정에서 두 가지 정보를 다시 구성해야 했습니다. 보유한 자산이 무엇인지, 거래소에 이미 어떤 주문을 요청했는지 말입니다.
체크포인트에서 계좌는 계약 0.04개를 보유하고 있었습니다. 주문은 0.01개 물량으로 열려 있었습니다. 전략 프로세스는 두 값을 모두 메모리에 캐시해 두었지만, 시작 절차에서는 포지션만 조회했습니다. 캐시된 주문은 사라진 것으로 처리했습니다.
09:25 — 첫 중복 주문이 나왔습니다
재시작 후 전략은 기존 롱 포지션을 확인하고 신호 로직을 실행한 뒤, 포지션을 줄이는 0.01개 주문을 하나 더 제출했습니다. 이제 페이퍼 거래소에는 유효한 주문이 2개 있었습니다. 각각만 보면 나쁜 주문은 아니었습니다. 하지만 둘 다 체결되면 의도한 양의 2배를 매도할 수 있었습니다.
처음에는 신호 루프가 문제라고 생각했습니다. 루프가 아니라 불완전한 스냅샷이 문제였습니다. 전략은 “내 포지션이 뭐지?”라고 물었지만, “아직 처리 중인 주문은 뭐가 있지?”라고 묻지 않았습니다.
| 재시작 후 상태 | 프로세스가 파악한 상태 | 계좌에 실제로 잡힌 상태 |
|---|---|---|
| 포지션 | 롱 0.04 | 롱 0.04 |
| 대기 중인 청산 주문 | 없음 | 각각 0.01인 주문 2개 |
| 주문 1개 체결 후 의도한 익스포저 | 롱 0.03 | 롱 0.02까지 줄어들 수 있음 |
10:00 — 복구를 고치자 타이밍 문제가 나타났습니다
새로운 의사결정을 허용하기 전에 계좌의 포지션과 미체결 주문을 바탕으로 상태를 다시 구성하도록 시작 절차를 바꿨습니다. 중복 주문은 사라졌습니다. 이어서 연결 끊김 상황을 더 까다롭게 만들었습니다. 전략이 오프라인인 동안 주문 하나가 체결되고, 체결 알림은 재연결 후에 도착하게 했습니다.
계좌 스냅샷에는 이미 체결 결과가 반영되어 있었습니다. 그런데 지연된 알림을 받고 로컬 포지션을 한 번 더 줄였습니다. 몇 초 동안 전략은 계좌에 0.03개가 있는데도 계약 0.02개를 보유한 것으로 판단했습니다. 다음 리밸런싱은 존재하지 않는 부족분을 기준으로 진행될 뻔했습니다.
조정 규칙을 추가했습니다. 계좌 스냅샷을 시작 상태로 삼고, 이벤트 식별자를 이용해 스냅샷에 이미 반영된 체결을 무시하며, 초기 동기화가 끝날 때까지 주문을 제출하지 않습니다. 알림은 늦게 오거나 두 번 올 수 있습니다. 복구 과정은 둘 다 견딜 수 있어야 합니다.
13:40 — 재생 테스트에서 조용한 불일치를 잡았습니다
원래 실행과 재시작한 실행에서 같은 가격 경로를 재생했습니다. 최종 손익만 비교했다면 문제를 놓쳤을 것입니다. 시장이 반전된 뒤 두 버전의 최종 포지션은 같았습니다. 주문 이벤트를 비교하자 차이가 드러났습니다.
각 의사결정과 함께 그때 읽은 상태도 기록했습니다. 포지션, 미체결 주문, 마지막 처리 체결 식별자, 신호값, 전략 버전이었습니다. 처음으로 갈라진 이벤트가 발생한 이유를 확인할 수 있었습니다. 한 실행은 처리 중인 주문을 봤지만 다른 실행은 목록이 비어 있다고 판단했습니다. 나중에는 한쪽이 체결을 두 번 반영했습니다.
최종 잔고가 같다고 동작까지 같았다는 뜻은 아닙니다. 특히 복구 경계 전후에는 의사결정과 주문 순서를 비교해야 합니다.
16:20 — 다음에는 이렇게 하겠습니다
계좌 상태 전환을 확인하기 전에 가격 데이터를 재생하는 데 너무 많은 시간을 썼습니다. 다음에는 먼저 실패 상황을 주입하고 시장 경로는 거의 평탄하게 유지하겠습니다. 변동성 있는 움직임이 진단을 흐리지 않아 소프트웨어 오류를 쉽게 확인할 수 있습니다.
- 포지션과 부분 체결된 주문이 있는 상태에서 재시작합니다.
- 주문 제출 후 연결을 끊고, 체결 알림이 도착하기 전에 다시 연결합니다.
- 같은 체결 이벤트를 두 번 전달하고 상태는 한 번만 바뀌는지 확인합니다.
- 포지션과 미체결 주문이 모두 조정될 때까지 새 주문을 차단합니다.
- 중단 없이 실행한 경우와 재시작한 경우의 의사결정 로그와 주문 로그를 비교합니다.
전략 버전 및 이벤트 로그와 함께 복구 스냅샷도 저장하기 시작했습니다. 정확한 재연결 순서를 누군가 기억해 내길 기다릴 필요 없이 몇 분 만에 실패 상황을 재현할 수 있었습니다.
프로세스가 살아 있는 동안에만 제대로 작동하는 페이퍼 전략은 전체 리허설을 통과한 것이 아닙니다. 포지션 보유 중에 재시작하고, 체결 알림을 늦게 전달한 다음, 이후 전송하는 모든 주문을 살펴보세요. 실패가 절대 없다는 것을 증명하려는 게 아닙니다. 페이퍼 계좌가 같은 교훈을 갑자기 알려 주기 전에 복구 과정을 눈에 보이게 만드는 것이 목표입니다.
← 전체 글


