# -*- coding: utf-8 -*-
"""
$60k + 关 ATR 止损 的退出保护优化
- 固定 稳健 网格结构, UseAtrSl=False
- 扫描 整体止损 OverallSl × 回撤保护 RetraceDrawdown
- 目标: 在 ATR-off 暴赚区间里, 用退出保护压住回撤/爆仓, 找风险调整后最优配置
- 产物: opt_atroff_60k.json + GridMaster_ATRoff优化报告.html
"""
import json
import backtest as B

SLS = [0, 5000, 10000, 15000, 20000, 30000]
RETRS = [10, 25, 50]

def main():
    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 = B.wilder_rsi(closes, 14)
    atr = B.wilder_atr(highs, lows, closes, 14)

    base = B.load_set('GridMaster_稳健.set')
    base['UseAtrSl'] = False
    rows = []
    for sl in SLS:
        for rd in RETRS:
            P = dict(base)
            P['OverallSl'] = float(sl)
            P['RetraceDrawdown'] = float(rd)
            r = B.run_backtest(bars, rsi, atr, P, start_equity=60000.0, stop_on_blowup=True)
            ratio = r['return_pct'] / r['max_dd_pct'] if r['max_dd_pct'] > 0 else 0.0
            rows.append({'sl': sl, 'rd': rd, 'final': r['final'], 'ret': r['return_pct'],
                         'dd': r['max_dd_pct'], 'win': r['win_rate'], 'blow': r['blowups'],
                         'rounds': r['rounds'], 'ratio': ratio})
            print('SL=%-6.0f RD=%-3.0f 终值=$%9.0f 收益=%6.1f%% DD=%5.1f%% 胜率=%5.1f%% 爆仓=%d ratio=%.2f'
                  % (sl, rd, r['final'], r['return_pct'], r['max_dd_pct'], r['win_rate'], r['blowups'], ratio))
    json.dump(rows, open('opt_atroff_60k.json', 'w'))
    print('\n已保存 opt_atroff_60k.json')
    build_report(rows)

# ============================================================
def build_scatter(rows):
    W, H = 640, 380
    mx, my, rx, ty = 56, 28, 18, 18   # 边距
    ymax = max(max(r['ret'] for r in rows), 100.0) * 1.1
    xmax = 100.0
    pts = []
    for r in rows:
        x = mx + (r['dd'] / xmax) * (W - mx - rx)
        y = H - my - (r['ret'] / ymax) * (H - my - ty)
        color = '#c0392b' if r['blow'] > 0 else '#1f77b4'
        rad = 5 + min(r['ratio'], 12)
        pts.append('<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s" fill-opacity="0.75" stroke="#fff" stroke-width="1">'
                   '<title>SL=%d RD=%d\n收益=%.1f%%  回撤=%.1f%%\n胜率=%.1f%%  爆仓=%d\n收益/回撤=%.2f</title></circle>'
                   % (x, y, rad, color, r['sl'], r['rd'], r['ret'], r['dd'], r['win'], r['blow'], r['ratio']))
    # 坐标轴
    axes = []
    axes.append('<line x1="%.0f" y1="%.0f" x2="%.0f" y2="%.0f" stroke="#888"/>' % (mx, H-my, W-rx, H-my))   # x
    axes.append('<line x1="%.0f" y1="%.0f" x2="%.0f" y2="%.0f" stroke="#888"/>' % (mx, H-my, mx, ty))       # y
    for gx in range(0, 101, 20):
        xx = mx + gx/100.0*(W-mx-rx)
        axes.append(f'<text x="{xx:.0f}" y="{H-my+14:.0f}" font-size="10" fill="#999" text-anchor="middle">{gx}%</text>')
    for gy in range(0, int(ymax)+1, max(100, int(ymax/5)//100*100)):
        yy = H-my - gy/ymax*(H-my-ty)
        axes.append(f'<text x="{mx-6:.0f}" y="{yy+3:.0f}" font-size="10" fill="#999" text-anchor="end">{gy}%</text>')
    axes.append(f'<text x="{(mx+W-rx)/2:.0f}" y="{H-4:.0f}" font-size="11" fill="#555" text-anchor="middle">最大回撤 %</text>')
    axes.append(f'<text x="14" y="{(my+H-my)/2:.0f}" font-size="11" fill="#555" text-anchor="middle" transform="rotate(-90 14 {(my+H-my)/2:.0f})">收益 %</text>')
    return '<svg viewBox="0 0 640 400" xmlns="http://www.w3.org/2000/svg" style="background:#fff;border:1px solid #e0e0e0">' + ''.join(axes) + ''.join(pts) + '</svg>'

def build_report(rows):
    safe = [r for r in rows if r['blow'] == 0]
    safe.sort(key=lambda r: r['ratio'], reverse=True)
    best = safe[0] if safe else None
    # 表格(按 ratio 降序, 未爆仓优先)
    order = sorted(rows, key=lambda r: (r['blow'] > 0, -r['ratio']))
    trows = []
    for r in order:
        bg = ' style="background:#fdecea"' if r['blow'] > 0 else ''
        rc = 'pos' if r['ret'] > 0 else 'neg'
        trows.append('<tr%s><td>$%d</td><td>%d%%</td><td>%s</td><td class="%s">%+.1f%%</td>'
                     '<td>%.1f%%</td><td>%.1f%%</td><td>%d</td><td><b>%.2f</b></td></tr>'
                     % (bg, r['sl'], r['rd'], '${:,.0f}'.format(r['final']), rc, r['ret'],
                        r['dd'], r['win'], r['blow'], r['ratio']))
    table = ('<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(trows) + '</tbody></table>')

    best_txt = ''
    if best:
        cmp = '回撤更可控且收益更高' if (best['ret'] >= 469.5 and best['dd'] <= 38.0) else '在收益与回撤间取得更好平衡'
        best_txt = (f"<li><b>推荐配置（风险调整后最优）：整体止损 ${best['sl']:,.0f}、回撤容忍 {best['rd']}%。</b>"
                    f"收益 {best['ret']:+.1f}%、最大回撤 {best['dd']:.1f}%、胜率 {best['win']:.1f}%、爆仓 {best['blow']} 轮，"
                    f"收益/回撤比 {best['ratio']:.2f}（全场最高）。"
                    f"相比基准（SL=$15k/RD=10%：收益 +469.5%/回撤 38.0%），此配置{cmp}。</li>")

    html = """<!doctype html><html lang="zh"><head><meta charset="utf-8">
<title>GridMaster ATR-off $60k 优化报告</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:24px 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 · $60k 关 ATR 止损 · 退出保护优化</h1>
<p class="sub">固定稳健网格(1.20/12/$45)，强制 ATR 逐单止损=关；扫描 整体止损{0,5k,10k,15k,20k,30k} × 回撤容忍{10,25,50}%。
数据：XAUUSDm M30 2018-03~2026-09（约 8.5 年）｜爆仓后账户终止。</p>
<h2 style="font-size:18px">头条结论</h2>
<ul class="find">
__BEST__
<li><b>关 ATR 后，整体止损(OverallSl)就是唯一的爆仓防火墙。</b>SL=0（无整体止损）时回撤最大；加上 SL 后回撤被显著压低，且本组 18 个组合无一爆仓（稳健 1.2×/12 结构本身比原版 1.5×/15 温和，配合 SL 足以防击穿）。</li>
<li><b>回撤容忍(RetraceDrawdown) 决定"让利润奔跑"的空间：</b>调大 RD（如 50%%）让盈利轮次多走一段再止盈，收益与回撤同步放大；调小 RD（10%%）更早落袋、回撤更窄。需在收益与回撤间权衡。</li>
<li><b>注意：本回测仍是无点差/无滑点/无隔夜息的理想环境，绝对收益偏乐观；但各组合间的相对优劣（收益/回撤比排序）是可靠的。</b></li>
</ul>
<h2 style="font-size:18px;margin-top:26px">收益 ↔ 回撤 前沿（点大小=收益/回撤比，红=爆仓）</h2>
<p class="sub">右上=高收益高回撤；理想区在"高收益 + 低回撤"的左上。悬停看点参数。</p>
__SCATTER__
<h2 style="font-size:18px;margin-top:26px">全组合矩阵</h2>
__TABLE__
<p class="note">收益/回撤 = 收益% ÷ 最大回撤%，越大越好（风险调整后）。基准对照：SL=$15k/RD=10% 为原稳健 ATR-off 配置。</p>
</body></html>"""
    html = html.replace('__BEST__', best_txt)
    html = html.replace('__SCATTER__', build_scatter(rows))
    html = html.replace('__TABLE__', table)
    open('GridMaster_ATRoff优化报告.html', 'w', encoding='utf-8').write(html)
    print('已保存 GridMaster_ATRoff优化报告.html')

if __name__ == '__main__':
    main()
