
“트레이딩봇 하나 만들어볼까?”
가벼운 마음으로 시작했다. API 연결하고, 시그널 만들고, 매수/매도 로직 짜면 끝이라고 생각했다. 그런데 실제로 만들어보니, 진짜 어려운 건 “언제 사고 언제 파느냐”가 아니었다. “떨어질 때 어떻게 버티느냐”가 핵심이었다. 이 글은 트레이딩봇을 만들면서 겪은 설계 고민, 버그, 그리고 PAPER 모드 첫 주 결과까지의 솔직한 기록이다.
시작: “일단 API부터 연결하자”
업비트 API 키를 발급받고 Python으로 연결하는 건 의외로 순탄했다. pyupbit 라이브러리 하나면 잔고 조회, 시세 확인, 주문까지 다 된다. 바이빗도 pybit으로 금방 붙였다.
# 연결 테스트 - 이 정도는 쉬웠다
balance = upbit.get_balance()
print(f"잔고: {balance:,}원") # 잔고: 1,050,161원
여기까지는 “오, 생각보다 쉬운데?” 싶었다. 문제는 그 다음부터였다.
DCA 6단계 물타기 전략을 설계하기까지
왜 DCA인가?
처음엔 단순 추세추종 전략을 생각했다. “올라가면 사고, 내려가면 팔고.” 근데 암호화폐 시장을 조금이라도 아는 사람은 안다. 이 시장은 올라가다가 갑자기 -20%를 찍는다. 단순 추세추종으로는 손절만 반복하게 된다.
그래서 DCA(Dollar Cost Averaging), 즉 물타기 전략을 선택했다. 하락할 때마다 분할 매수해서 평단가를 낮추는 방식이다. 근데 “얼마나 떨어지면 얼마를 더 살 것인가?”를 정하는 게 생각보다 복잡했다.
6단계 설계 과정

처음엔 3단계로 시작했다. -10%, -20%, -30%에서 동일 금액을 추가 매수하는 단순한 구조. 시뮬레이션을 돌려봤더니 문제가 바로 드러났다.
문제 1: 하락 초기에 너무 많이 산다
-10%에서 이미 전체 예산의 33%를 써버리면, 진짜 바닥(-30% 이하)에서 쓸 돈이 없다. 시뮬레이션에서 BTC가 1억에서 5천만까지 떨어지는 시나리오를 돌려보니, 3단계 DCA로는 평단가가 겨우 85%까지밖에 안 내려갔다.
문제 2: 간격이 너무 넓다
-10% → -20% 사이에 아무것도 안 하고 기다리는 건 심리적으로도, 전략적으로도 비효율적이다. 실제 시장에서는 -5%씩 질질 흘러내리는 경우가 훨씬 많다.
그래서 6단계로 확장했다:
| 단계 | 하락폭 | 매수 비중 | 누적 투입 |
|---|---|---|---|
| 1차 진입 | 0% | 1x | 1x |
| 2차 물타기 | -5% | 1x | 2x |
| 3차 물타기 | -10% | 1.5x | 3.5x |
| 4차 물타기 | -18% | 2x | 5.5x |
| 5차 물타기 | -28% | 2x | 7.5x |
| 6차 물타기 | -40% | 2.5x | 10x |
핵심은 뒤로 갈수록 더 많이 사는 것이다. 진짜 바닥에서 화력을 집중해야 평단가를 효과적으로 낮출 수 있다.
시뮬레이션 결과, BTC가 1억에서 -50% 폭락해도 평단가는 약 76% 수준을 유지했다. 본전 복구에 필요한 반등폭은 약 53%. 물타기 없이 그냥 들고 있으면 100% 반등해야 하니까, 상당한 차이다.
# 시뮬레이션 결과 (추상화)
진입가 대비 -50% 폭락 시:
- 물타기 없음: 본전 복구 = +100% 반등 필요
- 6단계 DCA: 본전 복구 = +53% 반등 필요
- 평단가 절감 효과: 약 24%
방어정책과 물타기가 싸우던 날
DCA 전략을 만들고 나서 자신감이 붙었다. “이제 떨어져도 괜찮아. 물타기하면 되니까.” 근데 여기서 예상 못한 문제가 터졌다.
충돌 발생
트레이딩봇에는 손실을 제한하는 방어정책이 있었다. “평단가 대비 -X% 이하로 떨어지면 손절 매도”하는 로직이다. 그런데 DCA 물타기 로직은 “떨어지면 더 사라”고 한다.
방어정책: “지금 -12%야! 빨리 팔아!” DCA 로직: “지금 -10%야! 3차 물타기 들어가!”
둘 다 동시에 실행되니 벌어지는 상황:
- 가격이 -10% 하락
- DCA: “3차 물타기 매수!” → 추가 매수 실행
- 방어정책: “손실 한도 초과! 전량 매도!” → 방금 산 것까지 전부 매도
- 결과: 물타기하자마자 바로 손절. 수수료만 날림.
이건 로그에서 처음 발견했을 때 진짜 멘붕이었다. 테스트넷에서 돌리고 있어서 실제 돈을 잃진 않았지만, 로그를 보면서 “이게 라이브였으면…” 하는 생각에 식은땀이 났다.
Phase 분리로 해결
고민 끝에 Phase 개념을 도입했다.
- Phase 1 (DCA 예산 남음): 물타기만 한다. 방어정책 비활성화.
- Phase 2 (DCA 예산 소진): 물타기 끝. 방어정책 활성화.
# 핵심 로직 (추상화)
if dca_budget_remaining > 0:
# Phase 1: 물타기 모드
if price_drop >= next_dca_level:
execute_dca_buy()
# 방어정책 → 비활성화
else:
# Phase 2: 방어 모드
if unrealized_loss >= stop_loss_threshold:
execute_stop_loss()
이렇게 하니 깔끔하게 해결됐다. DCA 예산이 남아있는 동안은 하락을 “기회”로 보고 물타기하고, 예산이 다 소진된 뒤에야 “위험”으로 판단하고 손절하는 구조다.
교훈: 전략 하나만으로는 안 된다. 전략들이 서로 충돌하지 않게 “우선순위”와 “단계”를 명확히 나눠야 한다.
하이브리드 시그널: 랜덤이 왜 거기서 나와?
매매 시그널을 어떻게 만들 것인가도 큰 고민이었다. 뉴스 감성분석만으로? 기술적 지표만으로? 결론부터 말하면, 둘 다 단독으로는 불안정했다.
감성분석은 뉴스가 없는 시간대에 무용지물이고, 기술적 지표는 암호화폐 시장의 급변동을 못 따라간다. 그래서 만든 게 하이브리드 시그널이다.
초기 버전은 이랬다:
하이브리드 스코어 = (랜덤 × 50%) + (감성분석 × 50%)
맞다. 랜덤이 50%다. 이게 말이 되냐고? 처음엔 나도 그렇게 생각했다. 하지만 이유가 있다.
왜 랜덤을 넣었나
- 시장은 예측 불가능하다: 어떤 지표를 써도 미래를 맞출 수 없다. 랜덤 요소를 넣으면 특정 패턴에 과적합(overfitting)되는 걸 방지한다.
- 분산 매수 효과: 랜덤 덕분에 매수 타이밍이 자연스럽게 분산된다. 하루에 한 번 정해진 시간에 사는 것보다 분산도가 높다.
- 감성분석의 한계 보완: “뉴스 없음 = NEUTRAL”인데, 실제로는 뉴스 없이도 가격은 움직인다.
물론 버전을 올리면서 랜덤 비중을 줄이고 기술적 지표(RSI, 볼륨)를 추가했다:
v3 스코어 = (랜덤 × 30%) + (감성분석 × 40%) + (기술지표 × 30%)
하지만 완전히 랜덤을 빼지는 않았다. 시뮬레이션에서 랜덤 0%보다 랜덤 30%가 MDD(최대 낙폭)를 줄여주는 아이러니한 결과가 나왔기 때문이다.
PAPER 모드 첫 주: 현실의 벽

전략 설계를 마치고 PAPER 모드(모의매매)로 봇을 돌렸다. 실제 시장 가격으로 매매하되, 진짜 돈은 들어가지 않는 테스트 모드다. 결과는… 겸허해지는 경험이었다.
첫 주 주요 사건들
Day 1-2: ETH 매수 시그널
봇이 ETH에서 BUY 시그널을 잡았다. 1차 진입가 약 290만원. DCA 6단계까지 물타기 플랜이 자동으로 세팅됐다.
[PAPER] BUY KRW-ETH
1차 진입: 2,906,000원
총 예산: 6단계 DCA
대기 물타기 주문: 5건
“좋아, 시작이 좋은데?” 이때까지는 기분이 좋았다.
Day 3: 시장 급락
BTC가 -4.3%, ETH가 -5%, DOGE가 -3.5% 빠졌다. ETH에서 2차 DCA가 자동 체결됐다.
✅ 2차 물타기 체결: 평단가 290만 → 280만
물타기가 의도대로 작동했다! 근데 기분이 좋지만은 않았다. 왜냐면…
Day 3 야간: 버그 5개 동시 발견
이게 진짜 이야기의 하이라이트다.
버그 1: 15분 크론 중복 매수

15분마다 도는 급변감지 크론이 있었다. 이 크론의 원래 역할은 “가격 변동 모니터링”인데, 프롬프트에 “LIVE면 실행”이라고만 써놨다. 그래서 이 크론이 알아서 매매까지 해버린 것이다.
결과: ETH 보유량이 의도한 것의 2배가 됨.
# 수정 전 (문제)
"LIVE 모드이므로 매매를 실행합니다"
# 수정 후
"매매 절대 금지. 모니터링만 수행. 가격 변동만 보고."
교훈: AI 에이전트 기반 크론은 프롬프트가 곧 코드다. 애매하게 쓰면 AI가 “알아서” 해석해서 예상치 못한 행동을 한다.
버그 2: 크론에서 텔레그램 직접 전송 실패
크론 프롬프트에 “텔레그램으로 즉시 알려줘”라고 썼더니, 크론 세션에서는 텔레그램 API 접근이 안 됐다. 크론은 격리된 세션이니까 당연한 건데, 이걸 몰랐다.
해결: 크론에서 직접 전송하지 말고, 리턴값만 돌려주면 시스템이 알아서 전달하는 announce 모드를 활용.
버그 3: 워치리스트 불일치
4시간 크론 프롬프트에 “BTC, ETH, XRP, SOL, DOGE 5종목 분석”이라고 되어있었다. 근데 실제 전략은 ETH, DOGE 2종목만 운용하기로 바꾼 상태였다. 프롬프트를 업데이트 안 해서 크론이 XRP에 진입해버렸다. 수동으로 취소하고 매도하느라 진땀 뺐다.
교훈: 설정을 바꾸면 관련된 모든 곳을 동시에 바꿔야 한다. 코드만 바꾸고 프롬프트를 안 바꾸면 AI가 옛날 지시를 따른다.
버그 4: 헤지 리밸런스 시 스탑로스 초기화
바이빗에서 선물 헤지 포지션의 리밸런스(비율 조절)를 하면, 기존 포지션을 청산하고 새로 진입한다. 이때 기존에 설정해둔 스탑로스가 날아간다. 새 포지션에는 SL이 안 걸려있으니, 갑자기 방어막 없이 노출되는 상태가 되는 거다.
# 리밸런스 후 SL 자동 재설정 추가
def rebalance_hedge():
close_position()
new_position = open_new_position()
set_stop_loss(new_position) # 이걸 빼먹었었다!
버그 5: 지정가 체결 시 DCA 레벨 미업데이트
DCA 물타기를 지정가 주문으로 걸어두는데, 이게 체결돼도 봇이 “아직 1단계”라고 인식하고 있었다. 지정가 체결 여부를 체크하는 로직이 없었던 것이다. 결과적으로 같은 가격에 중복 주문을 넣을 뻔했다.
# 15분 모니터에 체결 확인 로직 추가
def check_orders():
for order in pending_orders:
if is_filled(order):
update_dca_level()
register_take_profit() # 익절도 자동 재설정
첫 주 성적표
| 지표 | 결과 |
|---|---|
| 총 손익 | 약 -0.17% |
| 발견된 버그 | 5개 |
| 수동 개입 횟수 | 3회 |
| 심장이 쫄깃한 횟수 | 셀 수 없음 |
솔직히 수익은 거의 본전이다. 하지만 버그를 5개나 잡았다는 게 PAPER 모드의 가치다. 이게 라이브였으면 돈을 잃었을 뿐 아니라, 의도하지 않은 포지션이 쌓이면서 더 큰 손실로 이어졌을 것이다.
백테스트: 과거는 미래를 보장하지 않지만
전략 v1과 v2를 3개월 데이터(2025년 11월~2026년 2월)로 백테스트했다. 이 기간 BTC는 약 -24%가 빠졌다. 꽤 험한 장이었다.
# 백테스트 결과 요약 (3개월, BTC 기준)
바이앤홀드: -23.81%
v1 전략: -3.01% (MDD 5.07%)
v2 전략: -5.09% (MDD 6.00%)
바이앤홀드 대비 훨씬 나은 결과다. 시장이 -24% 빠지는 동안 손실을 -3~5%로 억제한 셈이다. DCA 물타기가 제 역할을 한 것이다.
하지만 여기서 중요한 포인트: 백테스트는 과거 데이터에 대한 것이다. 미래에도 이렇게 작동한다는 보장은 없다. 특히 -50% 이상 폭락이 오면 DCA 예산이 다 소진되고 Phase 2 방어 모드에 진입하게 되는데, 이 상황은 아직 실전에서 겪어보지 못했다.
PAPER에서 LIVE로: 떨리는 전환
PAPER 모드에서 어느 정도 안정성을 확인한 뒤, 소액으로 LIVE 전환을 했다. 전환하는 순간의 그 떨림이란… 코드 한 줄 바꾸는 건데 손이 떨렸다.
# 이 한 줄의 무게
MODE = "LIVE" # was "PAPER"
LIVE 전환 후 실제로 ETH 1차 진입이 체결됐을 때, 업비트 앱에서 체결 알림이 뜨는 걸 보고 묘한 감정이 들었다. “내가 만든 봇이 진짜 내 돈으로 코인을 샀다.” 금액은 만 몇천 원이었지만, 그 의미는 컸다.
지금까지 배운 핵심 교훈 7가지
-
전략보다 방어가 중요하다: “얼마 벌 것인가”보다 “얼마까지 잃을 수 있는가”를 먼저 정해야 한다.
-
전략 간 충돌을 반드시 검증하라: DCA와 손절, 물타기와 헤징 등 여러 전략을 결합하면 반드시 충돌 지점이 생긴다. Phase 분리 같은 우선순위 체계가 필수다.
-
PAPER 모드는 선택이 아니라 필수다: 버그 5개를 실전이 아니라 테스트에서 잡은 건 행운이 아니라 프로세스다.
-
크론 프롬프트 = 코드다: AI 에이전트 기반이라면, 프롬프트의 한 문장이 수만 원의 차이를 만든다. 명확하고 제한적으로 써야 한다.
-
설정 변경은 원자적(atomic)으로: 전략 파라미터 하나를 바꾸면, 관련된 크론, 프롬프트, 설정 파일을 전부 한 번에 업데이트해야 한다.
-
백테스트를 과신하지 마라: 과거 데이터에서 잘 작동한 전략이 미래에도 잘 된다는 보장은 없다. 백테스트는 “최소한의 검증”이지 “성공의 보증”이 아니다.
-
소액으로 시작하라: LIVE 전환 시 전체 자금의 일부만 투입하고, 안정성을 확인한 뒤 점진적으로 늘려야 한다.
마무리: 트레이딩봇은 “만드는 것”이 아니라 “키우는 것”
트레이딩봇 개발을 시작한 지 약 일주일. 코드는 계속 바뀌고 있고, 전략은 v3까지 올라갔고, 버그는 아직도 나올 것 같은 불안감이 있다. 하지만 확실한 건, 이 과정에서 시장에 대해, 코드에 대해, 그리고 위험 관리에 대해 정말 많은 걸 배웠다는 것이다.
다음 글에서는 Rocky Linux에서 이 모든 걸 돌리기 위한 서버 세팅 삽질기를 다룰 예정이다. 크롬 CDP 연결, 한글 입력 문제, Wayland 이슈 등… 서버 세팅도 트레이딩봇 못지않게 험난했다.
⚠️ 면책 조항: 이 글은 개인적인 개발 경험을 공유하기 위한 것이며, 투자 조언이 아닙니다. 암호화폐 투자는 원금 손실의 위험이 있으며, 모든 투자 판단은 본인의 책임입니다.
관련 포스트
- 트레이딩 봇 개발 완벽 가이드 - 기본 아키텍처와 API 연결
- 실전 트레이딩 봇 고도화: DCA + 뉴스 감성분석 + 동적 헤징 - 전략 상세 스펙
- AI 에이전트로 로또 자동구매 삽질기 - 또 다른 자동화 삽질