# -*- coding: utf-8 -*-
"""
ドローダウンが進んだ口座は、新品の口座と同じ器なのか。

問い:
  「あと少しで建値に戻る」口座と、買い直した新品の口座。同じ1円を稼ぐなら、どちらが得か。

答えの出し方:
  (A) 現在地ごとに「目標までの距離 ÷ 死ぬまでの距離」を出す。これが器の重さ。
  (B) モンテカルロで現在地ごとの P1突破率・ファンド到達率・期待受取を実測し、
      新品を 1.00 としたときの価値比を出す。
  (C) 「新品と傷んだ口座の期待受取の差」と「参加費」を比べる。
      差のほうが大きければ、買い直しが常に勝つ。

モデル:
  1トレード = 勝率 p で +R×r、負けで −r（r は初期資金比 = EA のハードSL）。
  フェーズ通過で残高をリセット。最大DDは初期資金基準の静的。失格したら終了。

未実装（結果はやや楽観側）:
  最低取引日数、一貫性ルール、スプレッド・手数料、出金サイクル。
  3つの現在地に等しく効くので、比較の向きは変わらない。

使い方: python scripts/sim-damaged-account-value.py
"""
import math

import numpy as np

SEED, N_PATHS, DAYS = 20260918, 40_000, 260

# Fintokei ProTrader サファイヤ（2,000万円）を例にする
ACCOUNT = 20_000_000
FEE = 109_800
SPLIT = 0.80
TARGET_P1, TARGET_P2 = 0.08, 0.06
MAX_DD = 0.10          # 初期資金基準の静的
RISK = 0.022           # EA のハードSL（Fintokei は 2.2%）

STATES = [
    ("新品（0円）", 1.000),
    ("−50万（−2.5%）", 0.975),
    ("−100万（−5%）", 0.950),
    ("−150万（−7.5%）", 0.925),
]
LEVELS = [
    ("エッジゼロ", 0.50, 1.00),
    ("PF1.30相当", 0.52, 1.20),
    ("PF1.59相当", 0.55, 1.30),
]


def simulate(start_eq, p, R):
    """現在地 start_eq から走らせて、P1突破率・ファンド到達率・期待受取を返す。"""
    rng = np.random.default_rng(SEED)
    eq = np.full(N_PATHS, start_eq)
    alive = np.ones(N_PATHS, dtype=bool)
    stage = np.zeros(N_PATHS, dtype=int)
    passed_p1 = np.zeros(N_PATHS, dtype=bool)
    targets = np.array([TARGET_P1, TARGET_P2, np.inf])

    for _ in range(DAYS):
        step = np.where(rng.random(N_PATHS) < p, R * RISK, -RISK)
        eq = eq + step * alive
        alive &= ~(alive & (eq <= 1.0 - MAX_DD))
        ok = alive & (stage < 2) & (eq >= 1.0 + targets[np.minimum(stage, 2)])
        if ok.any():
            passed_p1 |= ok & (stage == 0)
            stage = np.where(ok, stage + 1, stage)
            eq = np.where(ok, 1.0, eq)

    funded = alive & (stage == 2)
    payout = np.where(funded, np.maximum(eq - 1.0, 0.0), 0.0) * ACCOUNT * SPLIT
    return passed_p1.mean(), funded.mean(), payout.mean()


def sample_size_two_proportions(p1, p2, alpha_z=1.959964, power_z=0.8416):
    """合格率 p1 と p2 を見分けるのに必要な、1グループあたりの口座数。"""
    num = (alpha_z + power_z) ** 2 * (p1 * (1 - p1) + p2 * (1 - p2))
    return math.ceil(num / (p1 - p2) ** 2)


def signals_needed(p, R):
    """1トレードの期待値が 0 でないと言い切るのに必要なシグナル数。"""
    mu = p * R - (1 - p)
    sd = math.sqrt(p * R * R + (1 - p) - mu * mu)
    return math.ceil((1.959964 / (mu / sd)) ** 2)


if __name__ == "__main__":
    print(f"モンテカルロ {N_PATHS:,}本 / {DAYS}営業日 / 1トレード{RISK*100:.1f}%")
    print(f"対象: {ACCOUNT:,}円・P1 {TARGET_P1*100:.0f}% → P2 {TARGET_P2*100:.0f}%"
          f"・最大DD {MAX_DD*100:.0f}%（静的）・参加費 {FEE:,}円")
    print("※ 最低取引日数・一貫性・手数料は未実装（結果は楽観側）")

    for label, p, R in LEVELS:
        print()
        print("=" * 92)
        print(f"  【{label}】 現在地ごとの器")
        print("=" * 92)
        print(f"  {'現在地':24}{'目標÷残枠':>11}{'P1突破':>9}{'ファンド':>10}{'期待受取':>15}{'新品比':>9}")
        base = None
        for name, eq0 in STATES:
            need = (1.0 + TARGET_P1) - eq0
            room = eq0 - (1.0 - MAX_DD)
            p1r, fr, pay = simulate(eq0, p, R)
            if base is None:
                base = pay
            print(f"  {name:24}{need/room:>10.2f}{p1r*100:8.1f}%{fr*100:9.1f}%"
                  f"{pay:>14,.0f}円{pay/base:>8.2f}")
        worst = simulate(0.950, p, R)[2]
        gap = base - worst
        print(f"  → 新品と −100万 の差 {gap:,.0f}円 ÷ 参加費 {FEE:,}円 = "
              f"買い直しの倍率 {gap/FEE:.2f}倍")

    print()
    print("=" * 92)
    print("  【参考】口座の合否から「この波は弱い」と言うのに必要な口座数")
    print("=" * 92)
    for a, b in [(0.40, 0.25), (0.50, 0.25), (0.50, 0.20), (0.75, 0.25)]:
        n = sample_size_two_proportions(a, b)
        print(f"  合格率 {a*100:.0f}% 対 {b*100:.0f}% … 1波あたり {n} 口座 / 2波で {n*2} 口座")

    print()
    print("=" * 92)
    print("  【参考】EA のエッジが本物だと言うのに必要なシグナル数")
    print("=" * 92)
    for label, p, R in LEVELS[1:]:
        n = signals_needed(p, R)
        print(f"  {label} … {n} 本（週5本なら {n/5/4.33:.1f} か月）")
