본문으로 건너뛰기
엔지니어링부동소수점API 통합
부동소수점은 돈을 잃게 만듭니다: 트레이딩 시스템의 정밀도

부동소수점은 돈을 잃게 만듭니다: 트레이딩 시스템의 정밀도

반올림은 이미 해결된 문제처럼 들립니다. 숫자가 있고, 스텝 사이즈가 있으니, 가장 가까운 유효한 값으로 반올림하면 됩니다. 유틸리티 함수 하나, 스무 줄이면 영원히 끝나는 일처럼 보입니다.

함수 하나로 끝나지 않습니다. 여러 거래소에 주문을 보내는 시스템에서 정밀도는 하나의 영역이며, 여기서 발생하는 버그에는 고약한 공통점이 있습니다. 예외를 던지지 않습니다. 대신 약간 잘못된 주문을 만들어내거나, 사용자가 알 수 없는 이유로 거부되는 주문을 만들어냅니다.

우리 주문 경로에서 실제로 겪은 세 가지 문제를 통해, 이 작업이 실제로 어떤 모습인지 보여드리겠습니다.

핵심 요약

  • IEEE-754 때문에 스텝 정렬을 신뢰할 수 없습니다. 0.29에 100을 곱하면 28.999999999999996이 나오고, 이를 내림하면 정확히 한 스텝만큼 작은 값이 됩니다.
  • 세 거래소가 서로 호환되지 않는 세 가지 최소 주문 규칙을 적용합니다. 한 곳은 USDT 가치를 확인하고, 한 곳은 계약 수를 확인하며, 한 곳은 둘 다 확인합니다.
  • 이 영역에서 가장 심각했던 버그는 거래소에 존재하지 않는 규칙을 만들어낸 다음, 그 규칙으로 유효한 주문을 거부한 데서 비롯되었습니다.
  • API가 주지 않은 한도를 절대 만들어내지 마세요. 거래소에 명목 가치 최소값이 없다면, 다른 필드로 그것을 계산하는 것은 방어적 코딩이 아니라 날조입니다.
  • 거래소 메타데이터가 없으면 기본값으로 대체하지 말고 오류를 발생시켜야 합니다. 잘못된 기본값은 조용히 잘못된 주문을 만들어냅니다.

1. 산술 문제: 반올림이 숫자를 움직인다

가장 단순한 경우부터 시작하겠습니다. 사람들이 안전하다고 가정하는 바로 그 경우이기 때문입니다.

거래소는 수량을 스텝 사이즈의 배수로만 받습니다. 0.29가 있고 스텝은 0.01입니다. 이 값은 이미 유효하므로 내림을 해도 아무 변화가 없어야 합니다. 표준 구현은 값을 키우고, 내림한 뒤, 다시 줄입니다.

0.29 / 0.01 = 28.999999999999996   ← IEEE-754, not 29
floor(...)  = 28
28 × 0.01   = 0.28

이미 정렬되어 있던 값이 조용히 정확히 한 스텝 아래로, 더 작은 주문 방향으로 이동했습니다. 예외도 없고 경고도 없으며, 특정한 값과 스텝의 조합에서만 발생합니다. 바로 이 점이 알아차리기 어렵게 만듭니다.

해결책은 스케일링 과정에서 엡실론을 적용하는 것입니다. 표현 오차를 흡수할 만큼은 크되, 0.289999처럼 실제로 기준보다 낮은 값은 여전히 올바르게 내려가도록 충분히 작아야 합니다. 이것을 발견한 과정은 이 플랫폼을 만들며 과소평가했던 것들에서 다뤘습니다. 여기서 중요한 것은 이 버그가 속한 범주입니다.

수량에 대한 산술 연산을 신뢰할 수 없다는 사실을 일단 받아들이고 나면, 수량을 건드리는 다른 모든 것을 들여다보기 시작하게 됩니다. 진짜 놀라운 일들은 바로 거기에 있습니다.

2. 세 거래소, 서로 호환되지 않는 세 가지 최소값

모든 거래소는 최소 주문을 강제합니다. 규칙이 그 값에서는 다를 수 있어도 형태는 같을 것이라고 예상하기 마련입니다. 그런데 형태 자체가 다릅니다.

거래소규칙
Binanceqty × price >= minNotional (USDT 가치 확인)
Bybitqty >= minOrderQty 및 notional >= minNotional (두 가지 확인)
OKX 무기한 선물qty >= minSz (계약 수, 명목 가치 확인은 전혀 없음)

이 표를 같은 주문에 대해 던지는 세 가지 다른 질문이라고 읽어 보세요. Binance는 그것이 얼마의 가치인지 묻습니다. OKX는 그것이 몇 계약인지 묻습니다. Bybit는 둘 다 묻는데, 어느 한쪽만 충족해도 여전히 거부될 수 있습니다.

OKX에는 또 하나의 복잡한 점이 있습니다. 수량이 코인이 아니라 계약 단위이므로, 이 모든 것이 의미를 가지려면 먼저 계약 크기로 둘 사이를 변환해야 합니다. 한 거래소에서 “0.5 BTC”를 뜻하는 수량이 다른 거래소에서는 “50 계약”을 뜻합니다.

우리가 도달한 아키텍처 결론은 이것입니다. 각 거래소가 자신의 주문을 스스로 검증한다. 전략 엔진에는 어느 거래소와 통신하는지에 대한 조건문이 단 하나도 없습니다. 주문을 어댑터에 넘기면 어댑터가 자신의 규칙을 적용합니다. 이 규칙들을 하나의 추상화 뒤로 통합하려고 시도할 때마다, 네 번째 경우가 등장하는 순간 그 추상화는 거짓말이 되어 버렸습니다.

3. 비싼 대가를 치른 버그: 존재하지 않는 규칙 만들기

이것은 제가 다른 엔지니어에게 가장 피하라고 말하고 싶은 버그입니다. 그 실수가 마치 성실함처럼 보이기 때문입니다.

OKX 무기한 선물에는 명목 가치 최소값이 없습니다. 우리는 그 사실을 몰랐습니다. 다른 거래소를 유추해서, 우리는 그 값을 직접 계산했습니다. 최소 계약 수에 계약 크기를 곱하고 다시 가격을 곱했습니다. 합리적으로 보였습니다. 그럴듯한 USDT 수치가 나왔습니다. 그리고 그것은 허구였습니다.

어디서 문제가 터졌는지는 구체적이며 짚어 볼 만합니다. DCA 전략은 안전 주문을 진입가 아래에 배치합니다. 우리가 만들어낸 최소값은 한 가격에서 계산된 뒤 더 낮은 가격의 주문과 비교되었습니다. 그래서 사다리 맨 위에서는 통과했던 같은 수량이 더 아래로 내려가면 실패했습니다.

그 결과, 정확히 0.1 계약짜리 주문이, 즉 거래소의 최소값 그 자체여서 당연히 받아들여졌을 주문이, 우리 시스템을 떠나기도 전에 우리 자신의 검증기에 의해 거부되었습니다. 거래소는 그 주문을 본 적조차 없습니다. 사용자 입장에서 보면 전략이 설정한 대로 동작하지 않은 것뿐입니다.

이 교훈은 트레이딩을 훨씬 넘어서 일반화됩니다.

API가 주지 않은 제약을 만들어내지 마세요. 거래소가 최소값을 계약 단위로 노출한다면, 그 최소값은 계약 단위입니다. 가치 기반의 등가물을 도출하는 것은 신중한 것이 아니라, 규칙을 만들어낸 뒤 그것을 사용자에게 강제하는 것입니다.

돌이켜 보면 단서는 우리가 계산한 값이 가격에 의존한 반면, 거래소의 실제 규칙은 그렇지 않았다는 점이었습니다. 여러분이 만든 제약이 실제 규칙에는 없는 입력값을 가진다면, 그것은 모델링하려던 것과는 다른 무언가를 만든 것입니다.

4. 없는 메타데이터는 기본값이 아니라 오류가 되어야 한다

마지막 이야기는 버그라기보다 하나의 정책이며, 앞선 세 가지가 조용히 재발하는 것을 막아 주는 장치입니다.

모든 거래소는 심볼별 메타데이터를 제공합니다. 스텝 사이즈, 틱 사이즈, 최소 수량, 계약 크기입니다. 이 중 무엇이든 없을 수 있고, 이때 유혹적인 선택은 대체값을 쓰는 것입니다. 스텝 사이즈가 없다고요? 0.001이라고 가정합니다. 최소 수량이 없다고요? 0이라고 가정하고 거래소가 결정하게 둡니다.

우리는 정반대로 합니다. 필수 필드가 없으면 어떤 주문이 구성되기도 전에 즉시 예외를 던집니다. 그 이후의 어떤 코드도 추측 위에 무언가를 쌓을 기회를 얻지 못합니다.

그 근거는 결과의 비대칭성입니다. 던져진 오류는 즉시 드러나는 요란한 실패이며, 그것을 고칠 수 있는 사람 앞에 나타납니다. 기본값은 조용히 잘못된 답이며, 잘못된 크기의 주문을 내보낸 뒤 나중에 돈으로 정체를 드러냅니다. 무시할 수 없는 오류와 검증할 수 없는 숫자 사이에서, 오류 쪽이 언제나 더 저렴합니다.

이것은 메타데이터 계층이 자신이 아는 것에 대해 정직하다는 뜻이기도 합니다. 필수 필드를 읽는 코드는 실제 값을 받거나 예외를 받습니다. 실제 데이터의 형태를 뒤집어쓴 자리표시자를 받는 일은 결코 없습니다.

기억해 둘 것

  • 스텝과 틱 정렬을 서식 문제가 아니라 값을 움직일 수 있는 산술로 다루세요.
  • 정말로 다른 규칙을 억지로 통합하지 마세요. 거래소별 검증은 거래소가 추가되어도 계속 올바르게 유지됩니다. 호환되지 않는 규칙 위에 얹은 공용 추상화는 그렇지 못합니다.
  • API가 명시하지 않은 한도를 절대 도출하지 마세요. 여러분이 만든 제약이 실제 규칙에는 없는 입력값을 받는다면, 그것은 다른 제약입니다.
  • 메타데이터가 없으면 요란하게 실패하세요. 기본값은 눈에 보이는 오류를 눈에 보이지 않는 잘못된 가격의 주문으로 바꿔 버립니다.

정밀도 작업은 화려하지 않고 기능 목록에 절대 오르지 않습니다. 하지만 이 버그들은 하나같이 잘못된 주문 또는 아예 존재하지 않은 주문을 만들어냈고, 누군가를 대신해 거래하는 시스템에서 그것은 그 사람의 돈을 곧바로 잃는 것과 같은 범주의 실패입니다.


관련 읽을거리

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

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

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