본문으로 건너뛰기
엔지니어링배운 교훈프로그래밍
트레이딩 봇 플랫폼을 만들기 전 나 자신에게 해주고 싶은 6가지

트레이딩 봇 플랫폼을 만들기 전 나 자신에게 해주고 싶은 6가지

이 글은 Freya Finance를 만드는 과정에 관한 짧은 시리즈를 마무리합니다. 앞선 글들을 반복하기보다는, 시작 시점의 나 자신에게 실제로 해주고 싶은 말을 그것을 깨닫는 데 든 대가가 큰 순서대로 정리했습니다.

공통점이 하나 있는데, 불편한 진실입니다. 이 실패들 중 어느 것도 스스로 정체를 드러내지 않았습니다. 그럴듯한 숫자, 유효해 보이는 주문, 올바른 것처럼 보이는 화면을 만들어냈습니다. 누군가의 돈을 다루는 시스템에서 예외를 던지는 버그는 오히려 친절한 축에 속합니다.

핵심 요약

  • 서드파티 API는 조용히 실패합니다. 요청한 것보다 적은 데이터를 담은 HTTP 200이야말로 대비해야 할 실패 방식이며, 500이 아닙니다.
  • 시뮬레이터가 시뮬레이션하는 대상을 테스트하기 전에 시뮬레이터 자체를 먼저 테스트하세요. 고장 난 시뮬레이터는 정상 작동하는 것과 똑같아 보이는 숫자를 반환합니다.
  • 시뮬레이션하지 못한 것을 보고하세요. 조건 하나를 조용히 누락한 깔끔해 보이는 결과보다, 공백이 명시된 결과가 더 가치 있습니다.
  • 정밀도는 유틸리티 함수가 아니라 하나의 영역입니다. 스텝 크기, 틱 크기, 최소 단위는 거래쌍마다, 거래소마다, 시장 유형마다 다릅니다.
  • 제약을 일찍 선택하세요. 사용자 자금을 절대 보관하지 않기로 결정한 것은 문제를 해결하는 대신 문제의 한 범주 전체를 아예 없애버렸습니다.
  • 돈을 다루는 인터페이스에서는 플랫폼이 편집으로 취급하는 동작들을 점검하세요. 포커스된 숫자 입력창 위에서의 마우스 휠이 그중 하나입니다.

1. 모든 API는 가끔 잘못된 방식으로 성공한다고 가정하라

내가 저지른 가장 값비싼 가정은 실패한 요청이 실패처럼 보일 것이라는 생각이었습니다.

부하가 걸린 거래소는 요청한 것보다 적은 캔들을 담은 HTTP 200을 반환하며, 오류 필드도 경고도 없습니다. 우리 심층 백테스트 중 하나는 약 536,000개의 캔들을 요청했는데 58,000개를 받았습니다. 한 시간 뒤 동일한 요청은 전체 세트를 반환했습니다. 응답에서 두 경우를 구분할 만한 것은 아무것도 없었습니다.

확인하지 않으면 엔진은 의도한 기간의 일부에 대해서만 전략을 시뮬레이션하고 자신만만한 숫자를 보고합니다. 이는 크래시보다 나쁩니다. 크래시는 정직하기 때문입니다.

여기서 두 가지 규칙이 나왔습니다. 상태 코드뿐 아니라 성공 응답의 형태(shape)를 검증하라, 그리고 어떻게 대응할지 결정하기 전에 일시적 실패와 영구적 실패를 구분하라. 둘은 발생하는 순간에는 똑같아 보이지만 정반대의 대응이 필요합니다.

전체 이야기: 내가 과소평가한 것

2. 전략이 아니라 시뮬레이터를 테스트하라

모든 본능은 전략 로직을 테스트하라고 부추깁니다. 그게 흥미로운 부분이니까요. 함정은 고장 난 시뮬레이터가 정상 작동하는 것과 구별할 수 없는 출력을 만들어낸다는 데 있습니다. 데이터의 10분의 1만 조용히 처리한 실행도 여전히 숫자를 반환합니다. 청산이 한 번도 평가되지 않은 실행은 수상할 정도로 밋밋한 숫자를 반환합니다.

이를 해결한 전환은 전략이 바꿀 수 없는 것들에 대해 단언(assert)하는 것이었습니다. 처리된 캔들이 가져온 캔들과 일치하는지, 알려진 기간에 대한 실행이 실제로 그 기간을 온전히 포괄하는지, 반드시 트리거되도록 되어 있는 구성이 실제로 거래를 만들어내는지 확인하는 것입니다.

이러한 불변식(invariant)이 유지될 때 이상한 결과는 진짜 발견입니다. 유지되지 않을 때 그 결과는 정장을 차려입은 잡음일 뿐입니다.

3. 시뮬레이션하지 못한 것을 보고하라

이것은 버그라기보다 설계상의 선택이며, 가장 과소평가되는 부분이라고 생각합니다.

어떤 조건들은 재현할 수 없습니다. 외부 웹훅 시그널에는 과거 기록이 없습니다. 유혹적인 방법은 그것을 건너뛰고 나머지만 평가하는 것인데, 그러면 출력이 완전해 보이게 됩니다.

그것이 A AND B를 요구하는 전략에 어떤 영향을 미치는지 보세요. B를 시뮬레이션할 수 없으면 A가 혼자 결정하게 되고, 그래서 시뮬레이션된 봇은 실제 봇이라면 진입하지 않았을 곳에서 진입합니다. 이 오류는 무작위가 아닙니다. AND 로직에서 조건 하나를 누락하면 현실보다 더 많은 거래만 만들어낼 수 있으므로, 그 편향은 매번 더 좋아 보이는 결과 쪽을 가리킵니다.

우리는 시뮬레이션을 계속 실행하되, 결과에 무엇이 건너뛰어졌는지 명시하는 명확한 경고를 담습니다. 큰 소리로 실패하지 못하는 시뮬레이터는 조용히 실패하며, 숫자를 만들어내는 무언가에서의 조용한 실패는 존재하는 최악의 실패 방식입니다.

전체 이야기: 백테스트가 거짓말하는 이유

4. 매끄러운 자산 곡선은 답이 아니라 질문이다

앞의 내용과 관련이 있지만, 코드가 아니라 사람을 속이기 때문에 따로 떼어 다룰 가치가 있습니다.

손절매가 없는 DCA 안전 주문 사다리(하락 평균화 주문)는 거의 손실을 실현하지 않습니다. 보유하면서 계속 추가 매수합니다. 그래서 종료된 거래는 거의 전부 수익 거래이고, 승률은 훌륭해 보이며, 자산 곡선은 깔끔한 계단 모양이 됩니다.

위험은 어디로도 사라지지 않았습니다. 이연되었을 뿐이며, 그 이연에는 명확한 한계가 있습니다: 추가 안전 주문에 쓸 수 있는 남은 자본입니다. 유용한 질문은 "낙폭이 얼마였는가"가 아니라 "가격이 계속 불리하게 움직이면 어느 시점에 전략의 여력이 바닥나는가"입니다.

낙폭 자체도 똑같은 의심이 필요합니다. 낙폭은 최고점의 할당 자본을 기준으로 측정되므로, 동일한 200 USDT 손실이 500 USDT 할당에서는 40%로, 5,000에서는 4%로 표시됩니다. 동일하게 거래하는 두 봇이 극단적으로 다른 숫자를 보여줄 수 있습니다.

전체 이야기: 낙폭 이해하기

5. 정밀도는 헬퍼가 아니라 하나의 영역이다

"수량을 유효한 스텝 단위로 반올림하기"를 나는 하나의 유틸리티 함수로 분류했습니다. 그것은 하나의 작업 범주이며, 실수가 돈으로 이어지는 곳에 숨어 있습니다.

부동소수점이 그 시작입니다. 0.29를 0.01 스텝으로 나누면 28.999999999999996이 나오는데, 이는 조용히 한 스텝 낮게 내림되어 더 작은 주문 쪽으로 향합니다.

그다음으로는 거래소들이 규칙 자체에 대해 서로 다릅니다. 어떤 곳은 주문 가치를 USDT로 확인하고, 어떤 곳은 가치 확인 없이 계약 수만 확인하며, 어떤 곳은 둘 다 확인합니다. 이 영역에서 우리의 최악의 버그는 한 거래소에는 존재하지 않는 최소 단위를 지어낸 다음, 그것을 근거로 유효한 주문을 거부한 데서 비롯됐습니다. 돌이켜보면 단서는, 우리 버전의 제약 조건은 가격에 의존했지만 실제 제약은 그렇지 않았다는 점이었습니다.

거래소가 거래쌍마다 정의하는 것에 대해 하나의 상수를 적어 넣는 곳이라면 어디든, 당신은 언젠가 터질 시한폭탄 같은 버그를 작성하고 있는 것입니다.

전체 이야기: 부동소수점은 당신의 돈을 앗아간다

6. 제약을 일찍 선택하라

마지막 교훈은 실수가 아닙니다. 그 결과를 내가 좋은 쪽으로 과소평가했던 하나의 결정입니다.

Freya는 사용자의 거래 자금을 절대 보관하지 않습니다. API 키는 읽기 및 거래 권한을 가지며 출금 권한은 절대 갖지 않습니다. 그리고 출금 권한을 함께 지닌 채로 들어온 키는 받아들여 무시하는 것이 아니라 아예 거부됩니다. 이 구분은 들리는 것보다 더 중요합니다. "우리는 출금하지 않겠다"는 사용자가 검증할 수 없는 우리 행동에 관한 주장이지만, "그 키는 출금할 수 없다"는 거래소가 강제하며 사용자가 자신의 설정에서 직접 확인할 수 있습니다.

나는 이것이 마케팅 포인트가 될 것이라 예상했습니다. 알고 보니 이는 아키텍처의 문제였고, 시스템을 더 작게 만들었습니다: 수탁 원장도 없고, 대사(reconciliation)도 없고, 출금 대기열도 없으며, 거래 자본을 위한 콜드 스토리지 정책도 없습니다. 금융 소프트웨어에서 가장 어려운 문제 한 범주 전체가 존재하지 않는데, 그 문제를 만들어내는 것을 우리가 애초에 떠안지 않았기 때문입니다.

그 자리를 대신하는 것은 더 좁고 더 날카롭습니다. 시스템은 실시간으로 정확해야 합니다. 무엇도 되돌릴 수 없기 때문입니다. 다른 사람의 거래소 계정에서 체결된 주문은 최종적입니다.

전체 이야기: 비수탁형 거래 설계하기

필요할 줄 몰랐던 교훈 하나

보너스입니다. 나를 가장 놀라게 했기 때문입니다. 돈을 다루는 인터페이스에서 프런트엔드는 엔진과 똑같은 수준의 편집증이 필요합니다.

포커스된 <input type="number">는 그 위에서 휠을 스크롤하면 값이 증감합니다. 수량 선택기에서는 그저 성가신 정도입니다. 하지만 레버리지, 주문 크기, 가격에서는 사용자가 선택한 값을 조용히 다시 써버리는데도 폼은 여전히 유효성 검사를 통과합니다. 이 버그는 스크린샷에서 전혀 보이지 않습니다.

전체 이야기: 마우스 휠이 당신의 숫자 입력창을 편집하고 있다


이 모든 것을 한 줄로 압축한다면

스스로 정체를 드러내지 않는 실패를 대비해 만드세요. 예외를 던지는 실패들은 알아서 당신을 찾아옵니다.


관련 읽을거리

배운 것을 실전에 적용하세요.

오늘 Freya 계정을 만들어 배운 지식을 실전에 적용해 보세요.

무료로 시작
이 콘텐츠는 교육 목적으로만 제공되며 금융 자문을 구성하지 않습니다. 암호화폐 거래에는 상당한 리스크가 있습니다. 잃을 수 없는 금액 이상으로는 절대 투자하지 마세요.