# -*- coding: utf-8 -*-
"""물타기 = 마틴게일 — 영상 수치 재현 코드 (AlgoLab)

영상: 「자동매매 345번 다 이겼는데 16억을 놓쳤습니다」 (2026-08-31)
이 파일 하나로 영상의 모든 수치가 재현됩니다. 표준 라이브러리만 씁니다.

  python martingale_backtest.py

① 룰렛 마틴게일 수학 — 세트 승률 99.87% · 1000세트 내 10연패 확률 72%
② 비트코인/이더리움 마틴게일 봇 — 345승 0패 → 1742만원 (보유 17억 183만원)
③ 지수 물타기 (닛케이/코스피/S&P500) — 승률 100% · 전부 꼴찌
④ 본전 탈출만 끄면 — S&P500 2012만원 → 9130만원

데이터: 야후 파이낸스 v8 chart API (adjclose = 배당 재투자 총수익)
"""
import csv
import datetime
import io
import json
import os
import random
import urllib.parse
import urllib.request

# 영상을 만든 날 기준으로 고정 — 최신 데이터로 보려면 None 으로 바꾸세요.
# (고정하지 않으면 그날그날 데이터가 늘어나 영상과 숫자가 달라집니다)
ASOF = datetime.date(2026, 8, 28)

FEE = 0.0005          # 편도 0.05% (왕복 0.1%)
CAP = 1000.0          # 만원


# ──────────────────────────────────────────────────────────────────────────
# 데이터
# ──────────────────────────────────────────────────────────────────────────
def fetch(sym):
    """야후 일봉 (date, adjclose). ASOF 이후는 버린다. 같은 폴더에 CSV 캐시."""
    cache = f"_cache_{sym.replace('^', '')}.csv"
    if os.path.exists(cache):
        with io.open(cache, encoding="utf-8") as f:
            return [(r["date"], float(r["adjclose"])) for r in csv.DictReader(f)]
    p2 = int(datetime.datetime(ASOF.year, ASOF.month, ASOF.day).timestamp()) + 86400
    u = (f"https://query1.finance.yahoo.com/v8/finance/chart/{urllib.parse.quote(sym)}"
         f"?period1=0&period2={p2}&interval=1d")
    req = urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"})
    j = json.loads(urllib.request.urlopen(req, timeout=60).read())
    r = j["chart"]["result"][0]
    ts = r["timestamp"]
    close = r["indicators"]["quote"][0]["close"]
    adj = (r["indicators"].get("adjclose") or [{}])[0].get("adjclose") or close
    rows = []
    for t, c, a in zip(ts, close, adj):
        if c is None or a is None:
            continue
        d = datetime.date.fromtimestamp(t)
        if ASOF and d > ASOF:
            continue
        rows.append((d.isoformat(), a))
    with io.open(cache, "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["date", "adjclose"])
        w.writerows((d, f"{a:.6f}") for d, a in rows)
    return rows


# ──────────────────────────────────────────────────────────────────────────
# ① 룰렛 마틴게일 (순수 수학)
# ──────────────────────────────────────────────────────────────────────────
def roulette():
    p = 18 / 37                       # 유럽식 룰렛, 빨간색
    levels, capital = 10, 2 ** 10 - 1  # 1+2+...+512 = 1023단위
    q = (1 - p) ** levels             # 한 세트가 10연패로 끝날 확률
    print("① 룰렛 마틴게일")
    print(f"   한 판 승률          {p * 100:.1f}%")
    print(f"   세트 승률           {(1 - q) * 100:.2f}%")
    print(f"   1000세트 내 10연패   {(1 - (1 - q) ** 1000) * 100:.0f}%")
    print(f"   세트당 기대값        {(1 - q) * 1 - q * capital:+.2f}단위 (이길 때 +1, 질 때 -{capital})")


# ──────────────────────────────────────────────────────────────────────────
# ② 코인 마틴게일 봇 — 7레벨 2배 등비 · -5% 추가 · 평단 +3% 익절 · 일봉 종가
# ──────────────────────────────────────────────────────────────────────────
def run_bot(rows, levels=7, drop=0.05, take=0.03, mult=2.0):
    weights = [mult ** i for i in range(levels)]
    unit = CAP / sum(weights)
    cash, qty, invested, level = CAP, 0.0, 0.0, 0
    last_buy = None
    cycles = wins = 0
    stuck_from, longest = None, 0
    worst_unreal = 0.0
    for d, px in rows:
        if qty == 0:
            spend = min(unit * weights[0], cash)
            qty = spend * (1 - FEE) / px
            cash -= spend
            invested, level, last_buy = spend, 1, px
            continue
        avg = invested / qty
        if px >= avg * (1 + take):
            cash += qty * px * (1 - FEE)
            qty, invested, level, last_buy = 0.0, 0.0, 0, None
            cycles += 1
            wins += 1
            if stuck_from:
                span = (datetime.date.fromisoformat(d)
                        - datetime.date.fromisoformat(stuck_from)).days
                longest = max(longest, span)
                stuck_from = None
        elif level < levels and px <= last_buy * (1 - drop):
            spend = min(unit * weights[level], cash)
            if spend > 0:
                qty += spend * (1 - FEE) / px
                cash -= spend
                invested += spend
                level += 1
                last_buy = px
            if level >= levels and stuck_from is None:
                stuck_from = d
        elif level >= levels:
            if stuck_from is None:
                stuck_from = d
            worst_unreal = min(worst_unreal, (px - avg) / avg * 100)
    final = cash + qty * rows[-1][1]
    hold = rows[-1][1] / rows[0][1]
    return cycles, wins, final, hold, longest, worst_unreal


def bots():
    print("\n② 코인 마틴게일 봇 (1000만원 · 7단계 2배 등비 · -5% 추가 · 평단 +3% 익절)")
    for sym, name in (("BTC-USD", "비트코인"), ("ETH-USD", "이더리움")):
        rows = fetch(sym)
        cycles, wins, final, hold, longest, worst = run_bot(rows)
        print(f"   {name} {rows[0][0]} ~ {rows[-1][0]}")
        print(f"     {wins}승 {cycles - wins}패 · 최종 {final:,.0f}만원"
              f" · 그냥 보유 {hold * CAP:,.0f}만원 ({hold:.0f}배)")
        print(f"     최장 물림 {longest}일 · 물림 중 최악 평가손 {worst:.0f}%")


# ──────────────────────────────────────────────────────────────────────────
# ③④ 지수 물타기 — 1/4 진입 · 평단 -10%마다 1/4 추가 · 평단 +5% 전량 매도
#      sell=False 면 같은 매수에 "본전 탈출"만 끈 것 (팔지 않고 모은다)
# ──────────────────────────────────────────────────────────────────────────
def sim_water(rows, parts=4, drop=0.10, take=0.05, sell=True):
    part = CAP / parts
    cash, qty, invested, used = CAP, 0.0, 0.0, 0
    cycles = wins = 0
    for d, px in rows:
        avg = invested / qty if qty > 0 else None
        if qty == 0:
            spend = min(part, cash)
            qty = spend * (1 - FEE) / px
            cash -= spend
            invested, used = spend, 1
        elif sell and px >= avg * (1 + take):
            cash += qty * px * (1 - FEE)
            qty, invested, used = 0.0, 0.0, 0
            cycles += 1
            wins += 1
        elif used < parts and px <= avg * (1 - drop):
            spend = min(part, cash)
            if spend > 0:
                qty += spend * (1 - FEE) / px
                cash -= spend
                invested += spend
                used += 1
    final = cash + qty * rows[-1][1]
    return final, cycles, wins


def water():
    print("\n③ 지수 물타기 (1000만원 · 250만원씩 · 평단 -10%마다 매수 · 평단 +5% 전량 매도)")
    scen = (("^N225", "1989-12-29", "닛케이 1989 고점"),
            ("^KS11", "2007-10-31", "코스피 2007 고점"),
            ("SPY", "2000-03-24", "S&P500 2000 고점"))
    for sym, start, label in scen:
        rows = [r for r in fetch(sym) if r[0] >= start]
        w, c, win = sim_water(rows, sell=True)
        p, _, _ = sim_water(rows, sell=False)
        hold = rows[-1][1] / rows[0][1] * CAP * (1 - FEE)
        print(f"   {label} ({start} ~ {rows[-1][0]})")
        print(f"     물타기+본전탈출  {w:,.0f}만원 · {win}/{c} 전승")
        print(f"     탈출만 끔        {p:,.0f}만원")
        print(f"     그냥 보유        {hold:,.0f}만원")


if __name__ == "__main__":
    print(f"기준일 {ASOF} 고정 (최신으로 보려면 ASOF = None)\n")
    roulette()
    bots()
    water()
    print("\n전체 검증 스크립트·차트 코드: https://algolab.co.kr/mart")
