AlgoLab Blog · 퀀트 · 자산배분 방법론 · 2026

리밸런싱 주기 비교 — 월·분기·밴드 11년 실측

퀀트 · 리밸런싱 규칙 2026-09-08 · 약 8분 읽기 · 알고랩 AlgoLab
한 줄 요약

같은 자산·같은 기간(2015-01-02 ~ 2026-08-31, 2,862거래일)에 여덟 가지 리밸런싱 규칙을 직접 돌린 결과, CAGR은 9.90%~10.41% 한 구간에 전부 들어왔습니다 — 주기를 무엇으로 정하든 수익률은 사실상 안 움직였습니다. 실제로 바뀐 것은 MDD(-22.46% → -19.73%)매매 횟수(0회 → 139회)입니다. 즉 리밸런싱 주기는 수익을 고르는 손잡이가 아니라 낙폭과 손이 가는 횟수를 고르는 손잡이입니다. 부수적으로 확인된 것 하나 — 동일비중 네 자산에서는 5/25 룰±5%p 밴드와 소수점까지 같은 결과였습니다(상대 25%가 6.25%p라 절대 조건이 항상 먼저 걸립니다). 아래에 FinanceDataReader 재현 코드를 그대로 넣었습니다.

목차

  1. 무엇을 어떻게 돌렸나 — 조건 전부 공개
  2. 결과표 8가지 규칙
  3. 발견 ① 수익률로는 아무것도 못 고른다
  4. 발견 ② 짧게 할수록 좋아지지 않는다
  5. 발견 ③ 5/25 룰 = ±5%p 밴드 (동일비중일 때)
  6. 발견 ④ 매매비용은 이 조건에서 결정 요인이 아니었다
  7. 재현 코드 전문
  8. 봇으로 옮길 때 달라지는 것 3가지
  9. 이 실측의 한계
  10. FAQ

1. 무엇을 어떻게 돌렸나 — 조건 전부 공개

조건을 먼저 전부 적습니다. 아래를 그대로 쓰면 이 글의 표가 재현됩니다.

항목
기간2015-01-02 ~ 2026-08-31 (거래일 2,862일, 약 11.66년)
기준일 ASOF"2026-08-31" 고정 — 고정하지 않으면 실행일마다 숫자가 바뀝니다
자산069500 KODEX 200 · 143850 TIGER 미국S&P500선물(H) · 148070 KOSEF 국고채10년 · 132030 KODEX 골드선물(H)
목표 비중네 종목 동일비중 25% (특정 배분의 추천이 아니라 주기 비교용 고정 대조군)
데이터FinanceDataReader 0.9.202fdr.DataReader 종가 · pandas 2.2.3 · numpy 2.0.2
비용 가정리밸런싱 시 매도금액+매수금액 합계에 편도 0.1% (수수료+슬리피지 통합 가정)
세금미반영 — 국내 상장 ETF는 매도 시 증권거래세가 없고 분배금 과세는 계좌 유형에 따라 달라 계산에서 제외

국내주식·미국주식·국채·금으로 성격이 다른 넷을 골랐습니다. 리밸런싱은 서로 다르게 움직이는 자산 사이에서만 차이가 나기 때문입니다.

같은 기간 개별 자산 성적

먼저 재료가 어땠는지부터 봐야 조합의 숫자를 읽을 수 있습니다.

자산CAGRMDD연변동성
069500 KODEX 20015.69%-40.81%24.25%
143850 TIGER 미국S&P500선물(H)10.93%-34.99%17.38%
148070 KOSEF 국고채10년1.20%-20.29%5.43%
132030 KODEX 골드선물(H)8.98%-30.49%16.89%

데이터 성격 확인 — 같은 기간 KODEX 200은 ×5.47, KS200(코스피200 가격지수)은 ×4.38이었습니다. ETF가 가격지수보다 높다는 것은 FinanceDataReader가 주는 값이 분배금이 반영된 계열이라는 뜻입니다. 다만 정확한 조정 방식은 데이터 제공처 기준이므로 절대 수익률을 인용하려면 공식 자료로 확인하십시오. 이 글의 목적은 같은 데이터에 여덟 규칙을 적용한 상대 비교입니다.

2. 결과표 — 8가지 규칙

여덟 가지를 돌렸습니다. 캘린더형 셋(매월말·분기말·연말), 밴드형 넷(±3%p·±5%p·±10%p·5/25 룰), 그리고 대조군인 매수후보유입니다.

규칙CAGRMDD샤프연변동성리밸 횟수누적 매매금액
리밸런싱 안 함(매수후보유)10.41%-22.46%0.83513.24%0회0%
매월말9.96%-19.73%0.94610.94%139회378.7%
분기말10.21%-19.83%0.94411.25%46회216.0%
연말10.11%-19.85%0.88811.95%11회101.1%
밴드 ±3%p9.90%-20.03%0.93111.08%26회187.1%
밴드 ±5%p10.12%-19.89%0.95111.06%12회144.4%
밴드 ±10%p10.21%-20.57%0.92911.46%4회87.6%
5/25 룰10.12%-19.89%0.95111.06%12회144.4%

누적 매매금액 = 11년 동안 발생한 매도금액과 매수금액의 합계를 그 시점 평가액으로 나눈 값의 누적. 378.7%는 "평가액의 3.8배만큼 사고팔았다"는 뜻입니다.

MDD (막대가 짧을수록 낙폭이 작다) · ● 리밸런싱 횟수 안 함매월말분기말연말±3%p±5%p±10%p5/25 -22.46%-19.73%-19.83%-19.85%-20.03%-19.89%-20.57%-19.89% 013946112612412
2015-01-02~2026-08-31 · 4자산 동일비중 · 편도 0.1% · MDD 막대는 -19% 기준 확대 표시

3. 발견 ① 수익률로는 아무것도 못 고른다

여덟 규칙의 CAGR 최댓값과 최솟값 차이가 0.51%p(10.41% - 9.90%)입니다. 11년 넘는 기간에 이 정도면 규칙 선택이 아니라 시작일 하루 차이로도 뒤집힐 폭입니다.

그리고 CAGR 1등은 "리밸런싱 안 함"(10.41%)이었습니다. 이유는 표의 개별 자산 성적에 그대로 나와 있습니다 — KODEX 200이 CAGR 15.69%로 가장 높았고, 리밸런싱은 오른 자산을 팔아 덜 오른 자산을 사는 규칙이라 가장 많이 오른 자산의 비중을 계속 깎았습니다. 리밸런싱이 수익을 만든다는 설명은 이 데이터에서 성립하지 않습니다.

대신 위험 축에서는 방향이 뚜렷했습니다.

읽는 법 — 리밸런싱은 "수익을 늘리는 장치"가 아니라 "포트폴리오가 원래 설계에서 멀어지는 것을 막는 장치"입니다. 안 하면 시간이 갈수록 가장 많이 오른 자산 하나가 계좌를 지배하고, 그 자산의 낙폭이 곧 내 낙폭이 됩니다. 위 표에서 안 한 쪽의 MDD가 유독 큰 이유가 이것입니다.

4. 발견 ② 짧게 할수록 좋아지지 않는다

가장 자주 나오는 오해가 "자주 맞춰 줄수록 원래 비중에 가까우니 더 좋을 것"입니다. 표를 보면 반대에 가깝습니다.

비교매월말분기말차이
리밸런싱 횟수139회46회3.0배
누적 매매금액378.7%216.0%1.75배
CAGR9.96%10.21%분기가 +0.25%p
MDD-19.73%-19.83%0.10%p
샤프0.9460.9440.002

손이 3배 더 가는데 얻은 것은 MDD 0.10%p입니다. 샤프지수는 소수점 셋째 자리에서 갈리는 수준이고 CAGR은 분기 쪽이 높습니다. 이 조건에서 매월말은 비용이 아니라 "덜 오를 자산을 더 자주 사들이는 효과"로 손해를 봤습니다(비용을 0으로 놓고 다시 돌려도 매월 9.99% vs 분기 10.23%로 순서가 그대로입니다 — 6절 참조).

가장 인상적인 자리는 밴드 ±10%p입니다. 11년에 단 4회 리밸런싱했는데 CAGR 10.21%로 분기말과 같고 MDD도 -20.57%로 크게 나쁘지 않습니다. 횟수를 34분의 1로 줄여도(139회 → 4회) 결과가 거의 같다는 뜻입니다.

5. 발견 ③ 5/25 룰 = ±5%p 밴드 (동일비중일 때)

표에서 5/25 룰밴드 ±5%p리밸런싱 12회, CAGR 10.12%, MDD -19.89%, 샤프 0.951까지 소수점 전부 같습니다. 우연이 아니라 산수입니다.

5/25 룰은 "목표 비중에서 절대 5%p 벗어나거나 상대 25% 벗어나면 리밸런싱"이라는 규칙입니다. 그런데 목표가 25%면 —

절대 조건 : |현재비중 - 25%|   >= 5%p     → 20% 또는 30%에서 발동
상대 조건 : |현재비중/25% - 1| >= 25%     → 25% x 0.25 = 6.25%p
                                          → 18.75% 또는 31.25%에서 발동

5%p가 6.25%p보다 항상 먼저 걸립니다. 그래서 상대 조건은 11년 동안 한 번도 단독으로 발동하지 않았습니다. 동일비중 4자산에서 5/25 룰을 쓰는 것은 ±5%p 밴드를 이름만 바꿔 부르는 것과 같습니다.

그럼 5/25 룰은 언제 의미가 있나목표 비중이 20%보다 작은 자산이 있을 때입니다. 갈림길은 목표 x 0.25 = 5%p, 즉 목표 20%입니다. 목표가 5%인 위성 자산이라면 상대 25%는 1.25%p라서 절대 5%p보다 훨씬 먼저 걸립니다. 이 규칙의 원래 취지가 "작은 비중 자산이 두 배가 되어도 절대 밴드에는 안 걸리는 문제"를 막는 것이기 때문입니다. 동일비중 포트폴리오에 5/25를 넣는 것은 코드만 복잡해지고 동작은 같습니다.

6. 발견 ④ 매매비용은 이 조건에서 결정 요인이 아니었다

편도 0.1%를 0%로 바꿔 전부 다시 돌렸습니다.

규칙비용 0.1%비용 0%비용이 깎은 폭
매월말9.96%9.99%0.03%p
분기말10.21%10.23%0.02%p
연말10.11%10.12%0.01%p
밴드 ±5%p10.12%10.14%0.02%p
밴드 ±10%p10.21%10.22%0.01%p

가장 많이 매매한 매월말조차 11년 동안 0.03%p입니다. 비용을 0으로 놓아도 매월말이 분기말을 못 이깁니다(9.99% vs 10.23%). 즉 이 실측에서 매월말이 뒤처진 원인은 수수료가 아니라 리밸런싱 자체의 성격입니다.

이 결론을 그대로 옮기면 안 되는 경우 — ⑴ 개별주식은 매도 시 증권거래세가 붙습니다(국내 상장 ETF는 붙지 않습니다). ⑵ 해외자산 직접 보유는 환전 스프레드가 매번 붙습니다. ⑶ 호가 스프레드가 넓은 종목(거래대금이 작은 ETF·알트코인)은 편도 0.1% 가정이 너무 낙관적입니다. ⑷ 과세 계좌에서는 매도 확정이 세금 시점을 앞당깁니다. 비용 구조가 다르면 본인 조건으로 다시 돌려야 합니다 — 아래 코드의 COST 한 줄만 바꾸면 됩니다.

7. 재현 코드 전문

아래를 그대로 실행하면 위 표가 나옵니다. ASOF를 고정한 이유는 고정하지 않으면 실행하는 날마다 마지막 거래일이 달라져 숫자가 바뀌기 때문입니다. 최신 기준으로 보고 싶으면 ASOF를 오늘 날짜로 바꾸십시오.

# pip install finance-datareader pandas numpy
# 검증 환경: FinanceDataReader 0.9.202 / pandas 2.2.3 / numpy 2.0.2
import FinanceDataReader as fdr
import pandas as pd, numpy as np

ASOF   = "2026-08-31"      # 기준일 고정 — 풀면 실행일마다 숫자가 달라진다
START  = "2015-01-02"
COST   = 0.001             # 편도 0.1% (수수료+슬리피지 통합 가정)
ASSETS = {"069500": "KODEX 200",
          "143850": "TIGER 미국S&P500선물(H)",
          "148070": "KOSEF 국고채10년",
          "132030": "KODEX 골드선물(H)"}
TARGET = pd.Series({k: 0.25 for k in ASSETS})

px  = pd.DataFrame({c: fdr.DataReader(c, START, ASOF)["Close"] for c in ASSETS}).dropna()
ret = px.pct_change().fillna(0)

def run(mode, param=None):
    w, navs, n_rb, turn = TARGET.copy(), [], 0, 0.0
    idx = px.index
    if mode in ("M", "Q", "A"):
        rule  = {"M": "ME", "Q": "QE", "A": "YE"}[mode]
        marks = set(pd.Series(idx, index=idx).resample(rule).last().dropna())
    for i, dt in enumerate(idx):
        w   = w * (1 + ret.loc[dt])          # 자산별 평가액 갱신
        tot = w.sum()
        navs.append(tot)
        wn  = w / tot                        # 현재 비중
        if   mode in ("M", "Q", "A"): do = dt in marks
        elif mode == "BAND":          do = bool((wn - TARGET).abs().max() >= param)
        elif mode == "BAND525":       do = bool(((wn - TARGET).abs() >= 0.05).any()
                                            or ((wn / TARGET - 1).abs() >= 0.25).any())
        else:                         do = False
        if do and i < len(idx) - 1:
            t     = float((TARGET * tot - w).abs().sum()) / tot   # 매도+매수 금액 비율
            turn += t; n_rb += 1
            tot   = tot * (1 - t * COST)
            w     = TARGET * tot
    s    = pd.Series(navs, index=idx); s = s / s.iloc[0]
    yrs  = (idx[-1] - idx[0]).days / 365.25
    r    = s.pct_change().dropna()
    return dict(cagr   = s.iloc[-1] ** (1 / yrs) - 1,
                mdd    = (s / s.cummax() - 1).min(),
                sharpe = r.mean() / r.std() * np.sqrt(252),
                vol    = r.std() * np.sqrt(252),
                n      = n_rb, turn = turn)

for name, mode, p in [("리밸런싱 안 함", "NONE", None), ("매월말", "M", None),
                      ("분기말", "Q", None),           ("연말", "A", None),
                      ("밴드 ±3%p", "BAND", 0.03),     ("밴드 ±5%p", "BAND", 0.05),
                      ("밴드 ±10%p", "BAND", 0.10),    ("5/25 룰", "BAND525", None)]:
    x = run(mode, p)
    print(f"{name:14s} CAGR {x['cagr']*100:6.2f}%  MDD {x['mdd']*100:7.2f}%  "
          f"샤프 {x['sharpe']:.3f}  리밸 {x['n']:4d}회  누적매매 {x['turn']*100:6.1f}%")

샤프지수는 무위험수익률 0 가정(일간 평균 ÷ 표준편차 × √252)입니다. quantstats는 처리 방식이 달라 값이 조금 다르게 나옵니다 — 정의 차이는 샤프지수·MDD 계산 방법에 정리해 두었습니다.

8. 봇으로 옮길 때 달라지는 것 3가지

위 코드는 종가로 판정하고 종가로 체결합니다. 실제 계좌에서는 그럴 수 없습니다. 자동매매로 옮길 때 반드시 다시 설계해야 하는 세 자리가 있습니다.

① 판정 시각과 주문 시각을 분리한다

종가를 확인한 뒤에는 그 가격에 살 수 없습니다. 판정은 종가, 주문은 다음 거래일로 나누는 것이 기본형입니다. 이 시차를 무시하고 짠 코드가 백테스트에서만 좋아 보이는 전형적인 이유이고, 미래참조 편향의 가장 흔한 형태이기도 합니다.

# 판정(장 마감 후) → 주문(다음 거래일 시가 근처)
weights_now = evaluate_positions()                  # 잔고 조회로 현재 비중
if (weights_now - TARGET).abs().max() >= 0.05:      # 밴드 ±5%p
    plan = build_orders(weights_now, TARGET)        # 종목별 매수/매도 수량
    schedule_next_open(plan)                        # 다음 거래일 시가에 집행

② 부분체결을 전제로 짠다

목표 비중을 맞추려고 낸 주문이 절반만 체결되면 비중이 어정쩡하게 남습니다. 백테스트에는 없는 상태입니다. 주문을 낸 것으로 끝내지 말고 실제 체결 수량을 받아 다시 계산해야 합니다.

③ 최소 주문 단위가 밴드보다 클 수 있다

국내주식은 1주 미만을 살 수 없습니다. KODEX 200 1주가 10만원대라면 1,000만원 계좌에서 1주는 이미 1%입니다. 계좌가 작을수록 이 오차가 밴드 폭에 근접하므로, 밴드는 최소 주문 단위 오차보다 충분히 크게 잡아야 합니다.

주문 자체의 종류·체결 방식은 주문 유형(시장가·지정가·조건부)체결과 슬리피지에 정리돼 있습니다. 자산배분 규칙을 봇으로 굴리는 완성형 예시는 리밸런싱봇 페이지에서 볼 수 있습니다.

9. 이 실측의 한계

결과를 그대로 일반화하면 안 되는 지점을 접지 않고 적습니다.

10. FAQ

리밸런싱 주기는 월·분기·연 중 뭐가 제일 낫나요?

수익률로 고를 근거가 없었습니다. CAGR이 9.90~10.41%에 다 들어왔고 바뀐 것은 MDD(-22.46% → -19.73%)와 샤프(0.835 → 0.946)입니다. 과거 결과이며 미래를 보장하지 않습니다.

리밸런싱을 자주 하면 비용 때문에 손해인가요?

비용이 원인이 아니었습니다. 매월말 139회·누적 매매금액 378.7%인데도 비용이 깎은 폭은 0.03%p였고, 비용 0으로 놓아도 매월말(9.99%)이 분기말(10.23%)을 못 이겼습니다. 단 증권거래세·환전 스프레드·넓은 호가가 끼면 달라집니다.

5/25 룰과 ±5%p 밴드는 뭐가 다른가요?

동일비중 네 자산에서는 완전히 같습니다. 목표 25%의 상대 25%가 6.25%p라 절대 5%p가 항상 먼저 걸립니다(12회·10.12%·-19.89%·0.951로 동일). 목표 20% 미만 자산이 있어야 갈립니다.

이 계산을 그대로 재현하려면 무엇이 필요한가요?

FinanceDataReader 0.9.202 · pandas 2.2.3 · numpy 2.0.2, 그리고 ASOF = "2026-08-31" 고정입니다. 7절 코드를 그대로 붙여 넣으면 표의 숫자가 나옵니다. ASOF를 풀면 실행하는 날마다 결과가 달라집니다.

봇에 넣을 때 가장 많이 틀리는 곳은 어디인가요?

판정 시각과 주문 시각을 같게 둔 코드입니다. 종가를 보고 종가에 체결하는 백테스트를 그대로 옮기면 실계좌에서 재현되지 않습니다. 그다음이 부분체결 처리(TTTC0802U·kt10000 이후 체결 확인)와 최소 주문 단위 오차입니다.

마무리

리밸런싱 주기를 정하는 일은 "어느 쪽이 더 버나"를 고르는 문제가 아니었습니다. 11년 실측에서 수익률은 규칙을 무엇으로 바꿔도 0.51%p 안에서 놀았고, 뚜렷하게 갈린 것은 낙폭손이 가는 횟수였습니다. 그렇다면 실무적인 질문은 하나로 좁혀집니다 — "나는 이 규칙을 11년 동안 몇 번이나 지킬 수 있는가." 139회를 놓치지 않고 지킬 자신이 없다면, 같은 결과를 4회로 내는 규칙이 이미 표 안에 있습니다.

고지 — 알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객의 규칙을 코드로 옮기는 도구 제공만 합니다. 이 글은 특정 종목·자산배분·리밸런싱 주기를 추천하지 않고 수익을 보장하지 않습니다. 본문 수치는 2026-09-08에 ASOF=2026-08-31로 직접 계산한 과거 데이터이며 과거 성과는 미래를 보장하지 않습니다. 데이터·라이브러리 버전이 바뀌면 결과가 달라질 수 있으므로 절대 수익률을 인용할 때는 공식 자료로 확인하십시오.

리밸런싱 규칙, 코드로 옮겨 드립니다

판정 시각·부분체결·최소 주문 단위까지 실계좌 기준으로 설계합니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기