연금저축 · 리밸런싱 · 실측 재현 페이지

연금저축 반반 계좌,
매달 한 번 다시 맞추기

연금저축에 주식 ETF 와 채권 ETF 를 반반으로 담아 두셨다면, 그 반반은 시간이 지나면 저절로 무너집니다. 2009-08-03 ~ 2026-09-23(17.1년) 동안 KODEX 200 반 · KODEX 국고채3년 반으로 시작한 1,000만원을 그냥 둔 계좌, 매달 한 번 다시 반반으로 맞춘 계좌, 1년에 한 번 맞춘 계좌 열두 개(1~12월)로 나눠 돌렸습니다.

이 페이지는 연금저축, 일주일에 한 번 보고 통째로 옮기기(무엇을 담을지)와 다른 질문 — 이미 반반으로 담아 둔 계좌를 얼마나 자주 되돌리나 — 를 다룹니다. 섞으라고 권하는 페이지가 아닙니다.

86.5%그냥 둔 계좌의 올해 6월 22일(폭락 직전) 주식 비중 — 시작은 50%
648만원폭락 직전 잔고를 1,000만원으로 치면 7월 30일에 — 그냥 둔 계좌
784만원같은 셈으로 — 매달 한 번 반반으로 맞춘 계좌

오늘 드리는 것 — 설정값 그대로

항목값
주식 ETFKODEX 200 (069500) — 코스피200 지수
채권 ETFKODEX 국고채3년 (114260)
목표 비중반반
언제매달 날짜 하나를 정해 둔다 — 계산은 첫 거래일. 1~15번째 거래일 전부 거의 같다(아래 표)
무엇을그날 종가로 비중을 보고, 늘어난 쪽을 팔아 줄어든 쪽을 사서 다음 거래일에 반반으로
비용 가정옮긴 금액에만 편도 0.1% (파는 쪽·사는 쪽 둘 다) — 실제 ETF 수수료보다 넉넉하게

세 가지 계좌 · 17.1년 · 1,000만원

계좌끝에 남은 돈가장 크게 줄어든 폭끝 주식 비중되돌린 횟수수수료 합
그냥 둔다4,452만원-35.24%82.9%0번0원
1년에 한 번 (1월)3,904만원-28.37%63.7%17번26,802원
매달 한 번 (첫 거래일) — 이 편3,684만원-21.64%52.2%205번78,149원
매일3,662만원-22.15%50.0%4187번340,386원

가장 크게 줄어든 폭은 네 계좌 모두 2026-06-22 → 2026-07-30 이었습니다(KODEX 200 -40.8%).

올여름 폭락 — 폭락 직전 잔고를 1,000만원으로 치면

KODEX 200 의 2026년 최고 종가일(2026-06-22) → 그 뒤 최저 종가일(2026-07-30). 계좌마다 폭락 직전 잔고가 다르므로 1,000만원으로 맞춰 견줬습니다. 원금 1,000만원이 줄었다는 뜻이 아닙니다 — 실제 폭락 직전 잔고는 그냥 둔 계좌 5,587만원, 매달 맞춘 계좌 4,168만원였고, 폭락 바닥에서도 그냥 둔 계좌에 돈이 더 남아 있었습니다(3,618만원 대 3,266만원).

계좌그날 주식 비중1,000만원이 7월 30일에
그냥 둔다86.5%648만원
매달 한 번51.1%784만원
매일50.0%778만원

되돌리기 기록 — 매달 한 번 계좌의 2026년

1,000만원으로 시작한 계좌의 실제 체결. 6월 2일에 늘어난 주식을 덜어 냈고, 폭락 한가운데인 7월 2일에는 줄어든 주식을 다시 샀습니다. 그 뒤로도 주식은 7월 30일까지 더 떨어졌습니다 — 사고판 때가 좋았던 것이 아니라 비중이 반반으로 지켜진 것입니다.

체결일체결 전 주식 비중한 일옮긴 금액
2026-01-0553.7%주식 ETF 팔고 채권 ETF 삼991,280원
2026-02-0354.7%주식 ETF 팔고 채권 ETF 삼1,395,440원
2026-03-0449.2%채권 ETF 팔고 주식 ETF 삼241,583원
2026-04-0250.8%주식 ETF 팔고 채권 ETF 삼249,686원
2026-05-0659.3%주식 ETF 팔고 채권 ETF 삼3,352,459원
2026-06-0255.6%주식 ETF 팔고 채권 ETF 삼2,281,542원
2026-07-0246.5%채권 ETF 팔고 주식 ETF 삼1,337,768원
2026-08-0444.8%채권 ETF 팔고 주식 ETF 삼1,814,234원
2026-09-0250.8%주식 ETF 팔고 채권 ETF 삼292,243원

반론 ① 1년에 한 번이면 되지 않나

되돌리는 달만 1~12월로 바꿔 열두 계좌를 돌렸습니다. 같은 폭락에서 결과가 8월 681만원 ~ 6월 793만원 로 갈렸습니다. 6월 계좌는 폭락 직전(6월 2일)에 맞췄을 뿐입니다. 한편 17년 끝 금액은 1년에 한 번 계좌 열두 개 모두가 매달 계좌보다 많았습니다.

맞추는 달6월 22일 주식 비중1,000만원이 7월 30일에17년 가장 크게 줄어든 폭17년 끝 금액
1월69.8%716만원-28.37%3,904만원
2월65.6%733만원-26.66%3,918만원
3월66.4%730만원-26.97%3,875만원
4월65.6%733만원-26.66%4,005만원
5월56.7%770만원-23.01%4,250만원
6월 (가장 얕음)51.1%793만원-20.71%4,423만원
7월78.6%743만원-25.68%4,130만원
8월 (가장 깊음)78.3%681만원-31.86%3,878만원
9월78.1%682만원-31.82%3,855만원
10월75.5%693만원-30.75%3,858만원
11월72.4%706만원-29.43%3,818만원
12월72.8%704만원-29.61%3,818만원
매달 한 번 (비교)51.1%784만원-21.64%3,684만원

반론 ② 그건 올해 폭락 한 번 때문 아닌가

맞습니다. 2025년 말까지만 잘라 같은 표를 다시 재면 갈림이 훨씬 작습니다 — 달마다 결과가 갈린 것은 반년에 주식이 두 배 넘게 오른 뒤 무너진 올해가 만든 것입니다.

2025-12-31 까지가장 크게 줄어든 폭
그냥 둔다-23.86%
1년에 한 번 (열두 달)-20.95% ~ -19.93%
매달 한 번 (1~15번째 거래일)-20.68% ~ -20.43%

매달 몇 번째 거래일이든 — 1~15번째 전부

맞추는 날가장 크게 줄어든 폭끝 금액되돌린 횟수수수료 합
1번째 거래일-21.64%3,684만원205번78,149원
2번째 거래일-21.39%3,668만원206번75,356원
3번째 거래일-21.58%3,651만원206번74,411원
4번째 거래일-22.22%3,644만원206번73,340원
5번째 거래일-22.19%3,634만원206번76,483원
6번째 거래일-22.39%3,670만원206번76,181원
7번째 거래일-22.27%3,616만원206번70,042원
8번째 거래일-22.39%3,623만원206번72,263원
9번째 거래일-22.08%3,628만원206번74,049원
10번째 거래일-21.89%3,640만원206번77,509원
11번째 거래일-21.84%3,665만원205번79,985원
12번째 거래일-21.53%3,734만원205번84,186원
13번째 거래일-21.61%3,776만원205번86,107원
14번째 거래일-21.55%3,763만원205번87,253원
15번째 거래일-22.22%3,758만원205번85,102원

한계 — 먼저 읽어 주세요

받아가기

돌리는 법: zip 을 풀고 pip install numpy pandas 뒤 python run_banban.py.

그대로 돌린 출력

① 판 2009-08-03 ~ 2026-09-23 · 17.1년 · 4191거래일 · 원금 10,000,000원
   종목: KODEX 200(069500) 반 + KODEX 국고채3년(114260) 반 · 편도 0.1% · 체결은 다음 거래일

② 세 가지 계좌
   안함                 끝   44,517,210원 · 연  9.10% · 최대낙폭 -35.24% (2026-06-22→2026-07-30, 55,873,011→36,183,353원) · 끝 주식비중 82.9% · 되돌린 0번 · 수수료 0원
   1년에한번_1월           끝   39,042,312원 · 연  8.27% · 최대낙폭 -28.37% (2026-06-22→2026-07-30, 46,649,984→33,417,236원) · 끝 주식비중 63.7% · 되돌린 17번 · 수수료 26,802원
   한달에한번_첫거래일         끝   36,840,050원 · 연  7.91% · 최대낙폭 -21.64% (2026-06-22→2026-07-30, 41,683,887→32,662,212원) · 끝 주식비중 52.2% · 되돌린 205번 · 수수료 78,149원
   매일                 끝   36,623,214원 · 연  7.87% · 최대낙폭 -22.15% (2026-06-22→2026-07-30, 40,981,972→31,902,978원) · 끝 주식비중 50.0% · 되돌린 4187번 · 수수료 340,386원

③ 1년에 한 번 — 되돌리는 달만 1~12월로 바꿔 본다
    1월  끝   39,042,312원 · 최대낙폭 -28.37% (2026-06-22→2026-07-30)
    2월  끝   39,180,886원 · 최대낙폭 -26.66% (2026-06-22→2026-07-30)
    3월  끝   38,751,396원 · 최대낙폭 -26.97% (2026-06-22→2026-07-30)
    4월  끝   40,049,683원 · 최대낙폭 -26.66% (2026-06-22→2026-07-30)
    5월  끝   42,503,354원 · 최대낙폭 -23.01% (2026-06-22→2026-07-30)
    6월  끝   44,229,965원 · 최대낙폭 -20.71% (2026-06-22→2026-07-30)
    7월  끝   41,300,645원 · 최대낙폭 -25.68% (2026-06-22→2026-07-30)
    8월  끝   38,784,981원 · 최대낙폭 -31.86% (2026-06-22→2026-07-30)
    9월  끝   38,548,881원 · 최대낙폭 -31.82% (2026-06-22→2026-07-30)
   10월  끝   38,578,653원 · 최대낙폭 -30.75% (2026-06-22→2026-07-30)
   11월  끝   38,176,035원 · 최대낙폭 -29.43% (2026-06-22→2026-07-30)
   12월  끝   38,179,920원 · 최대낙폭 -29.61% (2026-06-22→2026-07-30)
   → 가장 깊은 달 8월 -31.86% · 가장 얕은 달 6월 -20.71% · 폭 11.15
   → 끝금액 범위 38,176,035 ~ 44,229,965원

④ 한 달에 한 번 — 그 달 몇 번째 거래일에 되돌리나만 바꿔 본다(1~15번째)
    1번째  끝   36,840,050원 · 최대낙폭 -21.64% · 되돌린 205번 · 수수료 78,149원
    2번째  끝   36,676,922원 · 최대낙폭 -21.39% · 되돌린 206번 · 수수료 75,356원
    3번째  끝   36,507,464원 · 최대낙폭 -21.58% · 되돌린 206번 · 수수료 74,411원
    4번째  끝   36,438,597원 · 최대낙폭 -22.22% · 되돌린 206번 · 수수료 73,340원
    5번째  끝   36,339,269원 · 최대낙폭 -22.19% · 되돌린 206번 · 수수료 76,483원
    6번째  끝   36,703,636원 · 최대낙폭 -22.39% · 되돌린 206번 · 수수료 76,181원
    7번째  끝   36,159,442원 · 최대낙폭 -22.27% · 되돌린 206번 · 수수료 70,042원
    8번째  끝   36,229,620원 · 최대낙폭 -22.39% · 되돌린 206번 · 수수료 72,263원
    9번째  끝   36,277,887원 · 최대낙폭 -22.08% · 되돌린 206번 · 수수료 74,049원
   10번째  끝   36,401,910원 · 최대낙폭 -21.89% · 되돌린 206번 · 수수료 77,509원
   11번째  끝   36,645,915원 · 최대낙폭 -21.84% · 되돌린 205번 · 수수료 79,985원
   12번째  끝   37,343,719원 · 최대낙폭 -21.53% · 되돌린 205번 · 수수료 84,186원
   13번째  끝   37,758,385원 · 최대낙폭 -21.61% · 되돌린 205번 · 수수료 86,107원
   14번째  끝   37,633,250원 · 최대낙폭 -21.55% · 되돌린 205번 · 수수료 87,253원
   15번째  끝   37,579,843원 · 최대낙폭 -22.22% · 되돌린 205번 · 수수료 85,102원
   → 최대낙폭 범위 -22.39 ~ -21.39% · 폭 1.00 · 끝금액 36,159,442 ~ 37,758,385원

⑤ 올여름 폭락 — KODEX 200 의 2026년 최고 종가일 → 그 뒤 최저 종가일
   2026-06-22 → 2026-07-30 · KODEX 200 -40.8%
   안함                 그날 주식  86.5% · 그날 1,000만원이던 계좌가 → 6,480,000원
   1년에한번_1월           그날 주식  69.8% · 그날 1,000만원이던 계좌가 → 7,160,000원
   1년에한번_2월           그날 주식  65.6% · 그날 1,000만원이던 계좌가 → 7,330,000원
   1년에한번_3월           그날 주식  66.4% · 그날 1,000만원이던 계좌가 → 7,300,000원
   1년에한번_4월           그날 주식  65.6% · 그날 1,000만원이던 계좌가 → 7,330,000원
   1년에한번_5월           그날 주식  56.7% · 그날 1,000만원이던 계좌가 → 7,700,000원
   1년에한번_6월           그날 주식  51.1% · 그날 1,000만원이던 계좌가 → 7,930,000원
   1년에한번_7월           그날 주식  78.6% · 그날 1,000만원이던 계좌가 → 7,430,000원
   1년에한번_8월           그날 주식  78.3% · 그날 1,000만원이던 계좌가 → 6,810,000원
   1년에한번_9월           그날 주식  78.1% · 그날 1,000만원이던 계좌가 → 6,820,000원
   1년에한번_10월          그날 주식  75.5% · 그날 1,000만원이던 계좌가 → 6,930,000원
   1년에한번_11월          그날 주식  72.4% · 그날 1,000만원이던 계좌가 → 7,060,000원
   1년에한번_12월          그날 주식  72.8% · 그날 1,000만원이던 계좌가 → 7,040,000원
   한달에한번_첫거래일         그날 주식  51.1% · 그날 1,000만원이던 계좌가 → 7,840,000원
   매일                 그날 주식  50.0% · 그날 1,000만원이던 계좌가 → 7,780,000원

⑥ 올해를 빼면? — 2025-12-31 까지만 잘라 같은 표를 다시 잰다
   안함 최대낙폭 -23.86% (2022-09-30) · 끝 주식비중 72.2%
   1년에 한 번 12달: -20.95 ~ -19.93% (폭 1.02)
   한 달에 한 번 15날: -20.68 ~ -20.43% (폭 0.25)

⑦ 되돌리기 한 번 — 한 달에 한 번(첫 거래일) 계좌의 2026년 체결 기록
   2026-01-05  체결 전 주식  14,405,859 · 채권  12,423,299 (주식 53.7%) → 주식 팔고 채권 삼 991,280원 · 수수료 1,983원
   2026-02-03  체결 전 주식  16,163,191 · 채권  13,372,312 (주식 54.7%) → 주식 팔고 채권 삼 1,395,440원 · 수수료 2,791원
   2026-03-04  체결 전 주식  14,315,576 · 채권  14,798,741 (주식 49.2%) → 주식 사고 채권 팜 241,583원 · 수수료 483원
   2026-04-02  체결 전 주식  14,996,117 · 채권  14,496,745 (주식 50.8%) → 주식 팔고 채권 삼 249,686원 · 수수료 499원
   2026-05-06  체결 전 주식  21,457,099 · 채권  14,752,182 (주식 59.3%) → 주식 팔고 채권 삼 3,352,459원 · 수수료 6,705원
   2026-06-02  체결 전 주식  22,624,612 · 채권  18,061,528 (주식 55.6%) → 주식 팔고 채권 삼 2,281,542원 · 수수료 4,563원
   2026-07-02  체결 전 주식  17,748,174 · 채권  20,423,710 (주식 46.5%) → 주식 사고 채권 팜 1,337,768원 · 수수료 2,676원
   2026-08-04  체결 전 주식  15,532,071 · 채권  19,160,540 (주식 44.8%) → 주식 사고 채권 팜 1,814,234원 · 수수료 3,628원
   2026-09-02  체결 전 주식  17,892,503 · 채권  17,308,018 (주식 50.8%) → 주식 팔고 채권 삼 292,243원 · 수수료 584원

→ 결과_banban.json
→ 수치표_banban.json

코드 전문

# -*- coding: utf-8 -*-
"""260927 연금저축 반반 계좌 — 되돌리는 주기 실측 (공개 코드).

계좌: KODEX 200(069500) 반 + KODEX 국고채3년(114260) 반으로 시작하는 1,000만원.
팔:  안함 · 1년에 한 번(1~12월 각각, 그 달 첫 거래일) · 한 달에 한 번(그 달 k번째 거래일, k=1..15) · 매일.
규약: 신호는 점검일 종가, 체결은 다음 거래일 종가(한 봉 지연) · 수수료는 **실제로 옮긴 금액에만**
      편도 0.1%(파는 쪽·사는 쪽 둘 다) · 가격은 야후 수정주가(분배금 재투자). 단 국고채3년(114260)은 야후 분배 기록이 2018-12 에서 끊겨 그 뒤 분배금(예: 2025-12 807원)이 빠져 있다 — 채권을 더 든 계좌에 불리한 쪽이다.
기준일 ASOF 고정 — 이 날짜까지의 봉만 쓴다(영상과 같은 숫자가 나오게).

실행: python run_banban.py          (같은 폴더의 data_banban/ 을 읽는다)
데이터: 야후 파이낸스 수정주가(분배금 재투자)를 ASOF 에서 잘라 얼린 것이다. 수정주가는 새 분배금이
      나올 때마다 과거 값이 조금씩 바뀌므로, 영상과 같은 숫자를 내려고 파일째 함께 싣는다.
"""
import argparse
import json
import os

import numpy as np
import pandas as pd

ASOF = "2026-09-23"
START = "2009-08-01"
원금 = 10_000_000
편도 = 0.001
위험, 안전 = "069500_KS", "114260_KS"

HERE = os.path.dirname(os.path.abspath(__file__))
기본폴더 = os.path.join(HERE, "data_banban")   # 함께 받은 zip 을 풀면 이 폴더가 생긴다


def 읽기(폴더, sym):
    d = pd.read_csv(os.path.join(폴더, sym + "_1d.csv"), parse_dates=["date"])
    d = d[["date", "close"]].dropna()
    d = d[(d["close"] > 0) & (d["date"] <= pd.Timestamp(ASOF))]
    return d.drop_duplicates("date").sort_values("date").reset_index(drop=True)


def 판(폴더):
    a, b = 읽기(폴더, 위험), 읽기(폴더, 안전)
    m = a.merge(b, on="date", suffixes=("_r", "_s"))       # 두 종목이 모두 거래된 날만
    m = m[m["date"] >= pd.Timestamp(START)].reset_index(drop=True)
    return m


def 점검일(dates, 방식, 인자=None):
    """점검(신호)이 켜지는 날 = True. 체결은 그다음 거래일."""
    n = len(dates)
    on = np.zeros(n, dtype=bool)
    if 방식 == "안함":
        return on
    if 방식 == "매일":
        on[1:] = True
        return on
    ym = dates.dt.year.values * 100 + dates.dt.month.values
    # 그 달에서 몇 번째 거래일인가 (1부터)
    k = np.ones(n, dtype=int)
    for i in range(1, n):
        k[i] = k[i - 1] + 1 if ym[i] == ym[i - 1] else 1
    if 방식 == "매월":                       # 인자 = 그 달 몇 번째 거래일
        on = k == 인자
    elif 방식 == "연1회":                    # 인자 = 몇 월 (그 달 첫 거래일)
        on = (k == 1) & (dates.dt.month.values == 인자)
    on[0] = False
    return on


def 돌리기(m, 방식, 인자=None, w0=0.5, fee=편도):
    r = m["close_r"].values
    s = m["close_s"].values
    n = len(r)
    on = 점검일(m["date"], 방식, 인자)
    r_amt, s_amt = 원금 * w0, 원금 * (1 - w0)
    eq = np.empty(n)
    wp = np.empty(n)
    eq[0], wp[0] = 원금, w0
    횟수, 수수료합, 옮긴합 = 0, 0.0, 0.0
    대기 = False
    for i in range(1, n):
        r_amt *= r[i] / r[i - 1]
        s_amt *= s[i] / s[i - 1]
        if 대기:
            tot = r_amt + s_amt
            d = abs(tot * w0 - r_amt)
            if d > 0:
                c = d * fee * 2.0
                tot -= c
                수수료합 += c
                옮긴합 += d
                횟수 += 1
                r_amt, s_amt = tot * w0, tot * (1 - w0)
            대기 = False
        tot = r_amt + s_amt
        eq[i] = tot
        wp[i] = r_amt / tot
        대기 = bool(on[i])
    return eq, wp, 횟수, 수수료합, 옮긴합


def 요약(m, eq, wp, 횟수, 수수료합, 옮긴합):
    dates = m["date"]
    yrs = (dates.iloc[-1] - dates.iloc[0]).days / 365.25
    peak = np.maximum.accumulate(eq)
    dd = eq / peak - 1
    j = int(dd.argmin())
    i = int(np.argmax(eq[: j + 1]))
    return {
        "끝금액": int(round(eq[-1])),
        "연수익률": round(((eq[-1] / eq[0]) ** (1 / yrs) - 1) * 100, 2),
        "최대낙폭": round(float(dd.min()) * 100, 2),
        "낙폭_고점일": str(dates.iloc[i].date()),
        "낙폭_바닥일": str(dates.iloc[j].date()),
        "낙폭_고점잔고": int(round(eq[i])),
        "낙폭_바닥잔고": int(round(eq[j])),
        "끝주식비중": round(float(wp[-1]) * 100, 1),
        "주식비중_최대": round(float(wp.max()) * 100, 1),
        "되돌린횟수": 횟수,
        "수수료합": int(round(수수료합)),
        "옮긴금액합": int(round(옮긴합)),
    }


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--csv-dir", default=기본폴더)
    ap.add_argument("--json", default=os.path.join(HERE, "결과_banban.json"))
    a = ap.parse_args()
    m = 판(a.csv_dir)
    yrs = (m["date"].iloc[-1] - m["date"].iloc[0]).days / 365.25
    out = {"구간": [str(m["date"].iloc[0].date()), str(m["date"].iloc[-1].date())],
           "연수": round(yrs, 1), "봉": len(m), "원금": 원금, "편도수수료": 편도, "팔": {}}

    print(f"① 판 {out['구간'][0]} ~ {out['구간'][1]} · {out['연수']}년 · {len(m)}거래일 · 원금 {원금:,}원")
    print("   종목: KODEX 200(069500) 반 + KODEX 국고채3년(114260) 반 · 편도 0.1% · 체결은 다음 거래일\n")

    def 기록(이름, 방식, 인자=None):
        g = 요약(m, *돌리기(m, 방식, 인자))
        out["팔"][이름] = g
        return g

    print("② 세 가지 계좌")
    for 이름, 방식, 인자 in [("안함", "안함", None), ("1년에한번_1월", "연1회", 1),
                           ("한달에한번_첫거래일", "매월", 1), ("매일", "매일", None)]:
        g = 기록(이름, 방식, 인자)
        print(f"   {이름:<18} 끝 {g['끝금액']:>12,}원 · 연 {g['연수익률']:5.2f}% · 최대낙폭 {g['최대낙폭']:6.2f}% "
              f"({g['낙폭_고점일']}→{g['낙폭_바닥일']}, {g['낙폭_고점잔고']:,}→{g['낙폭_바닥잔고']:,}원) · "
              f"끝 주식비중 {g['끝주식비중']}% · 되돌린 {g['되돌린횟수']}번 · 수수료 {g['수수료합']:,}원")

    print("\n③ 1년에 한 번 — 되돌리는 달만 1~12월로 바꿔 본다")
    연 = {}
    for 달 in range(1, 13):
        g = 기록(f"1년에한번_{달}월", "연1회", 달)
        연[달] = g
        print(f"   {달:>2}월  끝 {g['끝금액']:>12,}원 · 최대낙폭 {g['최대낙폭']:6.2f}% ({g['낙폭_고점일']}→{g['낙폭_바닥일']})")
    lo = min(연, key=lambda d: 연[d]["최대낙폭"])
    hi = max(연, key=lambda d: 연[d]["최대낙폭"])
    print(f"   → 가장 깊은 달 {lo}월 {연[lo]['최대낙폭']}% · 가장 얕은 달 {hi}월 {연[hi]['최대낙폭']}% · "
          f"폭 {연[hi]['최대낙폭'] - 연[lo]['최대낙폭']:.2f}")
    끝s = [연[d]["끝금액"] for d in 연]
    print(f"   → 끝금액 범위 {min(끝s):,} ~ {max(끝s):,}원")

    print("\n④ 한 달에 한 번 — 그 달 몇 번째 거래일에 되돌리나만 바꿔 본다(1~15번째)")
    월 = {}
    for k in range(1, 16):
        g = 기록(f"한달에한번_{k}번째", "매월", k)
        월[k] = g
        print(f"   {k:>2}번째  끝 {g['끝금액']:>12,}원 · 최대낙폭 {g['최대낙폭']:6.2f}% · 되돌린 {g['되돌린횟수']}번 · 수수료 {g['수수료합']:,}원")
    mdds = [월[k]["최대낙폭"] for k in 월]
    끝m = [월[k]["끝금액"] for k in 월]
    print(f"   → 최대낙폭 범위 {min(mdds)} ~ {max(mdds)}% · 폭 {max(mdds) - min(mdds):.2f} · 끝금액 {min(끝m):,} ~ {max(끝m):,}원")

    print("\n⑤ 올여름 폭락 — KODEX 200 의 2026년 최고 종가일 → 그 뒤 최저 종가일")
    r = m["close_r"].values
    y26 = np.where(m["date"].dt.year.values == 2026)[0]
    p = int(y26[np.argmax(r[y26])])
    t = int(p + np.argmin(r[p:]))
    print(f"   {m['date'].iloc[p].date()} → {m['date'].iloc[t].date()} · KODEX 200 {r[t] / r[p] * 100 - 100:.1f}%")
    폭락 = {"고점일": str(m["date"].iloc[p].date()), "바닥일": str(m["date"].iloc[t].date()),
          "KODEX200": round(r[t] / r[p] * 100 - 100, 1), "팔": {}}
    for 이름, 방식, 인자 in ([("안함", "안함", None)] + [(f"1년에한번_{d}월", "연1회", d) for d in range(1, 13)]
                          + [("한달에한번_첫거래일", "매월", 1), ("매일", "매일", None)]):
        eq, wp, *_ = 돌리기(m, 방식, 인자)
        g = {"고점날_주식비중": round(float(wp[p]) * 100, 1),
             "천만원이": int(round(원금 * eq[t] / eq[p], -4))}
        폭락["팔"][이름] = g
        print(f"   {이름:<18} 그날 주식 {g['고점날_주식비중']:5.1f}% · 그날 1,000만원이던 계좌가 → {g['천만원이']:,}원")
    out["폭락"] = 폭락

    print("\n⑥ 올해를 빼면? — 2025-12-31 까지만 잘라 같은 표를 다시 잰다")
    m25 = m[m["date"] <= pd.Timestamp("2025-12-31")].reset_index(drop=True)
    앞 = {}
    for 이름, 방식, 인자 in ([("안함", "안함", None)] + [(f"1년에한번_{d}월", "연1회", d) for d in range(1, 13)]
                          + [(f"한달에한번_{k}번째", "매월", k) for k in range(1, 16)]):
        앞[이름] = 요약(m25, *돌리기(m25, 방식, 인자))
    연25 = [앞[f"1년에한번_{d}월"]["최대낙폭"] for d in range(1, 13)]
    월25 = [앞[f"한달에한번_{k}번째"]["최대낙폭"] for k in range(1, 16)]
    print(f"   안함 최대낙폭 {앞['안함']['최대낙폭']}% ({앞['안함']['낙폭_바닥일']}) · 끝 주식비중 {앞['안함']['끝주식비중']}%")
    print(f"   1년에 한 번 12달: {min(연25)} ~ {max(연25)}% (폭 {max(연25) - min(연25):.2f})")
    print(f"   한 달에 한 번 15날: {min(월25)} ~ {max(월25)}% (폭 {max(월25) - min(월25):.2f})")
    out["2025까지"] = {"안함": 앞["안함"], "1년에한번_낙폭범위": [min(연25), max(연25)],
                     "한달에한번_낙폭범위": [min(월25), max(월25)]}

    print("\n⑦ 되돌리기 한 번 — 한 달에 한 번(첫 거래일) 계좌의 2026년 체결 기록")
    r_, s_ = m["close_r"].values, m["close_s"].values
    on = 점검일(m["date"], "매월", 1)
    r_amt, s_amt = 원금 * 0.5, 원금 * 0.5
    기록들 = []
    대기 = False
    for i in range(1, len(m)):
        r_amt *= r_[i] / r_[i - 1]
        s_amt *= s_[i] / s_[i - 1]
        if 대기:
            tot = r_amt + s_amt
            d = tot * 0.5 - r_amt                  # + 면 주식을 산다 · − 면 판다
            c = abs(d) * 편도 * 2.0
            if m["date"].iloc[i].year == 2026:
                기록들.append({"체결일": str(m["date"].iloc[i].date()), "체결전_주식": int(round(r_amt)),
                            "체결전_채권": int(round(s_amt)), "주식_사고판금액": int(round(d)),
                            "체결전_주식비중": round(r_amt / tot * 100, 1), "수수료": int(round(c))})
            tot -= c
            r_amt, s_amt = tot * 0.5, tot * 0.5
            대기 = False
        대기 = bool(on[i])
    for g in 기록들:
        방향 = "주식 팔고 채권 삼" if g["주식_사고판금액"] < 0 else "주식 사고 채권 팜"
        print(f"   {g['체결일']}  체결 전 주식 {g['체결전_주식']:>11,} · 채권 {g['체결전_채권']:>11,} "
              f"(주식 {g['체결전_주식비중']}%) → {방향} {abs(g['주식_사고판금액']):,}원 · 수수료 {g['수수료']:,}원")
    out["되돌리기_2026"] = 기록들

    out["요약"] = {
        "1년에한번_가장깊은달": lo, "1년에한번_가장얕은달": hi,
        "1년에한번_낙폭범위": [연[lo]["최대낙폭"], 연[hi]["최대낙폭"]],
        "한달에한번_낙폭범위": [min(mdds), max(mdds)],
        "한달에한번_끝금액범위": [min(끝m), max(끝m)],
        "1년에한번_끝금액범위": [min(끝s), max(끝s)],
    }
    with open(a.json, "w", encoding="utf-8") as f:
        json.dump(out, f, ensure_ascii=False, indent=1)
    print(f"\n→ {os.path.basename(a.json)}")

    # 수치표 — 대본·화면에 들어가는 숫자는 전부 여기서 나온다(손으로 적지 않는다).
    만 = lambda v: int(round(v / 10_000))                      # noqa: E731
    폭 = out["폭락"]["팔"]
    표 = {
        "설정": {"주식ETF": "KODEX 200 (069500)", "채권ETF": "KODEX 국고채3년 (114260)",
               "목표비중_%": 50, "편도수수료_%": 0.1, "원금_만원": 만(원금),
               "체결": "점검일 종가 신호 → 다음 거래일 종가", "매달_몇번째거래일_잰범위": [1, 15]},
        "구간": {"시작": out["구간"][0], "끝": out["구간"][1], "연수": out["연수"], "거래일": out["봉"]},
        "폭락": {"고점일": out["폭락"]["고점일"], "바닥일": out["폭락"]["바닥일"],
               "KODEX200_하락_%": abs(out["폭락"]["KODEX200"]),
               "그냥둠_고점날_주식비중_%": 폭["안함"]["고점날_주식비중"],
               "매달_고점날_주식비중_%": 폭["한달에한번_첫거래일"]["고점날_주식비중"],
               "1월_고점날_주식비중_%": 폭["1년에한번_1월"]["고점날_주식비중"],
               "그냥둠_천만원이_만원": 만(폭["안함"]["천만원이"]),
               "매달_천만원이_만원": 만(폭["한달에한번_첫거래일"]["천만원이"]),
               "매일_천만원이_만원": 만(폭["매일"]["천만원이"]),
               "1년에한번_달별_천만원이_만원": {f"{d}월": 만(폭[f"1년에한번_{d}월"]["천만원이"])
                                         for d in range(1, 13)}},
        "끝": {"그냥둠_끝금액_만원": 만(out["팔"]["안함"]["끝금액"]),
              "매달_끝금액_만원": 만(out["팔"]["한달에한번_첫거래일"]["끝금액"]),
              "그냥둠_끝주식비중_%": out["팔"]["안함"]["끝주식비중"],
              "매달_수수료합_원": out["팔"]["한달에한번_첫거래일"]["수수료합"],
              "매달_수수료합_만원_상한": int(np.ceil(out["팔"]["한달에한번_첫거래일"]["수수료합"] / 10_000)),
              "매달_되돌린횟수": out["팔"]["한달에한번_첫거래일"]["되돌린횟수"]},
        "매달_몇번째거래일": {"최대낙폭_범위_%": out["요약"]["한달에한번_낙폭범위"]},
        "2025까지": out["2025까지"]["1년에한번_낙폭범위"] + out["2025까지"]["한달에한번_낙폭범위"],
        "되돌리기_2026": [{"체결일": g["체결일"], "주식_사고판금액_원": g["주식_사고판금액"],
                        "체결전_주식비중_%": g["체결전_주식비중"]} for g in 기록들],
        "대본용_기타": {"폭락까지_주": 3, "달": list(range(1, 13))},
    }
    with open(os.path.join(HERE, "수치표_banban.json"), "w", encoding="utf-8") as f:
        json.dump(표, f, ensure_ascii=False, indent=1)
    print("→ 수치표_banban.json")


if __name__ == "__main__":
    main()

알고랩 Strategy Lab — 전략을 검증하고, 검증한 것만 자동매매로 옮깁니다. 주식이 한창 오를 때 매달 주식을 덜어 내는 일은 사람 손으로는 자꾸 미뤄집니다. 알고랩 자동매매 살펴보기 · 연금저축 주 1회 옮기기 · 전략 검증 전체 보기

과거 데이터를 돌려 본 결과이며 미래 수익을 보장하지 않습니다. 투자 판단과 책임은 본인에게 있습니다.