코드를 한 줄도 바꾸지 않은 날, 다시
7일차에서 운영 버그 4건을 수정하고 AI 에이전트로 코드 감사까지 마쳤다. 시스템이 한결 안정적이라고 느꼈다. 8일차에는 코인 선물 쪽 최근 거래 기록을 분석해 보기로 했다. 실제로 돈이 오간 내역을 들여다보는 작업이다.
결론부터 말하면, 코드를 한 줄도 바꾸지 않았다. 6일차도 코드를 쓰지 않은 날이었지만 성격이 다르다. 6일차는 미래를 설계한 날이었고, 8일차는 과거를 파헤친 날이다. 그리고 그 과거에서 예상치 못한 구멍을 발견했다.
최근 청산 내역을 보고 싶었다
3월 3일~4일 사이에 코인 선물 포지션 몇 건이 청산되었다. 얼마에 들어가서 얼마에 나왔는지, 수익인지 손실인지를 확인하고 싶었다. 당연히 DB에 기록되어 있을 거라고 생각했다.
트레이딩 봇은 모든 거래를 두 곳에 기록한다.
- 로그 파일:
trading.log,trading_2026-03-03.log,trading_2026-03-04.log - SQLite DB:
trade_history테이블
로그는 실시간 디버깅용이고, DB는 이후 분석과 리포트의 원천 데이터다. 두 곳의 데이터가 일치해야 한다.
로그에서 본 것
먼저 로그 파일을 뒤졌다. 청산(CLOSE) 관련 로그를 필터링했다.
2026-03-03 14:23:17 INFO [crypto_futures] CLOSE ETHUSDT SHORT |
entry=1,842.50 | exit=1,831.20 | pnl=+0.61% | trailing_stop triggered
2026-03-04 02:15:44 INFO [crypto_futures] CLOSE BTCUSDT LONG |
SL triggered | price=67,234.00
2026-03-04 09:41:33 INFO [crypto_futures] CLOSE SOLUSDT LONG |
TP triggered | price=142.85
트레일링 스탑으로 청산된 ETHUSDT는 entry/exit 가격과 PnL 퍼센트까지 깔끔하게 찍혀 있다. 그런데 SL(Stop Loss)과 TP(Take Profit)로 청산된 BTCUSDT, SOLUSDT는 청산 가격만 있고 entry 가격과 PnL이 없다.
이 시점에서는 “로그 포맷이 다른가 보다” 정도로 생각했다. 정확한 수치는 DB에서 보면 되니까.
DB에서 본 것
trade_history 테이블을 조회했다.
SELECT symbol, side, action, amount, price, pnl, timestamp
FROM trade_history
WHERE market = 'crypto_futures'
AND timestamp >= '2026-03-03'
ORDER BY timestamp;
결과는 이랬다.
symbol | side | action | amount | price | pnl | timestamp
----------|-------|--------|--------|----------|--------|-------------------
ETHUSDT | SHORT | OPEN | 0.05 | 1842.50 | NULL | 2026-03-03 10:15:22
ETHUSDT | SHORT | CLOSE | 0.05 | 1831.20 | +0.61 | 2026-03-03 14:23:17
BTCUSDT | LONG | OPEN | 0.003 | 67450.00 | NULL | 2026-03-03 18:30:05
BTCUSDT | LONG | CLOSE | 0.0 | 0.0 | NULL | 2026-03-04 02:15:44
SOLUSDT | LONG | OPEN | 0.5 | 141.20 | NULL | 2026-03-04 06:22:11
SOLUSDT | LONG | CLOSE | 0.0 | 0.0 | NULL | 2026-03-04 09:41:33
ETHUSDT의 CLOSE 레코드는 정상이다. amount=0.05, price=1831.20, pnl=+0.61. 트레일링 스탑이 발동했을 때 올바르게 기록되었다.
문제는 BTCUSDT와 SOLUSDT의 CLOSE 레코드다. amount=0.0, price=0.0, pnl=NULL. 포지션이 열린 기록(OPEN)은 있지만, 닫힌 기록(CLOSE)에 실질적인 데이터가 없다. 0.003 BTC를 67,450 USDT에 매수해서 포지션을 열었는데, 청산 시 금액이 0이라니. 이건 “손익이 0”이라는 뜻이 아니라 “기록이 안 되었다”는 뜻이다.
교차 분석: 왜 트레일링 스탑만 정상인가
로그와 DB를 대조하면 패턴이 보인다.
| 청산 유형 | 로그 기록 | DB 기록 |
|---|---|---|
| 트레일링 스탑 | entry/exit/pnl 모두 기록 | amount/price/pnl 정상 |
| SL (Stop Loss) | 청산 가격만 기록 | amount=0.0, price=0.0 |
| TP (Take Profit) | 청산 가격만 기록 | amount=0.0, price=0.0 |
트레일링 스탑과 SL/TP의 차이점은 청산을 누가 실행하느냐에 있다.
- 트레일링 스탑: 봇이 직접 가격을 모니터링하다가 조건 충족 시 시장가 주문을 보내고, 체결 결과를 받아서 DB에 기록한다. 봇의 코드 플로우 안에서 모든 것이 처리된다.
- SL/TP: Binance 거래소에 미리 조건 주문을 걸어놓는다. 가격이 조건에 도달하면 거래소가 자동으로 체결한다. 봇은 WebSocket이나 polling으로 포지션이 사라진 것을 감지하고, CLOSE 이벤트를 발생시킨다.
문제는 이 CLOSE 이벤트 핸들러에 있다. 트레일링 스탑의 CLOSE는 체결 응답에서 amount와 price를 직접 가져온다. 하지만 SL/TP의 CLOSE는 “포지션이 없어졌다”는 사실만 감지할 뿐, 체결 상세(amount, price, realized PnL)를 거래소에서 조회하지 않는다. 그래서 기본값인 0.0이 그대로 DB에 들어간다.
코드를 직접 수정하지는 않았지만, 구조를 파악한 것만으로 다음 수정 방향이 명확해졌다.
“기록되지 않는 거래”가 가장 위험한 이유
이 버그는 시스템을 멈추지 않는다. 에러 로그도 뜨지 않는다. 봇은 잘 돌아간다. 주문도 나가고, 포지션도 열리고 닫힌다. 모든 것이 정상처럼 보인다.
하지만 다음과 같은 질문에 답할 수 없게 된다.
“지난 한 달간 코인 선물에서 총 수익은 얼마인가?”
OPEN 기록은 있지만 CLOSE의 PnL이 NULL이다. 트레일링 스탑으로 청산된 건만 수익/손실이 기록되고, SL/TP 청산분은 빠져 있다. 합산하면 실제보다 수익이 높게 나올 수도 있고 (손절 기록이 누락되면), 낮게 나올 수도 있다 (익절 기록이 누락되면). 어느 쪽이든 데이터를 믿을 수 없다.
트레이딩 봇에서 데이터의 정합성은 핵심이다. 전략이 틀린 것은 고칠 수 있다. 코드에 버그가 있으면 찾아서 고치면 된다. 하지만 데이터 자체가 빠져 있으면, 전략이 맞는지 틀리는지를 판단할 근거가 없다. 판단의 기반이 무너진다.
이건 트레이딩에만 해당하는 이야기가 아니다. 결제 시스템에서 결제 완료 기록이 누락되거나, 물류 시스템에서 배송 완료 이벤트가 DB에 안 쓰이는 것과 같은 종류의 문제다. 시스템은 돌아가는데 기록이 빠지면, 나중에 “그래서 어떻게 된 거야?”라는 질문에 답할 수 없다.
발견 과정에서 얻은 교훈
로그와 DB는 반드시 교차 확인해야 한다
로그만 봤으면 “SL/TP 청산 시 PnL 로그가 좀 부족하네” 정도로 넘어갔을 것이다. DB만 봤으면 “amount=0.0이라 청산 금액이 0인가?” 하고 헷갈렸을 것이다. 두 개를 나란히 놓고 비교하니, 트레일링 스탑만 정상이고 SL/TP는 누락이라는 패턴이 선명하게 드러났다.
코드를 읽지 않아도 데이터로 버그를 찾을 수 있다
오늘은 소스 코드를 한 줄도 열어보지 않았다. 로그 파일과 DB 쿼리만으로 버그의 존재와 원인 범위를 특정했다. 데이터 분석이 곧 디버깅이다.
정상인 데이터와 비정상인 데이터를 나란히 놓아라
“SL/TP 청산 기록이 이상하다”만으로는 원인을 좁히기 어렵다. 트레일링 스탑 청산이 정상이라는 사실과 비교하니, “청산 유형에 따라 DB 기록 로직이 다르다”는 결론이 즉시 나왔다. 비교 대상이 있으면 디버깅 속도가 올라간다.
다음 할 일
오늘 발견한 문제의 수정 방향은 다음과 같다.
1. CLOSE 이벤트 핸들러 개선
SL/TP 청산 감지 시, Binance API에서 체결 상세를 조회해 amount, price, realized PnL을 가져온 뒤 DB에 기록해야 한다.
# 수정 방향 (의사코드)
def handle_position_closed(self, symbol, side):
# 거래소에서 최근 체결 내역 조회
trades = self.client.get_recent_trades(symbol)
last_trade = trades[-1]
self.db.record_close(
symbol=symbol,
side=side,
amount=last_trade['qty'],
price=last_trade['price'],
pnl=last_trade['realizedPnl']
)
2. SL/TP 청산 시에도 트레일링 스탑과 동일한 수준의 PnL 로그 남기기
로그 포맷을 통일해서, 어떤 방식으로 청산되든 entry/exit/pnl이 한 줄에 찍히도록 한다.
# 목표 로그 포맷 (통일)
CLOSE BTCUSDT LONG | entry=67,450.00 | exit=67,234.00 | pnl=-0.32% | SL triggered
CLOSE SOLUSDT LONG | entry=141.20 | exit=142.85 | pnl=+1.17% | TP triggered
3. 기존 누락 데이터 복구 검토
이미 amount=0.0, price=0.0으로 기록된 과거 데이터를 Binance 거래 내역 API로 소급 복구할 수 있는지 확인한다. 가능하다면 마이그레이션 스크립트를 작성한다.
8일간의 궤적
| 일차 | 핵심 작업 | 성격 |
|---|---|---|
| 1일차 | Claude API 529 폴백 + OpenAI 자동 전환 | 장애 대응 |
| 2일차 | 트레일링 스탑 구현 | 신규 기능 |
| 3일차 | 백테스트 프레임워크 구축 | 인프라 |
| 4일차 | 선물 백테스트 확장 | 기능 확장 |
| 5일차 | 30건 버그 수정 | 안정화 |
| 6일차 | 배포 자동화 + 이벤트 트래킹 설계 | 설계 |
| 7일차 | 운영 버그 4건 수정 + 전체 코드 감사 | 안정화 |
| 8일차 | 거래 기록 분석 → DB 누락 버그 발견 | 진단 |
새로운 유형의 작업이 등장했다. 기능 추가도 아니고, 버그 수정도 아니고, 설계도 아닌 진단. 코드를 고치기 전에 데이터를 들여다보고, 무엇이 잘못되었는지 파악하는 단계다.
7일차까지 코드를 감사한 것은 “코드에 문제가 있는가?”를 물은 것이었다. 8일차에 거래 기록을 분석한 것은 “시스템이 실제로 올바르게 동작하고 있는가?”를 물은 것이다. 두 질문은 다르다. 코드가 완벽해 보여도 데이터가 빠질 수 있다. 오늘 발견한 것이 정확히 그 경우다.
코인 선물 거래 기록을 분석하다 SL/TP 청산 시 DB에 amount=0.0, price=0.0이 기록되는 누락 버그를 발견한 8일차 기록이다.
댓글