# -*- coding: utf-8 -*-
"""
GridMaster 重测: 小本金($5k/$10k) + 关 ATR 逐单止损
- 固定 4 套参数结构, 扫描 本金 {60000,10000,5000} × ATR {on, off}
- 爆仓后账户终止交易 (stop_on_blowup=True), 更贴近真实爆仓分布
- 产物: retest_results.json + GridMaster_小本金与关ATR重测报告.html
"""
import json
import backtest as B

CAPS = [60000, 10000, 5000]
CONFIGS = [
    ('稳健', 'GridMaster_稳健.set'),
    ('保守', 'GridMaster_保守.set'),
    ('进取', 'GridMaster_进取.set'),
    ('原版复刻', 'GridMaster_原版复刻.set'),
]

def downsample(curve, n=400):
    if len(curve) <= n:
        return curve
    step = (len(curve) - 1) / (n - 1)
    return [curve[int(i * step)] for i in range(n)]

def run_matrix():
    bars = B.load_bars()
    closes = [b[4] for b in bars]
    highs = [b[2] for b in bars]
    lows = [b[3] for b in bars]
    rsi_arr = B.wilder_rsi(closes, 14)
    atr_arr = B.wilder_atr(highs, lows, closes, 14)

    matrix = {}
    for cname, cfn in CONFIGS:
        base = B.load_set(cfn)
        orig_atr = base['UseAtrSl']
        matrix[cname] = {}
        for cap in CAPS:
            for atr_on in (True, False):
                P = dict(base)
                P['UseAtrSl'] = orig_atr if atr_on else False
                r = B.run_backtest(bars, rsi_arr, atr_arr, P,
                                   start_equity=float(cap), stop_on_blowup=True)
                r['config'] = cname
                r['capital'] = cap
                r['atr_on'] = atr_on
                r['equity_curve'] = downsample(r['equity_curve'], 400)
                key = '%d_%s' % (cap, 'on' if atr_on else 'off')
                matrix[cname][key] = r
                print('%-8s $%-6d %-8s 终值=$%9.0f 收益=%6.1f%% DD=%5.1f%% 轮=%4d 胜率=%5.1f%% 爆仓=%4d 触顶=%3d' % (
                    cname, cap, 'ATR on' if atr_on else 'ATR off',
                    r['final'], r['return_pct'], r['max_dd_pct'], r['rounds'],
                    r['win_rate'], r['blowups'], r['deep_grids']))
    json.dump(matrix, open('retest_results.json', 'w'),
              default=lambda o: o.tolist() if hasattr(o, 'tolist') else o)
    print('\n已保存 retest_results.json')
    return matrix

# ============================================================
# 报告生成 (内联 SVG / HTML, 无第三方依赖)
# ============================================================
def esc(s):
    return str(s).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

def fmt_money(v):
    return '${:,.0f}'.format(v) if v >= 0 else '-${:,.0f}'.format(abs(v))

def build_equity_chart(matrix, cfg='稳健'):
    W, H = 640, 300
    scenarios = []
    palette = {
        (60000, True):  ('#1f77b4', False, '$60k · ATR开'),
        (60000, False): ('#1f77b4', True,  '$60k · ATR关'),
        (10000, True):  ('#2ca02c', False, '$10k · ATR开'),
        (10000, False): ('#2ca02c', True,  '$10k · ATR关'),
        (5000, True):   ('#d62728', False, '$5k · ATR开'),
        (5000, False):  ('#d62728', True,  '$5k · ATR关'),
    }
    for (cap, atr_on), (color, dash, label) in palette.items():
        key = '%d_%s' % (cap, 'on' if atr_on else 'off')
        curve = matrix[cfg][key]['equity_curve']
        scenarios.append((label, color, dash, curve, cap))

    lines = []
    legend = []
    for i, (label, color, dash, curve, cap) in enumerate(scenarios):
        n = len(curve)
        pts = []
        for j, eq in enumerate(curve):
            x = 10 + j / (n - 1) * (W - 20)
            val = max(eq, 0.0) / cap            # 归一: 权益/本金
            y = H - 10 - val * (H - 20)
            y = max(10, min(H - 10, y))
            pts.append('%.1f,%.1f' % (x, y))
        style = 'fill:none;stroke:%s;stroke-width:2' % color
        if dash:
            style += ';stroke-dasharray:5,3'
        lines.append('<polyline points="%s" style="%s"/>' % (' '.join(pts), style))
        legend.append('<rect x="%d" y="%d" width="12" height="3" fill="%s"/>'
                      '<text x="%d" y="%d" font-size="11" fill="#333">%s</text>'
                      % (10 + (i % 3) * 210, 16 + (i // 3) * 16, color,
                         26 + (i % 3) * 210, 20 + (i // 3) * 16, label))
    # 100% 基准线
    y100 = H - 10 - (H - 20)
    grid = '<line x1="10" y1="%.1f" x2="%d" y2="%.1f" stroke="#ccc" stroke-dasharray="3,3"/>' % (y100, W - 10, y100)
    grid += '<text x="%d" y="%.1f" font-size="10" fill="#999">100%% 本金</text>' % (W - 70, y100 - 4)
    return ('<svg viewBox="0 0 640 340" xmlns="http://www.w3.org/2000/svg" style="background:#fff;border:1px solid #e0e0e0">'
            + grid + ''.join(lines) + ''.join(legend) + '</svg>')

def build_matrix_table(matrix, cfg):
    rows = []
    for cap in CAPS:
        for atr_on in (True, False):
            key = '%d_%s' % (cap, 'on' if atr_on else 'off')
            r = matrix[cfg][key]
            blow = r['blowups'] > 0
            ret_cls = 'pos' if r['return_pct'] > 0 else 'neg'
            bg = ' style="background:#fdecea"' if blow else ''
            rows.append(
                '<tr%s><td>%s</td><td>%s</td><td class="%s">%+.1f%%</td>'
                '<td>%.1f%%</td><td>%d</td><td>%.1f%%</td>'
                '<td class="%s">%d</td><td>%d</td></tr>' % (
                    bg,
                    '$%s · %s' % ('{:,}'.format(cap), 'ATR开' if atr_on else 'ATR关'),
                    fmt_money(r['final']), ret_cls, r['return_pct'],
                    r['max_dd_pct'], r['rounds'], r['win_rate'],
                    'neg' if blow else '', r['blowups'], r['deep_grids']))
    return ('<table class="mt"><thead><tr><th>场景</th><th>终值</th><th>收益</th>'
            '<th>最大回撤</th><th>轮次</th><th>胜率</th><th>爆仓轮</th><th>触顶网格</th></tr></thead>'
            '<tbody>' + ''.join(rows) + '</tbody></table>')

def build_report(matrix):
    # 头条结论 (基于 稳健 + 原版复刻)
    def g(c, cap, atr):
        return matrix[c]['%d_%s' % (cap, 'on' if atr else 'off')]
    w60_on, w60_off = g('稳健', 60000, True), g('稳健', 60000, False)
    w5_on, w5_off = g('稳健', 5000, True), g('稳健', 5000, False)
    orig60_on, orig5_on = g('原版复刻', 60000, True), g('原版复刻', 5000, True)

    # 小本金爆仓统计
    blow_5k_on = sum(1 for c in CONFIGS if matrix[c[0]]['5000_on']['blowups'] > 0)
    blow_5k_off = sum(1 for c in CONFIGS if matrix[c[0]]['5000_off']['blowups'] > 0)
    blow_10k_on = sum(1 for c in CONFIGS if matrix[c[0]]['10000_on']['blowups'] > 0)

    # 头条结论 (基于实测数据; f-string 避免 % 逗号格式问题)
    o60off = g('原版复刻', 60000, False)
    c60off = g('保守', 60000, False)
    j60off = g('进取', 60000, False)
    c10off = g('保守', 10000, False)
    findings = [
        f"<li><b>$5k 小本金下 4/4 套参数全部爆仓</b>（ATR开与ATR关皆然，终值归零）。"
        f"根因：手数固定 0.01–0.4 不随本金缩放，$5k 把相对仓位推过爆仓阈值——"
        f"$60k 上的 0 爆仓只是本金够厚。$10k 开始分化：稳健/保守/进取 在 ATR开 下勉强不爆但回撤已达 70–96%，"
        f"ATR关 则多数爆仓（仅保守 $10k 幸存且飙到 +{c10off['return_pct']:.0f}%，属幸存者偏差）。"
        f"这套固定手数网格的「安全本金线」约在 $10k–$60k 之间，低于约 $10k 必死。</li>",

        f"<li><b>关 ATR 逐单止损 → 在 $60k 上把微亏变暴赚，证实「ATR 止损是最大拖累」：</b>"
        f"稳健 {w60_on['return_pct']:+.1f}%→{w60_off['return_pct']:+.1f}%、"
        f"保守 {g('保守',60000,True)['return_pct']:+.1f}%→{c60off['return_pct']:+.1f}%、"
        f"进取 {g('进取',60000,True)['return_pct']:+.1f}%→{j60off['return_pct']:+.1f}%，"
        f"胜率从 72% 跳到 96–98%。ATR 止损用「提前小亏」截断了网格自愈，关掉后逆势单能等到回补。"
        f"但原版复刻 $60k ATR关 反而爆仓（终值 {fmt_money(o60off['final'])}）——1.5×/15档/$25 无整体SL，"
        f"失去 ATR 阀门即被一波逆势击穿。</li>",

        f"<li><b>但 ATR 关在小本金是催命符：</b>$5k ATR关 4/4 爆仓、$10k ATR关 多数爆仓"
        f"（仅保守 $10k 幸存飙到 +{c10off['return_pct']:.0f}%）。即 ATR 止损是「爆仓防火墙」——"
        f"大本金时它压上限、小本金时它保命，不能简单说「关掉更好」。</li>",

        f"<li><b>原版复刻(1.5×/15档/$25/无整体SL) 最脆弱：</b>"
        f"$60k ATR开 仅 {orig60_on['return_pct']:+.1f}%（靠回撤保护捡回），$60k ATR关 直接爆仓，"
        f"$5k 必死。早前「原版搬 $60k 必爆仓」的建模在小本金下更夸张。</li>",
    ]

    tables = ''.join(
        '<h3>%s</h3>%s' % (c, build_matrix_table(matrix, c)) for c, _ in CONFIGS)

    html = """<!doctype html><html lang="zh"><head><meta charset="utf-8">
<title>GridMaster 小本金与关ATR重测报告</title>
<style>
 body{font-family:-apple-system,"Segoe UI",Roboto,"Microsoft YaHei",sans-serif;color:#222;max-width:900px;margin:24px auto;padding:0 16px;background:#fff}
 h1{font-size:22px;border-bottom:3px solid #1f77b4;padding-bottom:8px}
 h3{font-size:16px;margin:28px 0 8px;color:#1a4f7a}
 .sub{color:#666;font-size:13px;margin:4px 0 18px}
 ul.find{background:#f7f9fc;border-left:4px solid #1f77b4;padding:12px 18px;border-radius:4px}
 ul.find li{margin:8px 0;line-height:1.55}
 table.mt{border-collapse:collapse;width:100%;font-size:13px;margin:6px 0}
 table.mt th,table.mt td{border:1px solid #e2e2e2;padding:6px 8px;text-align:center}
 table.mt th{background:#eef3f8;font-weight:600}
 .pos{color:#1a7f37;font-weight:600}.neg{color:#c0392b;font-weight:600}
 .note{color:#888;font-size:12px;margin-top:8px}
 svg{margin-top:10px;max-width:100%}
</style></head><body>
<h1>GridMaster · 小本金($5k/$10k) 与 关 ATR 止损 重测报告</h1>
<p class="sub">数据：Exness MT5 Trial5 XAUUSDm M30，2018-03 ~ 2026-09（约 8.5 年 / 99,345 根有效 K 线）｜
引擎：Python 自研回测（忠实复刻 GridMaster.mq5 OnTick 逻辑）｜
近似：Close=(H+L)/2、点差=0、无隔夜息、<b>爆仓后账户终止交易</b></p>
<h2 style="font-size:18px">头条结论</h2>
<ul class="find">__FINDINGS__</ul>
<h2 style="font-size:18px;margin-top:26px">稳健版权益曲线（归一化：权益/本金，起点=100%）</h2>
<p class="sub">蓝=$60k，绿=$10k，红=$5k；实线=ATR开，虚线=ATR关。红线触底=该账户已爆仓终止。</p>
__CHART__
<h2 style="font-size:18px;margin-top:26px">全参数矩阵</h2>
__TABLES__
<p class="note">说明：触顶网格=单轮内加满 MaxOrders 档的次数；爆仓轮=账户权益≤0 的轮次（已终止）。
$60k 结果可与上一版回测报告对照（结构一致）。</p>
</body></html>"""
    html = html.replace('__FINDINGS__', '\n'.join(findings))
    html = html.replace('__CHART__', build_equity_chart(matrix, '稳健'))
    html = html.replace('__TABLES__', tables)

    open('GridMaster_小本金与关ATR重测报告.html', 'w', encoding='utf-8').write(html)
    print('已保存 GridMaster_小本金与关ATR重测报告.html')

if __name__ == '__main__':
    m = run_matrix()
    build_report(m)
