SW Architectural Desgin - FinAI6/arbitrage-trading GitHub Wiki

μΈν„°νŽ˜μ΄μŠ€ 기반 차읡거래 봇 ꡬ쑰

파일 ꡬ쑰

arbitrage_bot/
β”œβ”€β”€ config.py              # λͺ¨λ“  μ„€μ •
β”œβ”€β”€ exchanges/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ base_exchange.py   # κ±°λž˜μ†Œ 곡톡 μΈν„°νŽ˜μ΄μŠ€
β”‚   β”œβ”€β”€ binance.py         # λ°”μ΄λ‚ΈμŠ€ κ΅¬ν˜„
β”‚   └── bybit.py           # λ°”μ΄λΉ„νŠΈ κ΅¬ν˜„
β”œβ”€β”€ strategies/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ base_strategy.py   # μ „λž΅ 곡톡 μΈν„°νŽ˜μ΄μŠ€
β”‚   β”œβ”€β”€ market_strategy.py # μ‹œμž₯κ°€ μ „λž΅
β”‚   β”œβ”€β”€ limit_strategy.py  # μ§€μ •κ°€ μ „λž΅
β”‚   └── hybrid_strategy.py # ν˜Όν•© μ „λž΅
β”œβ”€β”€ main.py               # 메인 μ‹€ν–‰
└── requirements.txt

1. κ±°λž˜μ†Œ μΈν„°νŽ˜μ΄μŠ€ 섀계

exchanges/base_exchange.py

from abc import ABC, abstractmethod

class BaseExchange(ABC):
    """λͺ¨λ“  κ±°λž˜μ†Œκ°€ κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” 곡톡 μΈν„°νŽ˜μ΄μŠ€"""
    
    def __init__(self, api_key: str, secret: str):
        self.api_key = api_key
        self.secret = secret
        self.exchange = None
    
    @abstractmethod
    def connect(self) -> bool:
        """κ±°λž˜μ†Œ μ—°κ²°"""
        pass
    
    @abstractmethod
    def get_symbols(self) -> set:
        """거래 κ°€λŠ₯ν•œ 심볼 λͺ©λ‘"""
        pass
    
    @abstractmethod
    def get_prices(self) -> dict:
        """λͺ¨λ“  μ‹¬λ³Όμ˜ ν˜„μž¬κ°€"""
        pass
    
    @abstractmethod
    def get_volumes(self) -> dict:
        """24μ‹œκ°„ κ±°λž˜λŸ‰"""
        pass
    
    @abstractmethod
    def convert_symbol(self, raw_symbol: str) -> str:
        """심볼 ν˜•μ‹ λ³€ν™˜"""
        pass
    
    @abstractmethod
    def create_market_order(self, symbol: str, side: str, quantity: float) -> dict:
        """μ‹œμž₯κ°€ μ£Όλ¬Έ"""
        pass
    
    @abstractmethod
    def create_limit_order(self, symbol: str, side: str, quantity: float, price: float) -> dict:
        """μ§€μ •κ°€ μ£Όλ¬Έ"""
        pass
    
    @abstractmethod
    def get_order_status(self, order_id: str, symbol: str) -> dict:
        """μ£Όλ¬Έ μƒνƒœ 쑰회"""
        pass
    
    @abstractmethod
    def get_balance(self, asset: str = 'USDT') -> float:
        """μž”κ³  쑰회"""
        pass

exchanges/binance.py

import ccxt
from .base_exchange import BaseExchange

class BinanceExchange(BaseExchange):
    """λ°”μ΄λ‚ΈμŠ€ κ±°λž˜μ†Œ κ΅¬ν˜„"""
    
    def connect(self) -> bool:
        try:
            self.exchange = ccxt.binance({
                'apiKey': self.api_key,
                'secret': self.secret,
                'options': {'defaultType': 'future'},
                'enableRateLimit': True
            })
            return True
        except Exception as e:
            print(f"λ°”μ΄λ‚ΈμŠ€ μ—°κ²° μ‹€νŒ¨: {e}")
            return False
    
    def get_symbols(self) -> set:
        # λ°”μ΄λ‚ΈμŠ€ νŠΉν™” 둜직
        pass
    
    def get_prices(self) -> dict:
        # λ°”μ΄λ‚ΈμŠ€ API 호좜
        pass
    
    # ... λ‚˜λ¨Έμ§€ λ©”μ„œλ“œ κ΅¬ν˜„

exchanges/bybit.py

import ccxt
from .base_exchange import BaseExchange

class BybitExchange(BaseExchange):
    """λ°”μ΄λΉ„νŠΈ κ±°λž˜μ†Œ κ΅¬ν˜„"""
    
    def connect(self) -> bool:
        try:
            self.exchange = ccxt.bybit({
                'apiKey': self.api_key,
                'secret': self.secret,
                'enableRateLimit': True,
                'options': {
                    'defaultType': 'swap',
                    'defaultSubType': 'linear',
                }
            })
            return True
        except Exception as e:
            print(f"λ°”μ΄λΉ„νŠΈ μ—°κ²° μ‹€νŒ¨: {e}")
            return False
    
    def get_symbols(self) -> set:
        # λ°”μ΄λΉ„νŠΈ νŠΉν™” 둜직 (category=linear λ“±)
        pass
    
    # ... λ‚˜λ¨Έμ§€ λ©”μ„œλ“œ κ΅¬ν˜„

2. μ „λž΅ μΈν„°νŽ˜μ΄μŠ€ 섀계

strategies/base_strategy.py

from abc import ABC, abstractmethod
from typing import Dict, Any, Optional

class BaseStrategy(ABC):
    """λͺ¨λ“  μ „λž΅μ΄ κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” 곡톡 μΈν„°νŽ˜μ΄μŠ€"""
    
    def __init__(self, config: Dict[str, Any]):
        self.config = config
        self.spread_threshold = config['spread_threshold']
        self.exit_percent = config['exit_percent']
        self.spread_hold_count = config['spread_hold_count']
    
    @abstractmethod
    def should_enter(self, symbol: str, spread_data: Dict, history: Dict) -> bool:
        """μ§„μž… 쑰건 확인"""
        pass
    
    @abstractmethod
    def should_exit(self, symbol: str, current_spread: float, position: Dict) -> bool:
        """μ²­μ‚° 쑰건 확인"""
        pass
    
    @abstractmethod
    def create_entry_orders(self, long_exchange, short_exchange, symbol: str, 
                           quantity: float, prices: Dict) -> Dict:
        """μ§„μž… μ£Όλ¬Έ 생성"""
        pass
    
    @abstractmethod
    def create_exit_orders(self, long_exchange, short_exchange, position: Dict) -> Dict:
        """μ²­μ‚° μ£Όλ¬Έ 생성"""
        pass
    
    def get_strategy_name(self) -> str:
        """μ „λž΅ 이름 λ°˜ν™˜"""
        return self.__class__.__name__.replace('Strategy', '').lower()

strategies/market_strategy.py

from .base_strategy import BaseStrategy

class MarketStrategy(BaseStrategy):
    """μ‹œμž₯κ°€ μ „λž΅"""
    
    def should_enter(self, symbol: str, spread_data: Dict, history: Dict) -> bool:
        # 기쑴 should_enter_position 둜직
        return abs(spread_data['spread_pct']) >= self.spread_threshold
    
    def should_exit(self, symbol: str, current_spread: float, position: Dict) -> bool:
        # κΈ°μ‘΄ μ²­μ‚° 쑰건 둜직
        entry_spread = position['entry_spread']
        return abs(current_spread) < entry_spread - self.exit_percent
    
    def create_entry_orders(self, long_exchange, short_exchange, symbol: str, 
                           quantity: float, prices: Dict) -> Dict:
        # μ‹œμž₯κ°€ μ£Όλ¬Έ 생성
        long_order = long_exchange.create_market_order(symbol, 'buy', quantity)
        short_order = short_exchange.create_market_order(symbol, 'sell', quantity)
        
        return {
            'long_order': long_order,
            'short_order': short_order,
            'order_type': 'market'
        }
    
    def create_exit_orders(self, long_exchange, short_exchange, position: Dict) -> Dict:
        # μ‹œμž₯κ°€ μ²­μ‚°
        pass

strategies/limit_strategy.py

from .base_strategy import BaseStrategy

class LimitStrategy(BaseStrategy):
    """μ§€μ •κ°€ μ „λž΅"""
    
    def create_entry_orders(self, long_exchange, short_exchange, symbol: str, 
                           quantity: float, prices: Dict) -> Dict:
        # μ§€μ •κ°€ μ£Όλ¬Έ 생성
        buy_price = prices['lower_price'] * 1.001
        sell_price = prices['higher_price'] * 0.999
        
        long_order = long_exchange.create_limit_order(symbol, 'buy', quantity, buy_price)
        short_order = short_exchange.create_limit_order(symbol, 'sell', quantity, sell_price)
        
        return {
            'long_order': long_order,
            'short_order': short_order,
            'order_type': 'limit',
            'needs_monitoring': True  # 체결 λͺ¨λ‹ˆν„°λ§ ν•„μš”
        }

3. Factory νŒ¨ν„΄μœΌλ‘œ 객체 생성

exchanges/init.py

from .binance import BinanceExchange
from .bybit import BybitExchange

def create_exchange(exchange_name: str, api_key: str, secret: str):
    """κ±°λž˜μ†Œ νŒ©ν† λ¦¬"""
    exchanges = {
        'binance': BinanceExchange,
        'bybit': BybitExchange,
        # μƒˆλ‘œμš΄ κ±°λž˜μ†Œ μΆ”κ°€ μ‹œ μ—¬κΈ°λ§Œ μˆ˜μ •
    }
    
    if exchange_name not in exchanges:
        raise ValueError(f"μ§€μ›ν•˜μ§€ μ•ŠλŠ” κ±°λž˜μ†Œ: {exchange_name}")
    
    return exchanges[exchange_name](api_key, secret)

strategies/init.py

from .market_strategy import MarketStrategy
from .limit_strategy import LimitStrategy
from .hybrid_strategy import HybridStrategy

def create_strategy(strategy_name: str, config: dict):
    """μ „λž΅ νŒ©ν† λ¦¬"""
    strategies = {
        'market': MarketStrategy,
        'limit': LimitStrategy,
        'hybrid': HybridStrategy,
        # μƒˆλ‘œμš΄ μ „λž΅ μΆ”κ°€ μ‹œ μ—¬κΈ°λ§Œ μˆ˜μ •
    }
    
    if strategy_name not in strategies:
        raise ValueError(f"μ§€μ›ν•˜μ§€ μ•ŠλŠ” μ „λž΅: {strategy_name}")
    
    return strategies[strategy_name](config)

4. main.pyμ—μ„œ μ‚¬μš©

import os
from config import STRATEGIES, COMMON_CONFIG
from exchanges import create_exchange
from strategies import create_strategy

def main():
    # μ „λž΅ 선택
    strategy_name = input("μ „λž΅ 선택 (market/limit/hybrid): ")
    strategy_config = STRATEGIES[strategy_name]
    
    # κ±°λž˜μ†Œ 생성
    binance = create_exchange('binance', 
                             os.getenv('BINANCE_API_KEY'), 
                             os.getenv('BINANCE_SECRET'))
    bybit = create_exchange('bybit', 
                           os.getenv('BYBIT_API_KEY'), 
                           os.getenv('BYBIT_SECRET'))
    
    # μ „λž΅ 생성
    strategy = create_strategy(strategy_name, strategy_config)
    
    # κ±°λž˜μ†Œ μ—°κ²°
    if not binance.connect() or not bybit.connect():
        print("κ±°λž˜μ†Œ μ—°κ²° μ‹€νŒ¨")
        return
    
    # 메인 루프
    while True:
        # μŠ€ν”„λ ˆλ“œ 데이터 μˆ˜μ§‘
        spread_data = collect_spread_data(binance, bybit)
        
        # μ „λž΅ μ‹€ν–‰
        for item in spread_data:
            if strategy.should_enter(item['symbol'], item, {}):
                execute_entry(strategy, binance, bybit, item)
            
            # κΈ°μ‘΄ ν¬μ§€μ…˜ μ²­μ‚° 확인
            check_exit_conditions(strategy, binance, bybit)
        
        time.sleep(5)

if __name__ == "__main__":
    main()

μƒˆλ‘œμš΄ κ±°λž˜μ†Œ/μ „λž΅ μΆ”κ°€ 방법

μƒˆλ‘œμš΄ κ±°λž˜μ†Œ μΆ”κ°€ (예: Upbit)

  1. exchanges/upbit.py 파일 생성
  2. BaseExchange 상속받아 κ΅¬ν˜„
  3. exchanges/__init__.py의 νŒ©ν† λ¦¬μ— μΆ”κ°€
# exchanges/upbit.py
class UpbitExchange(BaseExchange):
    def connect(self):
        # Upbit νŠΉν™” μ—°κ²° 둜직
        pass
    
    # λ‚˜λ¨Έμ§€ λ©”μ„œλ“œ κ΅¬ν˜„...

# exchanges/__init__.py에 μΆ”κ°€
exchanges = {
    'binance': BinanceExchange,
    'bybit': BybitExchange,
    'upbit': UpbitExchange,  # 이 μ€„λ§Œ μΆ”κ°€!
}

μƒˆλ‘œμš΄ μ „λž΅ μΆ”κ°€ (예: Scalping)

  1. strategies/scalping_strategy.py 파일 생성
  2. BaseStrategy 상속받아 κ΅¬ν˜„
  3. strategies/__init__.py의 νŒ©ν† λ¦¬μ— μΆ”κ°€

μž₯점

βœ… ν™•μž₯μ„±: μƒˆλ‘œμš΄ κ±°λž˜μ†Œ/μ „λž΅ μΆ”κ°€κ°€ 쉬움
βœ… 일관성: λͺ¨λ“  κ±°λž˜μ†Œ/μ „λž΅μ΄ λ™μΌν•œ μΈν„°νŽ˜μ΄μŠ€ μ‚¬μš©
βœ… μœ μ§€λ³΄μˆ˜: 곡톡 λ‘œμ§μ€ base ν΄λž˜μŠ€μ—μ„œ 관리
βœ… ν…ŒμŠ€νŠΈ: 각 κ΅¬ν˜„μ²΄λ³„λ‘œ 독립적 ν…ŒμŠ€νŠΈ κ°€λŠ₯
βœ… λ‹¨μˆœν•¨: λ³΅μž‘ν•œ μ˜μ‘΄μ„± 없이 κΉ”λ”ν•œ ꡬ쑰

μ΄λ ‡κ²Œ ν•˜λ©΄ μƒˆλ‘œμš΄ κ±°λž˜μ†Œλ‚˜ μ „λž΅μ„ μΆ”κ°€ν•  λ•Œ κΈ°μ‘΄ μ½”λ“œμ— 거의 영ν–₯을 μ£Όμ§€ μ•Šκ³  ν™•μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€!