파이썬 자동매매 봇 만들기

파이썬 자동매매 봇, 왜 직접 만들어야 할까?

자동매매(알고리즘 트레이딩)는 더 이상 기관 투자자만의 영역이 아닙니다. 파이썬 자동매매 봇을 직접 만들면 감정 없는 일관된 매매가 가능하고, 백테스트를 통해 전략을 검증한 뒤 실전에 투입할 수 있습니다. 이 글에서는 파이썬으로 자동매매 봇을 구축하는 핵심 구조와 실전 팁을 정리합니다.

자동매매 봇의 기본 아키텍처

자동매매 봇은 크게 4가지 모듈로 구성됩니다.

  • 데이터 수집 모듈 — 거래소 API에서 실시간 가격, 호가, 체결 데이터를 수신
  • 전략 엔진 — 수집된 데이터를 기반으로 매수/매도 시그널 생성
  • 주문 실행 모듈 — 시그널에 따라 거래소에 주문을 전송
  • 리스크 관리 모듈 — 포지션 크기 제한, 손절, 일일 손실 한도 등을 관리
# 기본 봇 구조 예시
class TradingBot:
    def __init__(self, exchange, strategy, risk_manager):
        self.exchange = exchange
        self.strategy = strategy
        self.risk = risk_manager

    def run(self):
        while True:
            candles = self.exchange.fetch_ohlcv('BTC/USDT', '1h')
            signal = self.strategy.evaluate(candles)
            if signal and self.risk.check(signal):
                self.exchange.create_order(signal)

거래소 API 연동: ccxt 라이브러리

파이썬에서 거래소 연동의 표준은 ccxt 라이브러리입니다. 바이낸스, 업비트, 비트겟 등 100개 이상의 거래소를 하나의 인터페이스로 지원합니다.

import ccxt

exchange = ccxt.binance({
    'apiKey': 'YOUR_API_KEY',
    'secret': 'YOUR_SECRET',
    'options': {'defaultType': 'future'}  # 선물 거래
})

# OHLCV 데이터 조회
ohlcv = exchange.fetch_ohlcv('BTC/USDT', '1h', limit=100)

# 지정가 매수 주문
order = exchange.create_limit_buy_order('BTC/USDT', 0.001, 50000)

주의사항: API 키는 반드시 환경 변수나 별도 설정 파일로 관리하고, IP 화이트리스트를 설정하세요. 출금 권한은 절대 부여하지 마세요.

전략 구현: 이동평균 크로스오버 예시

가장 기본적인 퀀트 전략인 이동평균 크로스오버를 구현해보겠습니다. 단기 이동평균이 장기 이동평균을 상향 돌파하면 매수, 하향 돌파하면 매도하는 전략입니다.

import pandas as pd
import numpy as np

class MACrossStrategy:
    def __init__(self, short_period=7, long_period=25):
        self.short = short_period
        self.long = long_period

    def evaluate(self, ohlcv_data):
        df = pd.DataFrame(ohlcv_data,
                          columns=['ts','open','high','low','close','vol'])
        df['ma_short'] = df['close'].rolling(self.short).mean()
        df['ma_long'] = df['close'].rolling(self.long).mean()

        prev = df.iloc[-2]
        curr = df.iloc[-1]

        # 골든크로스 → 매수
        if prev['ma_short'] < prev['ma_long'] and 
           curr['ma_short'] > curr['ma_long']:
            return {'side': 'buy', 'symbol': 'BTC/USDT'}

        # 데드크로스 → 매도
        if prev['ma_short'] > prev['ma_long'] and 
           curr['ma_short'] < curr['ma_long']:
            return {'side': 'sell', 'symbol': 'BTC/USDT'}

        return None

리스크 관리: 봇의 생존을 결정하는 핵심

전략보다 중요한 것이 리스크 관리입니다. 아무리 좋은 전략이라도 리스크 관리 없이는 한 번의 급등락에 계좌가 날아갈 수 있습니다.

리스크 항목 권장 설정 설명
1회 최대 손실 총 자본의 1~2% 단일 포지션 손절 기준
일일 최대 손실 총 자본의 3~5% 하루 누적 손실 한도 도달 시 봇 정지
최대 동시 포지션 3~5개 과도한 분산 방지
레버리지 3배 이하 초보자는 1~2배 권장
class RiskManager:
    def __init__(self, max_loss_pct=0.02, daily_max_loss_pct=0.05):
        self.max_loss = max_loss_pct
        self.daily_max = daily_max_loss_pct
        self.daily_pnl = 0

    def check(self, signal):
        if abs(self.daily_pnl) >= self.daily_max:
            print("일일 손실 한도 도달 — 매매 중단")
            return False
        return True

    def update_pnl(self, pnl):
        self.daily_pnl += pnl

백테스트: 실전 투입 전 반드시 검증

전략을 만들었다면 과거 데이터로 백테스트하는 것이 필수입니다. 파이썬에서는 백테스트 프레임워크를 직접 구현하거나, Backtrader 또는 vectorbt 같은 라이브러리를 활용할 수 있습니다.

백테스트 시 반드시 확인해야 할 지표:

  • 샤프 비율(Sharpe Ratio) — 위험 대비 수익률. 1.0 이상이면 양호
  • 최대 낙폭(MDD) — 최고점 대비 최대 하락폭. 20% 이하 권장
  • 승률 — 전체 매매 중 수익 거래 비율
  • 손익비 — 평균 수익 / 평균 손실. 1.5 이상 권장

과최적화(Overfitting) 주의: 백테스트 성과가 지나치게 좋다면 과거 데이터에만 맞춰진 전략일 수 있습니다. 반드시 학습 기간과 검증 기간을 분리(Walk-Forward Analysis)하세요.

실전 운영 체크리스트

봇을 실전에 투입하기 전, 아래 항목을 반드시 점검하세요.

  • 소액 테스트 — 최소 금액으로 1~2주 실거래 테스트
  • 에러 처리 — 네트워크 끊김, API 오류, 주문 거부 등 예외 상황 대응 코드
  • 알림 시스템 — 텔레그램 봇 등으로 주문 체결, 에러 발생 시 즉시 알림
  • 자동 재시작 — systemd나 supervisor로 프로세스 관리
  • 로깅 — 모든 주문과 시그널을 DB나 파일에 기록

자동매매 봇은 만든다고 끝이 아닙니다. 지속적인 모니터링과 전략 개선이 핵심입니다. 계좌 생존 규칙을 숙지하고, 리스크 관리를 최우선으로 두면서 점진적으로 봇을 발전시켜 나가세요.

마무리

파이썬 자동매매 봇 개발은 데이터 수집 → 전략 구현 → 리스크 관리 → 백테스트 → 실전 운영의 단계를 거칩니다. 처음부터 복잡한 전략을 만들려 하지 말고, 단순한 전략으로 파이프라인을 완성한 뒤 점진적으로 고도화하는 것이 성공 확률을 높이는 방법입니다. 감정 없는 규칙 기반 매매의 세계에 도전해보세요.

7) 실전 봇 아키텍처: 모듈 구조

자동매매 봇은 전략 로직, 주문 실행, 리스크 관리, 데이터 수집을 모듈로 분리해야 유지보수가 가능합니다.

trading-bot/
├── main.py              # 엔트리포인트 (스케줄러)
├── config.py            # API 키, 파라미터
├── strategy/
│   ├── base.py          # 전략 인터페이스
│   ├── ma_crossover.py  # 이동평균 크로스오버
│   └── rsi_reversal.py  # RSI 역추세
├── execution/
│   ├── exchange.py      # 거래소 API 래퍼
│   └── order.py         # 주문 관리 (시장가/지정가)
├── risk/
│   ├── position_sizer.py  # 포지션 사이징
│   └── guard.py           # 일일 손실 한도, 쿨다운
├── data/
│   ├── candles.py       # OHLCV 수집
│   └── indicators.py    # 기술 지표 계산
└── utils/
    ├── logger.py        # 로깅
    └── notifier.py      # 텔레그램/디스코드 알림

8) 거래소 API 래퍼 구현 (ccxt)

import ccxt
import logging
from typing import Optional

class ExchangeClient:
    def __init__(self, exchange_id='binance', api_key='', secret=''):
        self.exchange = getattr(ccxt, exchange_id)({
            'apiKey': api_key,
            'secret': secret,
            'options': {'defaultType': 'future'},  # 선물 모드
        })
        self.logger = logging.getLogger(__name__)

    def get_ohlcv(self, symbol: str, timeframe='1h', limit=100):
        """캔들 데이터 조회"""
        return self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)

    def place_order(
        self, symbol: str, side: str, amount: float,
        order_type='market', price: Optional[float] = None,
        stop_loss: Optional[float] = None,
    ) -> dict:
        """주문 실행 + 손절 주문"""
        try:
            order = self.exchange.create_order(
                symbol, order_type, side, amount, price
            )
            self.logger.info(f"주문 체결: {side} {amount} {symbol} @ {order['price']}")

            # 손절 주문 자동 설정
            if stop_loss:
                sl_side = 'sell' if side == 'buy' else 'buy'
                self.exchange.create_order(
                    symbol, 'stop_market', sl_side, amount,
                    params={'stopPrice': stop_loss, 'reduceOnly': True}
                )
                self.logger.info(f"손절 설정: {stop_loss}")

            return order
        except ccxt.InsufficientFunds:
            self.logger.error("잔고 부족")
            raise
        except ccxt.NetworkError as e:
            self.logger.error(f"네트워크 오류: {e}")
            raise

9) 관련 글

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux