# -*- coding: utf-8 -*-
"""ISA 매달 100만원 — S&P500 ETF 대 나스닥100 ETF, 세금 뗀 끝 금액 (표준 라이브러리만)

자료(이 폴더 data/):
  SPY_1d.csv, QQQ_1d.csv  야후 close(배당 보정) · DEXKOUS.csv 미 연준 원/달러 · KR3M.csv 한국 3개월 금리
판: 매달 첫 거래일 종가에 100만원어치 삼(편도 0.1%). 만기 달 첫 거래일 종가에 평가(편도 0.1%).
세금: 일반형 ISA — 순이익 200만원 비과세, 넘는 몫 9.9%.
ASOF: 2026-11 첫 거래일까지. 실행: python run_isanq.py
"""
import csv
import json
import os
import statistics as st
import sys

try:
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
    pass
HERE = os.path.dirname(os.path.abspath(__file__))
ASOF = "2026-11"
COST = 0.001


def rd(name, datecol=0, valcol=1):
    out = []
    with open(os.path.join(HERE, "data", name), encoding="utf-8-sig", newline="") as f:
        r = csv.reader(f)
        next(r)
        for row in r:
            try:
                v = float(row[valcol])
            except (ValueError, IndexError):
                continue
            out.append((row[datecol][:10], v))
    out.sort()
    return out


def first_of_month(px, fx):
    """달 첫 거래일 원화값 — 환율은 그날까지의 마지막 값."""
    res, j, last, seen = {}, 0, None, set()
    for d, c in px:
        while j < len(fx) and fx[j][0] <= d:
            last = fx[j][1]
            j += 1
        m = d[:7]
        if m in seen or last is None or c <= 0 or m > ASOF:
            continue
        seen.add(m)
        res[m] = c * last
    return res


def header_idx(name, col):
    with open(os.path.join(HERE, "data", name), encoding="utf-8-sig", newline="") as f:
        return next(csv.reader(f)).index(col)


FX = rd("DEXKOUS.csv")
col = header_idx("SPY_1d.csv", "close")
S0 = first_of_month(rd("SPY_1d.csv", 0, col), FX)
N0 = first_of_month(rd("QQQ_1d.csv", 0, col), FX)
IDX = sorted(set(S0) & set(N0))
S = [S0[m] for m in IDX]
N = [N0[m] for m in IDX]
RATE = {}
for d, v in rd("KR3M.csv"):
    RATE[d[:7]] = v
_last = None
R = []
for m in IDX:
    if m in RATE:
        _last = RATE[m]
    R.append(_last)


def tax(end, paid, free=200.0, rate=0.099):
    return max(end - paid - free, 0.0) * rate


def acct(i0, T, w, mon=100.0):
    sn = ss = 0.0
    for k in range(T):
        sn += mon * w * (1 - COST) / N[i0 + k]
        ss += mon * (1 - w) * (1 - COST) / S[i0 + k]
    pre = (sn * N[i0 + T] + ss * S[i0 + T]) * (1 - COST)
    paid = mon * T
    return pre, pre - tax(pre, paid), paid


def deposit(i0, T, mon=100.0):
    v = 0.0
    for k in range(T):
        rate = R[i0 + k]
        v = (v + mon) * (1 + (rate or 0.0) / 100 / 12)
    return v - tax(v, mon * T)


def table(T):
    rows = []
    for i0 in range(len(IDX) - T):
        pS, s, paid = acct(i0, T, 0.0)
        pN, n, _ = acct(i0, T, 1.0)
        _, h, _ = acct(i0, T, 0.5)
        rows.append(dict(start=IDX[i0], end=IDX[i0 + T], S=s, N=n, H=h, S_tax=pS - s, N_tax=pN - n,
                         flip=(pN > pS) != (n > s), paid=paid, dep=deposit(i0, T) if R[i0] is not None else None))
    return rows


def recent_winner(i0, L=36):
    return "N" if N[i0] / N[i0 - L] > S[i0] / S[i0 - L] else "S"


def summary(T):
    t = table(T)
    w = sum(r["N"] > r["S"] for r in t)
    worst = {a: min(t, key=lambda r: r[a]) for a in "SNH"}
    out = {
        "창": len(t), "나스닥승": w, "승률": round(w / len(t), 4),
        "가운데": {a: round(st.median(r[a] for r in t)) for a in "SNH"},
        "세금가운데": {"S": round(st.median(r["S_tax"] for r in t)), "N": round(st.median(r["N_tax"] for r in t))},
        "세금으로뒤집힌창": sum(r["flip"] for r in t),
        "최악": {a: {"금액": round(worst[a][a]), "시작": worst[a]["start"]} for a in "SNH"},
        "원금미달": {a: sum(r[a] < r["paid"] for r in t) for a in "SNH"},
        "닷컴밖패배": sum(r["N"] <= r["S"] and not ("1999-01" <= r["start"] <= "2002-12") for r in t),
        "닷컴밖창": sum(not ("1999-01" <= r["start"] <= "2002-12") for r in t),
    }
    return out, t


def lookup(t, start):
    r = next(x for x in t if x["start"] == start)
    return {k: (round(v) if isinstance(v, float) else v) for k, v in r.items()}


def rule_vs_nasdaq(T):
    """시청자 기본값 — 계좌 여는 달 직전 3년 더 오른 쪽. 그 3년 자료가 있는 달만."""
    a, g, ng = 0, [], []
    for i0 in range(36, len(IDX) - T):
        _, n, paid = acct(i0, T, 1.0)
        _, s, _ = acct(i0, T, 0.0)
        pick = n if recent_winner(i0) == "N" else s
        g.append(pick); ng.append(n)
        a += n >= pick
    return {"창": len(g), "나스닥이같거나더": a, "나스닥이더많음": sum(x > y for x, y in zip(ng, g)),
            "최근승자가더많음": sum(y > x for x, y in zip(ng, g)),
            "가운데_최근승자": round(st.median(g)), "가운데_나스닥": round(st.median(ng))}


if __name__ == "__main__":
    print("구간", IDX[0], IDX[-1], len(IDX), "달")
    res = {}
    for T in (36, 60):
        s, t = summary(T)
        res[str(T)] = s
        print(T, json.dumps(s, ensure_ascii=False))
        res[f"최근승자_{T}"] = rule_vs_nasdaq(T)
        print(" 최근 3년 더 오른 쪽 고르기:", json.dumps(res[f"최근승자_{T}"], ensure_ascii=False))
    t60, t36 = table(60), table(36)
    res["사례60_최근"] = lookup(t60, t60[-1]["start"])
    res["사례60_꼭대기"] = lookup(t60, "2021-11")
    res["사례36_닷컴"] = lookup(t36, "1999-08")
    res["사례36_코로나전"] = lookup(t36, "2019-12")
    print("사례", json.dumps({k: res[k] for k in res if k.startswith("사례")}, ensure_ascii=False))
    dep = [r for r in t36 if r["dep"] is not None]
    res["예금36"] = {"창": len(dep), "가운데": round(st.median(r["dep"] for r in dep)),
                   "원금미달": sum(r["dep"] < r["paid"] for r in dep)}
    print("예금 3년:", res["예금36"])
    print("3년 패배 시작 달:", [r["start"] for r in t36 if r["N"] <= r["S"]])
    print("5년 패배 시작 달:", [r["start"] for r in t60 if r["N"] <= r["S"]])
    json.dump(res, open(os.path.join(HERE, "_공개코드출력.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1)
    def mdd(x):
        pk, m = x[0], 0.0
        for v in x:
            pk = max(pk, v)
            m = min(m, v / pk - 1)
        return round(m * 100, 1)
    res["원화월기준_최대낙폭"] = {"S": mdd(S), "N": mdd(N)}
    def vol(x):
        r = [b / a - 1 for a, b in zip(x, x[1:])]
        return round(st.pstdev(r) * 100, 2)
    res["월변동폭"] = {"S": vol(S), "N": vol(N)}
    print("최대낙폭(원화·월)", res["원화월기준_최대낙폭"], "월변동폭", res["월변동폭"])
    json.dump(res, open(os.path.join(HERE, "_공개코드출력.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1)
    res["전체배수"] = {"S": round(S[-1] / S[0], 1), "N": round(N[-1] / N[0], 1)}
    t60all, t36all = table(60), table(36)
    res["반반_사이"] = {"3년": sum(min(r["S"], r["N"]) <= r["H"] <= max(r["S"], r["N"]) for r in t36all), "5년": sum(min(r["S"], r["N"]) <= r["H"] <= max(r["S"], r["N"]) for r in t60all)}
    print("전체 배수", res["전체배수"], "반반이 사이", res["반반_사이"])
    # 국내 상장 실물로 같은 사례(2021-11 시작 5년) — 합성 아님
    cR = header_idx("133690_KS_1d.csv", "close")
    def mon_real(name):
        o = {}
        for d, c in rd(name, 0, cR):
            if d[:7] <= ASOF and c > 0:
                o.setdefault(d[:7], c)
        return o
    rn, rs = mon_real("133690_KS_1d.csv"), mon_real("360750_KS_1d.csv")
    ms = [m for m in IDX if "2021-11" <= m <= "2026-11"]
    def real_end(px):
        sh = sum(100 * (1 - COST) / px[m] for m in ms[:-1])
        e = sh * px[ms[-1]] * (1 - COST)
        return round(e - tax(e, 100.0 * (len(ms) - 1)))
    res["사례_실물"] = {"S": real_end(rs), "N": real_end(rn), "달수": len(ms) - 1}
    print("국내 상장 실물 같은 5년:", res["사례_실물"])
    json.dump(res, open(os.path.join(HERE, "_공개코드출력.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1)
    print("\n── 계좌별 결과 — 5년 (시작 달 · S&P500 · 나스닥100 · 반반, 세금 뗀 만원)")
    for r_ in t60all:
        print(r_["start"], round(r_["S"]), round(r_["N"]), round(r_["H"]))
    print("\n── 계좌별 결과 — 3년 (시작 달 · S&P500 · 나스닥100 · 반반 · 예금, 세금 뗀 만원)")
    for r_ in t36all:
        print(r_["start"], round(r_["S"]), round(r_["N"]), round(r_["H"]), round(r_["dep"]))
    print("\n끝.")
