# -*- coding: utf-8 -*-
"""SCHD 배당만 꺼내 쓰기 vs S&P500(SPY) 팔아 쓰기 — 알고랩 재현 코드 (2026-10-18 영상).

배당 계좌     : 1억원으로 배당 ETF 를 사서, 분배가 나올 때마다 전부 꺼내 쓴다.
팔아 쓰기 계좌 : 1억원으로 SPY 를 사서, SPY 자기 분배는 다시 산다.
                배당 계좌가 분배를 받는 날, 같은 금액을 SPY 를 팔아 꺼낸다(꺼낸 현금이 원 단위로 같다).
세전(기본) / 세후(국내상장 해외 ETF 모형: 분배 15.4% 떼고 꺼냄 · 팔아 쓰기는 같은 세후 현금을 만들도록 팔고
               판 금액 중 차익 몫(평균단가 기준)에만 15.4%) 두 판. 끝에 다 팔 때의 세금은 '청산후' 로 따로.
자료: data_schdsell/*.json — 야후 chart API 원 종가(분할 반영)·adjclose·분배 기록(2026-10-16 까지 얼림).
돌리는 법: python run_schdsell.py   (표준 라이브러리만 씁니다)
"""
import datetime
import json
import os

HERE = os.path.dirname(os.path.abspath(__file__))
D = os.path.join(HERE, "data_schdsell")
ASOF = "2026-10-16"          # 기준일 고정 — 영상과 같은 숫자가 나온다
TAU = 0.154


def won(x):
    x = round(x / 1e4)                       # 만원
    eok, man = divmod(x, 10000)
    return (f"{eok}억 {man:,}만원" if man else f"{eok}억원") if eok else f"{man:,}만원"


def days(a, b):
    f = datetime.date.fromisoformat
    return (f(b) - f(a)).days


def series(t):
    j = json.load(open(os.path.join(D, t.replace(".", "_") + ".json"), encoding="utf-8"))
    rows = [r for r in j["rows"] if r[0] <= ASOF]
    divs = {}
    for d, a in j["divs"]:
        divs[d] = divs.get(d, 0) + a
    dates = [r[0] for r in rows]
    return dates, [r[1] for r in rows], [divs.get(d, 0.0) for d in dates], [r[2] for r in rows]


def align(a, b):
    """두 종목의 공통 거래일. 한쪽만 거래한 날의 분배는 다음 공통일로 민다."""
    dA, cA, vA, _ = series(a); dB, cB, vB, _ = series(b)
    common = sorted(set(dA) & set(dB)); s = set(common)

    def al(dates, c, v):
        C, V, carry = [], [], 0.0
        for d, x, y in zip(dates, c, v):
            carry += y
            if d in s:
                C.append(x); V.append(carry); carry = 0.0
        return C, V
    CA, VA = al(dA, cA, vA); CB, VB = al(dB, cB, vB)
    return common, CA, VA, CB, VB


def run(CA, VA, CB, VB, i0, i1, td, tg, path=None):
    shA = 1e8 / CA[i0]
    shB = 1e8 / CB[i0]; basisB = 1e8
    took = 0.0
    for i in range(i0 + 1, i1 + 1):
        if VB[i]:                                    # SPY 자기 분배는 (세후) 다시 산다
            g = shB * VB[i] * (1 - td)
            shB += g / CB[i]; basisB += g
        if VA[i]:
            cash = shA * VA[i] * (1 - td)            # 배당 계좌가 손에 쥐는 현금
            took += cash
            avg = basisB / shB
            r = max(0.0, 1 - avg / CB[i])            # 판 금액 중 차익 몫
            x = cash / (1 - r * tg)                  # 세금 내고 같은 현금이 남도록 판다
            q = x / CB[i]
            basisB -= avg * q; shB -= q
        if path is not None:
            path.append(shB * CB[i] / (shA * CA[i]))
    vA = shA * CA[i1]; vB = shB * CB[i1]
    return vA, vB, took, vA - max(0.0, vA - 1e8) * tg, vB - max(0.0, vB - basisB) * tg


def month_starts(dates):
    out, last = [], None
    for i, d in enumerate(dates):
        if d[:7] != last:
            out.append(i); last = d[:7]
    return out


def rolling10(common, CA, VA, CB, VB, td, tg):
    n = len(common); w = t = 0; lose = []
    for s in month_starts(common):
        e_date = str(int(common[s][:4]) + 10) + common[s][4:]
        e = next((j for j in range(s, n) if common[j] >= e_date), None)
        if e is None:
            break
        r = run(CA, VA, CB, VB, s, e, td, tg); t += 1
        if r[1] > r[0]:
            w += 1
        else:
            lose.append(common[s][:7])
    return w, t, lose


def unit_tr(c, dv):
    u = [1.0]
    for i in range(1, len(c)):
        u.append(u[-1] * (c[i] + dv[i]) / c[i - 1])
    return u


def cagr(u0, u1, nd):
    return (u1 / u0) ** (365.25 / nd) - 1


def year_mdd(t, y):
    d, c, dv, _ = series(t)
    u = unit_tr(c, dv)
    idx = [i for i, x in enumerate(d) if x[:4] == y]
    pk = u[idx[0]]; m = 0.0
    for i in idx:
        pk = max(pk, u[i]); m = min(m, u[i] / pk - 1)
    return m * 100


def tr_cagr(t, start):
    d, c, dv, _ = series(t)
    i0 = next(i for i, x in enumerate(d) if x >= start)
    u = unit_tr(c, dv)
    return cagr(u[i0], u[-1], days(d[i0], d[-1])) * 100


def first5_gap(a, b):
    """상장 뒤 첫 5년 — 분배까지 다시 넣은 연 수익 차(배당 ETF − SPY, %포인트)."""
    common, CA, VA, CB, VB = align(a, b)
    uA = unit_tr(CA, VA); uB = unit_tr(CB, VB)
    e_date = str(int(common[0][:4]) + 5) + common[0][4:]
    e = next(j for j in range(len(common)) if common[j] >= e_date)
    nd = days(common[0], common[e])
    return common[0][:7], common[e][:7], (cagr(1, uA[e], nd) - cagr(1, uB[e], nd)) * 100


print(f"기준일 {ASOF} · 1억원 시작 · 금액은 달러 총수익을 1억원 단위로 환산(환율 변화 미반영)\n")

# ── 본편 ──
common, CA, VA, CB, VB = align("SCHD", "SPY")
n = len(common)
print(f"[본편 · SCHD 배당만 꺼내 쓰기 vs SPY 팔아 쓰기 · {common[0]} ~ {common[-1]} · {days(common[0], common[-1]) / 365.25:.1f}년]")
path = []
vA, vB, took, _, _ = run(CA, VA, CB, VB, 0, n - 1, 0.0, 0.0, path)
print(f"  15년 동안 SCHD 분배 {sum(1 for v in VA[1:] if v)}번(석 달에 한 번) · 나올 때마다 전부 꺼냄")
print(f"  세전: 두 계좌가 꺼낸 돈 {won(took)} → 끝 계좌 배당 {won(vA)} · 팔아 쓰기 {won(vB)} (차이 {won(vB - vA)})")
vA2, vB2, took2, lA, lB = run(CA, VA, CB, VB, 0, n - 1, TAU, TAU)
print(f"  세후: 꺼낸 세후 현금 {won(took2)} → 끝 계좌 배당 {won(vA2)} · 팔아 쓰기 {won(vB2)} · 끝에 다 팔고 세금까지: {won(lA)} · {won(lB)}")
ahead = sum(1 for r in path if r > 1) / len(path)
k = min(range(len(path)), key=lambda i: path[i])
print(f"  15년 중 팔아 쓰기 계좌가 앞선 날 {ahead * 100:.0f}% · 가장 뒤처진 날 {common[k + 1]}(배당 계좌의 {path[k] * 100:.0f}%)")
w, t, lose = rolling10(common, CA, VA, CB, VB, 0.0, 0.0)
print(f"  10년짜리(시작 달을 한 달씩 바꿔) {t}번: 팔아 쓰기가 더 남은 {w}번 · 배당이 더 남은 {t - w}번 (배당 쪽 시작 달 {lose[0]} ~ {lose[-1]})")
w2, t2, _ = rolling10(common, CA, VA, CB, VB, TAU, TAU)
print(f"  세후 판 10년짜리 {t2}번: 팔아 쓰기가 더 남은 {w2}번")
print(f"  분배까지 다시 넣은 연 수익: SCHD {tr_cagr('SCHD', common[0]):.2f}% · SPY {tr_cagr('SPY', common[0]):.2f}%")
print(f"  그해 가장 크게 떨어진 폭: 2022년 SCHD {year_mdd('SCHD', '2022'):.1f}% · SPY {year_mdd('SPY', '2022'):.1f}% / "
      f"2020년 SCHD {year_mdd('SCHD', '2020'):.1f}% · SPY {year_mdd('SPY', '2020'):.1f}%")

# ── 반론: 다른 미국 배당 ETF 8개 ──
print("\n[반론 · 다른 미국 배당 ETF 8개 · 각자 상장일부터 · 세전 · 1억원]")
wins = 0
for a in ["VYM", "DVY", "SDY", "HDV", "DGRO", "NOBL", "VIG", "SPYD"]:
    cm, A1, V1, B1, W1 = align(a, "SPY")
    x = run(A1, V1, B1, W1, 0, len(cm) - 1, 0.0, 0.0)
    rw, rt, _ = rolling10(cm, A1, V1, B1, W1, 0.0, 0.0)
    wins += x[1] > x[0]
    print(f"  {a:5s} {cm[0]}~ 끝 계좌 배당 {x[0] / 1e8:.2f}억 · 팔아 쓰기 {x[1] / 1e8:.2f}억 · 10년짜리 {rt}번 중 팔아 쓰기 {rw}번")
print(f"  8개 중 팔아 쓰기 계좌가 더 남은 ETF {wins}개")

print("\n[한계 · 상장 뒤 첫 5년, 분배까지 넣은 연 수익 차(배당 ETF − SPY)]")
for a in ["SCHD", "VYM", "DVY", "SDY", "HDV", "DGRO", "NOBL", "VIG", "SPYD"]:
    s0, s1, g = first5_gap(a, "SPY")
    print(f"  {a:5s} {s0}~{s1} {g:+.2f}%포인트")

# ── 한계: 국내 고배당 ──
print("\n[한계 · 국내 고배당 ETF · ARIRANG 고배당주(161510) vs KODEX200(069500) · 국내주식형이라 매도 차익 비과세]")
cm, A1, V1, B1, W1 = align("161510.KS", "069500.KS")
x = run(A1, V1, B1, W1, 0, len(cm) - 1, 0.0, 0.0)
rw, rt, _ = rolling10(cm, A1, V1, B1, W1, 0.0, 0.0)
rw2, rt2, _ = rolling10(cm, A1, V1, B1, W1, TAU, 0.0)
print(f"  {cm[0]}~ 끝 계좌 고배당 {x[0] / 1e8:.2f}억 · 코스피200 팔아 쓰기 {x[1] / 1e8:.2f}억")
print(f"  10년짜리 {rt}번 중 코스피200 팔아 쓰기가 더 남은 {rw}번 (분배세 15.4% 넣으면 {rw2}번)")
print("\n끝.")
