# -*- coding: utf-8 -*-
# =============================================================================
# AlgoLab · 3배 레버리지 ETF 실측
#   순서: ① 실물 TQQQ로 모델 검증 → ② 검증된 모델로 1999년까지 확장 → ③ 감쇠 분해
#   모델: r_lev = 3·r - 2·(rf + spread)/252 - er/252   (일일 리밸런싱)
#   가정은 전부 인자로 빼고 민감도까지 찍는다. 가정으로 결론이 뒤집히면 그것도 결과다.
# =============================================================================
import csv, datetime, math, os

HERE = os.path.dirname(os.path.abspath(__file__))


def load(slug):
    p = os.path.join(HERE, f"lev_{slug}_daily.csv")
    out = {}
    with open(p, encoding="utf-8") as f:
        for r in csv.DictReader(f):
            out[datetime.date.fromisoformat(r["date"])] = float(r["close"])
    return out


QQQ, SPY, TQQQ, UPRO, IRX = (load(s) for s in ("qqq", "spy", "tqqq", "upro", "irx"))


def rets(px, d0=None, d1=None):
    """일간 수익률 리스트 [(date, r)] — 구간 잘라내기 포함."""
    ds = sorted(d for d in px if (d0 is None or d >= d0) and (d1 is None or d <= d1))
    return [(ds[i], px[ds[i]] / px[ds[i - 1]] - 1) for i in range(1, len(ds))]


def rf_on(d, _cache={}):
    """해당일 무위험수익률(연, 소수). ^IRX 결측일은 직전값 사용."""
    if not _cache:
        ds = sorted(IRX)
        prev = 0.05
        for x in ds:
            v = IRX[x] / 100.0
            if v == v and v >= 0:
                prev = v
            _cache[x] = prev
        _cache["_keys"] = ds
    if d in _cache:
        return _cache[d]
    ks = [x for x in _cache["_keys"] if x <= d]
    return _cache[ks[-1]] if ks else 0.05


def sim(series, lev=3.0, er=0.0095, spread=0.005, d0=None, d1=None):
    """일일 리밸런싱 레버리지 시뮬레이션 → [(date, 누적배수)]"""
    path, eq = [], 1.0
    for d, r in rets(series, d0, d1):
        cost = (lev - 1) * (rf_on(d) + spread) / 252.0 + er / 252.0
        eq *= (1 + lev * r - cost)
        if eq <= 0:                       # 이론상 -33.3% 하루면 전멸
            eq = 0.0
        path.append((d, eq))
    return path


def path_of(series, d0=None, d1=None):
    out, eq = [], 1.0
    for d, r in rets(series, d0, d1):
        eq *= (1 + r)
        out.append((d, eq))
    return out


def stats(path):
    if not path or path[-1][1] <= 0:
        yrs = (path[-1][0] - path[0][0]).days / 365.25 if path else 0
        return dict(mult=0.0, cagr=-1.0, mdd=-1.0, yrs=yrs)
    yrs = (path[-1][0] - path[0][0]).days / 365.25
    mult = path[-1][1]
    peak, mdd = 0.0, 0.0
    for _, v in path:
        peak = max(peak, v)
        mdd = min(mdd, v / peak - 1)
    return dict(mult=mult, cagr=mult ** (1 / yrs) - 1, mdd=mdd, yrs=yrs)


def line(tag, s):
    print(f"  {tag:<26} {s['yrs']:5.1f}년  누적 {s['mult']:>12,.2f}배  "
          f"CAGR {s['cagr']*100:>7.2f}%  MDD {s['mdd']*100:>7.1f}%")


print("=" * 92)
print("① 모델 검증 — 실물 TQQQ vs 시뮬레이션 (2010-02-11 ~ 2026-07-22)")
print("=" * 92)
t0 = min(TQQQ)
real = path_of(TQQQ, t0)
line("실물 TQQQ", stats(real))
for er, sp in ((0.0095, 0.005), (0.0084, 0.005), (0.0095, 0.010), (0.0084, 0.000)):
    s = stats(sim(QQQ, 3.0, er, sp, t0))
    err = (s["cagr"] - stats(real)["cagr"]) * 100
    print(f"  시뮬 er={er*100:.2f}% spread={sp*100:.1f}%  "
          f"CAGR {s['cagr']*100:7.2f}%  (실물 대비 {err:+.2f}%p)  MDD {s['mdd']*100:7.1f}%")

print()
print("=" * 92)
print("② 검증된 모델로 확장 — QQQ 1999-03-10 ~ 2026-07-22 (닷컴버블 포함)")
print("=" * 92)
q0 = min(QQQ)
line("QQQ 1배(배당재투자)", stats(path_of(QQQ, q0)))
line("QQQ 3배 시뮬(TQQQ식)", stats(sim(QQQ, 3.0, 0.0095, 0.005, q0)))
line("QQQ 2배 시뮬", stats(sim(QQQ, 2.0, 0.0095, 0.005, q0)))

print()
print("  SPY 1993-01-29 ~ 2026-07-22")
s0 = min(SPY)
line("SPY 1배(배당재투자)", stats(path_of(SPY, s0)))
line("SPY 3배 시뮬(UPRO식)", stats(sim(SPY, 3.0, 0.0091, 0.005, s0)))

print()
print("=" * 92)
print("③ 구간별 — '3배가 3배가 아니다'의 정체")
print("=" * 92)
D = datetime.date
WIN = [("닷컴 붕괴 2000-03~2002-10", D(2000, 3, 24), D(2002, 10, 9)),
       ("금융위기 2007-10~2009-03", D(2007, 10, 9), D(2009, 3, 9)),
       ("대세상승 2010-01~2021-12", D(2010, 1, 1), D(2021, 12, 31)),
       ("횡보 2022-01~2023-06", D(2022, 1, 1), D(2023, 6, 30)),
       ("전구간 1999~2026", q0, max(QQQ))]
print(f"  {'구간':<28}{'QQQ 1배':>12}{'3배 시뮬':>14}{'단순3배 기대':>14}{'감쇠(차이)':>14}")
for tag, a, b in WIN:
    p1, p3 = path_of(QQQ, a, b), sim(QQQ, 3.0, 0.0095, 0.005, a, b)
    if not p1:
        continue
    s1, s3 = stats(p1), stats(p3)
    naive = (1 + (s1["mult"] - 1) * 3)          # "3배 오르겠지"라는 통념
    gap = s3["mult"] - naive
    print(f"  {tag:<28}{s1['mult']:>11.2f}배{s3['mult']:>13.2f}배{naive:>13.2f}배{gap:>13.2f}배")

print()
print("=" * 92)
print("④ 10년 보유 롤링 — 3배가 1배를 이긴 확률")
print("=" * 92)
for name, px, lev, er in (("QQQ→3배", QQQ, 3.0, 0.0095), ("SPY→3배", SPY, 3.0, 0.0091)):
    ds = sorted(px)
    wins = tot = 0
    worst = (None, 9e9)
    best = (None, -9e9)
    for i in range(0, len(ds) - 2520, 21):      # 매월 시작 · 10년(2520거래일) 보유
        a, b = ds[i], ds[i + 2520]
        m1 = stats(path_of(px, a, b))["mult"]
        m3 = stats(sim(px, lev, er, 0.005, a, b))["mult"]
        tot += 1
        wins += (m3 > m1)
        if m3 < worst[1]:
            worst = (a, m3)
        if m3 > best[1]:
            best = (a, m3)
    print(f"  {name}: 10년 보유 {tot}개 구간 중 3배 승 {wins}개 ({wins/tot*100:.1f}%)")
    print(f"          최악 시작일 {worst[0]} → {worst[1]:.2f}배 · "
          f"최고 시작일 {best[0]} → {best[1]:.2f}배")

print()
print("=" * 92)
print("⑤ 감쇠의 산수 — 기하수익 근사  g ≈ L·μ - L²σ²/2")
print("=" * 92)
for name, px, d0 in (("QQQ 1999~", QQQ, q0), ("SPY 1993~", SPY, s0)):
    rs = [r for _, r in rets(px, d0)]
    mu = sum(rs) / len(rs) * 252
    var = sum((r - sum(rs) / len(rs)) ** 2 for r in rs) / (len(rs) - 1) * 252
    sd = math.sqrt(var)
    print(f"  {name}  연평균 산술수익 μ={mu*100:.2f}%  변동성 σ={sd*100:.2f}%")
    for L in (1, 2, 3):
        print(f"      {L}배 기하수익 근사 = {L}×{mu*100:.2f}% - {L**2}×{var*100:.2f}%/2 "
              f"= {(L*mu - L*L*var/2)*100:6.2f}%")
