# -*- coding: utf-8 -*-
"""금만 들고 있을까, 금은비 80 에 은으로 반 옮길까 — 알고랩 재현 코드 (2026-10-20 영상).

규칙  : 금은비(미국 금 선물 종가 ÷ 은 선물 종가)가 80 을 넘으면 은 50% · 65 밑으로 내려오면 은 0%(금 100%) ·
        그 사이에서는 그대로. (2016년 1월 시작 때는 2015년 8월에 이미 80 을 넘은 상태라 은 50%)
상품  : KODEX 골드선물(H) 132030 · KODEX 은선물(H) 144600 (둘 다 분배 없음 · 종가)
시점  : 국내 날짜 D 의 장 마감에 'D 보다 앞선 마지막 미국 종가' 로 판정해 옮기고, D+1 수익부터 적용.
        목표가 바뀌는 날 + 매달 첫 거래일에 목표 비중으로 맞춤. 옮긴 금액 × 편도 0.1% × 2(팔고 사기).
자료  : data_goldsilver/*.csv — 야후 종가(2026-10-16 까지 얼림). GC_F·SI_F = 금·은 선물 · GLD·SLV = 미국 상장 ETF.
돌리는 법: python run_goldsilver.py   (표준 라이브러리만 씁니다)
"""
import csv
import datetime
import os

HERE = os.path.dirname(os.path.abspath(__file__))
D = os.path.join(HERE, "data_goldsilver")
ASOF = "2026-10-16"          # 기준일 고정 — 영상과 같은 숫자가 나온다
FEE = 0.001
HIGH, LOW, HALF = 80, 65, 0.5
START = "2016-01-01"


def load(name):
    with open(os.path.join(D, name + ".csv"), encoding="utf-8") as f:
        return {r["date"]: float(r["close"]) for r in csv.DictReader(f) if r["close"] and r["date"] <= ASOF}


def inner(*series):
    ds = sorted(set.intersection(*[set(s) for s in series]))
    return ds, [[s[d] for d in ds] for s in series]


def rets(c):
    return [0.0] + [c[i] / c[i - 1] - 1 for i in range(1, len(c))]


def fdate(s):
    return datetime.date.fromisoformat(s)


def stats(eq, dates):
    yrs = (fdate(dates[-1]) - fdate(dates[0])).days / 365.25
    cagr = eq[-1] ** (1 / yrs) - 1
    peak, mdd, mday = eq[0], 0.0, dates[0]
    for e, d in zip(eq, dates):
        peak = max(peak, e)
        if e / peak - 1 < mdd:
            mdd, mday = e / peak - 1, d
    return cagr * 100, mdd * 100, mday


def run_target(tgt, rg, rs, dates):
    a, b = 1 - tgt[0], tgt[0]
    prev, lastm = tgt[0], dates[0][5:7]
    eq, moves = [], []
    for i in range(len(rg)):
        m = dates[i][5:7]
        if tgt[i] != prev or m != lastm:
            tot = a + b
            p = tgt[i]
            moved = abs(b - tot * p)
            if tgt[i] != prev:
                moves.append(i)
            tot -= moved * FEE * 2
            a, b = tot * (1 - p), tot * p
            prev, lastm = tgt[i], m
        a *= 1 + rg[i]
        b *= 1 + rs[i]
        eq.append(a + b)
    return eq, moves


def cumprod(r):
    out, x = [], 1.0
    for v in r:
        x *= 1 + v
        out.append(x)
    return out


# ── 금은비 신호(미국 날짜 기준) ──
fd, (gc, si) = inner(load("GC_F"), load("SI_F"))
ratio = [g / s for g, s in zip(gc, si)]
state, cur = [], 0.0
for x in ratio:
    if x > HIGH:
        cur = 1.0
    elif x < LOW:
        cur = 0.0
    state.append(cur)


def known_before(d):
    """국내 날짜 d 에 알 수 있는 상태 = d 보다 앞선 마지막 미국 날짜의 상태."""
    lo, hi = 0, len(fd)
    while lo < hi:
        mid = (lo + hi) // 2
        if fd[mid] < d:
            lo = mid + 1
        else:
            hi = mid
    return (state[lo - 1], ratio[lo - 1], fd[lo - 1]) if lo > 0 else (0.0, None, None)


# ── 국내 판 ──
kd, (kg, ks) = inner(load("132030_KS"), load("144600_KS"))
rg_all, rs_all = rets(kg), rets(ks)
known = [known_before(d) for d in kd]
lag = [0.0] + [k[0] for k in known[:-1]]          # D 종가에 옮기고 D+1 수익부터


def man(x):
    return f"{round(x * 1000):,}만원"


def domestic(start, half=HALF, zero=()):
    idx = [i for i, d in enumerate(kd) if d >= start]
    dates = [kd[i] for i in idx]
    rg = [rg_all[i] for i in idx]
    rs = [rs_all[i] for i in idx]
    for a, b in zero:
        for j, d in enumerate(dates):
            if a <= d <= b:
                rg[j] = rs[j] = 0.0
    t = [half if lag[i] > 0.5 else 0.0 for i in idx]
    eq, mv = run_target(t, rg, rs, dates)
    gold = cumprod(rg)
    hh, _ = run_target([0.5] * len(t), rg, rs, dates)
    p = sum(t) / len(t)
    mix, _ = run_target([p] * len(t), rg, rs, dates)
    sw = [(dates[j], t[j], known[idx[j] - 1][1], known[idx[j] - 1][2]) for j in mv]
    return dict(dates=dates, eq=eq, gold=gold, hh=hh, mix=mix, p=p, sw=sw)


def line(name, eq, dates):
    c, m, md = stats(eq, dates)
    return f"  {name:<22s} 1,000만원 → {man(eq[-1])}  (연 {c:.2f}% · 가장 크게 떨어졌을 때 {m:.2f}% {md})"


print("금은비 절반 갈아타기 — 알고랩 재현 (기준일 %s)" % ASOF)
print("규칙: 금은비 %d 위 → 은 %d%% · %d 밑 → 금 100%% · 사이 그대로\n" % (HIGH, HALF * 100, LOW))

R = domestic(START)
d = R["dates"]
print(f"[본편] 국내 KODEX 골드선물(H)·은선물(H) {d[0]} ~ {d[-1]} ({(fdate(d[-1]) - fdate(d[0])).days / 365.25:.1f}년)")
print(line("금만 그대로", R["gold"], d))
print(line("금은비 규칙(절반)", R["eq"], d))
print(line("반반 고정(매달 맞춤)", R["hh"], d))
print(line(f"은 {R['p'] * 100:.1f}% 고정", R["mix"], d))
print(f"  갈아탄 날 {len(R['sw'])}번:")
for day, w, r, us in R["sw"]:
    print(f"    {day}  은 {w * 100:.0f}% 로 (금은비 {r:.1f} · 미국 {us} 종가)")
print(f"  시작 때 상태: 은 {HALF * 100 if lag[kd.index(d[0])] > 0.5 else 0:.0f}%")

print("\n[연말 잔고] 금은비 규칙 · 금만 (만원)")
yr = {}
for dd, e, g in zip(d, R["eq"], R["gold"]):
    yr[dd[:4]] = (e, g)
print("  " + " · ".join(f"{y} {round(e * 1000):,}/{round(g * 1000):,}" for y, (e, g) in yr.items()))

print("\n[반론] 큰 두 번을 빼면 (그 기간 수익을 모든 계좌에서 0 으로)")
for nm, z in (("2020-02-15~2020-08-31 빼기", [("2020-02-15", "2020-08-31")]),
              ("2025년 빼기", [("2025-01-01", "2025-12-31")]),
              ("둘 다 빼기", [("2020-02-15", "2020-08-31"), ("2025-01-01", "2025-12-31")])):
    X = domestic(START, zero=z)
    print(f"  {nm:<26s} 금만 {man(X['gold'][-1])} · 규칙 {man(X['eq'][-1])}")

X = domestic(START, half=1.0)
print("\n[다른 판] 절반 대신 전부 옮기기: " + line("", X["eq"], X["dates"]).strip())
X = domestic("2011-07-18")
print("[다른 판] 국내 상장일부터: " + f"{X['dates'][0]}~ 금만 {man(X['gold'][-1])} · 규칙 {man(X['eq'][-1])}")

# ── 미국 판(달러 · t 종가 판정 → t+1 수익) ──
ud, (ugc, usi, gld, slv) = inner(load("GC_F"), load("SI_F"), load("GLD"), load("SLV"))
keep = [i for i, x in enumerate(ud) if x >= START]
ud = [ud[i] for i in keep]
rg, rs = rets([gld[i] for i in keep]), rets([slv[i] for i in keep])
st = {x: s for x, s in zip(fd, state)}
t = [0.0] + [HALF if st[x] > 0.5 else 0.0 for x in ud[:-1]]
eq, mv = run_target(t, rg, rs, ud)
g = cumprod(rg)
c1, m1, _ = stats(eq, ud)
c2, m2, _ = stats(g, ud)
uh, _ = run_target([0.5] * len(t), rg, rs, ud)
c3, m3, _ = stats(uh, ud)
print(f"\n[미국 판] GLD·SLV {ud[0]} ~ {ud[-1]} (달러 기준 · 1,000 → )")
print(f"  금(GLD)만 {round(g[-1] * 1000):,} (최대 {m2:.2f}%) · 금은비 규칙 {round(eq[-1] * 1000):,} (최대 {m1:.2f}%) · 갈아타기 {len(mv)}번")
print(f"  반반 고정(매달 맞춤) {round(uh[-1] * 1000):,} (최대 {m3:.2f}%)")

# ── 문턱 이웃: 80·65 근처 다른 숫자(절반 옮기기 · 국내 2016~) ──
print(f"\n[문턱 이웃] 위 문턱 H · 아래 문턱 L 을 바꿔 같은 계산 (금만 그대로 {man(R['gold'][-1])})")
win = n = 0
idx = [i for i, d in enumerate(kd) if d >= START]
dd = [kd[i] for i in idx]
for H in (70, 75, 80, 85, 90):
    row = []
    for L in (50, 55, 60, 65):
        st2, c2 = [], 0.0
        for x in ratio:
            if x > H:
                c2 = 1.0
            elif x < L:
                c2 = 0.0
            st2.append(c2)
        stmap = dict(zip(fd, st2))
        k2 = [stmap[kn[2]] if kn[2] else 0.0 for kn in known]
        lag2 = [0.0] + k2[:-1]
        t2 = [HALF if lag2[i] > 0.5 else 0.0 for i in idx]
        e2, m2 = run_target(t2, [rg_all[i] for i in idx], [rs_all[i] for i in idx], dd)
        n += 1
        win += e2[-1] > R["gold"][-1]
        row.append(f"L{L} {round(e2[-1] * 1000):,}만({len(m2)}번·{stats(e2, dd)[1]:.1f}%)")
    print(f"  H{H}: " + " · ".join(row))
print(f"  {n}개 중 금만 그대로보다 더 남은 것 {win}개")

r16 = [(r, d) for r, d in zip(ratio, fd) if d >= START]
hi, lo = max(r16), min(r16)
print(f"\n[금은비 2016~] 가장 높을 때 {hi[0]:.1f} ({hi[1]}) · 가장 낮을 때 {lo[0]:.1f} ({lo[1]})")
print(f"\n[지금] {fd[-1]} 금은비 {ratio[-1]:.1f} → 규칙상 {'은 50%' if state[-1] > 0.5 else '금 100%'}")
print("끝.")
