# -*- coding: utf-8 -*-
"""알고랩 재현 코드 — 1,000만원을 달러로: 한 번에 바꿀까, 1년 동안 매달 나눠 바꿀까.

돌리는 법: 이 파일 옆에 data_dollarsplit/ 폴더를 두고
    pip install numpy
    python run_dollarsplit.py

자료(2026-10-09 에 얼림):
    data_dollarsplit/KRW_X_1d.csv             야후 KRW=X 일봉(원/달러 종가)
    data_dollarsplit/fred_TB3MS.csv           미국 3개월 국채 금리(월평균, 연%)  — FRED TB3MS
    data_dollarsplit/fred_IR3TIB01KRM156N.csv 한국 3개월 금리(월, 연%)          — FRED/OECD IR3TIB01KRM156N

규칙:
    - 1년 = 252거래일. 한 번에 = 첫날 1,000만원을 그날 종가로 전부 달러로 바꾼다.
    - 매달 = 21거래일 간격으로 12번, 같은 금액(83만 3,333원)을 그날 종가로 바꾼다.
    - 아직 안 바꾼 원화는 한국 3개월 금리, 이미 바꾼 달러는 미국 3개월 금리로 하루 (연%/252) 씩 불어난다.
    - 1년 끝에 손에 쥔 달러를 비교한다.
    - 시작 날을 하루씩 옮긴다(2005-10 ~ 2025-10 시작). 시작한 달의 미국 금리가 한국보다 높으면 '미국이 높은 때'.
    - 환전 수수료·스프레드·세금은 넣지 않았다. 종가에 바꾼 것으로 친다. 달러는 사기만 하고 1년 안에 되팔지 않는다.
"""
import csv
import os

import numpy as np

HERE = os.path.dirname(os.path.abspath(__file__))
D = os.path.join(HERE, "data_dollarsplit")
ASOF = "2026-10-09"
PIN = ("2004-12-31", "2026-09-26")
WON = 10_000_000
L = 252


def load_fx():
    d = {}
    with open(os.path.join(D, "KRW_X_1d.csv"), encoding="utf-8") as f:
        for r in csv.DictReader(f):
            try:
                c = float(r["close"])
            except (ValueError, TypeError):
                continue
            if c > 0 and r["date"][:10] <= ASOF:
                d[r["date"][:10]] = c
    ks = sorted(d)
    x = np.array([d[k] for k in ks])
    # 하루 ±8% 넘게 튀고 다음 날 되돌아오는 나쁜 프린트는 앞뒤 평균으로(이 구간에서는 0건)
    r = np.diff(np.log(x))
    bad = [i + 1 for i in range(len(r) - 1) if abs(r[i]) > 0.08 and abs(r[i + 1]) > 0.08 and r[i] * r[i + 1] < 0]
    for i in bad:
        x[i] = (x[i - 1] + x[i + 1]) / 2
    return ks, x, len(bad)


def load_fred(name, col):
    d = {}
    with open(os.path.join(D, name), encoding="utf-8") as f:
        for r in csv.DictReader(f):
            try:
                d[r["observation_date"][:7]] = float(r[col])
            except ValueError:
                pass
    return d


def daily(ks, m):
    out, last = [], None
    for k in ks:
        v = m.get(k[:7], last)  # 아직 안 나온 달은 직전 달 값
        out.append(v)
        last = v
    return np.array(out, float)


def weights(k):
    w = np.zeros(L)
    for i in range(k):
        w[int(round(i * L / k))] += 1.0 / k
    return w


def comma(v):
    return f"{v:,.0f}"


ks_all, x_all, nbad = load_fx()
sel = [i for i, k in enumerate(ks_all) if PIN[0] <= k <= PIN[1]]
ks, x = [ks_all[i] for i in sel], x_all[sel]
N = len(x)
US = load_fred("fred_TB3MS.csv", "TB3MS")
KR = load_fred("fred_IR3TIB01KRM156N.csv", "IR3TIB01KRM156N")
ru, rk = daily(ks, US), daily(ks, KR)
CU = np.concatenate([[1.0], np.cumprod(1 + ru / 100 / 252)])[:N]
CK = np.concatenate([[1.0], np.cumprod(1 + rk / 100 / 252)])[:N]
starts = np.arange(200, N - L)


def run(wk, cu, ck, fx):
    v = []
    for s in starts:
        e = s + L - 1
        if wk is None:
            v.append(WON / fx[s] * cu[e] / cu[s])
        else:
            j = np.arange(s, s + L)
            v.append(WON * (wk * (ck[j] / ck[s]) / fx[j] * (cu[e] / cu[j])).sum())
    return np.array(v)


one, mon, four = run(None, CU, CK, x), run(weights(12), CU, CK, x), run(weights(4), CU, CK, x)
gap = (ru - rk)[starts]
us = gap > 0
kr = ~us
up = np.array([x[s + L - 1] > x[s] for s in starts])
win = one > mon

print(f"[자료] 원달러 {ks[0]} ~ {ks[-1]} · 나쁜 프린트 보정 {nbad}건 · 1년 창 시작일 {len(starts):,}개 "
      f"({ks[starts[0]]} ~ {ks[starts[-1]]})")
print(f"[금리] 가장 최근 — 미국 3개월 {max(US)} {US[max(US)]}% · 한국 3개월 {max(KR)} {KR[max(KR)]}%")
print()

# 미국이 높은 때가 이어진 시기
runs_, cur = [], None
for i in np.where(us)[0]:
    m = ks[starts[i]][:7]
    if cur and i == cur[2] + 1:
        cur[1], cur[2] = m, i
    else:
        if cur:
            runs_.append(cur[:2])
        cur = [m, m, i]
runs_.append(cur[:2])
print("[미국 금리가 한국보다 높았던 날에 시작] 시기: " + " · ".join(f"{a}~{b}" for a, b in runs_))
n = int(us.sum())
w = int(win[us].sum())
print(f"  1년 뒤 한 번에가 더 받음 {w:,}번 · 매달이 더 받음 {n - w:,}번 (모두 {n:,}번)")
print(f"  한 번에가 3달 간격 4번보다 더 받음 {int((one[us] > four[us]).sum()):,}번")
d = one - mon
mi = np.where(us)[0]
mid = mi[np.argsort(d[mi])][len(mi) // 2]
s = starts[mid]
print(f"  가운데쯤(한 번에−매달 차이 순서 한가운데): {ks[s]} 시작 → {ks[s + L - 1]} · "
      f"한 번에 {comma(one[mid])}달러 · 매달 {comma(mon[mid])}달러 · 차 {comma(one[mid] - mon[mid])}달러 "
      f"(첫날 환율 {x[s]:,.2f}원 · 끝날 {x[s + L - 1]:,.2f}원 · 그 달 3개월 금리 미국 {ru[s]:.2f}% · 한국 {rk[s]:.2f}%)")
print(f"  1년 사이 달러가 오른 시작일 {int(up[us].sum()):,}번 ({up[us].mean() * 100:.1f}%) — "
      f"그중 한 번에가 더 받음 {win[us & up].mean() * 100:.1f}% · 달러가 내린 해엔 {win[us & ~up].mean() * 100:.1f}%")
print("  해별 (달러가 오른 시작일 / 매달이 더 받은 시작일 / 시작일):")
for y in sorted({ks[starts[i]][:4] for i in mi}):
    m = np.array([ks[starts[i]].startswith(y) for i in range(len(starts))]) & us
    print(f"    {y}: {int(up[m].sum())} / {int((~win[m]).sum())} / {int(m.sum())}")
for y in ("2019-01", "2023-01", "2025-01"):
    c = [i for i in mi if ks[starts[i]].startswith(y)]
    if c:
        i = c[0]
        print(f"  {ks[starts[i]]} 시작: 한 번에 {comma(one[i])}달러 · 매달 {comma(mon[i])}달러 · 4번 {comma(four[i])}달러")
print()

n2 = int(kr.sum())
w2 = int(win[kr].sum())
print("[한국 금리가 같거나 더 높았던 날에 시작]")
print(f"  1년 뒤 한 번에가 더 받음 {w2:,}번 · 매달이 더 받음 {n2 - w2:,}번 (모두 {n2:,}번)")
print(f"  1년 사이 달러가 오른 시작일 {up[kr].mean() * 100:.1f}%")
print()

# 이자만 떼어 보기 — 환율이 1년 내내 그대로였다면
flat = np.ones(N)
one_f, mon_f = run(None, CU, CK, flat), run(weights(12), CU, CK, flat)
print("[이자 몫만 — 환율이 1년 내내 그대로였다고 치면]")
print(f"  미국이 높은 때 한 번에가 더 받음 {(one_f[us] > mon_f[us]).mean() * 100:.1f}% · "
      f"한국이 높은 때 {(one_f[kr] > mon_f[kr]).mean() * 100:.1f}%")
# 환율 몫만 — 이자를 빼면
ONE = np.ones(N)
one_n, mon_n = run(None, ONE, ONE, x), run(weights(12), ONE, ONE, x)
print("[환율 몫만 — 이자를 빼면]")
print(f"  미국이 높은 때 한 번에가 더 받음 {(one_n[us] > mon_n[us]).mean() * 100:.1f}% · "
      f"한국이 높은 때 {(one_n[kr] > mon_n[kr]).mean() * 100:.1f}%")
# 두 몫의 크기(달러) — 한 번에 − 매달 차이를 이자로 생긴 몫과 환율로 생긴 몫으로 가른다
int_part = ((one - one_n) - (mon - mon_n))[us]
fx_part = (one_n - mon_n)[us]
print("[두 몫의 크기 — 미국이 높은 때, 한 번에 − 매달 차이]")
print(f"  이자 몫 한가운데 {np.median(int_part):,.0f}달러 (크기만 {np.median(np.abs(int_part)):,.0f}달러) · "
      f"환율 몫 한가운데 {np.median(fx_part):,.0f}달러 (크기만 {np.median(np.abs(fx_part)):,.0f}달러)")
seg = x[starts[mid]:starts[mid] + L]
print(f"  가운데쯤 경우의 1년 경로: 가장 높았던 날 {ks[starts[mid] + int(np.argmax(seg))]} {seg.max():,.2f}원 · "
      f"가장 낮았던 값 {seg.min():,.2f}원")
_j = sorted(set(list(range(0, L, 5)) + [L - 1, int(np.argmax(seg)), int(np.argmin(seg))]))
print("  가운데쯤 경우의 환율 경로(5거래일마다 · 꼭대기·바닥 포함): "
      + " ".join(f"{ks[starts[mid] + j]}={seg[j]:.2f}" for j in _j))

# 판정 시점 점검 — 시작한 달의 월평균은 그날엔 다 안 나온 값이다. 지난달 값으로 다시 가른다
def prev_month(m):
    y, mo = int(m[:4]), int(m[5:7])
    y, mo = (y - 1, 12) if mo == 1 else (y, mo - 1)
    return f"{y:04d}-{mo:02d}"


lag = np.array([US.get(prev_month(ks[s][:7]), np.nan) - KR.get(prev_month(ks[s][:7]), np.nan) for s in starts])
ul = lag > 0
print("[판정 시점 점검 — 지난달 금리로 '미국이 높은 때'를 가르면]")
print(f"  미국이 높은 때 {int(ul.sum()):,}번 · 한 번에가 더 받음 {int(win[ul].sum()):,}번 ({win[ul].mean() * 100:.1f}%) · "
      f"그 달 값 판정과 갈린 시작일 {int((ul != us).sum()):,}개")
print()
print("끝.")
