"""
목돈을 한 번에 넣을까, 나눠 넣을까 — 코스피 일시 투자 vs 분할(적립식) 투자

사용법:
    pip install pandas numpy matplotlib finance-datareader
    python dca_vs_lump_sum.py
    python dca_vs_lump_sum.py --synthetic     # 난수 데이터로 동작 확인

데이터: FinanceDataReader 의 코스피(KS11) 일별 종가를 월말 종가로 바꿔 쓴다(가격 지수, 배당 제외, 글과 같이 2026년 8월까지).
비교:   같은 목돈을 (1) 첫 달에 모두 넣는 일시 투자, (2) N개월에 나눠 넣는 분할 투자.
        분할 투자에서 아직 넣지 않은 돈은 현금으로 두며, 현금 이자율은 0%와 연 3%(가정) 두 가지.
        두 방법 모두 시작 후 10년(120개월) 시점의 평가액으로 비교한다.
결과:   output/dca_results.txt 와 차트 PNG
"""
import argparse
import os

import numpy as np
import pandas as pd

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "output")
os.makedirs(OUT, exist_ok=True)
H = 120                                           # 평가 시점: 10년
END = "2026-08-31"                                # 글의 표와 같은 기간(2026년 8월 말까지)


def load(synthetic=False):
    if synthetic:
        rng = np.random.default_rng(3)
        idx = pd.date_range("1995-05-31", "2026-08-31", freq="ME")
        return pd.Series(1000 * np.exp(np.cumsum(rng.normal(0.004, 0.08, len(idx)))), index=idx)
    import FinanceDataReader as fdr
    daily = fdr.DataReader("KS11", "1995-01-01")["Close"].dropna()
    m = daily.resample("ME").last().dropna()
    last_day = daily.index[-1]
    if last_day.month == (last_day + pd.offsets.BDay(1)).month:     # 다음 영업일이 같은 달이면 아직 안 끝난 달이므로 뺀다
        m = m.iloc[:-1]
    return m.loc[:END]


def paths(p, n, cash_rate):
    """p: 121개 월말 가격. 반환: (일시 투자 평가액 경로, 분할 투자 평가액 경로), 시작 금액 1."""
    mc = (1 + cash_rate) ** (1 / 12) - 1
    ls = p / p[0]
    shares, cash, dca = 0.0, 1.0, []
    for t in range(len(p)):
        if t > 0:
            cash *= 1 + mc
        if t < n:                                  # 매달 남은 현금 중 1/(남은 개월 수)를 넣는다
            buy = cash / (n - t)
            shares += buy / p[t]
            cash -= buy
        dca.append(shares * p[t] + cash)
    return ls, np.array(dca)


def mdd(v):
    return (v / np.maximum.accumulate(v) - 1).min()


def compare(m, n, cash_rate):
    rows = []
    for i in range(len(m) - H):
        p = m.values[i:i + H + 1]
        ls, dca = paths(p, n, cash_rate)
        rows.append({"start": m.index[i], "LS": ls[-1], "DCA": dca[-1],
                     "LS_mdd": mdd(ls), "DCA_mdd": mdd(dca),
                     "first_year": p[12] / p[0] - 1})
    return pd.DataFrame(rows).set_index("start")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--synthetic", action="store_true")
    a = ap.parse_args()
    m = load(a.synthetic)
    lines = [f"월말 종가 {m.index[0].date()} ~ {m.index[-1].date()} ({len(m)}개월), "
             f"10년 구간 시작월 {m.index[0].date()} ~ {m.index[-H - 1].date()} ({len(m) - H}개)"]
    res = {}
    for n in [12, 36]:
        for cr in [0.0, 0.03]:
            d = compare(m, n, cr)
            res[(n, cr)] = d
            gap = d.DCA / d.LS - 1
            lines.append(
                f"\n[{n}개월 분할, 현금 이자 연 {cr:.0%}]"
                f"\n  분할이 이긴 비율 {(d.DCA > d.LS).mean():.1%}, 평가액 차이 중앙값 {gap.median():+.1%}"
                f"\n  10년 뒤 평가액(시작=1) 하위 10%: 일시 {d.LS.quantile(.1):.2f}, 분할 {d.DCA.quantile(.1):.2f}"
                f" / 최저: 일시 {d.LS.min():.2f}, 분할 {d.DCA.min():.2f}"
                f"\n  10년 동안 계좌 최대 낙폭 중앙값: 일시 {d.LS_mdd.median():.1%}, 분할 {d.DCA_mdd.median():.1%}")
    d = res[(12, 0.0)]
    neg = d.first_year < 0
    lines.append(f"\n[12개월 분할, 이자 0%] 첫해 지수 수익률이 마이너스였던 구간 {neg.mean():.1%}:"
                 f" 분할 승률 {(d.DCA > d.LS)[neg].mean():.1%} / 플러스였던 구간: {(d.DCA > d.LS)[~neg].mean():.1%}")
    r = m.pct_change().dropna()
    lines.append(f"월평균 수익률(산술) {r.mean():.2%}, 12개월 분할의 평균 투입 지연 5.5개월")
    text = "\n".join(lines)
    print(text)
    with open(os.path.join(OUT, "dca_results.txt"), "w", encoding="utf-8") as fh:
        fh.write(text + "\n")

    try:
        import matplotlib
        matplotlib.use("Agg")
        import matplotlib.pyplot as plt
        gap = (d.DCA / d.LS - 1) * 100
        fig, axes = plt.subplots(1, 2, figsize=(12, 4.5), gridspec_kw={"width_ratios": [3, 2]})
        ax = axes[0]
        ax.bar(gap.index, gap.values, width=25, color=np.where(gap > 0, "#b22222", "#1f5fbf"))
        ax.axhline(0, color="black", linewidth=0.8)
        ax.set_title("10-year value: 12-month DCA vs lump sum, by start month")
        ax.set_ylabel("DCA / lump sum - 1 (%)  red: DCA ahead")
        ax.grid(alpha=0.3, axis="y")
        ax = axes[1]
        ax.scatter(d.first_year * 100, gap, s=12, c=np.where(gap > 0, "#b22222", "#1f5fbf"))
        ax.axhline(0, color="black", linewidth=0.8); ax.axvline(0, color="gray", linewidth=0.8)
        ax.set_xlabel("KOSPI return in the first 12 months (%)")
        ax.set_title("DCA wins when the first year goes badly")
        ax.grid(alpha=0.3)
        fig.tight_layout(); fig.savefig(os.path.join(OUT, "51_dca_vs_lump_sum.png"), dpi=120); plt.close(fig)
    except ImportError:
        pass


if __name__ == "__main__":
    main()
