실전으로 배우는 전략 패턴 (Strategy Pattern) – 트레이딩 봇에서 OOP 활용하기 w.Python

2025. 4. 25. 08:00프로그래밍 공부/Python

반응형
SMALL

전략 패턴이란?

전략 패턴(Strategy Pattern)은 알고리즘(전략)의 동적 교체를 가능하게 해주는 객체지향 디자인 패턴입니다.
행동(Behavior)을 캡슐화하고, 런타임 시에 객체에 전략을 주입해 변경 가능한 유연한 구조를 만들 수 있도록 도와줍니다.

“변화하는 부분을 분리하고, 고정된 부분은 그대로 두자”는 OOP 원칙의 대표 사례입니다.

https://github.com/99mini/trading 에서 소스코드를 확인할 수 있습니다.

구조 요약

trading-bot/
├── .venv/                      		# 가상환경
├── .gitignore
├── requirements.in             		# 의존성 명시
├── requirements.txt            		# 고정된 의존성
├── README.md
├── trading/                    		# 실시간 매매 모듈
│   ├── __init__.py
│   ├── base_trader.py         			# 트레이드 인터페이스 (추상 클레스)
│   └── binance_trader.py				# 바이낸스 실거래 모듈
├── backtesting/                		# 백테스팅 모듈
│   ├── __init__.py
│   └── backtester.py       			# 백테스팅 모듈
├── strategies/                   		# 전략 모듈
│	├── __init__.py             		# strategy 진입점
│	├── base_strategy.py             	# 전략 인터페이스 (추상 클래스)
│	├── volatility_breakout.py         	# 변동성 전략
│	└── moving_average.py			# 이평선 전략
└── scraping/
	└── coin_scraping.py			# 데이터 수집 헬퍼

전략 패턴 적용하기

전략 인터페이스 정의 (strategies/base_strategy.py)

from abc import ABC, abstractmethod
import pandas as pd

class BaseStrategy(ABC):
    @abstractmethod
    def generate_signal(self, data: pd.DataFrame) -> str:
        pass
    
    @abstractmethod
    def prepare_data(self, raw_data: pd.DataFrame) -> pd.DataFrame:
        pass

    @abstractmethod
    def __name__(self) -> str:
        return self.__class__.__name__

모멘텀 전략 구현 (strategies/moving_average.py)

import pandas as pd

from .base_strategy import BaseStrategy

class MovingAverageStrategy(BaseStrategy):
    def prepare_data(self, raw_data):
        df = raw_data.copy()
        df['MA20'] = df['close'].rolling(20).mean()
        df['MA50'] = df['close'].rolling(50).mean()
        return df.dropna()
    
    def generate_signal(self, data):
        latest = data.iloc[-1]
        if latest['MA20'] > latest['MA50']:
            return "buy"
        return "sell"
    
    def __name__(self):
        return "MovingAverageStrategy"

백테스팅 클래스 구현 (backtesting/backtester.py)

import pandas as pd
from typing import Type

from strategies import BaseStrategy

class Backtester:
    def __init__(self, strategy_cls: Type[BaseStrategy]):
        self.strategy = strategy_cls()
        self.historical_data = None
    
    def load_data(self, data_path: str):
        self.historical_data = pd.read_csv(data_path)
    
    def run(self) -> dict:
        processed_data = self.strategy.prepare_data(self.historical_data)
        signals = []
        
        for i in range(1, len(processed_data)):
            window = processed_data.iloc[:i]
            signal = self.strategy.generate_signal(window)
            signals.append(signal)
        # implement something

Backtester 클래스는 BaseStrategy 클래스를 주입받아 BaseStrategy에서 설계한 인터페이스만을 사용합니다. 개발자는 BaseStrategy의 인터페이스에 맞게 구체적인 구현(MovingAverageStrategy)을 작업하면 됩니다. 

Main 함수

from strategies import MovingAverageStrategy
from backtesting import Backtester

backtester = Backtester(MovingAverageStrategy)

backtester.load_data(data_path)

results = backtester.run()

진입점이되는 main함수는 Backtester 클래스와 MovingAverageStrategy 클래스를 import해서 Backtester 클래스에 각 전략을 주입하는 방식으로 사용할 수 있습니다.

해당 개시글에서는 백테스팅 클래스가 Strategy를 주입받아 사용하지만 Trader 클래스를 Backtester 클래스와 유사하게 Strategy를 주입받아 구현하게 되면 전략 패턴을 더욱 적극 사용할 수 있게 될 것입니다.

전략 패턴의 장점

  • 유연성: 전략을 객체로 주입받아 실행하므로, 다양한 전략을 손쉽게 교체하거나 추가할 수 있습니다.
  • 재사용성: 동일한 전략 클래스를 실시간 매매와 백테스팅 모듈에서 모두 활용할 수 있어 코드 중복을 줄입니다.
  • 유지보수 용이성: 전략 로직이 분리되어 있어, 특정 전략의 수정이 전체 시스템에 영향을 주지 않습니다.
  • 테스트 용이성: 각 전략을 독립적으로 테스트할 수 있어, 전략의 성능을 정확히 평가할 수 있습니다.
반응형
LIST