# -*- coding: utf-8 -*-
"""비트코인 돌파 매매 — 한 번에 다 사기 vs 오를 때 나눠 사기(터틀 사이징) · AlgoLab 재현 코드

표준 라이브러리만 씁니다. 같은 폴더의 data_turtle/ 에 얼려 둔 일봉(2026-10-16 까지)을 읽습니다.
  python run_turtle.py

규칙(일봉 · 종가로 판단 · 다음 날 시가에 체결 · 편도 수수료 0.1%)
  사는 신호   종가 > 직전 20일 최고가
  파는 신호   종가 < 직전 10일 최저가 → 전부 판다
  N          지난 20일 평균 변동폭(와일더 ATR 20)
  한 번에    계좌 평가액 × 1% ÷ N 개 (값이 N 만큼 떨어지면 계좌가 1% 줄어드는 양)
  더 사기    종가 ≥ 마지막으로 산 값 + 0.5N 이면 같은 양을 한 번 더 · 처음 산 것까지 최대 4번
  손절       종가 < 마지막으로 산 값 − 2N → 전부 판다
  현금이 모자라면 남은 현금만큼만 산다(빚 없음)
비교
  한 번에 다  같은 사는·파는 신호, 신호가 나면 전액(더 사기·손절 없음)
  같은 비중   나눠 사기 계좌의 평균 비트코인 비중을 늘 들고 있는 계좌(매달 첫날 시가에 맞춤)
  그냥 보유   2018-01-01 종가부터 들고 있기
"""
import csv
import datetime
import os

HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.join(HERE, "data_turtle")
FEE = 0.001
START, END, RECENT = "2018-01-01", "2026-10-16", "2024-01-01"
SEED = 10_000_000
FILES = [("비트코인", "바이낸스", "BTCUSDT_1d.csv"), ("비트코인", "업비트", "KRW-BTC_1d.csv"),
         ("이더리움", "바이낸스", "ETHUSDT_1d.csv"), ("이더리움", "업비트", "KRW-ETH_1d.csv")]


def load(name):
    d, o, h, l, c = [], [], [], [], []
    with open(os.path.join(DATA, name), encoding="utf-8") as f:
        r = csv.reader(f)
        next(r)
        for row in r:
            if row[0][:10] > END:
                continue
            d.append(row[0][:10]); o.append(float(row[1])); h.append(float(row[2]))
            l.append(float(row[3])); c.append(float(row[4]))
    return d, o, h, l, c


def atr20(h, l, c):
    n = len(c)
    tr = [h[0] - l[0]] + [max(h[t] - l[t], abs(h[t] - c[t - 1]), abs(l[t] - c[t - 1])) for t in range(1, n)]
    N = [None] * n
    N[19] = sum(tr[:20]) / 20
    for t in range(20, n):
        N[t] = (19 * N[t - 1] + tr[t]) / 20
    return N


def channel(h, l, n_in, n_out):
    n = len(h)
    hi = [max(h[t - n_in:t]) if t >= n_in else None for t in range(n)]
    lo = [min(l[t - n_out:t]) if t >= n_out else None for t in range(n)]
    return hi, lo


def run(D, n_in=20, n_out=10, risk=0.01, max_u=4, stop=True, full=False):
    d, o, c, N = D["d"], D["o"], D["c"], D["N"]
    hi, lo = channel(D["h"], D["l"], n_in, n_out)
    i0 = D["i0"]
    cash, qty, units, last_fill, n_ent = 1.0, 0.0, 0, 0.0, 0.0
    order, cur, trades, eq, expo = None, None, [], [], []
    for t in range(i0, len(c)):
        if order is not None:
            px = o[t]
            if order[0] == "sell":
                cash += qty * px * (1 - FEE)
                cur.update(out=d[t], why=order[1], pnl=cash / cur["e0"] - 1)
                trades.append(cur)
                qty, units, cur = 0.0, 0, None
            else:
                afford = cash / (px * (1 + FEE))
                q = afford if order[1] is None else min(order[1], afford)
                if q > 0:
                    if cur is None:
                        cur = dict(inn=d[t], e0=cash + qty * px, buys=[])
                    cash -= q * px * (1 + FEE)
                    qty += q
                    units += 1
                    last_fill, n_ent = px, order[2]
                    cur["buys"].append(qty * px / (cash + qty * px))
            order = None
        e = cash + qty * c[t]
        eq.append(e)
        expo.append(qty * c[t] / e if e > 0 else 0.0)
        if t == len(c) - 1 or hi[t] is None or lo[t] is None or N[t] is None:
            continue
        if units > 0:
            if c[t] < lo[t]:
                order = ("sell", "10일 최저가")
            elif not full and stop and c[t] < last_fill - 2 * n_ent:
                order = ("sell", "2N 손절")
            elif not full and units < max_u and c[t] >= last_fill + 0.5 * n_ent:
                order = ("buy", risk * e / N[t], N[t])
        elif c[t] > hi[t]:
            order = ("buy", None if full else risk * e / N[t], N[t])
    return eq, expo, trades


def same_weight(D, w):
    d, o, c, i0 = D["d"], D["o"], D["c"], D["i0"]
    cash, qty, eq = 1.0, 0.0, []
    for t in range(i0, len(c)):
        if t == i0 or d[t][:7] != d[t - 1][:7]:
            e = cash + qty * o[t]
            tq = w * e / o[t]
            dq = tq - qty
            cash -= dq * o[t] + abs(dq) * o[t] * FEE
            qty = tq
        eq.append(cash + qty * c[t])
    return eq


def stats(eq, dates):
    yrs = (datetime.date.fromisoformat(dates[-1]) - datetime.date.fromisoformat(dates[0])).days / 365.25
    pk, mdd = eq[0], 0.0
    for v in eq:
        pk = max(pk, v)
        mdd = min(mdd, v / pk - 1)
    return (eq[-1] / eq[0]) ** (1 / yrs) - 1, mdd, eq[-1] / eq[0]


def won(x):
    v = int(round(SEED * x, -4)) // 10000
    return (f"{v // 10000}억 {v % 10000:,}만원" if v % 10000 else f"{v // 10000}억원") if v >= 10000 else f"{v:,}만원"


def line(name, eq, dates):
    g, m, x = stats(eq, dates)
    j = dates.index(next(x for x in dates if x >= RECENT))
    g2, m2, _ = stats(eq[j:], dates[j:])
    return f"  {name:8s} 연 {g * 100:.2f}% · 가장 크게 줄어든 폭 {m * 100:.2f}% · 1,000만원 → {won(x)} · 2024-01~ 연 {g2 * 100:.2f}% / {m2 * 100:.2f}%"


def main():
    print(f"구간 {START} ~ {END} · 편도 수수료 {FEE * 100:.1f}% · 원금 1,000만원(달러 계좌는 같은 배수로 환산)\n")
    for coin, ex, f in FILES:
        d, o, h, l, c = load(f)
        i0 = next(i for i, x in enumerate(d) if x >= START)
        D = dict(d=d, o=o, h=h, l=l, c=c, N=atr20(h, l, c), i0=i0)
        dd = d[i0:]
        eq_t, ex_t, tr_t = run(D)
        eq_f, _, tr_f = run(D, full=True)
        w = sum(ex_t) / len(ex_t)
        eq_w = same_weight(D, w)
        bh = [x / c[i0] for x in c[i0:]]
        print(f"== {coin} · {ex} (마지막 봉 {d[-1]})")
        print(line("나눠 사기", eq_t, dd) + f" · 거래 {len(tr_t)}번 · 평균 비중 {w * 100:.1f}%")
        print(line("한 번에 다", eq_f, dd) + f" · 거래 {len(tr_f)}번")
        print(line("같은 비중", eq_w, dd))
        print(line("그냥 보유", bh, dd))
        if coin == "비트코인":
            lose = sum(t["pnl"] <= 0 for t in tr_t)
            small = sum(t["buys"][0] < 1 / 3 for t in tr_t)
            worst = min(t["pnl"] for t in tr_t)
            best = max(tr_t, key=lambda t: t["pnl"])
            print(f"  나눠 사기 거래 {len(tr_t)}번 중 손해 {lose}번 · 가장 큰 손해 {worst * 100:.2f}% · "
                  f"가장 큰 거래 {best['inn']} → {best['out']} {best['pnl'] * 100:+.2f}%")
            print(f"  처음 한 번에 산 양이 계좌의 3분의 1 미만이었던 거래 {small}번 / {len(tr_t)}번")
            t = len(c) - 1
            print(f"  {d[t]} 종가 기준 1,000만원 계좌의 처음 한 번 = 약 {round(SEED * 0.01 / D['N'][t] * c[t], -3):,.0f}원어치 "
                  f"(N = 종가의 {D['N'][t] / c[t] * 100:.2f}%)")
            eq_s2, _, _ = run(D, n_in=55, n_out=20)
            g, m, _ = stats(eq_s2, dd)
            eq_s2f, _, _ = run(D, n_in=55, n_out=20, full=True)
            g2, m2, _ = stats(eq_s2f, dd)
            print(f"  (원조 느린 규칙 55일 돌파·20일 청산) 나눠 사기 연 {g * 100:.2f}% · {m * 100:.2f}% / 한 번에 다 연 {g2 * 100:.2f}% · {m2 * 100:.2f}%")
        print()
    print("끝.")


if __name__ == "__main__":
    main()
