# -*- coding: utf-8 -*-
"""적금 넣던 돈을 S&P500 ETF로 옮기면 — 5년 뒤 그날 팔 돈과 2년 미룰 수 있는 돈 (AlgoLab 재현 코드 · 표준 라이브러리만)

판
  · 두 사람 모두 매달 100만원 × 5년(원금 6,000만원)
  · 적금 = 해마다 1년 정기적금(그 달 한국은행 ECOS 121Y002 「정기적금」 신규 금리 · 단리 78/12)
           만기금은 1년 정기예금(그 달 「정기예금」 금리)으로 굴림 · 이자소득세 15.4%
  · ETF  = 매달 첫 거래일 S&P500(SPY 수정종가 = 배당 재투자) × 원/달러(FRED DEXKOUS)로 산다
           사고팔 때 0.1% · 팔 때 이익의 15.4%
  · 그날 판다   = 5년 뒤 그 달 첫 거래일에 판다
  · 2년 미룬다  = 그날 ETF(세후)가 적금보다 아래면 매달 다시 보고, 적금을 앞서는 첫 달에 판다(최대 24달)
                  미루는 동안 적금 쪽 돈도 정기예금 이자(세후)를 받는다. 24달 끝에도 못 앞서면 그날 판다(진 것)
  · 시작 달 = 5년 + 24달이 금리 자료 안에 들어가는 모든 달(같은 시작 달로 '그날'과 '2년 미룸'을 견준다)
자료(data/ · 2026-10-23 까지 얼림 · 금리는 2026-08 까지)
  · SPY.csv(야후 일봉 · close = 수정종가) · DEXKOUS.csv(FRED 원/달러) · 수신금리_ECOS121Y002.csv(한국은행)
돌리는 법: python run_savingsetf.py
"""
import csv
import os
import statistics
import sys
from collections import Counter

sys.stdout.reconfigure(encoding="utf-8", errors="replace")
HERE = os.path.dirname(os.path.abspath(__file__))
D = os.path.join(HERE, "data")
ASOF = "2026-10-23"
COST, TAX, MON = 0.001, 0.154, 100.0  # 만원
Y, KMAX = 5, 24


def won(x):
    return f"{x:,.0f}만원"


# ── 자료 ──
fx = []
with open(os.path.join(D, "DEXKOUS.csv"), encoding="utf-8") as f:
    for r in csv.reader(f):
        try:
            fx.append((r[0], float(r[1])))
        except (ValueError, IndexError):
            pass
fx.sort()
spy = []
with open(os.path.join(D, "SPY.csv"), encoding="utf-8") as f:
    for r in csv.DictReader(f):
        v = float(r["close"])
        if v > 0 and r["date"] <= ASOF:
            spy.append((r["date"], v))
R = {}
with open(os.path.join(D, "수신금리_ECOS121Y002.csv"), encoding="utf-8") as f:
    for r in csv.DictReader(f):
        t = r["time"]
        R[f"{t[:4]}-{t[4:6]}"] = (float(r["정기적금"]), float(r["정기예금"]))

# SPY 각 날에 그날(없으면 직전) 원/달러를 곱하고, 달마다 첫 거래일 값을 쓴다
j, last, krw = 0, None, []
for d, v in spy:
    while j < len(fx) and fx[j][0] <= d:
        last = fx[j][1]
        j += 1
    if last is not None:
        krw.append((d, v * last))
mon = {}
for d, v in krw:
    mon.setdefault(d[:7], v)
per = sorted(mon)
px = [mon[k] for k in per]
for a, b in zip(per, per[1:]):
    assert int(a[:4]) * 12 + int(a[5:]) + 1 == int(b[:4]) * 12 + int(b[5:]), ("월 구멍", a, b)


def savings_end(s, H):
    pile = 0.0
    for y in range(H // 12):
        a_, d_ = R[per[s + 12 * y]]
        pile *= 1.0 + d_ / 100.0 * (1 - TAX)
        pile += MON * 12 + MON * (a_ / 100.0) * 78.0 / 12.0 * (1 - TAX)
    return pile


def etf_val(sh, i, H):
    v = sh * px[i] * (1 - COST)
    return v - max(v - MON * H, 0) * TAX


def play(s, kmax):
    H = 12 * Y
    sh = sum(MON * (1 - COST) / px[s + j_] for j_ in range(H))
    sav = savings_end(s, H)
    e0, s0 = etf_val(sh, s + H, H), sav
    for k in range(kmax + 1):
        i = s + H + k
        if k > 0:
            sav *= 1 + R[per[i - 1]][1] / 100 / 12 * (1 - TAX)
        e = etf_val(sh, i, H)
        if e > sav or k == kmax:
            break
    return dict(start=per[s], plan=per[s + H], sell=per[i], wait=k, etf0=e0, sav0=s0, etf=e, sav=sav,
                rate=R[per[s]][0], won0=e0 > s0, won=e > sav)


H = 12 * Y
S = [s for s in range(len(per) - H - KMAX) if all(per[s + H + k] in R for k in range(KMAX + 1))
     and all(per[s + 12 * y] in R for y in range(Y))]
rows = [play(s, KMAX) for s in S]
n = len(rows)
med = lambda xs: sorted(xs)[len(xs) // 2]
w0 = [r for r in rows if r["won0"]]
w2 = [r for r in rows if r["won"]]
dl = [r for r in rows if r["won"] and not r["won0"]]
lost = [r for r in rows if not r["won"]]

print(f"== S&P500(원화) 적립 대 적금 · 매달 100만원 × 5년 · 시작 달 {rows[0]['start']} ~ {rows[-1]['start']} · {n}번")
print(f"5년 뒤 그날 팔면 적금보다 더 남은 것 {len(w0)}번 / {n}번")
print(f"적금보다 아래면 최대 2년 미뤄 앞서는 첫 달에 팔면 {len(w2)}번 / {n}번")
print(f"그날 적금에 졌던 {n - len(w0)}번 중 2년 안에 앞지른 것 {len(dl)}번 · 기다린 달 가운데 값 {med([r['wait'] for r in dl])}달 · 최대 {max(r['wait'] for r in dl)}달")
print(f"기다려서 이긴 판의 적금 대비 차이 가운데 값 {won(med([r['etf'] - r['sav'] for r in dl]))} · 그날 이긴 판은 {won(med([r['etf0'] - r['sav0'] for r in w0]))}")
print(f"2년을 기다리고도 진 것 {len(lost)}번 · 시작 연도 {dict(sorted(Counter(r['start'][:4] for r in lost).items()))} · 모자란 돈 가운데 값 {won(med([r['etf'] - r['sav'] for r in lost]))}")
print(f"진 판 시작 달 적금 금리 가운데 값 연 {med([r['rate'] for r in lost])}% · 이긴 판 연 {med([r['rate'] for r in w2])}% · 지금(2026-08) 연 {R['2026-08'][0]}%")
r07 = [r for r in rows if r["start"] >= "2007"]
print(f"2007년 이후 시작 {len(r07)}번 중 그날 팔아도 더 남은 것 {sum(r['won0'] for r in r07)}번")

# 사례 = 기다려서 이긴 판 중 기다린 달이 가운데 값이고, 금액 차가 가운데 값에 가장 가까운 판(좋은 판을 고르지 않는다)
mw, mg = med([r["wait"] for r in dl]), med([r["etf"] - r["sav"] for r in dl])
c = min([r for r in dl if r["wait"] == mw], key=lambda r: abs((r["etf"] - r["sav"]) - mg))
print(f"[사례] {c['start']} 시작 · {c['plan']} ETF {won(c['etf0'])} / 적금 {won(c['sav0'])} (차이 {won(c['etf0'] - c['sav0'])})"
      f" → {c['wait']}달 기다린 {c['sell']} ETF {won(c['etf'])} / 적금 {won(c['sav'])} (차이 {won(c['etf'] - c['sav'])})")
ml = med([r["etf"] - r["sav"] for r in lost])
lc = min(lost, key=lambda r: abs((r["etf"] - r["sav"]) - ml))
print(f"[진 사례] {lc['start']} 시작 · {lc['plan']} ETF {won(lc['etf0'])} / 적금 {won(lc['sav0'])}"
      f" → 24달 기다린 {lc['sell']} ETF {won(lc['etf'])} / 적금 {won(lc['sav'])} · 시작 달 적금 금리 연 {lc['rate']}%")

print("\n== 미루기 한도를 바꿔 보면(같은 시작 달)")
for K in (0, 3, 6, 12, 18, 24):
    w = sum(play(s, K)["won"] for s in S)
    print(f"  최대 {K:2d}달 미룸 → 적금보다 더 남은 것 {w}번 / {n}번")

print("\n== 시작 연도별 (그날 판다 / 2년 미룬다 / 시작 달 수)")
for yy in sorted({r["start"][:4] for r in rows}):
    g = [r for r in rows if r["start"][:4] == yy]
    print(f"  {yy}  {sum(r['won0'] for r in g):2d} / {sum(r['won'] for r in g):2d} / {len(g):2d}  · 1월 적금 금리 연 {R[yy + '-01'][0]}%")
print("끝.")
