# -*- coding: utf-8 -*-
"""나스닥 레버리지 ETF — 10년 묻어 둘 거면 2배냐 3배냐 (AlgoLab · 2026-10-27)

표준 라이브러리만 씁니다. 같은 폴더의 data/ 를 읽습니다(야후 일봉, 2026-10-23까지 얼림).
돌리는 법:  python run_nasdaqlev.py

계산
- 나스닥100 총수익: 1999-03-10 부터 QQQ 수정종가(배당 재투자) · 그 전은 나스닥100 지수(^NDX) 하루 수익 + 배당 추정 연 0.7%
- L배 레버리지(매일 L배를 다시 맞추는 실제 ETF 방식):
    하루 수익 = L × 지수 하루 수익 − (L−1) × (미국 3개월 국채 금리 + 0.5%) ÷ 252 − 보수 연 0.95% ÷ 252
  1배는 지수 그대로(보수 없음)
- 계좌: 매달 첫 거래일 종가에 1,000만원을 한 번에 사고, 10년 뒤 같은 달 첫 거래일 종가로 본다(매수만 · 호가 차이 없음)
"""
import csv
import os
import statistics
import sys

sys.stdout.reconfigure(encoding="utf-8")

HERE = os.path.dirname(os.path.abspath(__file__))
ASOF = "2026-10-23"
SPREAD, FEE, NDX_DIV = 0.005, 0.0095, 0.007
WON = 1000  # 만원


def series(name):
    out = {}
    with open(os.path.join(HERE, "data", name + ".csv"), encoding="utf-8") as f:
        for r in csv.DictReader(f):
            if r["date"] <= ASOF and r["close"] not in ("", "nan") and float(r["close"]) > 0:
                out[r["date"]] = float(r["close"])
    return out


IRX = series("IRX")


def ndx_tr():
    ndx, qqq = series("NDX"), series("QQQ")
    ds = sorted(ndx)
    q0 = min(qqq)
    v, out = 1.0, {ds[0]: 1.0}
    for p, d in zip(ds, ds[1:]):
        if d > q0 and p in qqq and d in qqq:
            r = qqq[d] / qqq[p] - 1
        else:
            r = ndx[d] / ndx[p] - 1 + NDX_DIV / 252
        v *= 1 + r
        out[d] = v
    return out


def paths(px):
    """날짜 목록과 1·2·3배 누적 가치."""
    ds = sorted(px)
    ik = sorted(IRX)
    j, rf, P = 0, 0.0, {1: [1.0], 2: [1.0], 3: [1.0]}
    for k in range(1, len(ds)):
        while j < len(ik) and ik[j] <= ds[k]:
            rf = IRX[ik[j]] / 100.0
            j += 1
        r = px[ds[k]] / px[ds[k - 1]] - 1
        for L in (1, 2, 3):
            rl = r if L == 1 else L * r - (L - 1) * (rf + SPREAD) / 252 - FEE / 252
            P[L].append(P[L][-1] * (1 + max(rl, -0.999)))
    return ds, P


def month_starts(ds):
    idx, prev = [], None
    for i, d in enumerate(ds):
        if d[:7] != prev:
            idx.append(i)
            prev = d[:7]
    return idx


def windows(px, years=10):
    ds, P = paths(px)
    ms = month_starts(ds)
    n = years * 12
    rows = []
    for a in range(len(ms) - n):
        s, e = ms[a], ms[a + n]
        row = {"시작": ds[s][:7], "끝": ds[e][:7]}
        for L in (1, 2, 3):
            p = P[L]
            row[L] = WON * p[e] / p[s]
            row["바닥", L] = WON * min(p[s:e + 1]) / p[s]
            row["적립", L] = 100 * sum(p[e] / p[ms[k]] for k in range(a, a + n))
        rows.append(row)
    return rows


def won(x):
    return f"{x:,.0f}만원" if x >= 10 else f"{x:,.1f}만원"


def report(name, rows):
    n = len(rows)
    print(f"\n== {name} · 1,000만원 한 번에 · 10년 · 시작 달 {rows[0]['시작']} ~ {rows[-1]['시작']} · {n}번")
    w3 = [x for x in rows if x[3] > x[2]]
    l3 = [x for x in rows if x[3] <= x[2]]
    print(f"3배가 2배보다 많이 남은 10년 {len(w3)}번 · 2배가 많거나 같았던 10년 {len(l3)}번")
    rw = [x[3] / x[2] for x in w3]
    rl = [x[3] / x[2] for x in l3]
    print(f"3배가 이긴 {len(w3)}번: 3배÷2배 가운데 값 {statistics.median(rw):.2f} · 2배의 두 배 넘게 남긴 것 {sum(r >= 2 for r in rw)}번")
    print(f"3배가 진 {len(l3)}번: 3배÷2배 가운데 값 {statistics.median(rl):.2f} · 2배의 절반도 못 남긴 것 {sum(r < 0.5 for r in rl)}번")
    lo = {L: sum(1 for x in rows if x[L] < WON) for L in (1, 2, 3)}
    print(f"원금(1,000만원)보다 적게 끝난 10년: 1배 {lo[1]}번 · 2배 {lo[2]}번 · 3배 {lo[3]}번")
    only3 = sum(1 for x in rows if x[3] < WON <= x[2])
    only2 = sum(1 for x in rows if x[2] < WON <= x[3])
    print(f"2배라면 원금을 지켰는데 3배라서 원금 아래로 끝난 10년 {only3}번 · 그 반대 {only2}번")
    print(f"2배가 1배보다 많이 남은 10년 {sum(1 for x in rows if x[2] > x[1])}번")
    for a, b in (("1985", "1994"), ("1995", "2002"), ("2003", "2009"), ("2010", "2016")):
        sub = [x for x in rows if a <= x["시작"][:4] <= b]
        if sub:
            print(f"  시작 {a}~{b}년 {len(sub)}번 중 3배가 이긴 것 {sum(1 for x in sub if x[3] > x[2])}번")
    acc = sum(1 for x in rows if x["적립", 3] > x["적립", 2])
    acclo = {L: sum(1 for x in rows if x["적립", L] < 100 * 120) for L in (2, 3)}
    print(f"매달 100만원씩 10년 나눠 산 경우: 3배가 2배보다 많이 남은 10년 {acc}번 · 원금(1억 2천만원) 아래 2배 {acclo[2]}번 · 3배 {acclo[3]}번")
    for tag, grp, rs in (("3배가 진", l3, rl), ("3배가 이긴", w3, rw)):
        m = statistics.median(rs)
        x = min(zip(grp, rs), key=lambda t: abs(t[1] - m))[0]
        print(f"[사례 · {tag} 10년 중 3배÷2배가 가운데 값에 가장 가까운 달] {x['시작']} 시작 → {x['끝']}: "
              f"1배 {won(x[1])} · 2배 {won(x[2])} · 3배 {won(x[3])} · 10년 중 가장 낮았던 날 3배 {won(x['바닥', 3])}")


def real_vs_model(px, etf, L):
    ds, P = paths(px)
    e = series(etf)
    common = [i for i, d in enumerate(ds) if d in e]
    i0, i1 = common[0], common[-1]
    yrs = (i1 - i0) / 252
    syn = (P[L][i1] / P[L][i0]) ** (1 / yrs) - 1
    act = (e[ds[i1]] / e[ds[i0]]) ** (1 / yrs) - 1
    print(f"  {etf}({L}배) {ds[i0]} ~ {ds[i1]}: 계산 연 {syn * 100:.2f}% / 실제 연 {act * 100:.2f}%")


if __name__ == "__main__":
    print(f"기준일 {ASOF}")
    ndx = ndx_tr()
    sp = series("SP500TR")
    print("실제 레버리지 ETF 와 계산 대조(같은 구간):")
    for px, etf, L in ((ndx, "QLD", 2), (ndx, "TQQQ", 3), (sp, "SSO", 2), (sp, "UPRO", 3)):
        real_vs_model(px, etf, L)
    q, t = series("QLD"), series("TQQQ")
    d0 = min(t)
    d1 = max(d for d in t if d in q)
    print(f"실제 TQQQ 상장일 {d0} ~ {d1}: TQQQ {t[d1] / t[d0]:.0f}배 · QLD {q[d1] / q[d0]:.0f}배")
    rows = windows(ndx)
    report("나스닥100", rows)
    post = [x for x in rows if x["시작"] >= "2010-02"]
    print(f"  2010년 2월(TQQQ 상장) 이후 시작 {len(post)}번 중 3배가 이긴 것 {sum(1 for x in post if x[3] > x[2])}번 · "
          f"원금 아래 2배 {sum(1 for x in post if x[2] < WON)}번 · 3배 {sum(1 for x in post if x[3] < WON)}번")
    report("S&P500(총수익) — 확인용", windows(sp))
    print("\n끝.")
