본문 바로가기

자동 매매

아비트리지 봇을 스프레드 봇으로 바꿔보기

아비트리지 봇을 스프레드 봇으로 바꿔보기

업비트 현물 × 바이낸스 선물, 작은 괴리를 반복해서 수익화하는 구조

처음 이 프로그램을 만들 때 생각했던 것은 전형적인 아비트리지였다.

업비트에서 현물을 사고 동시에 바이낸스 선물을 숏으로 잡는다. 이후 두 시장의 가격 괴리가 줄어들면 양쪽 포지션을 정리한다.

그런데 실제 호가를 계속 관찰하면서 생각이 조금 달라졌다.

꼭 큰 가격 괴리만 기다릴 필요가 있을까?

업비트에는 한 틱 자체가 0.5~0.9% 정도 되는 종목들이 꽤 있다. 특히 가격 단위가 큰 종목에서는 한 호가만 잘 잡아도 수수료를 제외하고 남는 공간이 상당하다.

그래서 기존의 아비트리지 구조를 조금 다르게 활용해 보기로 했다.

가격 방향을 맞히는 매매가 아니라, 현물과 선물의 순간적인 가격 차이와 호가 구조를 반복적으로 거래하는 스프레드 봇

현재 테스트 종목은 PEPE다.

 

 


1. 기본 구조

거래소는 두 곳이다.

현물

업비트 PEPE/KRW

헤지

바이낸스 USDT-M 선물 1000PEPEUSDT

프로그램은 업비트의 매수호가와 바이낸스 선물 가격을 원화 기준으로 환산해서 비교한다.

예를 들어 프로그램에서 다음과 같이 표시된다.

BN=0.00474563 | USDT=1,347.5

L1 0.00473000 +0.330% WAIT
L2 0.00472000 +0.543% OK
L3 0.00471000 +0.756% OK
L4 0.00470000 +0.971% OK
L5 0.00469000 +1.186% OK
 

여기서 중요한 것은 +0.543% 같은 숫자가 이미 확정된 수익률이 아니라 진입 시점의 가격 괴리라는 것이다.

실제 수익은 이후의 현물 매도가격, 선물 청산가격, 양쪽 수수료까지 합쳐서 별도로 계산해야 한다.

이 둘을 코드에서 혼동하면 안 된다.


2. 왜 1호가만 보는 방식에서 다중 호가로 바꿨나?

초기 버전은 단순했다.

1호가의 스프레드가 0.35% 이상이면 매수 주문을 넣는다.

그런데 실제로 돌려보니 문제가 있었다.

1호가 +0.21%
2호가 +0.42%
3호가 +0.64%
4호가 +0.86%
5호가 +1.07%
 

이런 상황이 굉장히 자주 나온다.

1호가만 보면 조건 미달이다.

그러나 바로 아래에는 조건을 만족하는 가격들이 줄줄이 있다.

그래서 1호가만 감시하는 것은 체결 기회를 상당히 버리는 방식이라고 판단했다.

현재 구조는 Top 5를 각각 독립적으로 계산한다.

L1 → 조건 검사
L2 → 조건 검사
L3 → 조건 검사
L4 → 조건 검사
L5 → 조건 검사
 

그리고 각 호가가 진입 조건을 만족하면 독립적으로 매수 주문을 배치한다.

현재 테스트 설정은 대략 이런 형태다.

 
LADDER_COUNT = 5
ORDER_KRW = 20000
MAX_POSITION_KRW = 200000
 

즉 한 호가당 2만원씩 최대 5개의 주문을 운용하면서 전체 포지션은 20만원을 넘지 않도록 한다.


3. 이 구조에서 가장 중요한 점: 슬롯과 호가 순위는 같은 것이 아니다

코딩하면서 꽤 중요한 문제가 하나 나왔다.

예를 들어 처음에 이렇게 주문했다고 하자.

S1 = 0.004710
S2 = 0.004700
S3 = 0.004690
S4 = 0.004680
S5 = 0.004670
 

그런데 업비트 가격이 위로 올라간다.

그러면 원래 3호가였던 주문이 어느 순간 4호가, 5호가, 6호가로 밀릴 수 있다.

따라서 단순히

 
S1 == 1호가
S2 == 2호가
S3 == 3호가
 

라고 생각하면 안 된다.

슬롯 번호는 주문을 관리하기 위한 ID이고, 현재 호가 순위는 계속 변하는 값이다.

그래서 현재 주문가격을 현재 업비트 호가창과 계속 비교해 실제 Rank를 다시 계산한다.

GUI에서도 이것을 보여준다.

BUY 주문     주문가격       Rank

O           0.00468000     R2
O           0.00467000     R3
O           0.00466000     R4
O           0.00465000     R5
 

이 부분은 나중에 다른 종목으로 확장할 때도 반드시 유지해야 할 구조다.


4. Top 5 밖으로 밀린 주문 처리

현재 전략에서는 매수 주문을 현재 Top 5 안에 유지하는 것을 기본 원칙으로 한다.

예를 들어:

S5 @ 0.004670
 

이 주문이 가격 상승 때문에 현재 6호가가 됐다면:

[BUY S5 CANCEL]
0.00467000 | 현재 6호가 (Top5 밖)
 

취소한다.

그리고 다시 현재 Top 5 중 진입 조건을 만족하는 빈 가격을 찾아 주문을 배치한다.

이것 때문에 처음에는 상당히 많은 취소/재주문이 발생했다.

하지만 이 기능 자체는 필요하다.

그렇지 않으면 가격이 계속 올라갔을 때 프로그램의 주문이 호가창 아래쪽에 방치된다.


5. 하지만 기존 주문을 무조건 취소하면 안 된다

여기서 또 하나 중요한 문제를 발견했다.

프로그램을 껐다 다시 켰을 때다.

기존 업비트 주문이 이미 호가창에 있는데 프로그램이 그것을 모르면 새 주문을 또 넣을 수 있다.

그러면:

기존 주문 5개
+
재시작 후 신규 주문 5개
=
10개
 

가 되어버릴 수 있다.

특히 기존 주문이 먼저 들어가 있었다면 호가 우선순위까지 이미 확보한 주문이다.

그 주문을 괜히 취소하고 새로 넣으면 체결 우선순위를 스스로 포기하는 셈이다.

그래서 시작할 때 반드시 REST API를 이용해 현재 상태를 복원한다.

[STARTUP RESTORE]

[RESTORE UPBIT]
현물 수량
평균 매수가
현재 평가 원금

[RESTORE BINANCE SHORT]
현재 숏 수량
평균 진입가

[RESTORE ORDERS]
기존 BUY 주문
기존 SELL 주문
 

그리고 기존 주문이 있으면 그것부터 내부 슬롯에 등록한다.

재시작했다고 주문을 초기화하는 것이 아니다.

이 원칙은 실전 자동매매 프로그램에서 상당히 중요하다.


6. 현물 체결량만큼 선물을 숏한다

매수 주문 전체를 기준으로 선물을 먼저 잡으면 안 된다.

실제 체결량만큼만 숏을 잡아야 한다.

예를 들어:

업비트 매수주문 : 20,000원
실제 부분체결   : 8,973원
 

이라면 선물도 2만원이 아니라 8,973원에 해당하는 수량만 헤지한다.

실제 로그에서도:

[S1 FILLED]
0.00471000원
+1,905,018 PEPE
8,973원

[BINANCE SHORT]

현물체결 : 8,973원
목표선물 : 6.6612 USDT
계산선물 : 6.6577 USDT
주문수량 : 1899.0
 

처럼 동작했다.

그리고 나중에 같은 주문이 추가 체결되면 그 증분 체결량만큼 추가 숏을 잡는다.

이게 매우 중요하다.

전체 주문량을 계속 헤지하면 중복 숏이 발생한다.

따라서 코드에서는 개념적으로:

현재 누적 체결량
-
이미 헤지한 체결량
=
새로 헤지할 수량
 

을 관리해야 한다.


7. 1000PEPEUSDT는 특히 조심해야 한다

PEPE에서 가장 위험한 코딩 실수 중 하나다.

업비트는 PEPE 개수를 그대로 사용하지만 바이낸스 선물 심볼은:

1000PEPEUSDT
 

이다.

따라서 업비트 PEPE 1개와 Binance contract 1개를 단순히 1:1로 대응시키면 안 된다.

이 부분을 잘못 구현하면 헤지 수량이 1,000배 차이 날 가능성이 있다.

그래서 우리는 PEPE 개수를 직접 맞추는 방식보다 원화 노출금액 → USDT 노출금액 → 선물 계약수량 순서로 계산하는 쪽을 택했다.

개념적으로:

업비트 실제 체결원화
        ↓
KRW/USDT 환율
        ↓
헤지해야 할 USDT 금액
        ↓
Binance 선물가격
        ↓
1000PEPE 계약 단위 적용
        ↓
실제 SHORT QTY
 

이다.

이 과정은 반드시 로그로 남기는 게 좋다.

현물체결
목표선물 USDT
계산선물 USDT
최종 주문수량
 

숫자가 이상하면 바로 발견할 수 있기 때문이다.


8. 진입 스프레드와 실제 수익률을 분리해야 한다

이 프로그램에서 가장 중요한 개념 중 하나다.

예를 들어:

ENTRY SPREAD = +0.80%
 

라고 해서 0.8% 수익이 난 것이 아니다.

그것은 단순히 현재 업비트 매수가와 바이낸스 선물 가격의 차이다.

체결 이후부터는 완전히 다른 계산을 해야 한다.

실제 수익은:

현물 PnL
+
선물 PnL
-
업비트 수수료
-
바이낸스 수수료
=
NET PnL
 

이다.

그래서 보유 중에는 다음처럼 표시한다.

[HOLD]

SPOT 0.00469089 → 0.00473000  +181원
FUT  0.00472517 → 0.00472622   -4원
FEE                           -42원

NET +135원 (+0.621%)
 

이 숫자가 진짜 중요한 숫자다.


9. 현물과 선물 중 한쪽만 돈을 벌어도 된다

이 구조의 재미있는 점이다.

반드시:

현물 수익 +
선물 수익 +
 

일 필요가 없다.

예를 들어 실제 테스트에서:

현물 약 +0.6%
선물 약 -0.2%
수수료 차감
 

같은 구조가 나왔다.

합산해서 플러스면 거래 목적은 달성한 것이다.

반대로 급락하면:

현물 -
선물 +
 

가 될 수도 있다.

특히 급락할 때 아래쪽 2·3·4·5호가 매수가 연속으로 체결될 가능성이 높다.

그 순간 현물만 보면 손실일 수 있다.

하지만 체결 직후 숏을 잡기 때문에 이후 하락분 일부가 선물 수익으로 상쇄된다.

즉 이 전략에서는 현물 차트 방향보다 두 포지션의 합산 손익이 핵심이다.


10. 그래서 청산도 두 가지로 만들었다

초기에는 현물을 목표가격에 지정가 매도해두는 방식만 사용했다.

예를 들어:

평균 매수가 = 0.00469089
목표 매도가 = 0.00473000
 

이면:

[TARGET SELL]
수량       : 4,630,977 PEPE
목표 매도가 : 0.00473000
목표 NET   : 0.20%
 

를 계산하고 미리 매도를 걸어둔다.

이 방법의 장점은 maker 주문으로 좋은 가격을 선점할 수 있다는 것이다.

하지만 다른 기회가 있다.


11. 시장가로 지금 당장 끝내도 목표수익이면 청산한다

호가에 지정가 매도를 걸어놨더라도 현재 즉시 시장가 매도했을 때:

현물 시장가 손익
+
선물 즉시 청산 손익
-
수수료
>= 목표 NET
 

이면 굳이 지정가 체결을 기다릴 이유가 없다.

그래서 두 번째 청산 경로를 추가했다.

LIMIT EXIT
MKT EXIT
 

GUI에서도 둘을 따로 보여준다.

ASK 예상 NET
시장가 MKT NET
 

예를 들어:

ASK 예상 NET = +0.45%
MKT NET      = +0.23%
목표         = +0.20%
 

이라면 지정가 주문을 취소하고:

업비트 현물 시장가 매도
+
바이낸스 숏 시장가 청산
 

으로 바로 사이클을 종료할 수 있다.

작은 수익을 많이 반복하려는 전략에서는 이 기능의 가치가 꽤 크다고 본다.


12. 선물 쪽에서 큰 수익이 나도 전체 NET 기준으로 청산

반대 상황도 있다.

현물은 별로 움직이지 않았는데 바이낸스 선물이 크게 떨어질 수 있다.

그러면 숏 수익이 커진다.

예를 들어:

현물  -0.10%
선물  +0.50%
비용  -0.10%

NET   +0.30%
 

라면 이것도 훌륭한 청산 기회다.

따라서 프로그램은:

스프레드가 몇 %가 됐는가?

만 보지 않고,

지금 양쪽을 모두 종료하면 실제로 몇 원이 남는가?

를 계산하는 방향으로 발전했다.

이게 현재 버전에서 가장 중요한 변화다.


13. 포지션 상한은 반드시 필요하다

이 전략을 계속 돌리면 체결이 누적될 수 있다.

따라서:

 
MAX_POSITION_KRW = 200000
 

처럼 전체 포지션 한도를 둔다.

중요한 것은 체결된 현물만 계산하면 안 된다는 것이다.

현재 걸려 있는 BUY 주문도 언젠가 체결될 수 있기 때문이다.

그래서:

COMMITMENT
=
실제 현물 보유금액
+
현재 미체결 BUY 주문금액
 

으로 관리한다.

예를 들어:

현물 보유 = 100,000원
BUY 주문  = 100,000원

COMMIT = 200,000원
 

이면 신규 BUY를 중단한다.

하지만 프로그램 자체를 멈추는 것은 아니다.

신규 진입만 중단하고 기존 포지션 모니터링과 청산은 계속한다.


14. 통신 장애 때 가장 위험한 것은 '잘못된 청산'

처음에는 WebSocket이 몇 초만 늦어도 거래를 정지하도록 만들었다.

그런데 실제 운용 목적을 생각해보니 너무 민감했다.

진입할 때 통신이 완전히 끊길 확률보다 더 위험한 것은:

현물을 가지고 있는데 오래된 Binance 가격을 최신 가격이라고 착각해서 선물만 청산해 버리는 것

이다.

그래서 통신 보호의 핵심을 진입 차단보다 청산 보호에 두었다.

현재는 대략:

Binance WS stale > 60초
 

이면 선물 청산을 금지하는 식이다.

GUI에서도:

Binance WS age : 0.01 sec
 

를 보여준다.

정상이라면 계속 0.x초 근처에 있다.

WebSocket 데이터가 끊기면:

1 sec
5 sec
20 sec
60 sec
 

처럼 올라간다.

60초가 넘어가면 오래된 가격을 근거로 자동 청산하지 않는다.

이건 수익 로직보다 중요한 안전 로직이다.


15. WebSocket과 REST API를 분리해야 한다

처음 자동매매를 만들 때 가장 쉽게 하는 실수 중 하나가 모든 것을 REST로 조회하는 것이다.

현재가 REST
호가 REST
선물가격 REST
주문상태 REST
잔고 REST
...
 

이렇게 만들면 호출량이 금방 커진다.

현재 구조에서는 빠르게 변하는 시장 데이터는 최대한 WebSocket으로 받는다.

업비트도 공식적으로 WebSocket을 통한 실시간 호가 스트림을 제공한다.

바이낸스 선물 역시 WebSocket market stream을 제공하므로 가격 감시는 스트리밍으로 처리하는 게 적합하다.

즉:

실시간 가격/호가
        ↓
WebSocket

주문 생성/취소
주문 상태 동기화
재시작 상태복원
        ↓
REST
 

로 역할을 나눈다.


16. 429는 그냥 에러 메시지가 아니다

실전 테스트에서 가장 많이 본 것이 이것이다.

[UPBIT 429]
 

업비트 공식 문서상 429 Too Many Requests는 요청 제한을 초과했다는 의미이며, 반복적인 제한 위반은 일시 차단으로 이어질 수 있다. 현재 문서에는 418 I'm a teapot을 반복 위반에 따른 일시 제한 상태로 설명하고 있다.

따라서 429를:

 
except:
    pass
 

처럼 완전히 무시해서는 안 된다.

반대로 429 한 번 나왔다고 프로그램 전체를 10초씩 멈춰버리는 것도 이 전략에는 좋지 않았다.

그래서 단계적 대응이 낫다.

429 1회
→ 짧게 기다리고 재시도

429 연속 발생
→ 재시도 간격 증가

429 지속
→ 신규 BUY 일시 중단

기존 포지션
→ 계속 관리

청산
→ 안전하게 유지
 

현재 업비트는 REST 응답의 Remaining-Req 헤더에서 해당 그룹의 초당 잔여 요청량을 확인할 수 있으므로, 앞으로는 단순히 429를 받은 뒤 대응하는 것보다 이 값을 읽어 선제적으로 throttle하는 구조로 발전시키는 것도 좋겠다.


17. GUI는 API를 추가로 호출하지 않도록 만들었다

콘솔만 보다가 GUI를 추가한 이유도 단순하다.

5개 호가를 계속 보고 있으면 콘솔은 금방 복잡해진다.

현재 GUI에서는 한 화면에서:

현재 Position
ASK 예상 NET
시장가 MKT NET
누적 실현손익
누적 실현수익률

BUY / SELL 수
SHORT QTY

L1~L5 가격
Binance 원화환산 가격
진입 Spread
WAIT / OK
BUY 주문 O/X
주문가격
현재 Rank

현물수량
현물평단
선물수량
선물 원화평단
Commitment
Pending Hedge
Pending Close
Binance WS age
 

를 볼 수 있다.

중요한 점은 GUI 갱신을 위해 거래소 API를 다시 호출하지 않는 것이다.

구조는:

WebSocket / REST
       ↓
프로그램 내부 상태
       ↓
GUI
 

이다.

GUI가 0.2초마다 다시 그려지더라도 내부 변수만 읽는다.

따라서 화면을 빠르게 갱신한다고 API 요청량까지 같은 속도로 증가하면 안 된다.

 

 


18. 멀티스레딩에서 GUI를 직접 건드리지 않는 것도 중요하다

WebSocket은 별도 스레드에서 돌아가고 GUI는 Tkinter main thread에서 돌아간다.

여기서 WebSocket thread가 직접:

 
label.config(...)
 

같은 작업을 계속 하면 불안정해질 수 있다.

그래서 시장 데이터 thread는 내부 상태만 변경하고:

WS Thread
   ↓
shared state
   ↓
Tkinter after()
   ↓
GUI refresh
 

방식으로 화면을 갱신하는 게 좋다.

이건 앞으로 종목을 여러 개 띄울 때 더욱 중요해질 부분이다.


19. 프로그램 재시작은 '새로운 거래'가 아니다

이 원칙도 이번에 확실히 정리했다.

프로그램을 껐다 켰다고:

포지션 = 0
주문 = 0
 

이라고 생각하면 안 된다.

거래소가 진짜 상태다.

프로그램 시작 시:

1. 업비트 잔고 확인
2. 업비트 평균매수가 확인
3. 업비트 미체결 BUY 확인
4. 업비트 미체결 SELL 확인
5. Binance SHORT 확인
6. Binance 평균 진입가 확인
7. 내부 POSITION 복원
8. 기존 주문을 슬롯에 매핑
9. 이후 정상 루프 시작
 

순으로 가야 한다.

메모리가 아니라 거래소 상태가 Source of Truth가 되어야 한다.


20. 부분 체결은 반드시 별도로 생각해야 한다

자동매매에서 가장 까다로운 부분 중 하나다.

2만원 주문을 넣었다고 2만원이 한 번에 체결되는 것이 아니다.

실제로:

8,973원 체결
↓
숏

11,027원 추가 체결
↓
추가 숏
 

처럼 나뉠 수 있다.

따라서:

 
if order_closed:
    hedge()
 

만으로는 부족하다.

체결 증가량(delta fill) 을 계속 추적해야 한다.

이 로직이 없으면 부분체결 상태에서 현물만 노출되거나 반대로 과헤지가 발생한다.


21. 현물 SELL과 선물 CLOSE의 순서도 중요하다

시장가 청산 조건이 만족했다고 해서 두 API 요청이 완전히 동시에 체결되는 것은 아니다.

실제로는:

현물 SELL 성공
선물 CLOSE 실패
 

또는 반대로:

선물 CLOSE 성공
현물 SELL 실패
 

가 발생할 수 있다.

그래서 코드 내부에는:

Pending Hedge
Pending Close
 

같은 상태가 필요하다.

단순히:

 
sell()
close_short()
position = 0
 

해버리면 안 된다.

실제 거래소 체결을 확인한 뒤 내부 포지션을 줄여야 한다.

이 부분은 금액을 크게 올리기 전에 가장 철저하게 테스트해야 할 영역이라고 생각한다.


22. 선물 펀딩비는 보너스이지 기본 수익으로 잡지 않는다

현물 Long + 선물 Short 구조이므로 펀딩비 방향에 따라 숏 포지션이 펀딩을 받을 수도 있다.

하지만 이것을 전략의 기본 예상수익에 포함시키지는 않는 편이 좋다.

펀딩비는 변하고 방향도 바뀔 수 있기 때문이다.

따라서 기본 모델은:

Spread 수익
+ Spot PnL
+ Futures PnL
- Trading Fee
 

로 계산하고,

실제로 발생한 펀딩비는 별도 손익으로 더하는 방식이 안전하다.


23. 현재 프로그램의 전체 흐름

정리하면 현재 버전은 다음과 같이 움직인다.

           ┌─────────────────┐
           │ WebSocket 시세  │
           └────────┬────────┘
                    ↓
        업비트 Top5 ↔ Binance
                    ↓
            Spread 각각 계산
                    ↓
        ┌──── 진입조건 만족? ────┐
        │                        │
       NO                       YES
        │                        ↓
       대기                업비트 BUY
                                 ↓
                            부분체결 감지
                                 ↓
                       체결 증가량만 계산
                                 ↓
                      Binance SHORT Hedge
                                 ↓
                       실제 평균단가 저장
                                 ↓
             ┌────────────────────────┐
             │      보유 상태         │
             │                        │
             │ Spot PnL               │
             │ Futures PnL            │
             │ Fee                    │
             │ NET PnL                │
             └──────────┬─────────────┘
                        ↓
               두 가지 EXIT 계산
                 ↙              ↘
          지정가 목표          즉시 MKT NET
              ↓                    ↓
         SELL 대기         목표수익 이상?
                                  ↓
                                 YES
                                  ↓
                       기존 SELL 주문 취소
                                  ↓
                         현물 시장가 SELL
                                  ↓
                         Futures SHORT CLOSE
                                  ↓
                          실제 손익 기록
                                  ↓
                             다음 사이클
 

24. 앞으로 발전시킬 아이디어

지금 구조를 만들고 나니 오히려 다음 아이디어들이 더 많이 보인다.

가장 먼저 해보고 싶은 것은 PEPE 한 종목이 아니라 스프레드 검색기가 찾아낸 종목을 자동으로 후보군에 넣는 것이다.

업비트 현물과 바이낸스 USDT-M 선물이 동시에 존재하는 종목을 검색해서:

한 틱 %
평균 Spread
Spread 변동성
호가잔량
체결량
Funding
최근 체결 횟수
평균 NET
 

를 기록한다.

그러면 단순히:

지금 스프레드가 가장 큰 종목

이 아니라:

하루 동안 실제로 이 전략으로 반복 수익을 내기 좋은 종목

을 찾을 수 있다.

이게 훨씬 중요하다.


25. 특히 '한 틱 가치'를 점수화해보고 싶다

업비트에는 한 틱이 상당히 큰 종목들이 있다.

예를 들어 한 틱이:

0.2%
0.4%
0.6%
0.9%
 

인 종목들은 구조적으로 이런 전략에 흥미롭다.

단순 변동성이 아니라:

Tick %
×
평균 호가잔량
×
Spread 발생빈도
×
체결확률
÷
평균 보유시간
 

같은 지표를 만들면 꽤 재미있을 것 같다.

결국 내가 찾고 싶은 것은 한 번 크게 먹는 종목이 아니라 작은 엣지가 하루에도 계속 반복되는 종목이다.


26. 이번 개발에서 가장 중요하게 배운 것

처음에는 아비트리지 프로그램이라고 생각했다.

하지만 실제로 코드를 계속 수정하다 보니 핵심은 단순히 거래소 가격 차이를 계산하는 것이 아니었다.

진짜 어려운 부분은 주문 상태 관리였다.

특히 다음 문제들이 훨씬 중요했다.

  • 부분체결과 증분 헤지
  • 1000PEPE 같은 계약단위 변환
  • 기존 주문 보존
  • 프로그램 재시작 후 상태 복원
  • Top N 호가의 동적 Rank 추적
  • 포지션 + 미체결 주문을 합친 Commitment 관리
  • 429 대응
  • WebSocket stale 감지
  • 통신 장애 중 잘못된 청산 방지
  • 지정가 청산과 시장가 청산의 공존
  • 실제 양쪽 체결을 확인하기 전 포지션을 0으로 만들지 않는 것
  • 진입 Spread와 실제 NET 수익률을 완전히 분리하는 것

결국 자동매매에서 전략 아이디어 자체보다 어려운 것은:

내가 생각한 포지션과 실제 거래소에 존재하는 포지션이 언제나 동일하도록 만드는 것

이었다.


마무리 — 이제는 '아비트리지'보다 '스프레드 수확'에 가깝다

현재 테스트하는 방식은 전통적인 의미의 무위험 차익거래라고 부르기에는 조금 다르다.

체결 시차도 있고, 슬리피지도 있고, 부분체결도 있으며, 두 거래소 중 한쪽 주문만 성공할 위험도 있다.

따라서 무위험 아비트리지라고 표현하면 안 된다.

오히려 내가 생각하는 현재 구조는:

업비트 현물 호가의 작은 가격 비효율을 바이낸스 선물 헤지와 결합해 반복적으로 수확하는 스프레드 매매

에 가깝다.

한 번에 5%, 10%를 노리는 전략이 아니다.

0.2%
0.3%
0.4%
 

처럼 작은 순수익 기회를 가능한 한 많이 반복한다.

그리고 거래금액을 키우기 전에 2만원 단위의 작은 주문으로 부분체결·재시작·통신장애·429·시장가 청산·과헤지 여부를 충분히 검증한다.

지금 단계에서 가장 중요한 것도 수익률을 높이는 것이 아니다.

100번, 1,000번 반복해도 포지션과 헤지가 틀어지지 않는 구조를 만드는 것.

그게 확인되고 나면 그다음에야 주문금액, 종목 수, 동시 포지션 수를 올릴 수 있다.

그리고 여기까지 안정화되면 다음 단계는 단순하다.

“어떤 코인이 가장 많이 벌리는가?”를 사람이 찾는 것이 아니라 스프레드 검색기가 찾아내게 만드는 것.

그 단계부터는 하나의 PEPE 봇이 아니라, 여러 종목의 작은 가격 괴리를 계속 찾아다니는 멀티종목 스프레드 수확 시스템으로 발전시킬 수 있을 것 같다.

API 구현 시에는 정책이 바뀔 수 있으므로 업비트의 공식 Rate Limit 문서WebSocket 가이드를 기준으로 다시 확인하는 것이 좋다. 특히 현재 공식 문서도 한도 변경 및 추가 제한 가능성을 명시하고 있으므로, 숫자를 코드에 영구적인 상수처럼 가정하기보다는 Remaining-Req 등을 활용해 동적으로 관리하는 쪽이 장기적으로 안전하다.