# -*- coding: utf-8 -*-
"""プロップでは資産曲線を滑らかにしてはいけない、を実データで検証する。

問い:
  普通の運用では「ばらつきを抑える」のが正義。プロップでも同じか。

答えの出し方:
  (A) 実データの戦略を、ロット（＝ばらつき）を変えながらチャレンジに通し、
      「届かない／届く／壁に当たる」の3領域を描く。
  (B) 複数の戦略を1口座にまとめた場合と、別々の口座に分けた場合を比べる。

データは 2024-04〜2026-09 の実価格から作った時間帯アノマリー。
スプレッド控除後、建値からの最大逆行(MAE)でハードSLの発動を判定、
実際の並びのまま（ブートストラップなし）で爆破→買い直しを回す。

使い方:
  python scripts/fetch_wave_data.py      # 先に日足データ（他スクリプト用）
  python scripts/sim-prop-spiky-vs-smooth.py
"""
import os
import sys

import numpy as np
import pandas as pd

HERE = os.path.dirname(os.path.abspath(__file__))
CACHE = os.path.join(os.path.dirname(HERE), "data", "cache")

ACCOUNT, FEE, SPLIT = 15_000_000, 75_000, 0.80
P1, P2, MAX_DD, DAILY = 0.08, 0.05, 0.10, 0.05
HARD_SL, PAYOUT_AT, FUNDED_LOT = 0.022, 0.03, 0.5


def _jst(name):
    df = pd.read_csv(os.path.join(CACHE, name), index_col=0, parse_dates=True)
    return df.tz_convert("Asia/Tokyo") if df.index.tz else df.tz_localize("UTC").tz_convert("Asia/Tokyo")


def wave(name, hour_in, hour_out, side, spread, weekday=None):
    """1つの『波』の トレード列。戻り値は日付ごとの (損益, 最大逆行)。"""
    df = _jst(name)
    lag = (hour_out - hour_in) % 24
    opens = df["open"]
    rows = []
    for t, px in opens[opens.index.hour == hour_in].items():
        if weekday is not None and t.dayofweek != weekday:
            continue
        exit_t = t + pd.Timedelta(hours=lag)
        if exit_t not in opens.index:
            continue
        seg = df.loc[t:exit_t]
        if len(seg) < 2:
            continue
        if side > 0:
            mae, ret = seg["low"].min() / px - 1.0, opens[exit_t] / px - 1.0
        else:
            mae, ret = 1.0 - seg["high"].max() / px, 1.0 - opens[exit_t] / px
        rows.append((t.tz_localize(None).normalize(), ret - spread / px, min(mae, 0.0)))
    out = pd.DataFrame(rows, columns=["d", "ret", "mae"])
    return out.groupby("d").agg({"ret": "sum", "mae": "min"})


def run(d, lot):
    """実際の並びのまま、爆破したら買い直して回す。年あたりの純益を返す。"""
    k = (0.010 * lot) / d["ret"].std(ddof=1)
    rets, maes = d["ret"].values * k, d["mae"].values * k
    years = (d.index[-1] - d.index[0]).days / 365.25
    eq, stage, cash, fees, blown = 1.0, 0, 0.0, FEE, 0
    for r, m in zip(rets, maes):
        size = FUNDED_LOT if stage >= 2 else 1.0
        x = -HARD_SL if m * size <= -HARD_SL else r * size
        eq += x
        if x <= -DAILY or eq <= 1.0 - MAX_DD:      # 壁に当たった
            blown += 1; fees += FEE; eq, stage = 1.0, 0
            continue
        if stage < 2 and eq >= 1.0 + (P1 if stage == 0 else P2):
            stage += 1; eq = 1.0
        if stage >= 2 and eq >= 1.0 + PAYOUT_AT:
            cash += (eq - 1.0) * ACCOUNT * SPLIT; eq = 1.0
    return (cash - fees) / years, blown / years


def dd_depth(d):
    """1トレードの標準偏差を1%に揃えたとき、最大DDがSD何個ぶん沈むか。"""
    k = 0.010 / d["ret"].std(ddof=1)
    x = np.where(d["mae"].values * k <= -HARD_SL, -HARD_SL, d["ret"].values * k)
    eq = np.cumsum(x)
    return -(eq - np.maximum.accumulate(eq)).min() / 0.01


WAVES = [
    ("JP225 月", lambda: wave("jp225_1h.csv", 14, 5, +1, 12.0, 0)),
    ("ゴールド買 水", lambda: wave("gold_1h.csv", 4, 11, +1, 0.30, 2)),
    ("GBPJPY 火", lambda: wave("gbpjpy_1h.csv", 23, 9, +1, 0.025, 1)),
    ("USTEC 木", lambda: wave("ustec_1h.csv", 0, 10, +1, 1.5, 3)),
    ("ゴールドバランス 水", None),      # BUY+SELL の合成。下で作る
]
LOTS = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 8.0]


def balanced_wed():
    b = wave("gold_1h.csv", 4, 11, +1, 0.30, 2)
    s = wave("gold_1h.csv", 17, 20, -1, 0.30, 2)
    j = pd.concat([b, s], axis=1, keys=["b", "s"]).fillna(0)
    return pd.DataFrame({"ret": j[("b", "ret")] + j[("s", "ret")],
                         "mae": np.minimum(j[("b", "mae")], j[("s", "mae")])})


def load_all():
    out = []
    for name, fn in WAVES:
        d = balanced_wed() if fn is None else fn()
        out.append((name, d))
    return out


if __name__ == "__main__":
    data = load_all()
    print("1トレードのばらつき（ロット）を変えると何が起きるか")
    print(f"$100K相当・2フェーズ 8%→5%・最大DD10%・ハードSL {HARD_SL*100:.1f}%・爆破したら買い直し\n")
    print("=" * 96)
    print(f"  {'波':18}{'DD深さ':>8}" + "".join(f"{f'{l}x':>8}" for l in LOTS))
    print("=" * 96)
    for name, d in data:
        row = "".join(f"{run(d, l)[0]/10000:>8.0f}" for l in LOTS)
        print(f"  {name:18}{dd_depth(d):>7.1f}個{row}")
    print("\n  ※ 単位は万円/年。太い山の位置が戦略ごとにまったく違うことに注目")
    print("\n" + "=" * 96)
    print("  まとめて1口座 vs 分けて複数口座")
    print("=" * 96)
    strong = [d for n, d in data if n in ("JP225 月", "ゴールド買 水", "GBPJPY 火", "USTEC 木")]
    x = pd.concat(strong).sort_index()
    blend = x.groupby(x.index).agg({"ret": "sum", "mae": "min"})
    def best(d):
        b = max(((l, run(d, l)[0]) for l in LOTS), key=lambda t: t[1])
        return b[0], run(d, max(b[0] / 2, 0.5))[0]
    bl_lot, bl_net = best(blend)
    sep = sum(best(d)[1] for d in strong)
    print(f"  まとめて1口座（週4回）… 最適 {bl_lot}x / 推定最適の半分で {bl_net/10000:,.0f}万円")
    print(f"  分けて4口座（各週1回）… 合計 {sep/10000:,.0f}万円")
    print(f"  → 分けたほうが {(sep-bl_net)/10000:+,.0f}万円")
