# -*- coding: utf-8 -*-
"""알고랩 · 목돈, 한 번에 넣을까 12달 나눠 넣을까 (재현 코드)

영상: 「목돈 생기면 한 번에 넣을까, 12달 나눠 넣을까」 편 (2026-10-08)
필요: python 3.9+ · numpy          실행: python run_lumpsum.py

무엇을 하나
  원금 1(= 1억 원으로 환산해 말함)을 매달 첫 거래일에 시작해
    ① 한 번에: 첫날 전부 산다
    ② N달 나눠: 시작일부터 매달 첫 거래일에 남은 돈을 같은 몫으로 나눠 산다(N=12 가 본편)
  아직 안 산 돈은 이자를 받는다 — S&P500 은 미국 3개월 국채(^IRX 연율, 달력일 일할), 국내·일본은 연 0% / 3% 두 판.
  살 때마다 0.05% 수수료. 종가에 산다. 공매도·빚 없음. 세금·환율·호가 차이 제외.
  5년 뒤 = 시작 후 60번째 달 첫 거래일 평가액. '무서운 순간' = 시작 후 24개월 동안 계좌(주식+기다리는 돈) 최저 평가액.
  가격: data_lumpsum/*.csv 의 close = 야후 수정주가(배당 재투자). 닛케이(^N225)는 가격 지수(배당 없음).
  코스피 대표 ETF(KODEX200): 야후 자료가 2007-02-07 ~ 2009-04-17 통째로 비어 있어, 그 앞은 네이버 종가(분배금 미반영)를
  2009-04-17 에서 이어 붙인 069500_splice_1d.csv 를 쓴다(2007-01-29 부터). 분배금이 빠진 구간은 오래 들고 있는 '한 번에'에 불리하다.
  2026-10-07 에서 얼렸다. 시작 달은 1개월씩 옮기므로 이웃한 결과끼리 5년이 거의 다 겹친다(따로 된 실험이 아니다).
"""
import csv, datetime as dt, os
import numpy as np

HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.join(HERE, "data_lumpsum")
ASOF = "2026-10-07"
COST = 0.0005
H5, SCARY = 60, 24
NS = [1, 2, 3, 4, 6, 12, 24]


def load(fn, start=""):
    d, c = [], []
    for r in csv.DictReader(open(os.path.join(DATA, fn), encoding="utf-8")):
        if r["date"] < start or r["date"] > ASOF or not r["close"]:
            continue
        d.append(r["date"][:10]); c.append(float(r["close"]))
    return np.array(d), np.array(c)


irx_d, irx_c = load("_IRX_1d.csv"); IRX = dict(zip(irx_d, irx_c))


def rate(dates, mode, kr):
    out, last = np.zeros(len(dates)), 0.0
    for i, x in enumerate(dates):
        if mode == "irx":
            last = IRX[x] / 100 if x in IRX else last
            out[i] = last
        else:
            out[i] = kr
    return out


def month_starts(dates):
    idx, prev = [], None
    for i, x in enumerate(dates):
        if x[:7] != prev:
            idx.append(i); prev = x[:7]
    return np.array(idx)


def run(dates, px, r, ns=NS, keep_path=None):
    ms = month_starts(dates)
    od = np.array([dt.date.fromisoformat(x).toordinal() for x in dates])
    grow = np.cumprod(1 + r * np.diff(od, prepend=od[0]) / 365.0)
    res = {N: {"끝": [], "바닥": [], "시작": []} for N in ns}
    paths = {}
    for k in range(len(ms)):
        if k + H5 >= len(ms):
            break
        s, e = ms[k], ms[k + H5]
        se = ms[min(k + SCARY, len(ms) - 1)]
        for N in ns:
            buys = list(ms[k:k + N]); remain, sh, bi = 1.0, 0.0, 0
            path = np.empty(e + 1 - s)
            for t in range(s, e + 1):
                if bi < N and t == buys[bi]:
                    take = remain / (N - bi)
                    sh += take * grow[t] / grow[s] * (1 - COST) / px[t]
                    remain -= take; bi += 1
                path[t - s] = sh * px[t] + remain * grow[t] / grow[s]
            res[N]["끝"].append(path[-1]); res[N]["바닥"].append(path[: se - s + 1].min()); res[N]["시작"].append(dates[s])
            if keep_path and dates[s].startswith(keep_path):
                paths[N] = (dates[s:e + 1], path)
    for N in ns:
        for key in ("끝", "바닥"):
            res[N][key] = np.array(res[N][key])
    return res, paths


def 만원(x):
    return f"{x * 1e4:,.0f}만원"


d, c = load("VFINX_1d.csv")
res, paths = run(d, c, rate(d, "irx", 0), keep_path="2007-10")
L, S = res[1], res[12]
n = len(L["끝"]); win = int((L["끝"] > S["끝"]).sum())
print(f"가격 S&P500 인덱스펀드(VFINX) {d[0]} ~ {d[-1]} · 시작 {L['시작'][0]} ~ {L['시작'][-1]} · 수수료 살 때마다 {COST*100:.2f}%")
print("\n① 1억을 5년 뒤 — 한 번에 vs 12달 나눠 (S&P500)")
print(f"  시작 {n}번 중 한 번에가 더 남음 {win}번 · 12달이 더 남음 {n - win}번")
print(f"  평균 한 번에 {만원(L['끝'].mean())} · 12달 {만원(S['끝'].mean())} · 차 {(L['끝'].mean() - S['끝'].mean()) * 1e4:,.0f}만원")
print(f"  5년 뒤 1억 아래로 끝남 — 한 번에 {int((L['끝'] < 1).sum())}번 · 12달 {int((S['끝'] < 1).sum())}번")

print("\n② 넣은 뒤 2년 안에 가장 깊이 빠진 때 (S&P500)")
i = int(L["바닥"].argmin()); j = int(S["바닥"].argmin())
print(f"  한 번에 가장 깊이: {L['시작'][i]} 넣음 → 바닥 {만원(L['바닥'][i])} · 같은 날 12달 바닥 {만원(S['바닥'][i])}")
print(f"  5년 뒤 그 시작: 한 번에 {만원(L['끝'][i])} · 12달 {만원(S['끝'][i])}")
print(f"  12달이 가장 깊이: {S['시작'][j]} 넣음 → 바닥 {만원(S['바닥'][j])}")
print(f"  2년 안 잔고가 7,000만원 아래로 내려간 시작 — 한 번에 {int((L['바닥'] < 0.7).sum())}번 · 12달 {int((S['바닥'] < 0.7).sum())}번")
pd_, pp = paths[1]
k0 = int(np.argmin(pp)); rec = next(pd_[t] for t in range(k0, len(pp)) if pp[t] >= 1.0)
print(f"  {L['시작'][i]} 한 번에 1억: 바닥 {pd_[k0]} · 원금 회복 {rec}")

print("\n③ 몇 달로 나누든 — 5년 뒤 평균 (S&P500 · 1억)")
for N in [1, 2, 3, 4, 6, 12, 24]:
    print(f"  {N:2d}달  {만원(res[N]['끝'].mean())}  · 2년 안 가장 깊이 {만원(res[N]['바닥'].min())}")

print("\n④ S&P500 달마다 — 오른 달 / 내린 달")
ms = month_starts(d); me = list(ms[1:]) + [len(d)]
mret = [c[e - 1] / c[s - 1] - 1 for s, e in zip(ms[1:], me[1:])]
mret = [r for r, s in zip(mret, ms[1:]) if d[s] < "2026-10-01"]
print(f"  오른 달 {sum(1 for r in mret if r > 0)} · 내린(같은) 달 {sum(1 for r in mret if r <= 0)}")

print("\n⑤ 다른 시장 — 한 번에 vs 12달")
for name, fn, mode, st in [("코스피 대표 ETF(KODEX200)", "069500_splice_1d.csv", "kr", "2007-01-29"), ("일본 닛케이(가격 지수)", "_N225_1d.csv", "kr", "")]:
    dd, cc = load(fn, st)
    for kr in (0.0, 0.03):
        rr, _ = run(dd, cc, rate(dd, mode, kr), ns=[1, 12])
        a, b = rr[1]["끝"], rr[12]["끝"]
        print(f"  {name} · 기다리는 돈 연 {kr*100:.0f}% · 시작 {len(a)}번 중 한 번에가 더 남음 {int((a > b).sum())}번 · 평균 차 {(a.mean() - b.mean()) * 1e4:,.0f}만원")

print("\n⑥ 2007년 10월 시작 경로(달 첫 거래일 평가액 · 만원)")
for N in (1, 12):
    dd, p = paths[N]
    m = month_starts(dd)
    print(f"  {N:2d}달: " + " ".join(f"{dd[t][:7]}={p[t] * 1e4:,.0f}" for t in m))
print("\n끝.")
