시스템설계 청산 손익만 남기면 알수 없다. 추세추종 봇에 붙인 거래 계측
게시물 옵션
본문
청산된 거래의 손익만 남기면 그 거래가 보유 중 어디까지 올랐다가 내려왔는지 알 수 없다. 코인랩이 빗썸에서 운용하는 추세추종 봇은 매수·매도 판정을 바꾸지 않은 채 보유 중 최고·최저 지점과 청산 사유를 기록하는 계측을 붙였다. 그 설계 결정과 구현 중 놓칠 뻔한 곳을 정리한다.
트레일링 스탑이란 무엇인가 이익 보호 장치의 원리에서는 이익을 지키는 장치의 원리를 다뤘다. 그런데 그런 장치를 넣을지는 원리가 아니라 우리 거래가 실제로 어떻게 끝났는지로 정해야 한다. 이 계측을 붙이기 전까지 추세추종 봇은 청산 시점의 실현 손익만 남겼다. 고점 대비 얼마나 반납하고 나왔는지 물어도 답할 자료가 없었다. 이 글은 그 자료를 쌓는 장치를 어떻게 설계했는지 다룬다.
1. 무엇을 기록하기로 했나
거래 기록에는 보유 기간 중 가장 유리했던 지점(MFE)과 가장 불리했던 지점(MAE)의 가격, 수익률, 시각을 더했다. 여기에 진입 가격, 청산 사유 라벨, 진입 시점의 변동성, 당시 전략 설정의 사본도 붙였다. 설정을 바꿀 때마다 한 줄씩 남기는 변경 이력도 새로 만들었다. 목적은 하나다. 트레일링 스탑 같은 후속 개선이 얼마나 효과가 있을지를 실거래 기록으로 계속 다시 확인하는 것이다.
2. 설계 결정 다섯 가지
이 작업은 설계를 문서로 먼저 쓰고 여러 번 고쳐 쓴 뒤에 구현했다. 한 단계가 끝날 때마다 결과를 확인하고서 다음 단계로 넘어갔다. 아래 표는 그중 결과에 영향을 준 결정이다.
| 결정 | 선택 | 이유 |
|---|---|---|
| 어디에 저장하나 | 청산 기록과 보유 중 상태 양쪽 | 이 봇은 매 루프 상태를 새로 읽고 메모리에 남기지 않는다. 청산 기록에만 칸을 두면 보유 중 누적이 불가능하다. |
| 언제 갱신하나 | 매도 조건 판단 앞 | 뒤에 두면 손절을 일으킨 바로 그 순간의 가격이 기록되지 않는다. |
| 청산 사유 라벨 | 한 곳에서만 파생 | 호출하는 쪽이 라벨을 직접 정하는 경로를 없애면 사유와 라벨이 어긋날 수 없다. |
| 과거 거래 | 다시 재야 하는 값은 비움 | 이미 있는 값을 옮겨 적는 진입가와 사유 라벨만 소급해 채웠다. |
| 조회 방식 | 스냅샷 파일 중계 | 웹이 거래 DB를 직접 여는 예외를 처음 만들 만큼 필요한 규모가 아니었다. |
두 번째 결정이 가장 미묘하다. 손절은 가격이 기준선을 뚫는 순간 발동하고 함수는 거기서 끝난다. 갱신을 판단 뒤에 두면 손절을 일으킨 그 가격은 영원히 기록되지 않는다. 그러면 손절로 끝난 모든 거래에서 최저 지점이 실제보다 얕게 남는다. 트레일링 스탑의 개선폭을 한쪽으로 과대평가하게 만드는 편향이다. 매매 판단 조건식은 전혀 건드리지 않는다는 것을 변경 내용 비교로 확인한 뒤 앞쪽 배치로 확정했다.
네 번째 결정은 원칙 하나로 가른다. 다시 측정해야 나오는 값, 즉 최고·최저 지점, 진입 변동성, 전략 설정은 과거 거래를 NULL로 남겼다. 지금 시세로 과거를 재구성하면 기록이 아니라 추정이 되기 때문이다. 반면 진입가와 청산 사유는 이미 기록에 있는 원본을 옮겨 적는 일이다. 같은 종목·거래소에서 매수와 매도가 항상 번갈아 나온다는 사실을 기록 88행 전수로 확인했고, 위반은 0건이었다. 그 근거로 순서대로 짝지어 채웠다.
3. 구현 중 놓칠 뻔한 곳
청산 경로는 두 개였다. 일반 청산 함수 말고 서킷브레이커가 별도 경로로 기록을 남긴다. 처음엔 일반 경로만 고치고 다 끝났다고 여겼다. 사전에 조사하며 적어 둔 메모에는 서킷브레이커가 일반 청산을 거치지 않는다고 쓰여 있었다. 그 메모와 변경 내용을 다시 대조하다 누락을 발견했다.
서킷브레이커는 정의상 가장 위험한 상황에서 나가는 거래다. 그 거래들의 기록이 통째로 비면 이 계측이 가장 필요한 지점에 공백이 생긴다.
수정은 간단했다. 그 함수가 이미 읽고 있던 보유 상태를 그대로 기록 호출에 넘겼다. 합성 포지션을 만들어 서킷브레이커를 강제로 태웠더니 필드 9개가 모두 기록되고 보유 상태는 전부 비워졌다. 이 확인은 모의 상황에서 한 것이다. 기록을 붙이는 작업에서 새 칸을 만드는 일보다 어려운 것은 기록을 남기는 호출부를 빠짐없이 찾는 일이라는 점을 이 사건이 보여 준다.
4. 판정이 안 바뀐 것을 확인한 방법
매수와 매도 여부를 가르는 조건식 4개가 수정 전후로 문자 단위로 같음을 확인했다. 실제 주문을 내는 파일은 작업 내내 수정 시각이 그대로였다. 적용은 프로세스를 종료하면 와치독이 약 5초 안에 다시 띄우는 방식이었다.
재시작 시점에 보유 중이던 포지션은 예상과 달리 4건이었다. 작업 중에도 라이브 봇이 계속 거래했기 때문이다. 재시작 전후로 이 4건의 기존 값 32개를 전수 대조했고 모두 일치했다. 2~3루프, 약 57초가 지나자 4건 모두 최고·최저 값이 비어 있던 상태에서 실제 값으로 채워지기 시작했다. 진입 변동성과 설정 사본은 여전히 비어 있었다. 매수가 새 코드 반영 이전이라 예상된 동작이었다.
5. 쌓인 기록이 보여준 것
계측을 붙이고 한 달쯤 지난 9월 20일, 빗썸 실거래 청산 43건(8월 22일~9월 20일)을 청산 사유별로 나눠 봤다. 최고·최저 기록이 있는 거래는 40건이었고 초기 3건은 계측 이전 거래라 비어 있었다.
| 청산 사유 | 건수 | 평균 손익 | 평균 보유(시간) |
|---|---|---|---|
| 다음 거래일 시가 청산 | 33 | +0.83% | 14.2 |
| 트레일링 스탑 | 5 | +2.91% | 11.7 |
| 손절 | 3 | -7.03% | 6.0 |
| 서킷브레이커 | 2 | +2.49% | 23.0 |
표의 손익은 당시 수수료를 0으로 설정해 두었던 기간의 기록값(수수료 반영 전)이다. 빗썸 수수료는 10월 6일 편도 0.04%로 정정했고, 이후 성과는 수수료를 반영해 다시 집계하고 있다.
손절 3건의 평균은 -7.03%였고, 손실은 사실상 이 3건에 몰려 있었다. 사유 라벨 덕에 이런 구분이 표 하나로 나온다. 더 흥미로운 것은 최고 지점 기록이다.
손절 3건이 청산 전에 도달한 최고 수익률. 세 건 모두 결국 손절로 마감됐다. 표본이 3건이라 방향만 본다.
앞의 두 건은 크게 오른 뒤에 손절까지 내려왔다. 이 두 건이 트레일링 스탑을 도입하게 된 계기였다. 마지막 SUI 건은 애초에 오른 적이 없다. 실현 손익만 있었다면 세 건은 같은 손절로 보였을 것이다. 계측 덕분에 두 건은 이익을 반납한 거래이고 한 건은 오르지 못한 거래라는 구분이 가능해졌다.
6. 이 기록의 한계
첫째, 최고·최저는 20초 간격 루프로 샘플링한 값이라 근사다. 샘플 사이에 지나간 극단값은 잡히지 않는다. 둘째, 사유별 표본이 손절 3건, 서킷브레이커 2건, 트레일링 5건으로 작다. 셀 단위 결론은 낼 수 없고 방향만 읽는다.
셋째, 이 표는 계측을 만든 뒤 쌓인 기록을 나중에 모아 본 사후 분석이다. 손절 3건 중 두 건은 트레일링 스탑을 만든 계기가 된 사건이고, 같은 두 건이 계측이 처음 채운 표본이기도 했다. 패턴을 찾은 데이터로 그 패턴을 검증할 수는 없다. 그래서 이 표를 트레일링 스탑이 필요하다는 증거로 쓰지 않는다. 계측의 가치는 앞으로 새로 쌓이는 거래에서 같은 질문을 다시 물을 수 있다는 데 있다.
넷째, 소급해서 채운 진입가와 청산 사유는 새로 계산한 값이 아니라 원본을 옮겨 적은 값이다. 그 한도에서만 과거 거래에 적용했다.
7. 바꾼 것과 바꾸지 않은 것
바꾼 것은 기록과 조회다. 거래 기록 칸, 보유 중 상태 칸, 설정 변경 이력, 관리자만 보는 읽기 전용 조회 페이지가 생겼다. 바꾸지 않은 것은 매수와 매도를 결정하는 조건식, 그리고 실제 주문을 내는 코드다. 위 비교와 파일 수정 시각이 그 근거다.
아직 모르는 것
- 20초보다 촘촘하게 샘플링하면 사유별 평균 최고·최저가 얼마나 달라지는지는 모른다.
- 손절 3건에서 본 구분이 거래가 더 쌓여도 유지되는지는 모른다.
- 조회용 스냅샷 방식은 거래가 5,000건을 넘으면 설계를 다시 보기로 했다. 지금 규모에서는 부담이 무시할 수준임을 실측으로 확인했다.
함께 읽기
- 이전 편: 새필터는 실거래에 넣기전에 관찰 기록부터 쌓는다
- 같은 주제의 원리 편: 트레일링 스탑이란 무엇인가 이익 보호 장치의 원리
- 관련 공지: [업데이트] 트렌드 대시보드 오픈 — 추세추종 봇의 실제 운용 화면