# -*- coding: utf-8 -*-
"""알고랩 재현 코드 — 적립식 계좌가 마이너스면 멈출까, 계속 살까 (2026-10-09 영상)

같은 폴더의 data_dcastop/ 에 얼린 가격 자료만 쓴다(인터넷 접속 없음). 기준일 ASOF 고정.
  python run_dcastop.py        (numpy·pandas 필요)

규칙
- 계속: 매달 첫 거래일 종가에 50만원어치 산다.
- 멈춤: 그달 첫 거래일의 '전날 종가'로 계좌 주식 평가액이 주식에 넣은 원금보다 낮으면(마이너스) 그달은 사지 않고
        50만원을 현금으로 쌓는다(이자 붙음). 다음 달 플러스면 다시 산다. 쌓인 현금은 끝까지 현금으로 둔다.
- 멈춤·몰아넣기: 멈춤과 같되, 다시 사는 날 쌓인 현금을 그날 종가에 전부 넣는다.
- 10년(120번 납입) 창을 시작 달 한 달씩 옮겨 가며 비교. 평가 = 121번째 달 첫 거래일 전날 종가. 평가액 = 주식 + 현금.
- 한 번도 멈추지 않은 창은 두 계좌가 똑같아서 승·패에서 뺀다.
- 수수료·세금·호가 없음 · 공매도·빚 없음.
자료
- 069500_splice_1d.csv : KODEX200. 2009-04-17 이후 야후 수정가(분배금 재투자), 그 전은 네이버 종가(분배금 미반영)를 이어 붙였다
                         (야후 069500 은 2007-02 ~ 2009-04 가 통째로 비어 있다).
- VFINX_1d.csv         : 뱅가드 S&P500 인덱스펀드(배당 재투자 · 달러).
- _IRX_1d.csv          : 미국 3개월 국채 금리(연 %) — S&P500 쪽 쉬는 현금의 이자.
"""
import os
import numpy as np
import pandas as pd

HERE = os.path.dirname(os.path.abspath(__file__))
D = os.path.join(HERE, "data_dcastop")
ASOF = "2026-10-08"
MONTHLY = 50          # 만원
H = 120


def load(fn):
    df = pd.read_csv(os.path.join(D, fn), parse_dates=["date"])
    s = df.set_index("date")["close"].astype(float).dropna()
    s = s[(s > 0) & (s.index <= ASOF)]
    return s[~s.index.duplicated()]


IRX = load("_IRX_1d.csv")


def cash_index(idx, mode):
    if mode == "irx":
        r = IRX.reindex(idx, method="ffill").bfill().values / 100.0
        daily = (1 + r) ** (1 / 252.0)
    else:
        daily = np.full(len(idx), (1 + mode) ** (1 / 252.0))
    daily[0] = 1.0
    return np.cumprod(daily)


def month_starts(idx):
    s = pd.Series(np.arange(len(idx)), index=idx)
    return s.groupby([idx.year, idx.month]).first().values


def sim(price, C, cs, E, arm, path=None):
    """arm: A=계속 · B1=멈춤 · B2=멈춤·몰아넣기. 반환 (평가액(납입 1 단위), 멈춘 달 위치, 산 달 위치).
    path 에 리스트를 주면 매 납입일 직전(전날 종가) 평가액을 담는다."""
    sh = 0.0; cash = 0.0; cash_cost = 0.0; inv = 0.0
    paused = False; stopped, bought = [], []
    for c in cs:
        if path is not None:
            path.append(sh * price[c - 1] + cash * C[c - 1])
        inv += 1.0
        if arm == "A":
            sh += 1.0 / price[c]; bought.append(c); continue
        stock_cost = inv - 1.0 - cash_cost
        neg = sh > 0 and sh * price[c - 1] < stock_cost - 1e-12
        if neg:
            cash += 1.0 / C[c]; cash_cost += 1.0; stopped.append(c); paused = True
        else:
            sh += 1.0 / price[c]; bought.append(c)
            if paused and arm == "B2" and cash > 0:
                sh += cash * C[c] / price[c]; cash = 0.0; cash_cost = 0.0
            paused = False
    if path is not None:
        path.append(sh * price[E] + cash * C[E])
    return sh * price[E] + cash * C[E], stopped, bought


def 만(x):
    return f"{x:,.0f}만원"


def run(label, fn, cash_mode):
    p = load(fn)
    idx = p.index; price = p.values
    C = cash_index(idx, cash_mode)
    ms = month_starts(idx)
    ms = ms[ms >= 1]
    nwin = len(ms) - H
    print(f"\n[{label}] 자료 {idx[0].date()} ~ {idx[-1].date()} · 10년 창 {nwin}개 "
          f"(시작 {str(idx[ms[0]].date())[:7]} ~ {str(idx[ms[nwin - 1]].date())[:7]}) · 월 {MONTHLY}만원 · 원금 {만(MONTHLY * H)}")
    res = {}
    for arm, name in (("B1", "멈춤"), ("B2", "멈춤·몰아넣기")):
        rows = []
        for w in range(nwin):
            cs = ms[w:w + H]; E = ms[w + H] - 1
            wa, _, _ = sim(price, C, cs, E, "A")
            wb, st, _ = sim(price, C, cs, E, arm)
            if not st:
                continue
            cheap = float(np.mean(price[st]) / np.mean(price[cs]) - 1)
            rows.append(dict(w=w, 시작=str(idx[cs[0]].date())[:7], 끝=str(idx[E].date()), A=wa * MONTHLY, B=wb * MONTHLY,
                             r=wb / wa, 멈춘달=len(st), 싼정도=cheap))
        r = np.array([x["r"] for x in rows])
        mid = rows[int(np.argsort(r)[len(r) // 2])]
        wi = rows[int(np.argmin(r))]; bi = rows[int(np.argmax(r))]
        cheaper = sum(1 for x in rows if x["싼정도"] < 0)
        print(f"  {name}: 멈춘 적 있는 창 {len(rows)}번 중 계속이 더 남음 {int((r < 1).sum())}번 · {name}이 더 남음 {int((r > 1).sum())}번")
        print(f"    가운데쯤(끝 잔고 비율 순서 한가운데): {mid['시작']} 시작 → {mid['끝']} · 계속 {만(mid['A'])} · {name} {만(mid['B'])} · 차 {만(round(mid['A']) - round(mid['B']))} · 멈춘 달 {mid['멈춘달']}번")
        print(f"    {name}에 가장 불리: {wi['시작']} 시작 · 계속 {만(wi['A'])} · {name} {만(wi['B'])}")
        print(f"    {name}에 가장 유리: {bi['시작']} 시작 · 계속 {만(bi['A'])} · {name} {만(bi['B'])}")
        print(f"    멈춘 달 평균 가격이 그 10년 납입일 평균 가격보다 쌌던 창: {cheaper}/{len(rows)} · "
              f"얼마나 쌌나(창별 값의 한가운데) {np.median([x['싼정도'] for x in rows]) * 100:+.1f}%")
        res[arm] = dict(rows=rows, mid=mid)
    return res, (idx, price, C, ms)


def path_of(fn, cash_mode, start):
    p = load(fn); idx = p.index; price = p.values; C = cash_index(idx, cash_mode)
    ms = month_starts(idx); ms = ms[ms >= 1]
    w = [i for i, c in enumerate(ms) if str(idx[c].date())[:7] == start][0]
    cs = ms[w:w + H]; E = ms[w + H] - 1
    out = {}
    for arm in ("A", "B1"):
        pa = []
        _, st, _ = sim(price, C, cs, E, arm, path=pa)
        out[arm] = pa; out[arm + "_멈춤"] = [str(idx[c].date())[:7] for c in st]
    labels = [str(idx[c].date())[:7] for c in cs] + [str(idx[E].date())]
    return labels, out


if __name__ == "__main__":
    k3, _ = run("KODEX200 · 쉬는 돈 연 3% 이자", "069500_splice_1d.csv", 0.03)
    k0, _ = run("KODEX200 · 쉬는 돈 이자 0%", "069500_splice_1d.csv", 0.0)
    sp, _ = run("S&P500 인덱스펀드 VFINX · 달러 · 쉬는 돈 미국 3개월 국채 이자", "VFINX_1d.csv", "irx")

    # 가운데쯤 장면(KODEX200 · 3%) — 12달마다 계좌 평가액(전날 종가 · 만원)
    s0 = k3["B1"]["mid"]["시작"]
    labels, pth = path_of("069500_splice_1d.csv", 0.03, s0)
    print(f"\n[장면] KODEX200 · {s0} 시작 · 12달마다 평가액(만원) — 계속 / 멈춤")
    for i in list(range(0, len(labels), 12)) + ([len(labels) - 1] if (len(labels) - 1) % 12 else []):
        print(f"  {labels[i]}  {pth['A'][i] * MONTHLY:,.0f} / {pth['B1'][i] * MONTHLY:,.0f}")
    print(f"  멈춘 달({len(pth['B1_멈춤'])}번): {' '.join(pth['B1_멈춤'])}")
    print("  월별경로 계속: " + " ".join(f"{labels[i]}={pth['A'][i] * MONTHLY:.0f}" for i in range(len(labels))))
    print("  월별경로 멈춤: " + " ".join(f"{labels[i]}={pth['B1'][i] * MONTHLY:.0f}" for i in range(len(labels))))

    # 올해 3월부터 KODEX200 매달 50만원 — ASOF 종가 평가
    p = load("069500_splice_1d.csv"); idx = p.index; price = p.values; C = cash_index(idx, 0.03)
    ms = month_starts(idx); ms = ms[ms >= 1]
    cs = [c for c in ms if str(idx[c].date()) >= "2026-03-01"]
    print(f"\n[올해 3월부터] KODEX200 매달 {MONTHLY}만원 · 납입 {len(cs)}번({str(idx[cs[0]].date())} ~ {str(idx[cs[-1]].date())}) · {ASOF} 종가 평가")
    for arm, name in (("A", "계속"), ("B1", "멈춤")):
        v, st, _ = sim(price, C, cs, len(price) - 1, arm)
        print(f"  {name}: 원금 {만(len(cs) * MONTHLY)} · 평가 {v * MONTHLY:,.1f}만원 · {(v / len(cs) - 1) * 100:+.2f}% · 멈춘 달 {[str(idx[c].date())[:7] for c in st]}")
    print("\n끝.")
