본문 바로가기

Quant

퀀트 전략 업데이트 (7) : Backtrader skeleton code

회사 동료 분이 같이 저녁을 먹다가 이걸 왜 열심히 하냐 물어보셨다.

뭐라 답하지 조금 고민하다가 무지개를 쫓아가는 중이라 말씀드렸다.

무지개의 끝에 닿지 못할 수도 있지만, 그 끝을 따라가는 과정이 즐겁다고 했다.

 

봄이 오고 웃을 일도 많이 생기면서 마음도 조금 풀린거 같다.

처음에는 세상을 노려보는 마음으로 시작했다가, 지금은 다시 세상을 바라보면서 즐기고 있다.

좋아하는 공일오비 노래에서 떠오른 생각인거 같다


Backtrader 라이브러리를 활용하려는데 자유도가 상당히 높아서 시작점을 어떻게 잡아야 할 지 고민을 했다.

 

1. 하책: 매뉴얼을 모두 정독하고 하나씩 쌓아올린다.

- 시간이 오래 걸리고, 모든 기능이 다 필요하지 않다.

- 그래도 알아두면 써먹을 데가 있다.

 

2. 중책: LLM한테 내 코드를 주고 다 변경해달라 한다.

- LLM은 정답을 말하지 않는다. '그럴듯한' 코드를 뱉어주기에 어차피 검증은 내가 해야한다.

- 코드 짜는데 10분. 수정하는데 1시간이 걸릴 거 같다.

 

3. 상책: 부분적으로 변경할 수 있는 코드를 점진적으로 변경한다.

- lean start. 맨땅에 헤딩할 때는 대부분 옳은 방식이다.

- 점진적으로 해당 라이브러리 활용도를 높여갈 수 있다.

- 코드 변경에 대한 side-effect를 조절할 수 있다.

- 생성형 AI로 컨트롤 가능하게 생산성을 높일 수 있다.

 


1. Skeleton 코드 작성

Docs의 Quuick start와 LLM을 참고해 간단한 코드를 작성한다.

import backtrader as bt
import pandas as pd
from datetime import datetime

from logic.get_data import GetData


class SimpleStrategy(bt.Strategy):
    def __init__(self):
        self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=10)

    def next(self):
        if not self.position:
            if self.data.close[0] > self.sma[0]:
                self.buy()
        else:
            if self.data.close[0] < self.sma[0]:
                self.sell()



# 1. 데이터 로딩
code = 'A000500'
date = datetime(2024, 11, 25)
df = GetData.get_one_day_ohlcv_data(code, date)
df['timestamp'] = pd.to_datetime(df['timestamp'])
df.set_index('timestamp', inplace=True)

bt_df = df[['open', 'high', 'low', 'close', 'volume']].copy()

# 2. Backtrader용 데이터 포맷 변환
data = bt.feeds.PandasData(
    dataname=bt_df,
    timeframe=bt.TimeFrame.Minutes,
    compression=5
)
# 3. Cerebro 엔진 설정
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(SimpleStrategy)
cerebro.run()
cerebro.plot()

 

 

 

2. 코드 교체

대장주 교체가 빈번했던 일자 + 교체된 대장주가 다시 대장주가 된 일자를 파악해 해당 일자를 인자로 넣어 코드 교체를 진행한다.

 

현재 프로세스는 아래와 같다.

1) 대장주 IN & OUT 시각 체크  -> 2) 대장주 5분봉 Dataframe 조회 -> 3) Dataframe slicing -> 4) Concat Dataframe -> 5) Run backtesitng -> 6) 매매 내역 정리 -> 7) 수익률 / 승률 계산

 

5를 적용하고 6, 7 가능 여부를 체크한다. 6, 7은 뭐 안 될리가 없겠지..

 

# Calc & print portfolio
if not top_stock_df.empty:
    df = top_stock_df
    df = df.sort_values(by="timestamp").reset_index(drop=True)
    df.set_index("timestamp", inplace=True)

    # 필요한 컬럼만 유지
    df = df[['open', 'high', 'low', 'close', 'volume', 'code']]

    data = bt.feeds.PandasData(
        dataname=df,
        timeframe=bt.TimeFrame.Minutes,
        compression=5
    )

    cerebro = bt.Cerebro()
    cerebro.addstrategy(FocusStockStrategy)
    cerebro.adddata(data)
    strategies = cerebro.run()
    
    # ✅ 전략 인스턴스로부터 매매 내역 가져오기
    strategy = strategies[0]
    trade_df = strategy.trades_df

    # 출력 (Jupyter 환경 기준)
    display(trade_df)

 

 

결과는..

 

뭔가 잘못됐다.

아마 중간에 빵꾸난 데이터, 코드가 많을 것으로 사료된다.

뼈대는 잡았으니, 이제 디버깅을 하며 try & error 반복과 backtrader 매뉴얼을 정독해야한다.

 

728x90