본문 바로가기

Quant

Backtrader 리팩토링

 

이직 악귀 퇴마 完


일자별 백테스팅 매매 내역 분석을 하는데

시각화 된 차트와 수치화 된 dataframe이 싱크가 안 맞는 문제를 발견했다.

 

문제를 파악해보니 Backtrader 라이브러리를 백테스팅에 사용 중인데,

하루에 여러 ticker를 다루는 매매를 적용하고자 단일 dataframe에 혼재한 데이터를 넣어서 사용한 게 문제였다.

이렇게 하니, 예외 처리를 위해 코드와 변수는 복잡해지고 의도치 않은 결과가 나오게 된다.

 

왜 처음에 이렇게 설게를 했을까 생각해보니,

커스터마이징 했던 백테스팅 로직에서 backtrader 라이브러리로 넘어가는 과정에서

변경점을 최소화 하고 싶어서 했던 선택인 듯 하다.

 

복잡도를 내리고, 코드를 줄이는 방법으로 다시 개선한다.

그 과정에서 validation 로직도 강화하여 정확한 input / output 을 체크한다.

 

 

1. Validation 로직 추가

def validate_five_minute_continuous(df: pd.DataFrame):
    if df.shape[0] <= 1:
        return  # 비교할 게 없음

    timestamps = df["timestamp"].reset_index(drop=True)
    time_deltas = timestamps.diff().dropna()

    for i in range(len(time_deltas)):
        prev_time = timestamps[i]
        curr_time = timestamps[i + 1]

        # 마지막 구간이고 15:20 → 15:30일 때만 10분 허용
        if (
            prev_time.time() == pd.to_datetime("15:20:00").time()
            and curr_time.time() == pd.to_datetime("15:30:00").time()
            and i == len(time_deltas) - 1
        ):
            if time_deltas.iloc[i] != pd.Timedelta(minutes=10):
                raise ValueError(
                    f"마지막 구간이 10분이 아님: {prev_time} -> {curr_time}"
                )
        else:
            if time_deltas.iloc[i] != pd.Timedelta(minutes=5):
                raise ValueError(f"5분봉 연속성이 깨짐: {prev_time} -> {curr_time}")

 

2. Backtrader 코드 정리

변경되는 코드를 관리하기 위해 불필요한 변수를 제거하고, 이를 다루는 함수들도 삭제해준다.

 

3. Backtesting 로직 정리

    # Run backtesting
    top_stock_list = [
        df.sort_values(by='timestamp').reset_index(drop=True)
        for df in top_stock_list
    ]
    top_stock_list = sorted(top_stock_list, key=lambda df: df.iloc[0]['timestamp'])
    if top_stock_list:
        top_stock_df = pd.concat(top_stock_list, ignore_index=True)
        top_stock_df = top_stock_df.sort_values(by='timestamp').reset_index(drop=True)

    for df in top_stock_list:
        validate_time_df(df)
        validate_five_minute_continuous(df)
        result = run_backtesting(df, current_cash, prohibited_stock)[0]

        trade_df = pd.concat([trade_df, result.trade_df])
        current_cash = result.cash
        prohibited_stock = result.prohibited_stock


    if not trade_df.empty:
        trade_df['datetime'] = pd.to_datetime(trade_df['datetime'])
        top_stock_df['timestamp'] = pd.to_datetime(top_stock_df['timestamp'])
        top_stock_df = top_stock_df.rename(columns={'timestamp': 'datetime'})

        trade_df = pd.merge(
            trade_df,
            top_stock_df,
            how='left',
            on=['datetime', 'code'],
        )

        total_trades = pd.concat([total_trades, trade_df])

        return_pct = (result.cash - day_cash) / day_cash * 100

        print(f"💸 잔고   : {day_cash} -> {result.cash}")
        print(f"📊 수익률 : {return_pct:.2f}%")
        day_cash = current_cash

 

거래일: 41
거래 수: 50.0
순수익: 48,796,340 원
📊 수익률: 48.80%
✅ 승률: 46.00%

'Quant' 카테고리의 다른 글

백테스팅 결과 분석  (1) 2025.07.16
재진입 필터링 및 데이터 분석  (2) 2025.07.10
연산 최적화  (3) 2025.07.07
보조지표 추가, ADX  (0) 2025.07.06
전략 최적화 (3)  (1) 2025.07.05