# -*- coding: utf-8 -*-
"""
AI旺财 EA 马丁格尔网格参数优化模型
本金 60000 USD | XAUUSD | 1手=100oz | 1point=0.01USD
"""
import random
import math

PRICE0 = 4320.0
CONTRACT = 100.0
POINT = 0.01
EQUITY = 60000.0
LEV = 2000.0
STEP = 0.01


# ---------------------------------------------------------------- 网格基础
def ladder(init, mult, n, maxlot=99.0):
    """按券商最小步进 0.01 规整后的实际手数阶梯"""
    out, v = [], init
    for _ in range(n):
        v = min(v, maxlot)
        q = round(round(v / STEP) * STEP, 2)
        out.append(q if q >= STEP else STEP)
        v = v * mult
    return out


def grid_stats(init, mult, n, dist_pts, maxlot=99.0):
    L = ladder(init, mult, n, maxlot)
    d = dist_pts * POINT
    S = sum(L)
    dd = sum(L[i] * CONTRACT * (i * d) for i in range(n))
    return dict(L=L, S=S, dd=dd, d=d, span=(n - 1) * d,
                margin=S * CONTRACT * PRICE0 / LEV,
                be=dd / (S * CONTRACT) if S else 0.0)


def solve_init(mult, n, dist_pts, target_dd, maxlot=99.0):
    """反解：满仓浮亏 = target_dd 时的起手手数"""
    lo, hi, best = 0.001, 50.0, 0.001
    for _ in range(80):
        mid = (lo + hi) / 2
        if grid_stats(mid, mult, n, dist_pts, maxlot)['dd'] <= target_dd:
            best = mid
            lo = mid
        else:
            hi = mid
    return max(0.01, round(best / STEP) * STEP)


# ---------------------------------------------------------------- 解套难度
def unbias_ratio(mult, n):
    """保本反弹 / 覆盖区间。越小越容易解套，理论下限 0.5（等手数网格）"""
    s0 = sum(mult ** i for i in range(n))
    s1 = sum((mult ** i) * i for i in range(n))
    return (s1 / s0) / (n - 1)


def show_unbias():
    print('=' * 92)
    print('[1] 加仓倍数对解套难度的影响  (保本反弹 / 网格覆盖区间)')
    print('    该比值与起手手数无关，只由 倍数/档数 决定。下限0.5=等手数网格')
    print('=' * 92)
    print('%-10s' % '倍数' + ''.join('%10s' % ('N=%d' % n) for n in (6, 8, 10, 12, 15)))
    print('-' * 92)
    for mult in (1.0, 1.1, 1.15, 1.2, 1.25, 1.3, 1.4, 1.5, 1.6):
        line = '%-10s' % ('%.2f' % mult)
        for n in (6, 8, 10, 12, 15):
            line += '%9.1f%%' % (unbias_ratio(mult, n) * 100)
        print(line)


# ---------------------------------------------------------------- 蒙特卡洛
def simulate(init, mult, n, dist_pts, maxlot, tp, sl, n_sim=400,
             hours=6240, sigma=0.28, swap_per_lot_day=-25.0, seed=42):
    """
    逐小时 GBM 模拟黄金价格，按网格规则交易
    tp: 整体止盈金额(USD)  sl: 整体止损金额(USD, 0=不启用)
    返回 爆仓率/年化收益/最大回撤 等
    """
    rng = random.Random(seed)
    d = dist_pts * POINT
    L = ladder(init, mult, n, maxlot)
    dt = 1.0 / hours
    sq = math.sqrt(dt)
    drift = -0.5 * sigma * sigma * dt
    vol = sigma * sq

    results = []
    for _ in range(n_sim):
        eq = EQUITY
        peak = eq
        maxdd = 0.0
        price = PRICE0
        t = 0
        # 网格状态
        pos = []          # [(lot, entry_price)]
        nxt = 0           # 下一档索引
        opened = 0
        rounds = 0
        dead = False

        while t < hours:
            price *= math.exp(drift + vol * rng.gauss(0, 1))
            t += 1

            if not pos:
                # 新一轮：随机方向首单
                if rng.random() < 0.02:      # 平均每50小时一次首单机会
                    pos.append((L[0], price, 1 if rng.random() < 0.5 else -1))
                    nxt = 1
                    opened = 1
                    rounds += 1
                continue

            direction = pos[0][2]
            # 加仓判定
            if nxt < n:
                last = pos[-1][1]
                if direction * (price - last) <= -d:
                    pos.append((L[nxt], price, direction))
                    nxt += 1
                    opened += 1

            # 盈亏
            pnl = sum(lot * CONTRACT * direction * (price - e) for lot, e, _ in pos)
            # 隔夜利息（每24小时计一次）
            pnl += sum(lot for lot, _, _ in pos) * swap_per_lot_day * (t // 24) / 1.0 * 0  # 见下注
            equity = eq + pnl

            if equity > peak:
                peak = equity
            dd = peak - equity
            if dd > maxdd:
                maxdd = dd

            # 保证金检查 (0% stop out)
            margin = sum(lot for lot, _, _ in pos) * CONTRACT * price / LEV
            if equity - margin <= 0 or equity <= 0:
                dead = True
                break

            if pnl >= tp:
                eq += pnl
                pos = []
                nxt = 0
                continue
            if sl > 0 and pnl <= -sl:
                eq += pnl
                pos = []
                nxt = 0
                continue

        # 未平仓按市价结算
        if pos:
            pnl = sum(lot * CONTRACT * direction * (price - e) for lot, e, _ in pos)
            eq += pnl
        results.append(dict(dead=dead, ret=(eq - EQUITY) / EQUITY,
                            maxdd=maxdd / EQUITY, rounds=rounds))

    alive = [r for r in results if not r['dead']]
    dead_rate = len([r for r in results if r['dead']]) / len(results)
    rets = sorted(r['ret'] for r in results)
    dds = sorted(r['maxdd'] for r in results)
    return dict(
        dead_rate=dead_rate,
        ret_med=rets[len(rets) // 2] if rets else -1,
        ret_p10=rets[int(len(rets) * 0.1)] if rets else -1,
        ret_p90=rets[int(len(rets) * 0.9)] if rets else -1,
        dd_med=dds[len(dds) // 2] if dds else 1,
        dd_p90=dds[int(len(dds) * 0.9)] if dds else 1,
        rounds=sum(r['rounds'] for r in results) / len(results),
    )


if __name__ == '__main__':
    show_unbias()
    print()
    print('=' * 92)
    print('[2] 候选方案：在「满仓浮亏<=目标回撤」约束下反解起手手数')
    print('=' * 92)
    print('%-34s %7s %7s %9s %7s %8s %7s %9s' % (
        '方案', '起手', '满仓', '满仓浮亏', '回撤%', '覆盖USD', '覆盖%', '保本反弹'))
    print('-' * 92)

    plans = [
        ('原版 1.5/N15/2500pt', 1.5, 15, 2500, 0.4, None),
        ('P1 保守 1.15/N12/4500pt', 1.15, 12, 4500, 99.0, 0.10),
        ('P2 稳健 1.20/N12/4500pt', 1.20, 12, 4500, 99.0, 0.15),
        ('P3 均衡 1.25/N12/4500pt', 1.25, 12, 4500, 99.0, 0.20),
        ('P4 进取 1.30/N12/4500pt', 1.30, 12, 4500, 99.0, 0.28),
        ('P5 激进 1.40/N12/4500pt', 1.40, 12, 4500, 99.0, 0.40),
        ('P6 宽网 1.20/N15/4000pt', 1.20, 15, 4000, 99.0, 0.20),
    ]
    built = []
    for label, mult, n, dpt, maxlot, tgt in plans:
        if tgt is None:
            init = 0.01
        else:
            init = solve_init(mult, n, dpt, EQUITY * tgt, maxlot)
        g = grid_stats(init, mult, n, dpt, maxlot)
        print('%-34s %7.2f %7.2f %9.0f %6.1f%% %8.0f %6.2f%% %9.1f' % (
            label, init, g['S'], g['dd'], g['dd'] / EQUITY * 100,
            g['span'], g['span'] / PRICE0 * 100, g['be']))
        built.append((label, init, mult, n, dpt, maxlot, g))

    print()
    print('=' * 92)
    print('[3] 各方案阶梯明细')
    print('=' * 92)
    for label, init, mult, n, dpt, maxlot, g in built:
        print('  %-30s 保证金 $%.0f (%.2f%%)' % (label, g['margin'], g['margin'] / EQUITY * 100))
        print('     %s' % ' '.join('%.2f' % x for x in g['L']))

    print()
    print('=' * 92)
    print('[4] 蒙特卡洛 (年化波动28%, 1年逐小时, 400次, 止盈=本金1%=$600)')
    print('    A: 不设整体止损   B: 设整体止损=本金12%=$7200')
    print('=' * 92)
    print('%-34s %8s %9s %9s %8s | %8s %9s %9s' % (
        '方案', 'A爆仓率', 'A收益中位', 'A回撤P90', 'A轮次', 'B爆仓率', 'B收益中位', 'B回撤P90'))
    print('-' * 92)
    for label, init, mult, n, dpt, maxlot, g in built:
        a = simulate(init, mult, n, dpt, maxlot, tp=600.0, sl=0.0, n_sim=250)
        b = simulate(init, mult, n, dpt, maxlot, tp=600.0, sl=7200.0, n_sim=250)
        print('%-34s %7.1f%% %8.1f%% %8.1f%% %8.0f | %7.1f%% %8.1f%% %8.1f%%' % (
            label, a['dead_rate'] * 100, a['ret_med'] * 100, a['dd_p90'] * 100, a['rounds'],
            b['dead_rate'] * 100, b['ret_med'] * 100, b['dd_p90'] * 100))
