바이낸스 선물 자동매매 봇 청산 오류 -2019를 정리했다.
-2022를 잡고 다음날 발생한 오류로, 0.001 BTC 포지션을 1 BTC로 착각해 증거금 부족이 발생했다.
이 글에서는 ccxt fetch_positions의 contracts 단위 오류 원인과 해결법을 정리한다.
-2022를 잡았다.
청산 시장가 주문에서 reduceOnly 파라미터를 제거하고 정상 작동을 확인했다.
5월 27일에 수정을 완료했다.
5월 28일 새벽 1시.
또 청산 오류가 났다.
이번엔 다른 코드였다.
{"code": -2019, "msg": "Margin is insufficient."}
증거금이 부족하다는 뜻이다.
이상했다.
BTC 롱 포지션 0.001을 청산하는 건데 증거금이 왜 부족한가.
바이낸스 -2019 오류가 청산 도중에 발생하는 이유
-2019는 "증거금(Margin)이 부족하다"는 바이낸스 API 오류 코드다.
선물 거래에서 포지션을 열거나 주문을 낼 때 계좌에 충분한 증거금이 있어야 한다.
잔고가 주문에 필요한 증거금보다 적으면 바이낸스가 -2019로 거부한다.
일반적으로는 잔고 부족이 원인이다.
그런데 이번 경우는 잔고가 충분했다.
청산인데 증거금이 왜 부족하다는 건지 이해가 안 됐다.
청산이 신규 숏 진입으로 처리되면 -2019 오류가 발생한다
봇이 BTC 롱 0.001을 청산하려고 했다.
바이낸스가 -2019로 거부했다.
청산인데 증거금이 부족할 리가 없다.
청산은 포지션을 줄이는 행위이고, 증거금을 더 요구하는 게 아니라 오히려 돌려받는 쪽이다.
이건 청산이 청산으로 처리되지 않고 신규 주문으로 처리된 것이었다.
contracts 단위 오류로 수량이 1000배 부풀려졌다
-2022를 고치는 과정에서 실제 수량을 조회하기 위해 fetch_positions를 도입했다.
ccxt 라이브러리의 fetch_positions는 포지션 정보를 딕셔너리 형태로 반환한다.
그 딕셔너리 안에 'contracts'라는 키가 있다.
여기서 단위 오류가 발생했다.
바이낸스 선물에서 BTC/USDT의 최소 거래 단위는 0.001 BTC다.
ccxt의 'contracts'는 계약 수를 반환한다.
0.001 BTC = 1계약이다.
즉, BTC 0.001짜리 포지션이 있으면 contracts = 1을 반환한다.
봇이 이 값을 그대로 amount로 사용했다.
amount = 1.0으로 인식했다는 뜻이다.
그래서 봇이 1 BTC 매도를 시도했다.
계좌에 있는 포지션은 0.001 BTC다.
봇이 1 BTC 매도를 시도하면 어떻게 될까.
선물에서 보유량을 초과하는 매도 주문을 내면 초과분은 신규 숏 진입 시도로 처리된다.
0.001 BTC 청산 후 남은 0.999 BTC 매도는 신규 숏 0.999 BTC 진입 시도가 된다.
0.999 BTC 숏 포지션을 열려면 상당한 증거금이 필요하다.
그 증거금이 없으니 -2019가 반환된 것이다.
정리하면 이렇다.
청산하려 했는데 실제로는 "청산 + 대규모 신규 숏 진입 시도"가 됐고,
신규 진입에 필요한 증거금이 없어서 거부됐다.

2봇~5봇에 같은 문제가 없었던 이유
2봇~5봇을 확인했다.
모두 청산 수량으로 self.position['amount']를 그대로 사용하고 있었다.
봇이 포지션에 진입할 때 실제 체결 수량을 BTC 단위로 계산해서 JSON 파일에 저장한다.
BTC/USDT 선물 0.001 BTC 진입 → JSON에 amount = 0.001로 저장.
청산 시 이 값을 그대로 쓴다.
1봇은 -2022를 고치면서 fetch_positions로 실제 수량을 조회하도록 바꿨다.
그 과정에서 contracts 단위를 BTC 단위로 혼동한 것이다.
수정이 오히려 새 버그를 만들었다.
저장된 수량으로 되돌려서 -2019를 해결했다
AI에게 이렇게 요청했다.
"fetch_positions로 수량을 조회하도록 수정했는데 p['contracts']가 BTC 단위가 아니라 계약 수 단위라서 1 BTC로 잘못 인식하는 문제가 생겼다. 2봇~5봇처럼 self.position['amount']를 그대로 쓰는 방식으로 되돌려줘."
수정 전 코드 (문제):
```python
# contracts 단위 오류
positions = self.exchange.fetch_positions([self.symbol])
actual_amount = None
for p in positions:
if float(p['contracts']) > 0:
actual_amount = float(p['contracts']) # 계약 수 반환 (0.001 BTC = 1계약 → 1.0 반환)
break
amount = actual_amount # 1.0으로 인식 → 1 BTC 매도 시도
```
수정 후 코드:
```python
# 2봇~5봇과 동일한 방식
amount = self.position['amount'] # 진입 시 저장한 BTC 단위 값 (0.001)
```
fetch_positions 로직 전체를 제거하고 원래 방식으로 되돌렸다.
수정 후 청산이 정상 작동했다.
결론
-2019가 청산 중에 발생하면 수량 단위를 먼저 의심해야 한다.
ccxt 라이브러리를 사용할 때 fetch_positions가 반환하는 'contracts'는 계약 수 단위다.
BTC/USDT에서 0.001 BTC = 1계약이므로 이 값을 그대로 쓰면 수량이 1000배 뻥튀기된다.
BTC 단위가 필요하면 'contractSize'를 곱하거나 'notional'을 현재가로 나눠야 한다.
가장 간단한 해결책은 진입 시 저장한 수량을 그대로 쓰는 것이다.
진입 시점에 이미 BTC 단위로 계산한 값이 JSON에 있다.
청산 시 굳이 다시 조회할 필요가 없다.
이번 버그 3연속 경험에서 공통적으로 느낀 것이 있다.
수정할 때 검증된 다른 봇의 방식을 참고하지 않고 새 로직을 추가하면 새 버그가 생긴다.
2봇~5봇은 처음부터 self.position['amount']를 써왔고 같은 문제가 단 한 번도 없었다.
기존 방식이 잘 되고 있을 때는 이유 없이 바꾸지 않는 것이 낫다.
'바이낸스 선물 자동매매' 카테고리의 다른 글
| 12화 바이낸스 자동매매, $300이 10년 뒤 $3만이 되는 이유 (0) | 2026.07.13 |
|---|---|
| 바이낸스 봇 리스크 분산, 같은 코인에 3개 몰았더니 다 같이 -$183 (0) | 2026.07.11 |
| 바이낸스 봇 청산 실패 -2022 오류, 고쳤는데 다음날 또 터졌다 (0) | 2026.07.10 |
| 바이낸스 선물 봇 OCO 오류, 진입하자마자 손절 익절이 안 걸리는 이유 (0) | 2026.07.09 |
| 바이낸스 봇 한 달 내내 손절 익절이 안 걸렸다 (0) | 2026.07.09 |