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)
exchanges/upbit.py
νμΌ μμ±BaseExchange
μμλ°μ ꡬν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)
strategies/scalping_strategy.py
νμΌ μμ±BaseStrategy
μμλ°μ ꡬνstrategies/__init__.py
μ ν©ν 리μ μΆκ°
μ₯μ
β
νμ₯μ±: μλ‘μ΄ κ±°λμ/μ λ΅ μΆκ°κ° μ¬μ
β
μΌκ΄μ±: λͺ¨λ κ±°λμ/μ λ΅μ΄ λμΌν μΈν°νμ΄μ€ μ¬μ©
β
μ μ§λ³΄μ: κ³΅ν΅ λ‘μ§μ base ν΄λμ€μμ κ΄λ¦¬
β
ν
μ€νΈ: κ° κ΅¬ν체λ³λ‘ λ
립μ ν
μ€νΈ κ°λ₯
β
λ¨μν¨: 볡μ‘ν μμ‘΄μ± μμ΄ κΉλν ꡬ쑰
μ΄λ κ² νλ©΄ μλ‘μ΄ κ±°λμλ μ λ΅μ μΆκ°ν λ κΈ°μ‘΄ μ½λμ κ±°μ μν₯μ μ£Όμ§ μκ³ νμ₯ν μ μμ΅λλ€!