반응형
이 코드는 RSI 지표를 활용한 매매 전략을 구현하며, 거래량 필터와 추세 필터를 추가하여 전략의 성능을 강화했습니다.
1. 데이터 로딩 및 처리
def fetch_data(ticker):
data = yf.download(ticker, '2024-12-01', '2025-03-12', auto_adjust=True)
# MultiIndex 처리 및 컬럼 이름 소문자화
if isinstance(data.columns, pd.MultiIndex):
data.columns = [col[0].lower() for col in data.columns]
else:
data.columns = [col.lower() for col in data.columns]
# 필요한 열만 선택
required_columns = ['open', 'high', 'low', 'close', 'volume']
data = data[[col for col in data.columns if col in required_columns]]
return PandasData(dataname=data)
설명:
- yfinance를 사용해 데이터를 다운로드합니다.
- 데이터가 MultiIndex 형식일 경우 이를 단일 레벨로 변환합니다.
- 필요한 컬럼(open, high, low, close, volume)만 선택하여 Backtrader와 호환되도록 처리합니다.
2. RSI 기반 매매 전략
class EnhancedRSIStrategy(bt.Strategy):
params = (
('rsi_period', 14), # RSI 계산에 사용할 기간
('ema_period', 200), # 추세 필터용 EMA 기간
('volume_mult', 1.5), # 거래량 필터 배수 기준
('atr_period', 14), # ATR(평균 진폭) 계산 기간
('risk_per_trade', 0.02) # 거래당 위험 비중 (2%)
)
def __init__(self):
# RSI 지표
self.rsi = bt.indicators.RSI(self.data.close, period=self.p.rsi_period)
# 추세 필터: 200일 EMA
self.ema = bt.indicators.EMA(self.data.close, period=self.p.ema_period)
# 거래량 필터: 20일 평균 거래량
self.vol_sma = bt.indicators.SMA(self.data.volume, period=20)
# ATR(평균 진폭) 지표: 리스크 관리용
self.atr = bt.indicators.ATR(self.data, period=self.p.atr_period)
# RSI 돌파 신호
self.crossup = bt.indicators.CrossUp(self.rsi, 30) # RSI가 30 상향 돌파
self.crossdown = bt.indicators.CrossDown(self.rsi, 70) # RSI가 70 하향 돌파
설명:
- RSI 지표: 과매도(30 이하) 및 과매수(70 이상) 구간을 활용해 매매 신호를 생성합니다.
- EMA(추세 필터): 주가가 200일 EMA 위에 있을 때만 매수 신호를 허용하여 상승 추세에서만 매매합니다.
- 거래량 필터: 현재 거래량이 최근 20일 평균 거래량의 1.5배 이상일 때만 신호를 유효하게 만듭니다.
- ATR(리스크 관리): 평균 진폭을 기반으로 손절매 및 포지션 크기를 조정합니다.
3. 매수/매도 로직
def next(self):
if not self.position: # 포지션이 없을 때 (매수 조건)
if (self.crossup[0] and
self.data.volume[0] > self.vol_sma[0] * self.p.volume_mult and
self.data.close[0] > self.ema[0]): # 거래량 & 추세 조건 충족
size = (self.broker.getvalue() * self.p.risk_per_trade) / self.atr[0] # 포지션 크기 계산
self.order = self.buy(size=size)
self.stop_price = self.data.close[0] - 2 * self.atr[0] # 손절매 설정
else: # 포지션이 있을 때 (매도 조건)
if self.crossdown[0] or self.data.close[0] < self.stop_price: # RSI 하향 돌파 또는 손절 조건
self.sell()
설명:
- 매수 조건:
- RSI가 과매도 구간(30)을 상향 돌파 (self.crossup).
- 현재 거래량이 평균 거래량의 1.5배 이상 (self.data.volume > self.vol_sma * self.p.volume_mult).
- 주가가 EMA 위에 있어 상승 추세임 (self.data.close > self.ema).
- ATR 기반으로 포지션 크기를 계산하고 손절 가격을 설정.
- 매도 조건:
- RSI가 과매수 구간(70)을 하향 돌파 (self.crossdown).
- 현재 주가가 손절 가격 아래로 떨어짐 (self.data.close < self.stop_price).
4. 리스크 관리 및 성과 분석
cerebro.broker.setcash(10000000) # 초기 자본 설정 (1천만 원)
cerebro.broker.setcommission(commission=0.0015) # 수수료 설정 (0.15%)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe') # 샤프 비율 분석기 추가
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown') # 최대 낙폭 분석기 추가
# 실행 결과 출력
results = cerebro.run()
strat = results[0]
print('최종 포트폴리오 가치: %.2f원' % cerebro.broker.getvalue())
print('샤프 비율:', strat.analyzers.sharpe.get_analysis()['sharperatio'])
print('최대 낙폭:', strat.analyzers.drawdown.get_analysis()['max']['drawdown'], '%')
설명:
- 초기 자본은 1천만 원으로 설정되며, 매매 시마다 0.15%의 수수료를 적용합니다.
- 샤프 비율(위험 대비 초과 수익률)과 최대 낙폭(MDD)을 분석하여 전략의 성과를 평가합니다.
5. 시각화
cerebro.plot(style='candlestick', barup='green', bardown='red')
설명:
- 캔들스틱 차트를 사용하여 주가와 매수/매도 신호를 시각적으로 확인할 수 있습니다.
- 상승 캔들은 초록색(barup='green'), 하락 캔들은 빨간색(bardown='red')으로 표시됩니다.
핵심 요약:
- RSI 지표 활용: 과매도/과매수 구간에서 발생하는 신호를 기반으로 매수/매도를 결정.
- 거래량 필터: 충분한 거래량이 동반된 신호만 유효하게 처리.
- 추세 필터: 상승 추세에서만 매수를 허용하여 전략의 안정성 강화.
- ATR 기반 리스크 관리: 변동성에 따라 적응형 손절 가격과 포지션 크기 설정.
- 성과 평가: 샤프 비율과 최대 낙폭을 통해 전략의 효율성과 리스크 평가.
이 코드는 단순한 RSI 전략에 추가적인 필터와 리스크 관리 기능을 결합하여 실전 투자 환경에서도 활용 가능한 수준입니다.
전체 코드는 다음과 같습니다.
import backtrader as bt
import yfinance as yf
import pandas as pd
class EnhancedRSIStrategy(bt.Strategy):
params = (
('rsi_period', 14),
('ema_period', 200), # 추세 필터용 EMA
('volume_mult', 1.5), # 거래량 배수 기준
('atr_period', 14), # ATR 기간
('risk_per_trade', 0.02), # 거래당 위험 비중
)
def __init__(self):
# 기본 지표
self.rsi = bt.indicators.RSI(self.data.close, period=self.p.rsi_period)
self.ema = bt.indicators.EMA(self.data.close, period=self.p.ema_period)
self.atr = bt.indicators.ATR(self.data, period=self.p.atr_period)
# 거래량 필터 지표
self.vol_sma = bt.indicators.SMA(self.data.volume, period=20)
# 신호 지표
self.crossup = bt.indicators.CrossUp(self.rsi, 30)
self.crossdown = bt.indicators.CrossDown(self.rsi, 70)
def next(self):
# 포지션 없을 때 매수 조건
if not self.position:
if (self.crossup[0] and
self.data.volume[0] > self.vol_sma[0] * self.p.volume_mult and
self.data.close[0] > self.ema[0]):
# ATR 기반 포지션 사이징
size = (self.broker.getvalue() * self.p.risk_per_trade) / self.atr[0]
self.order = self.buy(size=size)
self.stop_price = self.data.close[0] - 2 * self.atr[0] # 2ATR 손절
# 포지션 있을 때 매도 조건
else:
if self.crossdown[0] or self.data.close[0] < self.stop_price:
self.sell()
def notify_order(self, order):
if order.status == order.Completed:
if order.isbuy():
print(f'매수: {order.executed.price:.0f}원, 수량: {order.executed.size:.0f}주')
else:
print(f'매도: {order.executed.price:.0f}원, 수익: {order.executed.pnl:.0f}원')
class PandasData(bt.feeds.PandasData):
def start(self):
if not self.params.dataname.empty:
# 컬럼 이름이 튜플인 경우 처리
if isinstance(self.params.dataname.columns, pd.MultiIndex):
self.params.dataname.columns = ['_'.join(col).strip() if isinstance(col, tuple) else col for col in self.params.dataname.columns]
# 모든 컬럼 이름을 소문자로 변경
self.params.dataname.columns = [col.lower() if isinstance(col, str) else col for col in self.params.dataname.columns]
super(PandasData, self).start()
def fetch_data(ticker):
data = yf.download(ticker, '2022-12-01', '2025-03-12', auto_adjust=True)
print("Original columns:", data.columns)
# MultiIndex 처리
if isinstance(data.columns, pd.MultiIndex):
data.columns = [col[0].lower() for col in data.columns]
else:
data.columns = [col.lower() for col in data.columns]
print("Processed columns:", data.columns)
# 필요한 열만 선택
required_columns = ['open', 'high', 'low', 'close', 'volume']
data = data[[col for col in data.columns if col in required_columns]]
print("Final columns:", data.columns)
return PandasData(dataname=data)
# 백테스팅 엔진 설정
cerebro = bt.Cerebro()
cerebro.addstrategy(EnhancedRSIStrategy)
data = fetch_data('010140.KS')
cerebro.adddata(data)
# 초기 자본 설정
cerebro.broker.setcash(10000000)
cerebro.broker.setcommission(commission=0.0015)
# 분석기 추가
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
# 실행
print('초기 포트폴리오 가치: %.2f원' % cerebro.broker.getvalue())
results = cerebro.run()
print('최종 포트폴리오 가치: %.2f원' % cerebro.broker.getvalue())
# 결과 분석
strat = results[0]
print('샤프 비율:', strat.analyzers.sharpe.get_analysis()['sharperatio'])
print('최대 낙폭:', strat.analyzers.drawdown.get_analysis()['max']['drawdown'], '%')
# 시각화
cerebro.plot(style='candlestick', barup='green', bardown='red')
이 코드 실행결과

저는 삼성중공업이라는 회사로 테스트를 해 보았습니다.
초기 포트폴리오 가치: 10000000.00원
매수: 7810원, 수량: 681주
매도: 6880원, 수익: -930원
매도: 7050원, 수익: -760원
매도: 7260원, 수익: -550원
매도: 7230원, 수익: -580원
매도: 7170원, 수익: -640원
매도: 7150원, 수익: -660원
매도: 7050원, 수익: -760원
매도: 7230원, 수익: -580원
매도: 7150원, 수익: -660원
매도: 7120원, 수익: -690원
매도: 8770원, 수익: 960원
매도: 9000원, 수익: 1190원
매도: 10520원, 수익: 2710원
매도: 11190원, 수익: 3380원
매도: 10960원, 수익: 3150원
매도: 11400원, 수익: 3590원
매도: 11740원, 수익: 3930원
매도: 13300원, 수익: 5490원
최종 포트폴리오 가치: 14258927.37원
샤프 비율: 0.8403278993369693
최대 낙폭: 12.591181381140164 %
위와 같이 결과가 출력되는 것을 확인할 수 있습니다.
반응형
'투자' 카테고리의 다른 글
Intel 에 대한 2025년 투자 의견 (0) | 2025.02.28 |
---|---|
파이코인의 2025년 전망 (1) | 2025.02.25 |
뉴스 검색 결과가 주가에 미치는 영향: 실시간 데이터 분석 (2) | 2024.12.11 |
5. 투자를 결정할 지표들 (금리:base interest rate) (7) | 2023.09.05 |
3. 투자를 결정할 지표들 (실업률:Unemployment Rate) (0) | 2023.08.31 |