수정주가를 잘못 쓰면 백테스트가 어떻게 망가질까? 액면분할·배당 비교

주가가 하루 만에 98% 하락했다면 대부분의 전략은 큰 손실로 기록합니다. 하지만 그날이 액면분할일이라면 이야기가 달라집니다. 주당 가격은 낮아져도 보유 주식 수가 같은 비율로 늘기 때문에 투자자의 지분 가치가 98% 사라진 것이 아닙니다.

이 구분을 빼먹은 가격 데이터는 액면분할을 폭락으로 만들고, 배당을 제외한 데이터는 실제로 받은 현금 수익을 지웁니다. 전략 규칙이 정확해도 입력 가격이 틀리면 백테스트 전체가 다른 실험이 되는 이유입니다.

먼저 확인할 결론

  • 삼성전자 50대 1 액면분할일을 명목 가격으로 연결하면 일간 수익률이 -98.04%로 계산됩니다.
  • 분할 조정 종가에서는 같은 날 수익률이 -2.08%입니다.
  • 배당까지 포함한 수정주가는 장기 수익률뿐 아니라 이동평균 신호와 낙폭도 바꿨습니다.
  • 가격 열의 정의를 확인하지 않은 백테스트 수익률은 전략 성과로 해석할 수 없습니다.
명목 종가와 분할·배당 조정 가격의 백테스트 비교

액면분할은 손실이 아닙니다

삼성전자는 2018년 1월 50대 1 액면분할을 결의했습니다. 분할 전 1주를 보유했다면 분할 뒤에는 50주가 되고, 이론적인 주당 가격은 50분의 1이 됩니다.

분할 직전 종가 265만원과 분할 뒤 종가 5만1,900원을 그대로 이어 붙이면 수익률 계산은 다음처럼 보입니다.

TEXT
잘못 계산한 수익률 = 51,900 ÷ 2,650,000 - 1 = -98.04%

하지만 보유 수량도 50배가 됐습니다. 분할 전 가격을 50분의 1로 맞춘 5만3,000원과 비교하면 실제 가격 변화는 -2.08%입니다. 기업가치가 50분의 1이 된 것이 아니라 한 주를 더 작은 단위로 나눈 결과입니다.

액면분할일 가격 기준 전일 비교 가격 당일 종가 계산 수익률
명목 종가를 그대로 연결 2,650,000원 51,900원 -98.04%
분할 기준을 과거 가격에 반영 53,000원 51,900원 -2.08%
배당 포함 수정주가 43,071원 42,177원 -2.08%

수정주가는 과거를 예쁘게 보이도록 바꾼 가격이 아닙니다. 주식 수를 바꾸는 분할·병합과 현금이 빠져나가는 배당 전후를 같은 투자 가치 기준으로 비교하기 위한 가격입니다.

종가와 수정주가는 무엇이 다를까?

이 글에서는 세 가지 가격을 구분했습니다.

가격 기준 반영하는 항목 적합한 용도 그대로 쓰면 생기는 문제
당시 명목 종가 없음 과거 주문 가격과 호가 단위 확인 분할·병합을 수익이나 손실로 오인
분할 조정 종가 액면분할·병합 가격 패턴과 분할 전후 연속 비교 배당 수익 누락
배당 포함 수정주가 분할·병합·배당 총수익률과 장기 성과 비교 실제 현금 배당의 세금·재투자 시점은 별도 가정 필요

yfinance.download()auto_adjustactions 옵션을 제공합니다. yfinance 공식 문서에 따르면 auto_adjust=True는 OHLC를 자동 조정하고, actions=True는 배당과 주식분할 정보를 함께 내려받습니다.

어떤 옵션이 무조건 정답인 것은 아닙니다. 과거 주문 가격을 복원하려면 당시 명목 가격이 필요하고, 전략의 경제적 성과를 비교하려면 기업행사를 반영한 연속 가격이나 별도의 현금흐름 처리가 필요합니다. 중요한 것은 열 이름보다 그 열이 무엇을 반영했는지 먼저 확인하는 일입니다.

같은 이동평균 전략을 세 가격으로 돌렸습니다

가격 입력 외의 조건은 모두 같게 고정했습니다. 복잡한 전략을 고른 것이 아니라 데이터 오류가 결과에 미치는 영향만 보기 위해 200일 이동평균 규칙을 사용했습니다.

항목 검증 조건
대상 삼성전자 보통주
기간 2010년 1월 4일~2026년 8월 19일
진입·청산 종가가 200일 이동평균 위면 보유, 아래면 현금
신호 반영 당일 종가로 신호를 확정하고 다음 거래일부터 적용
비용 포지션 변경 시 편도 0.10%
비교 가격 명목 종가, 분할 조정 종가, 배당 포함 수정주가
현금 수익 0%

여기서 명목 종가는 분할 전 주당 가격을 복원한 시계열입니다. 이것을 주식 수량 조정 없이 한 줄의 가격으로 이어 붙이는 경우를 의도적으로 재현했습니다. 국내 수정 종가와 분할 조정 종가는 액면분할 전후 구간에서 같은 값인지 교차 확인했습니다.

잘못된 가격 열은 전략을 거의 전손으로 만들었습니다

가격 기준 누적수익률 CAGR MDD 샤프 진입 횟수 시장 노출
명목 종가 -77.2% -8.5% -98.7% 0.35 51회 60.3%
분할 조정 종가 867.1% 14.6% -44.8% 0.66 55회 61.0%
배당 포함 수정주가 1,011.9% 15.6% -42.8% 0.69 58회 62.7%

명목 종가 백테스트의 -98.7% MDD는 전략이 실제 시장에서 그런 손실을 냈다는 뜻이 아닙니다. 50대 1 분할을 98% 폭락으로 오인한 데이터 오류가 자산곡선에 들어간 결과입니다. 이후 수익이 나더라도 원금이 2% 안팎으로 줄어든 상태에서는 회복이 어렵습니다.

분할 조정 종가와 배당 포함 수정주가의 차이도 작지 않았습니다. 같은 기간 누적수익률은 867.1%1,011.9%, MDD는 -44.8%-42.8%로 갈렸습니다. 배당락을 단순 가격 하락으로 처리할지, 받은 배당을 재투자한 총수익으로 처리할지가 장기 결과에 누적됐기 때문입니다.

매수 후 보유에서도 배당 누락은 쌓입니다

같은 기간 처음부터 끝까지 보유한 단순 비교에서는 분할 조정 종가 수익률이 1,429.7%, 배당 포함 수정주가 수익률이 1,992.2%였습니다. 두 값의 차이는 주가 방향을 맞히는 능력에서 나온 것이 아닙니다. 배당을 투자 수익으로 포함했는지에서 시작했습니다.

다만 수정주가의 총수익은 배당을 즉시 같은 종목에 재투자한다고 보는 계산입니다. 실제 계좌에서는 배당소득세, 지급일, 재투자 단위와 매매비용 때문에 결과가 달라질 수 있습니다. 더 정밀한 검증에서는 배당을 별도 현금흐름으로 받아 지급 가능한 날짜에 재투자해야 합니다.

재현 코드에서 확인할 부분

다음 코드는 auto_adjust=False로 가격 열과 기업행사를 함께 받은 뒤 세 가격 기준을 같은 규칙으로 비교합니다. 차트 생성과 파일 저장은 제외하고 결과 계산에 필요한 부분만 남겼습니다.

설치가 필요하면 먼저 다음 명령을 실행합니다.

TEXT
pip install yfinance pandas numpy
PYTHON
#!/usr/bin/env python3
"""수정주가 기준에 따른 200일 이동평균 백테스트 비교 예제."""

import numpy as np
import pandas as pd
import yfinance as yf


TICKER = "005930.KS"
START_DATE = "2010-01-01"
END_DATE = "2026-08-20"
MOVING_AVERAGE_DAYS = 200
ONE_WAY_COST = 0.001


def reconstruct_nominal_close(price_data):
    nominal_close = price_data["Close"].copy()
    split_events = price_data.loc[price_data["Stock Splits"].gt(0), "Stock Splits"]
    for split_date, split_ratio in split_events.items():
        nominal_close.loc[nominal_close.index < split_date] *= float(split_ratio)
    return nominal_close


def run_backtest(price):
    result = pd.DataFrame({"price": price}).dropna()
    result["moving_average"] = result["price"].rolling(MOVING_AVERAGE_DAYS).mean()
    result["signal"] = result["price"].gt(result["moving_average"])
    result["position"] = result["signal"].shift(1).fillna(False).astype(float)
    result["asset_return"] = result["price"].pct_change().fillna(0.0)
    result["turnover"] = result["position"].diff().abs().fillna(result["position"])
    result["strategy_return"] = result["position"] * result["asset_return"] - result["turnover"] * ONE_WAY_COST
    result["equity"] = (1.0 + result["strategy_return"]).cumprod()
    result["drawdown"] = result["equity"].div(result["equity"].cummax()).sub(1.0)
    return result


def summarize(result):
    elapsed_years = (result.index[-1] - result.index[0]).days / 365.25
    final_equity = result["equity"].iloc[-1]
    daily_return = result["strategy_return"]
    return {
        "누적수익률(%)": (final_equity - 1.0) * 100,
        "CAGR(%)": (final_equity ** (1 / elapsed_years) - 1.0) * 100,
        "MDD(%)": result["drawdown"].min() * 100,
        "샤프": daily_return.mean() / daily_return.std() * np.sqrt(252),
        "진입횟수": result["position"].diff().eq(1).sum(),
        "시장노출(%)": result["position"].mean() * 100,
    }


price_data = yf.download(TICKER, start=START_DATE, end=END_DATE, auto_adjust=False, actions=True, progress=False)
if isinstance(price_data.columns, pd.MultiIndex):
    price_data.columns = price_data.columns.get_level_values(0)
price_data = price_data.dropna(subset=["Close", "Adj Close"])

price_columns = {
    "명목 종가": reconstruct_nominal_close(price_data),
    "분할 조정 종가": price_data["Close"],
    "배당 포함 수정주가": price_data["Adj Close"],
}
comparison = pd.DataFrame({name: summarize(run_backtest(price)) for name, price in price_columns.items()}).T
print(comparison.round(2))

코드에서 가장 중요한 부분은 Stock Splits가 발생한 날짜보다 앞선 명목 가격에 분할 비율을 곱해 당시 주당 가격을 복원하는 과정입니다. 이 시계열은 올바른 백테스트 입력이 아니라, 분할 조정을 하지 않았을 때 어떤 오류가 생기는지 보여주기 위한 비교군입니다.

수정주가를 쓸 때도 남는 함정

1. 거래 가격과 성과 계산 가격을 섞지 않습니다

주문 체결 가능성을 검증할 때는 당시 시가·고가·저가·종가와 거래량이 필요합니다. 총수익률용 수정주가를 실제 주문 가격처럼 사용하면 체결 수량과 비용 계산이 틀어질 수 있습니다. 신호 계산용 가격, 체결 가격, 성과 평가용 수익률을 구분하는 편이 안전합니다.

2. 거래량도 같은 기준으로 맞춥니다

가격만 분할 조정하고 과거 거래량을 그대로 두면 거래대금과 유동성 필터가 왜곡될 수 있습니다. 데이터 제공처가 거래량을 분할 기준으로 함께 조정했는지 확인해야 합니다.

3. 모든 기업행사가 같은 방식은 아닙니다

유상증자, 무상증자, 권리락, 합병, 분할상장과 상장폐지는 단순한 배당 조정만으로 해결되지 않습니다. 조정계수와 종목 연결 기준을 별도로 보관하고, 어떤 사건을 반영했는지 검산해야 합니다.

4. 수정주가라는 이름만 믿지 않습니다

데이터 제공처마다 조정 대상과 적용 시점이 다를 수 있습니다. 분할일 전후 수익률, 배당락일의 가격 변화, 주식 수와 시가총액 연속성을 표본으로 확인해야 합니다.

백테스트 전에 가격 데이터부터 확인합니다

이번 비교에서 확인한 내용은 세 가지입니다.

  1. 액면분할을 반영하지 않은 가격은 존재하지 않는 대규모 손실을 만듭니다.
  2. 배당을 제외하면 장기 총수익률과 전략 성과를 낮게 계산할 수 있습니다.
  3. 신호·체결·성과 평가에 필요한 가격 기준을 각각 명시해야 결과를 재현할 수 있습니다.

백테스트 결과를 믿기 전 확인할 세 가지는 미래참조·생존자 편향·과최적화를 점검합니다. 수수료·세금·슬리피지까지 반영한 백테스트는 가격 데이터가 정리된 뒤 거래비용을 더하는 순서를 설명합니다. 종가 신호와 다음 날 체결의 차이도 함께 보면 입력 가격과 체결 시점이 각각 어떤 오류를 만드는지 구분할 수 있습니다.

이 글은 과거 데이터와 단순 규칙을 이용한 교육용 검증입니다. 특정 종목의 수익 가능성을 예측하거나 매수·매도를 권하지 않습니다.