# -*- coding: utf-8 -*-
"""
국내주식·미국주식·국내ETF(대용)·가상자산 상관관계와 투자전략 실증분석
— 생성형 AI(Gemini)가 제시한 상관계수·전략·'샘플 백테스트' 수치를 실제 시세로 검증

실행: python -X utf8 corr_model.py
입력: ../01_로우데이터/*_weekly.csv  (Alpha Vantage 주간 시세, 2026-10-16 수집)
출력: outputs/ (표 CSV, 그림 PNG, key_results.json)
작성: 조봉수 / AI(Claude) 협업 — 원자료·코드 공개, 투자 권유 아님
"""
import json, os, warnings
import numpy as np, pandas as pd
import matplotlib; matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm
warnings.filterwarnings("ignore")

HERE = os.path.dirname(os.path.abspath(__file__))
RAW = os.path.join(HERE, "..", "01_로우데이터"); OUT = os.path.join(HERE, "outputs"); os.makedirs(OUT, exist_ok=True)
for p in ["/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", "C:/Windows/Fonts/malgun.ttf"]:
    if os.path.exists(p): fm.fontManager.addfont(p); plt.rcParams["font.family"] = fm.FontProperties(fname=p).get_name(); break
plt.rcParams["axes.unicode_minus"] = False
C = {"US": "#1F4E79", "KR": "#C0392B", "BOND": "#2E8B57", "CRYPTO": "#E08E0B", "CASH": "#7F8C8D", "GOLD": "#B8860B", "NDX": "#5B8DB8"}

# ── 1. 자료: 주간(금요일 기준) 원화 환산
SYM = {"US": "SPY", "NDX": "QQQ", "KR": "EWY", "BOND": "IEF", "GOLD": "GLD", "BILL": "BIL", "CRYPTO": "BTC"}
LABEL = {"US": "미국주식(S&P500·SPY)", "NDX": "미국기술주(나스닥100·QQQ)", "KR": "국내주식(MSCI Korea·EWY)",
         "BOND": "미국채 7-10년(IEF)", "GOLD": "금(GLD)", "CRYPTO": "비트코인(BTC)", "USD": "달러(USD/KRW)"}

def load(sym):
    d = pd.read_csv(os.path.join(RAW, f"{sym}_weekly.csv"), parse_dates=["timestamp"]).set_index("timestamp").sort_index()
    if sym == "BTC":   # 비트코인 주봉은 일요일 마감 → 같은 주(월~금) 금요일 칸에 넣는다 (한 주 밀림 방지)
        d.index = d.index - pd.Timedelta(days=2)
    if "adjusted close" in d:      # 배당 반영: 고가·저가도 같은 비율로 조정
        f = d["adjusted close"] / d["close"]; d["high"] *= f; d["low"] *= f; d["close"] = d["adjusted close"]
    return d[["high", "low", "close"]].resample("W-FRI").last()

fx = load("USDKRW")
px = {k: load(s) for k, s in SYM.items()}
start = max(fx.dropna().index.min(), px["CRYPTO"].dropna().index.min())
krw, usd, hi, lo = {}, {}, {}, {}
for k, d in px.items():
    usd[k] = d["close"]
    if k == "BILL": continue
    krw[k] = d["close"] * fx["close"]; hi[k] = d["high"] * fx["close"]; lo[k] = d["low"] * fx["close"]
krw["USD"] = fx["close"]; usd["USD"] = fx["close"]
P = pd.DataFrame(krw).loc[start:].dropna()
PU = pd.DataFrame(usd).loc[start:].dropna()
H = pd.DataFrame(hi).loc[P.index]; L = pd.DataFrame(lo).loc[P.index]
BILL = pd.DataFrame(usd)["BILL"].reindex(P.index).ffill()
cash_r = BILL.pct_change().fillna(0)          # 현금 수익률: 미국 단기국채(BIL) 원화 미환산 = 단기금리 대용
R = P.pct_change().dropna(); RU = PU.pct_change().dropna()

# ── 2. 상관관계
ASSETS = ["KR", "US", "NDX", "CRYPTO", "BOND", "GOLD", "USD"]
def corr_tbl(r, a, b):
    return r.loc[a:b, ASSETS].corr().round(3)
periods = {"전체(2014.11~2026.10)": (None, None), "2015~2019": ("2015-01-01", "2019-12-31"),
           "2020~2024(Gemini 검증구간)": ("2020-01-01", "2024-12-31"), "2025~2026.10": ("2025-01-01", None)}
corr_out = {}
for name, (a, b) in periods.items():
    corr_out[name] = {"KRW": corr_tbl(R, a, b), "USD": corr_tbl(RU.drop(columns=["BILL"], errors="ignore"), a, b)}
    corr_out[name]["KRW"].to_csv(os.path.join(OUT, f"T1_상관_{name.split('(')[0].replace('~', '-')}_원화.csv"), encoding="utf-8-sig")
    corr_out[name]["USD"].to_csv(os.path.join(OUT, f"T1_상관_{name.split('(')[0].replace('~', '-')}_달러.csv"), encoding="utf-8-sig")

GEM = {("KR", "US"): (0.5, 0.7), ("US", "CRYPTO"): (0.4, 0.6), ("KR", "CRYPTO"): (0.2, 0.3)}
check = []
for (a, b), (lo_, hi_) in GEM.items():
    for base in ["KRW", "USD"]:
        v = float(corr_out["2020~2024(Gemini 검증구간)"][base].loc[a, b]); vf = float(corr_out["전체(2014.11~2026.10)"][base].loc[a, b])
        v19 = float(corr_out["2015~2019"][base].loc[a, b])
        check.append({"짝": f"{a}-{b}", "기준통화": base, "Gemini_범위": f"{lo_}~{hi_}", "2015~2019": round(v19, 3),
                      "2020~2024": round(v, 3), "전체": round(vf, 3), "Gemini_범위안(2020~24)": lo_ <= v <= hi_})
check = pd.DataFrame(check); check.to_csv(os.path.join(OUT, "T2_Gemini상관계수_검증.csv"), index=False, encoding="utf-8-sig")

# 52주 이동 상관
roll = pd.DataFrame({"국내-미국": R["KR"].rolling(52).corr(R["US"]), "미국-비트코인": R["US"].rolling(52).corr(R["CRYPTO"]),
                     "국내-비트코인": R["KR"].rolling(52).corr(R["CRYPTO"]), "미국-미국채": R["US"].rolling(52).corr(R["BOND"])}).dropna()
roll.to_csv(os.path.join(OUT, "T3_52주이동상관_원화.csv"), encoding="utf-8-sig")

# ── 3. 전략 백테스트 (주간)
W_STAR = {"US": .40, "KR": .20, "BOND": .25, "CRYPTO": .10, "CASH": .05}
BAND = {"US": .20, "KR": .20, "BOND": .20, "CRYPTO": .30}       # 상대 허용오차
COST = {"US": .001, "KR": .001, "BOND": .001, "CRYPTO": .0025, "CASH": 0.0}

def rsi(s, n=14):
    d = s.diff(); up = d.clip(lower=0).ewm(alpha=1 / n, adjust=False).mean(); dn = (-d.clip(upper=0)).ewm(alpha=1 / n, adjust=False).mean()
    return 100 - 100 / (1 + up / dn)
def atr(h, l, c, n=14):
    tr = pd.concat([h - l, (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1)
    return tr.ewm(alpha=1 / n, adjust=False).mean()
P_KRW, R_KRW, H_KRW, L_KRW = P, R, H, L
HU = pd.DataFrame({k: px[k]["high"] for k in px if k != "BILL"}).reindex(PU.index); LU = pd.DataFrame({k: px[k]["low"] for k in px if k != "BILL"}).reindex(PU.index)
def set_base(base):
    global P, R, H, L, RSI, ATR
    if base == "KRW": P, R, H, L = P_KRW, R_KRW, H_KRW, L_KRW
    else:
        P = PU.drop(columns=["BILL"]); R = P.pct_change().dropna(); H, L = HU, LU
    RSI = pd.DataFrame({k: rsi(P[k]) for k in ["US", "KR", "BOND", "CRYPTO"]})
    ATR = pd.DataFrame({k: atr(H[k], L[k], P[k]) for k in ["KR", "CRYPTO"]})
set_base("KRW")
def k_dca(x):
    if np.isnan(x): return 1.0
    return 1.5 if x <= 30 else 1.2 if x <= 45 else 1.0 if x < 60 else 0.7 if x < 70 else 0.5

def run(a, b, mode, v0=1e8, m=0.0, stop_frac=1.0, k_mult=2.5):
    """mode: 'B6040' | 'STATIC' | 'BAND' | 'BAND_STOP' | 'FULL'(BAND_STOP+DCA)"""
    idx = P.loc[a:b].index
    ret = R.reindex(idx).fillna(0); cr = cash_r.reindex(idx).fillna(0)
    assets = ["US", "BOND"] if mode == "B6040" else list(W_STAR)
    tgt = {"US": .6, "BOND": .4} if mode == "B6040" else W_STAR
    hold = {k: v0 * tgt[k] for k in assets}
    units, nav, flows, values, trades, costs = v0, [], [], [], 0, 0.0
    stopped = {"KR": False, "CRYPTO": False}; pmax = {"KR": P.loc[idx[0], "KR"], "CRYPTO": P.loc[idx[0], "CRYPTO"]}
    reserve = 0.0; log = []
    def trade(k, amt):
        nonlocal trades, costs
        if abs(amt) < 1: return
        c = abs(amt) * COST.get(k, 0); hold[k] += amt; costs += c; trades += 1
        hold["CASH" if "CASH" in hold else assets[0]] -= c
    def full_rebal(excl=()):
        V = sum(hold.values())
        live = {k: w for k, w in tgt.items() if k not in excl}
        extra = sum(w for k, w in tgt.items() if k in excl)
        for k in assets:
            want = V * (live.get(k, 0) + (extra if k == "CASH" else 0)) if k in live or k == "CASH" else 0
            if k in excl: want = 0
            trade(k, want - hold[k])
    for i, t in enumerate(idx):
        if i > 0:
            for k in assets:
                hold[k] *= 1 + (cr[t] if k == "CASH" else ret.loc[t, k])
            reserve *= 1 + cr[t]
        V = sum(hold.values()) + reserve
        # 자금 유입(월 첫 주)
        flow = 0.0
        if m > 0 and i > 0 and t.month != idx[i - 1].month:
            flow = m
            if mode in ("FULL", "DCA"):
                for k in ["US", "KR", "BOND", "CRYPTO"]:
                    if k in stopped and stopped[k]: reserve += m * W_STAR[k]; continue
                    kk = k_dca(RSI.loc[t, k]); amt = m * W_STAR[k] * min(kk, 1.0)
                    hold[k] += amt; reserve += m * W_STAR[k] * (1 - min(kk, 1.0))
                    if kk > 1.0 and reserve > 0:                      # 저평가: 적립해 둔 현금을 추가 투입
                        add = min(reserve, m * W_STAR[k] * (kk - 1.0)); hold[k] += add; reserve -= add
                hold["CASH"] += m * W_STAR["CASH"]
            else:
                for k in assets: hold[k] += m * tgt[k]
        # 손절(주간 종가 기준)
        if mode in ("BAND_STOP", "FULL"):
            for k in ["KR", "CRYPTO"]:
                if stopped[k]: continue
                pmax[k] = max(pmax[k], P.loc[t, k])
                if not np.isnan(ATR.loc[t, k]) and P.loc[t, k] < pmax[k] - k_mult * ATR.loc[t, k]:
                    amt = hold[k] * stop_frac; trade(k, -amt); hold["CASH"] += amt; stopped[k] = True; log.append((t, "손절", k))
        quarter_end = (i + 1 < len(idx) and idx[i + 1].quarter != t.quarter) or i + 1 == len(idx)
        if mode != "B6040":
            if quarter_end:
                hold["CASH"] += reserve; reserve = 0.0
                full_rebal(); stopped = {"KR": False, "CRYPTO": False}
                pmax = {"KR": P.loc[t, "KR"], "CRYPTO": P.loc[t, "CRYPTO"]}; log.append((t, "분기리밸런싱", ""))
            elif mode in ("BAND", "BAND_STOP", "FULL"):
                Vn = sum(hold.values())
                for k, th in BAND.items():
                    if mode != "BAND" and k in stopped and stopped[k]: continue
                    w = hold[k] / Vn
                    if abs(w - W_STAR[k]) > W_STAR[k] * th:
                        amt = Vn * W_STAR[k] - hold[k]; trade(k, amt); hold["CASH"] -= amt; log.append((t, "밴드", k))
                if hold["CASH"] < 0: full_rebal(excl=[k for k, s in stopped.items() if s])
        V = sum(hold.values()) + reserve
        if i == 0: units = V / 1.0
        else: units += flow / (nav[-1] if nav else 1.0)
        nav.append(V / units); flows.append(flow); values.append(V)
    s = pd.Series(nav, idx); return s, pd.Series(values, idx), pd.Series(flows, idx), trades, costs, log

def stats(nav, a, b):
    r = nav.pct_change().dropna(); crr = cash_r.reindex(r.index).fillna(0)
    yrs = (nav.index[-1] - nav.index[0]).days / 365.25
    cagr = nav.iloc[-1] ** (1 / yrs) - 1
    dd = nav / nav.cummax() - 1
    vol = r.std() * np.sqrt(52); sh = (r - crr).mean() / r.std() * np.sqrt(52)
    return {"CAGR": round(cagr * 100, 2), "MDD": round(dd.min() * 100, 2), "변동성": round(vol * 100, 2), "샤프": round(sh, 2)}

WINDOWS = {"Gemini구간(2020-01~2024-12)": ("2020-01-03", "2024-12-27"), "확장구간(2015-01~2026-10)": ("2015-01-02", "2026-10-16")}
MODES = [("B6040", "① 60/40 단순보유(미국주식60·미국채40)"), ("STATIC", "② 5자산 고정비중·분기 리밸런싱"),
         ("BAND", "③ ②+허용오차 밴드"), ("BAND_STOP", "④ ③+ATR 손절(국내·비트코인)"), ("FULL", "⑤ ④+RSI 가중 적립식(Gemini 동적 모델)")]
res, navs = [], {}
for base in ["KRW", "USD"]:
  set_base(base)
  for wn0, (a, b) in WINDOWS.items():
    wn = wn0 if base == "KRW" else wn0 + "·달러기준"
    for mode, lab in MODES:
        m = 0.0     # 거치식 비교(1억 원). 적립 규칙은 T8 적립식 시나리오에서 따로 검증
        nav, val, fl, ntr, cst, lg = run(a, b, mode, v0=1e8, m=m)
        st = stats(nav, a, b); yrs = (nav.index[-1] - nav.index[0]).days / 365.25
        st.update({"구간": wn, "전략": lab, "연평균거래횟수": round(ntr / yrs, 1), "누적비용(원)": int(cst),
                   "손절횟수": sum(1 for x in lg if x[1] == "손절"), "밴드발동": sum(1 for x in lg if x[1] == "밴드")})
        res.append(st); navs[(wn, mode)] = nav
set_base("KRW")
RES = pd.DataFrame(res)[["구간", "전략", "CAGR", "MDD", "변동성", "샤프", "연평균거래횟수", "손절횟수", "밴드발동", "누적비용(원)"]]
RES.to_csv(os.path.join(OUT, "T4_백테스트_성과.csv"), index=False, encoding="utf-8-sig")

def irr_monthly(values, flows, v0):
    cf = [-v0] + [-f for f in flows.iloc[1:]]; cf[-1] += values.iloc[-1]
    w = np.array(cf)
    lo_, hi_ = -0.99, 1.0
    for _ in range(200):
        r_ = (lo_ + hi_) / 2; npv = sum(c / (1 + r_) ** (i / 52) for i, c in enumerate(w))
        lo_, hi_ = (r_, hi_) if npv > 0 else (lo_, r_)
    return r_
acc = []
for wn, (a, b) in WINDOWS.items():
    for mode, lab in [("STATIC", "정액 적립(목표비중 그대로)"), ("DCA", "RSI 가중 적립만(분기 리밸런싱)"), ("FULL", "RSI 가중 적립 + 밴드 + 손절(Gemini 동적 모델)")]:
        nav, val, fl, ntr, cst, lg = run(a, b, mode, v0=1e6, m=1e6)
        paid = 1e6 + fl.sum()
        acc.append({"구간": wn, "방식": lab, "납입원금(만원)": round(paid / 1e4), "최종평가액(만원)": round(val.iloc[-1] / 1e4),
                    "평가액/원금": round(val.iloc[-1] / paid, 3), "연환산 금액가중수익률(%)": round(irr_monthly(val, fl, 1e6) * 100, 2),
                    "MDD(시간가중,%)": stats(nav, a, b)["MDD"]})
ACC = pd.DataFrame(acc); ACC.to_csv(os.path.join(OUT, "T8_적립식_비교.csv"), index=False, encoding="utf-8-sig")

GEMC = {"① 60/40": {"CAGR": 8.4, "MDD": -24.5, "샤프": .65, "변동성": 14.2}, "⑤ 동적모델": {"CAGR": 13.8, "MDD": -11.2, "샤프": 1.28, "변동성": 9.8}}
g = RES[RES["구간"] == "Gemini구간(2020-01~2024-12)"].set_index("전략")
gu = RES[RES["구간"] == "Gemini구간(2020-01~2024-12)·달러기준"].set_index("전략")
vs = pd.DataFrame([{"전략": k, "지표": m_, "Gemini_제시": GEMC[k][m_], "실제_원화": g.loc[MODES[0][1] if k.startswith("①") else MODES[4][1], m_],
                    "실제_달러": gu.loc[MODES[0][1] if k.startswith("①") else MODES[4][1], m_]}
                   for k in GEMC for m_ in ["CAGR", "MDD", "변동성", "샤프"]])
vs["실제_검증"] = vs["실제_원화"]
vs["차이_원화"] = (vs["실제_원화"] - vs["Gemini_제시"]).round(2); vs["차이_달러"] = (vs["실제_달러"] - vs["Gemini_제시"]).round(2)
vs.to_csv(os.path.join(OUT, "T5_Gemini성과주장_대조.csv"), index=False, encoding="utf-8-sig")

# 2022년 하락장 시나리오
y22 = {}
for mode, lab in MODES:
    n = navs[("Gemini구간(2020-01~2024-12)", mode)].loc["2022-01-01":"2022-12-31"]
    y22[lab] = {"2022_수익률": round((n.iloc[-1] / n.iloc[0] - 1) * 100, 2), "2022_최대낙폭": round((n / n.cummax() - 1).min() * 100, 2)}
pd.DataFrame(y22).T.to_csv(os.path.join(OUT, "T6_2022하락장.csv"), encoding="utf-8-sig")
mkt22 = {k: round((P.loc["2022-12-30", k] / P.loc["2021-12-31", k] - 1) * 100, 1) for k in ["US", "NDX", "KR", "CRYPTO", "BOND"]}

# 민감도: 손절 배수·청산비율, 현금 0%
sens = []
a, b = WINDOWS["Gemini구간(2020-01~2024-12)"]
for km in [2.0, 2.5, 3.0]:
    for sf in [0.5, 1.0]:
        nav, *_ = run(a, b, "BAND_STOP", k_mult=km, stop_frac=sf); s_ = stats(nav, a, b); s_.update({"ATR배수": km, "청산비율": sf}); sens.append(s_)
pd.DataFrame(sens).to_csv(os.path.join(OUT, "T7_손절민감도.csv"), index=False, encoding="utf-8-sig")

# ── 4. 그림
fig, ax = plt.subplots(figsize=(9, 4.6), dpi=150)
for col, c in zip(roll.columns, [C["KR"], C["CRYPTO"], "#8E44AD", C["BOND"]]): ax.plot(roll.index, roll[col], lw=1.6, color=c, label=col)
ax.axhline(0, color="grey", lw=.8); ax.axvspan(pd.Timestamp("2020-01-01"), pd.Timestamp("2024-12-31"), color="#F2E6D0", alpha=.5, label="Gemini 검증구간")
ax.set_title("그림 1. 52주 이동 상관계수(원화 기준 주간수익률)"); ax.set_ylim(-.8, 1); ax.legend(ncol=3, fontsize=8, frameon=False); ax.grid(alpha=.25)
fig.tight_layout(); fig.savefig(os.path.join(OUT, "fig1_rolling_corr.png")); plt.close(fig)

cm = corr_out["2020~2024(Gemini 검증구간)"]["KRW"]
fig, ax = plt.subplots(figsize=(7, 6), dpi=150); im = ax.imshow(cm.values, cmap="RdBu_r", vmin=-1, vmax=1)
lab_short = {"KR": "국내주식", "US": "미국주식", "NDX": "나스닥100", "CRYPTO": "비트코인", "BOND": "미국채", "GOLD": "금", "USD": "달러"}
ax.set_xticks(range(len(cm))); ax.set_yticks(range(len(cm))); ax.set_xticklabels([lab_short[x] for x in cm.columns], rotation=45); ax.set_yticklabels([lab_short[x] for x in cm.index])
for i in range(len(cm)):
    for j in range(len(cm)): ax.text(j, i, f"{cm.values[i, j]:.2f}", ha="center", va="center", fontsize=8, color="white" if abs(cm.values[i, j]) > .5 else "black")
ax.set_title("그림 2. 상관계수 행렬 (2020~2024, 원화 기준 주간)"); fig.colorbar(im, fraction=.046); fig.tight_layout(); fig.savefig(os.path.join(OUT, "fig2_corr_matrix.png")); plt.close(fig)

fig, ax = plt.subplots(figsize=(9, 4.8), dpi=150)
for (mode, lab), c in zip(MODES, ["#7F8C8D", "#1F4E79", "#2E8B57", "#C0392B", "#E08E0B"]):
    n = navs[("Gemini구간(2020-01~2024-12)", mode)]; ax.plot(n.index, n / n.iloc[0], lw=1.6, color=c, label=lab)
ax.set_title("그림 3. 전략별 누적 성과(2020~2024, 원화, 시간가중)"); ax.legend(fontsize=7.5, frameon=False); ax.grid(alpha=.25); ax.set_ylabel("시작=1")
fig.tight_layout(); fig.savefig(os.path.join(OUT, "fig3_backtest.png")); plt.close(fig)

fig, ax = plt.subplots(figsize=(8, 4.2), dpi=150)
x = np.arange(4); mets = ["CAGR", "MDD", "변동성", "샤프"]
gv = [GEMC["⑤ 동적모델"][m_] for m_ in mets]; rv = [g.loc[MODES[4][1], m_] for m_ in mets]
ax.bar(x - .2, gv, .4, color="#BBBBBB", label="Gemini가 제시한 값"); ax.bar(x + .2, rv, .4, color="#E08E0B", label="실제 시세로 검증한 값")
for xi, (a_, b_) in enumerate(zip(gv, rv)): ax.text(xi - .2, a_, f"{a_}", ha="center", va="bottom" if a_ >= 0 else "top", fontsize=8); ax.text(xi + .2, b_, f"{b_}", ha="center", va="bottom" if b_ >= 0 else "top", fontsize=8)
ax.set_xticks(x); ax.set_xticklabels(["연평균수익률(%)", "최대낙폭(%)", "변동성(%)", "샤프지수"]); ax.axhline(0, color="black", lw=.6)
ax.set_title("그림 4. Gemini '동적 모델' 주장 대 실제 검증 (2020~2024)"); ax.legend(frameon=False, fontsize=8); ax.grid(axis="y", alpha=.25)
fig.tight_layout(); fig.savefig(os.path.join(OUT, "fig4_gemini_vs_real.png")); plt.close(fig)

K = {"자료": {"출처": "Alpha Vantage 주간 시세(2026-10-16 수집)", "구간": [str(P.index.min().date()), str(P.index.max().date())], "주수": len(P)},
     "상관검증": check.to_dict("records"), "백테스트": RES.to_dict("records"), "Gemini대조": vs.to_dict("records"), "적립식": ACC.to_dict("records"),
     "2022": y22, "2022_시장": mkt22, "손절민감도": sens,
     "상관_2020_24_원화": cm.to_dict(), "상관_2015_19_원화": corr_out["2015~2019"]["KRW"].to_dict(), "상관_전체_원화": corr_out["전체(2014.11~2026.10)"]["KRW"].to_dict()}
json.dump(K, open(os.path.join(OUT, "key_results.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1, default=str)
print(check.to_string()); print(RES.to_string()); print(vs.to_string()); print(pd.DataFrame(y22).T); print(mkt22); print(pd.DataFrame(sens)); print(ACC.to_string())
print(cm.to_string()); print(corr_out["2015~2019"]["KRW"].to_string())
pk = {c: (round(float(roll[c].max()), 3), str(roll[c].idxmax().date()), round(float(roll[c].min()), 3)) for c in roll}; print(pk)
K["이동상관_최대최소"] = pk; json.dump(K, open(os.path.join(OUT, "key_results.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1, default=str)
