스테이블코인(USDT) 세븐 스플릿 자동매매 프로그램 개발기
환율 변동이 적은 스테이블코인(USDT)의 특성을 이용해, 소액으로 꾸준히 수익을 내는 그리드 트레이딩 봇을 만들었다.

환율 변동이 적은 스테이블코인(USDT)의 특성을 이용해, 소액으로 꾸준히 수익을 내는 그리드 트레이딩 봇을 만들었다.
어느날 라디오를 듣다가, 세븐 스플릿의 저자이자 스플릿인베스트의 대표가 나와 얘기하는 것을 듣고, 진행하게 된 프로젝트이다.
시중에 자동매매 봇은 많지만, 대부분 비트코인, 이더리움 같은 변동성이 높은 코인을 대상으로 한다.
나는 반대로, 변동 폭이 적은 USDT/KRW 마켓에서 소소하지만 꾸준한 수익을 목표로 했다.(세븐 스플릿의 전략을 따라..)
선택한 자산인 테더는 달러에 페깅된 스테이블 코인이라, 급락 리스크가 적고 일정 범위 안에서 횡보하는 특성이 있어 그리드 전략에 적합하다.
기술 스택
Python
SQLite (WAL:Write-Ahead Logging 모드)
Upbit REST API
Rich (터미널 대시보드)
Telegram Bot API (매수/매도 알림)
그리드 트레이딩이란
그리드 트레이딩은 일정 가격 간격(그리드) 마다 매수/매도 주문을 걸어놓는 전략이다.
예를들어 나의 경우 그리드 간격을 3원으로 설정하였는데,
1,476원에 매수 -> 1,479원에 매도
1,473원에 매수 -> 1,476원에 매도
1,470원에 매수 -> 1,473원에 매도
이런식으로, 가격이 내려가면 자동으로 분할 매수하고, 올라가면 각 포지션을 개별적으로 매도한다. 한 건당 수익은 크지 않지만, 이걸 24시간 반복하여 수익을 쌓는 방식이다.
수익 구조
현재 검증단계로, 건당 매수금액을 5만원, 그리드 간격 3원으로 잡았을때,
매수: 50,000 KRW + 수수료 25 KRW = 50,025 KRW
매도: 50,101 KRW - 수수료 25 KRW = 50,076 KRW
순이익: 약 51 KRW / 건
업비트 기준 수수료는 0.05%로 매수/매도시 왕복 약 50원이 수수료로 나가게 된다.
3원 간격에서 총 101원의 차익이 발생하는데, 이중 수수료 50원을 제하면 건당 약 51원가량의 순수익이 남게된다.
큰 수익은 아니지만, 하루에 많은 건이 체결되면 수익이 쌓이게 된다.
아키텍처
main.py ─ 진입점 (데몬 / 대시보드)
├── strategy.py ─ 그리드 매매 로직
├── upbit_api.py ─ Upbit REST API 래퍼
├── database.py ─ SQLite 포지션/거래 관리
├── dashboard.py ─ Rich 터미널 대시보드
└── notifier.py ─ Telegram 알림main.py ─ 진입점 (데몬 / 대시보드)
├── strategy.py ─ 그리드 매매 로직
├── upbit_api.py ─ Upbit REST API 래퍼
├── database.py ─ SQLite 포지션/거래 관리
├── dashboard.py ─ Rich 터미널 대시보드
└── notifier.py ─ Telegram 알림24시간 작동을 위해 서버에서는 systemd 서비스로 돌리고, 모니터링이 필요할 때 별도로 대시보드 모드로 실행(--dashboard-only)하여 확인할 수 있다.
포지션 생명주기
지정가 매수 주문 → pending_buy (체결 대기 3스텝이상 차이날경우 주문취소)
↓ 체결
active (보유중)
↓ 지정가 매도 주문
pending_sell (매도 대기)
↓ 체결
sold (완료, 수익 확정)지정가 매수 주문 → pending_buy (체결 대기 3스텝이상 차이날경우 주문취소)
↓ 체결
active (보유중)
↓ 지정가 매도 주문
pending_sell (매도 대기)
↓ 체결
sold (완료, 수익 확정)모든 주문은 지정가로 주문한다.
왜 지정가 주문인가
처음에는 시장가 매수 + 시장가 매도로 구현을 했었다.(이부분에서 손해를 입었다..)
로직이 단순하고 즉시 체결되기 때문에 편했지만. USDT/KRW 마켓자체에 호가 스프레드가 1~2원 정도 존재하는데, 시장가 매수를 하게되면 매도호가로 체결이 되고, 시장가 매도를 하게되면 매수호가로 체결이된다. 그리드 간격을 3원으로 설정했지만, 스프레드로 1~2원의 손해를 입게되면 실질적으로 마진이 없거나, 마이너스인 경우가 생긴다.
의도: 1,476원 매수 → 1,479원 매도 (3원 차익)
실제: 1,477원 매수 → 1,478원 매도 (1원 차익, 수수료 내면 손해)의도: 1,476원 매수 → 1,479원 매도 (3원 차익)
실제: 1,477원 매수 → 1,478원 매도 (1원 차익, 수수료 내면 손해)지정가 주문으로 전환한 후에는 정확히 그리드 가격에 체결되어 의도한 만큼의 차익을 얻을 수 있었다.
사고기록
무한 매수 사건
실질적으로 가장 큰 손해를 입힌 사고였다. SQLite DB가 readonly 상태가 되면서, 매수 주문 후 포지션을 기록해야하는데, 기록하지 못하는 에러가 발생했고. 포지션이 기록되지 않으니 매수한 금액대인지여부가 계속해서 False를 반환하는 탓에 봇이 같은 그리드에서 매수를 반복하는 일이 발생했다.
업비트 잔고만큼 전부 매수하게되어 약 45만원어치 과다매수가 일어났다.. 이로 인해 수수료 및 정신적 충격을 받았다.
이 사건 이후 DB 안전장치를 강화했다.
1. 매 틱마다 DB 접근 가능 여부 체크
2. DB 기록 실패 시 봇을 즉시 중단
3. 연속 에러 3회시 자동 정지
그리드 간격 최적화
처음에는 10원 간격으로 시작했는데, 하루동안 단 한 건의 거래도 발생하지 않았다..
USDT/KRW의 52주 가격 범위는 약 1,339 ~ 1,655원이고, 하루 변동폭은 보통 5~15원 수준이라 10원 간격은 너무 넓었다.
5원으로 줄였더니 거래가 발생하기 시작했고, 최종적으로 3원으로 설정하기로 했다.
1원 간격은 왕복 수수료(5만원 기준 50원수준)가 차익보다 커서 오히려 손해다. 수수료를 고려한 최소 간격은 약 2원이었다.
터미널 대시보드
서버에서 --dashboard-only모드로 실행하면 Rich 라이브러리를 이용하여 렌더링된 대시보드를 볼 수 있다.
매수/매도 체결 시에는 텔레그램으로 수수료를 포함한 상세 알림이 온다.
마치며
이 프로젝트를 하면서 금융시스템에서는 방어적 프로그래밍이 굉장히 중요하다는 것을 다시한번 느낄 수 있었다. 일반적인 웹 서비스에서는 사용자 불편 정도로 끝나지만, 금융서비스에서의 버그는 곧 돈과 직결되었다. DB 쓰기 실패 한번에 45만원의 과다매수가 일어났고, 시장가/지정가 방식차이로 건당 수십 원의 손실이 발생했다.
작은 금액으로 검증단계부터 시작한 덕분에 큰 손실을 막을 수 있었고, 현재는 안정적으로 운영 중이며 건당 51원이라는 소소한 수익이 하루에도 여러건 쌓이고 있다.