# -*- coding: utf-8 -*-
# =============================================================================
# AlgoLab · 매매 비용 백테스트 — 공개용
#   영상 「같은 코스피, 같은 10년 — 3393만원 vs 12만원」의 숫자를 그대로 재현합니다.
#   믿지 말고 직접 돌려보세요. 세율·수수료·기간을 바꿔 여러분의 조건으로 실험해도 됩니다.
#
# 실행:  python trading_cost_backtest.py
# 필요:  Python 3.8+ / numpy  (pip install numpy)
#        데이터는 Yahoo Finance 공개 API 에서 자동으로 받습니다(API 키 불필요).
#
# ── 무엇을 계산하나 ──────────────────────────────────────────────────────────
# "매매를 잘해서 수익률이 달라지는 부분"은 계산에 넣지 않습니다.
# 같은 수익률(코스피 지수)을 냈다고 쳤을 때, **매매 횟수만 다를 때 비용으로 얼마나
# 갈리는가**를 봅니다. 사고판다 = 전부 팔고 곧바로 같은 걸 다시 산다(왕복 1회).
#
# ── 세율 근거 (2026-08-30 확인, 두 곳 교차) ──────────────────────────────────
#   · 코스피: 증권거래세 0.05% + 농어촌특별세 0.15% = 0.20% (2026년 1월 양도분부터)
#   · 코스닥: 0.20% (농특세 없음).  2025년까지는 둘 다 0.15%
#   · 위탁수수료: MTS 기준 0.014~0.015% → 여기서는 0.015% (매수·매도 각각)
#   · 국내 주식형 ETF: 증권거래세 면제 → 왕복 비용은 수수료뿐(0.03%)
#
# ── 이 계산의 한계 (영상에서도 밝힙니다) ─────────────────────────────────────
#   · 코스피 '지수'를 그대로 사고판다고 가정합니다. 지수에는 배당이 빠져 있습니다.
#   · 단타로 시장보다 더 벌 수 있다면 결과는 달라집니다. 그건 이 계산의 범위 밖입니다.
#   · 세금은 매도 금액에, 수수료는 매수·매도 양쪽에 물립니다.
#   · 호가 스프레드·시장충격은 넣지 않았습니다(넣으면 격차는 더 벌어집니다).
# =============================================================================
import datetime, json, os, urllib.request

import numpy as np

FEE = 0.00015          # 위탁수수료 — 매수·매도 각각
TAX_2026 = 0.0020      # 증권거래세 2026년 (코스피 0.05 + 농특세 0.15)
TAX_2025 = 0.0015      # 2025년까지
SEED = 1000            # 만원 = 1000만원

# ★ 기준일 — 영상을 만든 날까지만 씁니다.
#   이걸 안 고정하면 시장이 움직인 만큼 결과가 달라져 **영상의 숫자와 안 맞습니다**
#   (실측: 고정 없이 돌리면 3393만원이 3277만원으로 나옵니다. 코스피가 그새 내렸습니다).
#   오늘까지로 보고 싶으면 None 으로 바꾸세요. 결론(횟수가 늘수록 준다)은 그대로입니다.
ASOF = datetime.date(2026, 8, 27)

FREQ = [("never", "안 판다"), ("yearly", "연 1회"), ("quarterly", "분기 1회"),
        ("monthly", "월 1회"), ("weekly", "주 1회"), ("daily", "매일")]


def load_kospi(cache="kospi_daily.csv"):
    """코스피 일봉. 한 번 받으면 옆에 csv 로 저장해 두고 다음부터는 그걸 씁니다."""
    if os.path.exists(cache):
        import csv
        d, c = [], []
        for r in csv.DictReader(open(cache, encoding="utf-8")):
            d.append(datetime.date(*map(int, r["date"].split("-"))))
            c.append(float(r["close"]))
    else:
        url = ("https://query1.finance.yahoo.com/v8/finance/chart/%5EKS11"
               "?range=30y&interval=1d")
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        j = json.loads(urllib.request.urlopen(req, timeout=30).read())
        r = j["chart"]["result"][0]
        q = r["indicators"]["quote"][0]
        d, c = [], []
        for i, ts in enumerate(r["timestamp"]):
            if q["close"][i] is None:
                continue
            d.append(datetime.datetime.utcfromtimestamp(ts).date())
            c.append(q["close"][i])
        with open(cache, "w", encoding="utf-8", newline="") as f:
            f.write("date,close\n")
            for x, y in zip(d, c):
                f.write("{},{}\n".format(x, round(y, 4)))
        print("  데이터를 받아 {} 에 저장했습니다 ({}일)".format(cache, len(d)))

    # ⚠ 기준일 자르기는 **캐시를 쓰든 새로 받든 똑같이** 적용해야 한다.
    #   한쪽에만 걸면 두 번째 실행부터 결과가 달라진다.
    if ASOF:
        keep = [i for i, x in enumerate(d) if x <= ASOF]
        d = [d[i] for i in keep]
        c = [c[i] for i in keep]
    return d, np.array(c)


def sell_days(dates, freq):
    """회전 주기마다 True. 그날 팔고 곧바로 다시 삽니다(왕복 1회)."""
    n = len(dates)
    m = np.zeros(n, dtype=bool)
    if freq == "never":
        return m
    if freq == "daily":
        m[1:] = True
        return m
    for i in range(1, n):
        a, b = dates[i - 1], dates[i]
        if freq == "weekly":
            m[i] = b.weekday() < a.weekday()          # 주가 바뀌는 첫 거래일
        elif freq == "monthly":
            m[i] = b.month != a.month
        elif freq == "quarterly":
            m[i] = (b.month - 1) // 3 != (a.month - 1) // 3
        elif freq == "yearly":
            m[i] = b.year != a.year
    return m


def run(close, dates, freq, roundtrip):
    """지수 수익률을 그대로 따라가되, 회전할 때마다 왕복 비용을 뺍니다."""
    ret = np.zeros(len(close))
    ret[1:] = close[1:] / close[:-1] - 1
    sd = sell_days(dates, freq)
    eq, out, trades = 1.0, np.empty(len(close)), 0
    for i in range(len(close)):
        eq *= (1 + ret[i])
        if sd[i]:
            eq *= (1 - roundtrip)
            trades += 1
        out[i] = eq
    return out, trades


def table(title, header, rows):
    print("\n■ " + title + "\n")
    print("| " + " | ".join(header) + " |")
    print("|" + "|".join(["---"] * len(header)) + "|")
    for r in rows:
        print("| " + " | ".join(str(x) for x in r) + " |")


if __name__ == "__main__":
    d, c = load_kospi()
    rt26 = TAX_2026 + FEE * 2      # 2026년 개별주식 왕복 0.23%
    rt25 = TAX_2025 + FEE * 2      # 2025년까지        0.18%
    rt_etf = FEE * 2               # 국내 주식형 ETF   0.03%

    # ── 최근 10년 (영상의 핵심 표) ────────────────────────────────────────
    i10 = next(i for i, x in enumerate(d) if x >= d[-1] - datetime.timedelta(days=3653))
    d10, c10 = d[i10:], c[i10:]
    print("■ 코스피 {} ~ {}".format(d10[0], d10[-1]))
    print("  지수 {:.0f} → {:.0f}  ({:.1f}배)".format(c10[0], c10[-1], c10[-1] / c10[0]))
    print("  왕복 비용 — 개별주식 {:.2f}% · 국내 주식형 ETF {:.2f}%"
          .format(rt26 * 100, rt_etf * 100))

    base = run(c10, d10, "never", rt26)[0][-1]
    rows = []
    for key, lab in FREQ:
        v = run(c10, d10, key, rt26)[0][-1]
        rows.append([lab, "{:,.0f}만원".format(v * SEED), "{:.1f}%".format(v / base * 100)])
    table("1000만원을 넣고 매매 횟수만 다르게 했을 때 (최근 10년)",
          ["매매 주기", "남은 돈", "안 판 것 대비"], rows)

    # ── 개별주식 vs 국내 주식형 ETF ───────────────────────────────────────
    rows = []
    for key, lab in FREQ:
        a = run(c10, d10, key, rt26)[0][-1]
        b = run(c10, d10, key, rt_etf)[0][-1]
        rows.append([lab, "{:,.0f}만원".format(a * SEED), "{:,.0f}만원".format(b * SEED),
                     "{:,.0f}만원".format((b - a) * SEED)])
    table("거래세가 붙느냐 안 붙느냐 (같은 10년, 같은 1000만원)",
          ["매매 주기", "개별 주식", "국내 주식형 ETF", "차이"], rows)

    # ── 순수 비용만 (지수가 제자리였다고 가정) ────────────────────────────
    rows = []
    for key, lab in FREQ:
        if key == "never":
            continue
        n = {"daily": 246, "weekly": 52, "monthly": 12, "quarterly": 4, "yearly": 1}[key]
        rows.append([lab, "{}회".format(n),
                     "{:,.0f}만원".format((1 - rt26) ** n * SEED),
                     "{:,.1f}만원".format((1 - rt26) ** (n * 10) * SEED)])
    table("주가가 전혀 움직이지 않았다고 쳤을 때 (비용만)",
          ["매매 주기", "연 왕복", "1년 뒤", "10년 뒤"], rows)

    # ── 100만원 한 번 사고팔 때 ───────────────────────────────────────────
    print("\n■ 100만원어치를 한 번 사고팔면\n")
    print("  살 때 수수료 {:,.0f}원 · 팔 때 수수료 {:,.0f}원 · 세금 {:,.0f}원 → 합계 {:,.0f}원"
          .format(1e6 * FEE, 1e6 * FEE, 1e6 * TAX_2026, 1e6 * rt26))
    print("  국내 주식형 ETF 는 세금이 없어 {:,.0f}원 — 개별 주식의 {:.0f}분의 1"
          .format(1e6 * rt_etf, rt26 / rt_etf))
    print("\n  ⚠ 이 계산은 매매 실력에 따른 수익률 차이를 넣지 않았습니다.")
    print("     지수에는 배당이 빠져 있고, 호가 스프레드·시장충격도 넣지 않았습니다.")
