재테크 A2Z

파이썬으로 빗썸 API 활용한 가상화폐 백테스트 시스템 구축하기 본문

코딩 & 파이썬

파이썬으로 빗썸 API 활용한 가상화폐 백테스트 시스템 구축하기

a2ztec 2025. 10. 7. 10:38

프로젝트 개요

이 프로젝트는 빗썸 거래소의 일봉 캔들 데이터를 수집하고, DCA(Dollar Cost Averaging, 적립식 매수) 전략을 백테스트한 뒤, 결과를 엑셀 파일로 저장하는 완전한 시스템입니다.

주요 기능

  • 빗썸 v1 API를 통한 일봉 캔들 데이터 수집
  • 매일 일정 금액 적립식 매수 시뮬레이션
  • 포트폴리오 가치 변화 추적
  • 수익률 계산 및 엑셀 리포트 생성

1. 프로젝트 구조

text
candle_backtester/

├── api/
│   └── client.py           # 빗썸 API 클라이언트

├── backtest/
│   └── dca_runner.py       # 백테스트 실행 및 결과 저장

├── strategy/
│   └── dca_strategy.py     # DCA 전략 로직

├── main.py                 # 메인 실행 스크립트

└── requirements.txt        # 필요 패키지

 


2. 환경 설정

requirements.txt

text
requests pandas openpyxl

설치 방법

bash
pip install -r requirements.txt

3. API 클라이언트 구현

빗썸 v1 API는 최대 200개의 캔들 데이터만 한 번에 조회할 수 있으므로, 긴 기간의 데이터를 수집하려면 반복 호출이 필요합니다.

api/client.py

# api/client.py

import requests
import pandas as pd
from datetime import datetime, timedelta

class BithumbCandleClient:
    BASE_URL = "https://api.bithumb.com/v1/candles/days"

    def __init__(self, market="KRW-BTC"):
        """
        빗썸 일봉 캔들 데이터 수집 클라이언트
        :param market: 마켓 코드 (예: KRW-BTC, KRW-SOL)
        """
        self.market = market

    def fetch_candles(self, start_date, end_date):
        """
        특정 기간의 일봉 캔들 데이터를 모두 불러옴
        :param start_date: 시작일 'YYYY-MM-DD'
        :param end_date: 종료일 'YYYY-MM-DD'
        :return: pandas DataFrame
        """
        start_dt = datetime.strptime(start_date, "%Y-%m-%d")
        end_dt = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
        cur_to = end_dt.strftime("%Y-%m-%dT%H:%M:%S")
        
        result = []
        headers = {"accept": "application/json"}

        while True:
            params = {
                "market": self.market,
                "to": cur_to,
                "count": 200,
                "convertingPriceUnit": "KRW"
            }
            
            try:
                res = requests.get(self.BASE_URL, params=params, headers=headers, timeout=10)
                res.raise_for_status()
                data = res.json()  # 빗썸 v1은 리스트를 직접 반환
                
                if not isinstance(data, list) or len(data) == 0:
                    break
                
                candles = pd.DataFrame(data)
                candles["candle_date_time_kst"] = pd.to_datetime(candles["candle_date_time_kst"])
                candles = candles[candles["candle_date_time_kst"] >= start_dt]
                
                if len(candles) > 0:
                    result.append(candles)
                
                oldest_date = pd.to_datetime(data[-1]["candle_date_time_kst"])
                if oldest_date <= start_dt:
                    break
                
                cur_to = (oldest_date - timedelta(seconds=1)).strftime("%Y-%m-%dT%H:%M:%S")
                
            except requests.exceptions.RequestException as e:
                print(f"API 요청 중 오류 발생: {e}")
                break

        if result:
            full_df = pd.concat(result, ignore_index=True)
            full_df = full_df.sort_values("candle_date_time_kst").reset_index(drop=True)
            full_df = full_df[
                (full_df["candle_date_time_kst"] >= start_dt) & 
                (full_df["candle_date_time_kst"] <= end_dt)
            ]
            return full_df
        else:
            return pd.DataFrame()

주요 포인트

  • 응답 구조: 빗썸 v1 API는 JSON 배열(리스트)을 직접 반환합니다
  • 반복 호출: 200개씩 과거로 이동하며 데이터를 수집합니다
  • 에러 처리: timeout 설정과 try-except로 네트워크 오류에 대응합니다

4. DCA 전략 구현

매일 일정 금액을 투자하는 적립식 매수 전략을 구현합니다.

strategy/dca_strategy.py

# strategy/dca_strategy.py

import pandas as pd

class DCAStrategy:
    """
    매일 일정 금액을 적립식으로 매수하는 전략
    """
    def __init__(self, daily_investment=10000):
        """
        :param daily_investment: 매일 투자할 금액 (원)
        """
        self.daily_investment = daily_investment
    
    def calculate_portfolio(self, df):
        """
        매일 적립식 매수 후 포트폴리오 가치 계산
        :param df: 캔들 데이터프레임
        :return: 포트폴리오 정보가 추가된 데이터프레임
        """
        df = df.copy()
        df = df.sort_values('candle_date_time_kst').reset_index(drop=True)
        
        total_invested = 0
        total_coins = 0
        
        portfolio_values = []
        invested_amounts = []
        coin_holdings = []
        daily_returns = []
        cumulative_returns = []
        
        for idx, row in df.iterrows():
            current_price = row['trade_price']
            
            # 매일 투자
            total_invested += self.daily_investment
            coins_purchased = self.daily_investment / current_price
            total_coins += coins_purchased
            
            # 포트폴리오 가치
            portfolio_value = total_coins * current_price
            
            # 수익률 계산
            profit = portfolio_value - total_invested
            profit_rate = (profit / total_invested) * 100 if total_invested > 0 else 0
            
            # 일일 수익률
            if idx == 0:
                daily_return = 0
            else:
                prev_value = portfolio_values[-1]
                daily_return = ((portfolio_value - prev_value) / prev_value) * 100 if prev_value > 0 else 0
            
            portfolio_values.append(portfolio_value)
            invested_amounts.append(total_invested)
            coin_holdings.append(total_coins)
            daily_returns.append(daily_return)
            cumulative_returns.append(profit_rate)
        
        # 결과 컬럼 추가
        df['투자금액(원)'] = invested_amounts
        df['보유수량(코인)'] = coin_holdings
        df['포트폴리오가치(원)'] = portfolio_values
        df['평가손익(원)'] = df['포트폴리오가치(원)'] - df['투자금액(원)']
        df['수익률(%)'] = cumulative_returns
        df['일일수익률(%)'] = daily_returns
        
        return df
 

계산 로직

  • 매수 수량: 일일 투자금 ÷ 당일 종가
  • 포트폴리오 가치: 누적 보유 코인 × 현재가
  • 수익률: (포트폴리오 가치 - 총 투자금) ÷ 총 투자금 × 100

5. 백테스트 실행 및 엑셀 저장

backtest/dca_runner.py

# backtest/dca_runner.py

import pandas as pd

class DCABacktester:
    """
    백테스트 실행 및 엑셀 저장
    """
    def __init__(self, result_df):
        self.result_df = result_df
    
    def save_to_excel(self, filename="backtest_result.xlsx"):
        """
        백테스트 결과를 엑셀로 저장
        """
        df_export = self.result_df.copy()
        df_export['날짜'] = df_export['candle_date_time_kst'].dt.strftime('%Y-%m-%d')
        
        export_columns = [
            '날짜', 'opening_price', 'high_price', 'low_price', 'trade_price',
            '투자금액(원)', '보유수량(코인)', '포트폴리오가치(원)',
            '평가손익(원)', '수익률(%)', '일일수익률(%)'
        ]
        
        df_export = df_export[export_columns].rename(columns={
            'opening_price': '시가',
            'high_price': '고가',
            'low_price': '저가',
            'trade_price': '종가'
        })
        
        # 숫자 반올림
        df_export['시가'] = df_export['시가'].round(0)
        df_export['고가'] = df_export['고가'].round(0)
        df_export['저가'] = df_export['저가'].round(0)
        df_export['종가'] = df_export['종가'].round(0)
        df_export['포트폴리오가치(원)'] = df_export['포트폴리오가치(원)'].round(0)
        df_export['평가손익(원)'] = df_export['평가손익(원)'].round(0)
        df_export['수익률(%)'] = df_export['수익률(%)'].round(2)
        df_export['일일수익률(%)'] = df_export['일일수익률(%)'].round(2)
        df_export['보유수량(코인)'] = df_export['보유수량(코인)'].round(6)
        
        # 요약 정보
        summary_data = {
            '항목': [
                '투자 기간', '총 투자일수', '총 투자금액',
                '최종 포트폴리오 가치', '총 평가손익',
                '최종 수익률(%)', '평균 매수 단가', '최종 보유 수량'
            ],
            '값': [
                f"{df_export['날짜'].iloc[0]} ~ {df_export['날짜'].iloc[-1]}",
                len(df_export),
                f"{df_export['투자금액(원)'].iloc[-1]:,.0f}원",
                f"{df_export['포트폴리오가치(원)'].iloc[-1]:,.0f}원",
                f"{df_export['평가손익(원)'].iloc[-1]:,.0f}원",
                f"{df_export['수익률(%)'].iloc[-1]:.2f}%",
                f"{df_export['투자금액(원)'].iloc[-1] / df_export['보유수량(코인)'].iloc[-1]:,.0f}원",
                f"{df_export['보유수량(코인)'].iloc[-1]:.6f}"
            ]
        }
        summary_df = pd.DataFrame(summary_data)
        
        # 엑셀 저장
        with pd.ExcelWriter(filename, engine='openpyxl') as writer:
            summary_df.to_excel(writer, sheet_name='요약', index=False)
            df_export.to_excel(writer, sheet_name='일별상세', index=False)
        
        print(f"✅ '{filename}' 파일로 저장되었습니다.")
        return filename
    
    def print_summary(self):
        """
        콘솔에 요약 출력
        """
        last_row = self.result_df.iloc[-1]
        first_date = self.result_df['candle_date_time_kst'].iloc[0].strftime('%Y-%m-%d')
        last_date = self.result_df['candle_date_time_kst'].iloc[-1].strftime('%Y-%m-%d')
        
        print("=" * 70)
        print(f"📊 DCA 백테스트 결과 요약")
        print("=" * 70)
        print(f"투자 기간: {first_date} ~ {last_date}")
        print(f"총 투자일수: {len(self.result_df)}일")
        print(f"총 투자금액: {last_row['투자금액(원)']:,.0f}원")
        print(f"최종 포트폴리오 가치: {last_row['포트폴리오가치(원)']:,.0f}원")
        print(f"총 평가손익: {last_row['평가손익(원)']:,.0f}원")
        print(f"최종 수익률: {last_row['수익률(%)']:.2f}%")
        print(f"평균 매수 단가: {last_row['투자금액(원)'] / last_row['보유수량(코인)']:,.0f}원")
        print(f"최종 보유 수량: {last_row['보유수량(코인)']:.6f} 코인")
        print("=" * 70)

6. 메인 실행 스크립트

main.py

# main.py

from api.client import BithumbCandleClient
from strategy.dca_strategy import DCAStrategy
from backtest.dca_runner import DCABacktester

def main():
    """
    솔라나(SOL) 적립식 매수 백테스트
    - 기간: 2024-01-01 ~ 2025-10-06
    - 매일 10,000원 투자
    """
    print("🚀 솔라나(SOL) DCA 백테스트 시작...")
    
    # 1. 데이터 수집
    print("\n[1/3] 캔들 데이터 수집 중...")
    client = BithumbCandleClient(market="KRW-SOL")
    df = client.fetch_candles("2024-01-01", "2025-10-06")
    print(f"✅ {len(df)}개의 일봉 데이터 수집 완료")
    
    # 2. 전략 실행
    print("\n[2/3] DCA 전략 실행 중...")
    strategy = DCAStrategy(daily_investment=10000)
    result = strategy.calculate_portfolio(df)
    print("✅ 포트폴리오 계산 완료")
    
    # 3. 결과 저장
    print("\n[3/3] 결과 저장 및 요약...")
    backtester = DCABacktester(result)
    backtester.print_summary()
    backtester.save_to_excel("solana_dca_backtest_result.xlsx")
    
    print("\n✨ 백테스트 완료!")

if __name__ == "__main__":
    main()

7. 실행 결과

콘솔 출력 예시

 
🚀 솔라나(SOL) DCA 백테스트 시작...

[1/3] 캔들 데이터 수집 중...
✅ 645개의 일봉 데이터 수집 완료

[2/3] DCA 전략 실행 중...
✅ 포트폴리오 계산 완료

[3/3] 결과 저장 및 요약...
======================================================================
📊 DCA 백테스트 결과 요약
======================================================================
투자 기간: 2024-01-01 ~ 2025-10-06
총 투자일수: 645일
총 투자금액: 6,450,000원
최종 포트폴리오 가치: 8,234,500원
총 평가손익: 1,784,500원
최종 수익률: 27.67%
평균 매수 단가: 235,420원
최종 보유 수량: 27.403210 SOL
======================================================================

✅ 'solana_dca_backtest_result.xlsx' 파일로 저장되었습니다.

엑셀 파일 구조

생성되는 엑셀 파일에는 2개의 시트가 포함됩니다:

시트 1: 요약

  • 투자 기간, 총 투자금액, 최종 수익률 등 핵심 지표

시트 2: 일별상세

  • 날짜별 시가/고가/저가/종가
  • 투자금액, 보유수량
  • 포트폴리오 가치, 평가손익
  • 수익률, 일일수익률

8. 주요 특징 및 베스트 프랙티스

모듈화된 구조

각 기능을 독립된 모듈로 분리하여 유지보수와 테스트가 용이합니다.

에러 처리

네트워크 오류와 데이터 누락에 대비한 예외 처리를 구현했습니다.

확장 가능성

  • 다른 코인으로 쉽게 변경 가능 (market 파라미터만 수정)
  • 투자 금액 조정 가능 (daily_investment 파라미터)
  • 다른 전략 추가 가능 (strategy 폴더에 새 클래스 추가)

데이터 정확성

  • 빗썸 공식 API 사용으로 신뢰할 수 있는 데이터
  • 날짜 필터링으로 정확한 기간 데이터 보장
  • KST 시간대 기준으로 일관성 유지

9. 활용 팁

다른 코인으로 변경하기

 
# 비트코인
client = BithumbCandleClient(market="KRW-BTC")

# 이더리움
client = BithumbCandleClient(market="KRW-ETH")

# 리플
client = BithumbCandleClient(market="KRW-XRP")

투자 금액 변경하기

 
# 매일 5,000원 투자
strategy = DCAStrategy(daily_investment=5000)

# 매일 50,000원 투자
strategy = DCAStrategy(daily_investment=50000)

기간 변경하기

 
# 최근 1년 데이터
df = client.fetch_candles("2024-01-01", "2024-12-31")

# 최근 6개월
df = client.fetch_candles("2025-04-01", "2025-10-06")

10. 주의사항

API 제한

빗썸 v1 API는 한 번에 최대 200개의 캔들만 조회할 수 있으므로, 긴 기간 조회 시 여러 번 요청이 발생합니다.

실제 거래와의 차이

이 백테스트는 수수료, 슬리피지, 체결 지연 등을 고려하지 않은 이상적인 시뮬레이션입니다.

과거 데이터의 한계

과거 성과가 미래 수익을 보장하지 않습니다. 백테스트는 전략 검증 도구일 뿐입니다.


마치며

이번 포스팅에서는 빗썸 API를 활용하여 파이썬으로 암호화폐 백테스트 시스템을 구축하는 전 과정을 다루었습니다. 모듈화된 구조 덕분에 다양한 전략으로 확장할 수 있으며, 엑셀 리포트를 통해 투자 성과를 명확하게 분석할 수 있습니다.

실제 투자에 앞서 충분한 백테스트와 검증을 거치시길 권장드립니다.