"""FTMO（2-Step / 1-Step）と Hantec（Enhanced / EnhancedX / Endurance）を1年間まわして比べるモンテカルロ。

ルール（公式で確認）:
- FTMO（2026-09-27 ftmo.com/en/trading-objectives/ と比較表）
  2-Step: 目標 10%→5%、日次5%（当日0時の残高 − 初期の5%）、最大10%静的（ファンド口座も同じ、出金しても動かない）、
          最低4取引日、分配80%（スケールで90%）、参加費は初回報酬で100%返金
  1-Step: 目標10%、日次3%（同上）、最大10%は日末の最高残高に追従（上限なし）、報酬を出して新しいファンド口座になると
          初期残高の90%に戻る（報酬は口座に残せない＝全額）、ベストデイ50%（合格と各報酬）、分配90%、返金なし
- Hantec（2026-09-27 公式ヘルプ）: scripts/sim-hantec-plans.py と同じ

前提（固定）: 1日の損失は全プラン共通で初期残高の2.5%まで（超えそうならその日は張らない）・$100K・250営業日・1日3トレード・RR1:1・ロットは初期残高の r% 固定・ポジションは1つずつ・
             コストは1トレードあたり 0.05R・失格したら同じプランを買い直す・スケーリングなし・年末に口座に残った利益は数えない。
動かすもの: プラン、勝率、r、出金時に口座へ残す余裕 B（EnhancedX・FTMO 1-Step は全額出金なので 0 固定）。
"""
import sys
import json
import numpy as np
from numba import njit, prange

DAYS, K, CYCLE = 250, 3, 10
DAILY_STOP = 0.025  # 全プラン共通: 1日の損失は初期残高の2.5%まで（会社の日次損失がもっと厳しければそちら）
EURUSD = 1.146

# name: price, targets, n_stages, mdd, daily, daily_on_initial, lock_on_payout, trail(0なし 1確定残高追従&初期でロック 2日末最高残高追従&上限なし),
#       reset_on_payout, mindays(0/1利益日0.5%/2取引日), mindays_n, cons(0なし), consmode(0: best/総利益 1: best/プラス日の合計),
#       full_payout, split, refund
PLANS = {
    "Hantec Enhanced":  dict(price=599, tg=(0.10, 0.05, 0.0), ns=2, mdd=0.10, daily=0.05, doi=0, lock=1, trail=0, reset=0, md=1, mdn=3, cons=0.0, cm=0, full=0, split=0.8, refund=0),
    "Hantec EnhancedX": dict(price=599, tg=(0.08, 0.04, 0.0), ns=2, mdd=0.08, daily=0.04, doi=0, lock=0, trail=0, reset=0, md=0, mdn=0, cons=0.35, cm=0, full=1, split=0.8, refund=0),
    "Hantec Endurance": dict(price=299, tg=(0.06, 0.06, 0.06), ns=3, mdd=0.08, daily=0.04, doi=0, lock=0, trail=0, reset=0, md=2, mdn=3, cons=0.0, cm=0, full=0, split=0.8, refund=0),
    "FTMO 2-Step":      dict(price=round(540 * EURUSD), tg=(0.10, 0.05, 0.0), ns=2, mdd=0.10, daily=0.05, doi=1, lock=0, trail=0, reset=0, md=2, mdn=4, cons=0.0, cm=0, full=0, split=0.8, refund=1),
    "FTMO 1-Step":      dict(price=round(499 * EURUSD), tg=(0.10, 0.0, 0.0), ns=1, mdd=0.10, daily=0.03, doi=1, lock=0, trail=2, reset=1, md=0, mdn=0, cons=0.50, cm=1, full=1, split=0.9, refund=0),
}
# よく使う割引: Hantec は公式常設 NEW35（35%OFF）、FTMO は 2026-09-27 時点の公式サイトのセール価格（€439 / €399）
SALE = {"Hantec Enhanced": 0.65, "Hantec EnhancedX": 0.65, "Hantec Endurance": 0.65,
        "FTMO 2-Step": 439 / 540, "FTMO 1-Step": 399 / 499}


@njit(parallel=True, cache=True)
def run(n, seed, p, r, cost, targets, n_stages, mdd, daily, doi, lock, trail, reset, mdkind, mdn, cons, cm, buf, full, split):
    paid = np.zeros(n)
    buys = np.zeros(n)
    firstpay = np.zeros(n)  # 初回報酬を受けた回数（返金の回数）
    funded_n = np.zeros(n)
    for i in prange(n):
        np.random.seed(seed + i)
        mode = 0; stage = 0
        bal = 1.0; line = 1.0 - mdd; high = 1.0; eodhigh = 1.0
        buys[i] = 1.0
        pdays = 0; tdays = 0; best = 0.0; possum = 0.0
        since = 0; locked = 0; paidthis = 0
        for d in range(DAYS):
            start = bal
            dead = False
            reached = False
            dl = start - daily if doi == 1 else start * (1.0 - daily)
            for k in range(K):
                if reached:
                    break
                # 自分で決めた1日の損失上限（全プラン共通で初期残高の2.5%）と会社の日次損失の、厳しいほうを超えそうならその日は張らない
                stopline = start - DAILY_STOP
                if dl > stopline:
                    stopline = dl
                if bal - r * (1.0 + cost) < stopline - 1e-12:
                    break
                if np.random.random() < p:
                    bal += r * (1.0 - cost)
                else:
                    bal -= r * (1.0 + cost)
                if trail == 1 and locked == 0:
                    if bal > high:
                        high = bal
                    nl = high - mdd
                    if nl >= 1.0:
                        nl = 1.0
                    if nl > line:
                        line = nl
                if bal < line - 1e-12 or bal < dl - 1e-12:
                    dead = True
                    break
                if mode == 0 and bal >= 1.0 + targets[stage] - 1e-12:
                    reached = True
            if dead:
                buys[i] += 1.0
                mode = 0; stage = 0; bal = 1.0; line = 1.0 - mdd; high = 1.0; eodhigh = 1.0
                pdays = 0; tdays = 0; best = 0.0; possum = 0.0; since = 0; locked = 0; paidthis = 0
                continue
            dp = bal - start
            tdays += 1
            if dp >= 0.005 - 1e-12:
                pdays += 1
            if dp > best:
                best = dp
            if dp > 0:
                possum += dp
            if trail == 2:
                # 日末の最高残高に追従（初期残高より下がらない、上限なし）
                if bal > eodhigh:
                    eodhigh = bal
                nl = eodhigh - mdd
                if nl > line:
                    line = nl
            if mode == 0:
                if bal >= 1.0 + targets[stage] - 1e-12:
                    ok = True
                    if mdkind == 1 and pdays < mdn:
                        ok = False
                    if mdkind == 2 and tdays < mdn:
                        ok = False
                    if cons > 0:
                        denom = (bal - 1.0) if cm == 0 else possum
                        if best > cons * denom + 1e-12:
                            ok = False
                    if ok:
                        stage += 1
                        bal = 1.0; line = 1.0 - mdd; high = 1.0; eodhigh = 1.0
                        pdays = 0; tdays = 0; best = 0.0; possum = 0.0
                        if stage >= n_stages:
                            mode = 1; since = 0; locked = 0; paidthis = 0
                            funded_n[i] += 1.0
            else:
                since += 1
                if since >= CYCLE:
                    profit = bal - 1.0
                    ok = True
                    if mdkind == 1 and pdays < mdn:
                        ok = False
                    if cons > 0 and profit > 0:
                        denom = profit if cm == 0 else possum
                        if best > cons * denom + 1e-12:
                            ok = False
                    if full == 1:
                        amt = profit
                        if cm == 0 and cons > 0 and amt < 0.02 - 1e-12:
                            ok = False  # EnhancedX は最低2%
                    else:
                        amt = bal - (1.0 + buf)
                    if ok and amt >= 0.0002:
                        paid[i] += amt * split
                        if paidthis == 0:
                            firstpay[i] += 1.0
                            paidthis = 1
                        bal -= amt
                        since = 0; pdays = 0; best = 0.0; possum = 0.0
                        if lock == 1:
                            line = 1.0
                            locked = 1
                        if reset == 1:
                            # 新しいファンド口座（初期残高・最大損失は初期の90%から）
                            bal = 1.0; line = 1.0 - mdd; eodhigh = 1.0
    return paid, buys, firstpay, funded_n


def main():
    n = int(sys.argv[1]) if len(sys.argv) > 1 else 20000
    out = sys.argv[2] if len(sys.argv) > 2 else "ftmo_vs_hantec.json"
    edges = [("ゼロ", 0.50), ("小", 0.52), ("中", 0.55), ("強", 0.58)]
    risks = [0.005, 0.01, 0.015]
    bufs = [0.0, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.16]
    cost = 0.05
    rows = []
    for name, P in PLANS.items():
        for ename, p in edges:
            for r in risks:
                for b in ([0.0] if P["full"] else bufs):
                    paid, buys, fp, fn = run(n, 12345, p, r, cost, np.array(P["tg"]), P["ns"], P["mdd"], P["daily"], P["doi"],
                                             P["lock"], P["trail"], P["reset"], P["md"], P["mdn"], P["cons"], P["cm"], b, P["full"], P["split"])
                    paid_usd = paid * 100000
                    for disc, dname in ((1.0, "定価"), (SALE[name], "割引")):
                        price = P["price"] * disc
                        fee = buys * price - (fp * price if P["refund"] else 0)
                        net = paid_usd - fee
                        rows.append(dict(plan=name, edge=ename, r=r, buf=b, price=dname, net=float(net.mean()),
                                         pos=float((net > 0).mean()), paid=float(paid_usd.mean()), fee=float(fee.mean()),
                                         buys=float(buys.mean()), funded=float((fn > 0).mean())))
    json.dump(rows, open(out, "w", encoding="utf-8"), ensure_ascii=False)
    full = {"Hantec Enhanced": 0.015, "Hantec EnhancedX": 0.0125, "Hantec Endurance": 0.0125, "FTMO 2-Step": 0.015, "FTMO 1-Step": 0.009}
    for price in ("定価", "割引"):
        for title, rf in (("1回0.5%", lambda p: 0.005), ("1回1.0%", lambda p: 0.01), ("1回1.5%", lambda p: 0.015)):
            print(f"\n## {title} [{price}]")
            for ename, _ in edges:
                cells = []
                for pl in PLANS:
                    c = [x for x in rows if x["plan"] == pl and x["edge"] == ename and abs(x["r"] - rf(pl)) < 1e-9 and x["price"] == price]
                    b = max(c, key=lambda x: x["net"])
                    cells.append(f"{pl.split()[-1]:9s}{b['net']:+8.0f}({b['pos']*100:3.0f}% B{b['buf']*100:.0f} 買{b['buys']:.1f})")
                print(f"{ename:3s} " + "  ".join(cells))


if __name__ == "__main__":
    main()
