{# 三所共用 standalone 主页 — 由 scripts/build_unified_index.py 生成,勿手改三所副本 #}
﻿<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/instance_theme.js?v=50"></script>
    <link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
    <link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
    <script src="/static/account_risk_badge.js?v=4"></script>

    <meta name="theme-color" content="#0b0d14">
    <meta name="apple-mobile-web-app-title" content="{{ pwa_app_name }}">
    <link rel="icon" href="/static/icons/favicon.ico" sizes="32x32">
    <link rel="icon" href="/static/icons/icon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
    <link rel="manifest" href="/static/icons/manifest.webmanifest">
    <title>{{ pwa_app_name }}</title>
    <link rel="stylesheet" href="/static/instance_page.css?v=9">
    <link rel="stylesheet" href="/static/instance_theme.css?v=96">

</head>
<body
    data-page="{{ page }}"
    data-intraday-discipline="{% if intraday_discipline %}1{% else %}0{% endif %}"
    data-risk-percent="{{ risk_percent }}"
    data-position-sizing-mode="{{ position_sizing_mode }}"
    data-btc-leverage="{{ btc_leverage }}"
    data-alt-leverage="{{ alt_leverage }}"
    data-full-margin-buffer="{{ full_margin_buffer_ratio }}"
    data-price-refresh-ms="{{ price_refresh_seconds * 1000 }}"
>
{% macro period_stats_pane(period_key, s) %}
{% set win_pct = s.win_rate_pct if s.win_rate_pct is not none else 0 %}
{% set profit_sum = (s.net_pnl_u + s.loss_sum_u) if s.closed_count else 0 %}
{% set loss_sum = s.loss_sum_u %}
{% set pnl_total = profit_sum + loss_sum %}
{% set profit_bar_w = (profit_sum / pnl_total * 100) if pnl_total > 0 else 0 %}
{% set loss_bar_w = (loss_sum / pnl_total * 100) if pnl_total > 0 else 0 %}
{% set net_cls = 'pos-pnl-profit' if s.net_pnl_u > 0 else ('pos-pnl-loss' if s.net_pnl_u < 0 else '') %}
<div class="stats-period-pane" data-stats-period="{{ period_key }}" role="tabpanel"{% if period_key != 'day' %} hidden{% endif %}>
    <div class="stats-period-range">{{ s.range_label }}</div>
    <div class="inst-stats-viz">
        {% if s.closed_count %}
        <div class="inst-stats-kpis">
            <div class="inst-stats-kpi inst-stats-kpi--pnl">
                <span class="inst-stats-kpi-val {{ net_cls }}">{% if s.net_pnl_u > 0 %}+{% endif %}{{ funds_fmt(s.net_pnl_u) }}U</span>
                <span class="inst-stats-kpi-lbl">净盈亏</span>
            </div>
            <div class="inst-stats-kpi inst-stats-kpi--win">
                <div class="inst-stats-ring" style="--win-pct: {{ win_pct }}">
                    <span class="inst-stats-ring-label">{% if s.win_rate_pct is not none %}{{ win_pct|round(0)|int }}%{% else %}—{% endif %}</span>
                </div>
                <span class="inst-stats-kpi-lbl">{{ s.win_count }}胜 {{ s.loss_count }}负</span>
            </div>
            <div class="inst-stats-kpi inst-stats-kpi--trades">
                <span class="inst-stats-kpi-val">{{ s.opens_count }} / {{ s.closed_count }}</span>
                <span class="inst-stats-kpi-lbl">开单 / 平仓</span>
            </div>
        </div>
        <div class="inst-stats-block">
            <div class="inst-stats-block-title">盈亏构成</div>
            <div class="inst-stats-stacked-bar">
                <div class="inst-stats-stacked-fill inst-stats-stacked-fill--profit" style="width: {{ '%.1f'|format(profit_bar_w) }}%"></div>
                <div class="inst-stats-stacked-fill inst-stats-stacked-fill--loss" style="width: {{ '%.1f'|format(loss_bar_w) }}%"></div>
            </div>
            <div class="inst-stats-bar-labels">
                <span class="pos-pnl-profit">盈利 {{ funds_fmt(profit_sum) }}U</span>
                <span class="pos-pnl-loss">亏损 {{ funds_fmt(loss_sum) }}U</span>
            </div>
        </div>
        <div class="inst-stats-block inst-stats-block--risk">
            <div class="inst-stats-risk-grid">
                <div class="inst-stats-risk-item">
                    <span class="k">最大回撤</span>
                    <span class="v pos-pnl-loss">{{ funds_fmt(s.max_drawdown_u) }}U</span>
                </div>
                <div class="inst-stats-risk-item">
                    <span class="k">连续亏损</span>
                    <span class="v">{{ s.consecutive_losses }} 笔</span>
                </div>
                <div class="inst-stats-risk-item">
                    <span class="k">最长连亏日</span>
                    <span class="v">{{ s.max_loss_streak_days }} 天</span>
                </div>
                <div class="inst-stats-risk-item">
                    <span class="k">最大亏损日</span>
                    <span class="v">{% if s.worst_day %}{{ s.worst_day }} ({{ funds_fmt(s.worst_day_pnl) }}U){% else %}—{% endif %}</span>
                </div>
            </div>
        </div>
        {% else %}
        <p class="inst-stats-empty">当前区间暂无平仓数据</p>
        {% endif %}
    </div>
    <details class="inst-stats-details" open>
        <summary>详细指标</summary>
        <div class="stats-detail">
            <div class="stat-item"><div class="label">开单次数</div><div class="value">{{ s.opens_count }}</div></div>
            <div class="stat-item"><div class="label">平仓笔数</div><div class="value">{{ s.closed_count }}</div></div>
            <div class="stat-item"><div class="label">胜率</div><div class="value">{% if s.win_rate_pct is not none %}{{ s.win_rate_pct }}%{% else %}-{% endif %}</div></div>
            <div class="stat-item"><div class="label">净盈亏(U)</div><div class="value">{{ funds_fmt(s.net_pnl_u) }}</div></div>
            <div class="stat-item"><div class="label">亏损额合计(U)</div><div class="value">{{ funds_fmt(s.loss_sum_u) }}</div></div>
            <div class="stat-item"><div class="label">单笔最大亏损(U)</div><div class="value">{% if s.max_single_loss is not none %}{{ funds_fmt(s.max_single_loss) }}{% else %}-{% endif %}</div></div>
            <div class="stat-item"><div class="label">单笔最大盈利(U)</div><div class="value">{% if s.max_single_profit is not none %}{{ funds_fmt(s.max_single_profit) }}{% else %}-{% endif %}</div></div>
            <div class="stat-item"><div class="label">最大回撤(U)</div><div class="value">{{ funds_fmt(s.max_drawdown_u) }}</div></div>
            <div class="stat-item"><div class="label">当前连续亏损笔数</div><div class="value">{{ s.consecutive_losses }}</div></div>
            <div class="stat-item"><div class="label">最长连续亏损(交易日)</div><div class="value">{{ s.max_loss_streak_days }} 天</div></div>
            <div class="stat-item"><div class="label">期内最大亏损日</div><div class="value">{% if s.worst_day %}{{ s.worst_day }}({{ funds_fmt(s.worst_day_pnl) }}U){% else %}-{% endif %}</div></div>
        </div>
    </details>
</div>
{% endmacro %}
<div class="container">
    <div class="header">
        <h1>加密货币｜交易监控 + AI复盘一体化</h1>
    </div>
    <div class="top-nav">
        <a href="/dashboard" data-embed-tab="dashboard" class="{% if page == 'dashboard' %}active{% endif %}"{% if not display.show_nav_dashboard %} style="display:none"{% endif %}>数据看板</a>
        <a href="/key_monitor" class="{% if page == 'key_monitor' %}active{% endif %}">关键位监控</a>
        <a href="/trade" class="{% if page == 'trade' %}active{% endif %}">实盘下单</a>
        {% if not intraday_discipline and display.show_nav_strategy %}
        <a href="/strategy" class="{% if page in ('strategy', 'strategy_trend', 'strategy_roll') %}active{% endif %}">策略交易</a>
        {% endif %}
        {% if not intraday_discipline and display.show_nav_strategy_records %}
        <a href="/strategy/records" class="{% if page == 'strategy_records' %}active{% endif %}">策略交易记录</a>
        {% endif %}
        {% if display.show_nav_records %}
        <a href="/records" class="{% if page == 'records' %}active{% endif %}">交易记录与复盘</a>
        {% endif %}
        {% if display.show_nav_stats %}
        <a href="/stats" class="{% if page == 'stats' %}active{% endif %}">统计分析</a>
        {% endif %}
        {% if options_nav_visible and display.show_nav_options %}
        <a href="/options" class="{% if page == 'options' %}active{% endif %}">期权</a>
        {% endif %}
        {% if options_nav_visible and display.show_nav_options_review %}
        <a href="/options/review" class="{% if page == 'options_review' %}active{% endif %}">期权复盘</a>
        {% endif %}
        {% if hedge_plan_nav_visible and display.show_nav_hedge_plan %}
        <a href="/hedge-plan" class="{% if page == 'hedge_plan' %}active{% endif %}">对冲计划</a>
        {% endif %}
        {% if display.show_nav_risk_policy %}
        <a href="/risk_policy" class="{% if page == 'risk_policy' %}active{% endif %}">风控说明</a>
        {% endif %}
        {% if display.show_nav_env_config %}
        <a href="/env_config" class="{% if page == 'env_config' %}active{% endif %}">env配置</a>
        {% endif %}
        <a href="/settings" class="{% if page == 'settings' %}active{% endif %}">系统设置</a>
    </div>
    {% with msg=get_flashed_messages() %}{% if msg %}<div class="flash">{{ msg[0] }}</div>{% endif %}{% endwith %}

    {% include 'instance_header_panel.html' %}
    {% if page not in ('settings', 'risk_policy', 'env_config', 'options', 'options_review', 'hedge_plan') %}
    {% include 'instance_top_bar.html' %}
    {% endif %}

    <div class="grid">
        {% if page == 'dashboard' %}
        {% include 'dashboard_panel.html' %}
        {% elif page == 'key_monitor' %}
        {% include 'key_monitor_panel.html' %}
        {% elif page == 'trade' %}
        <div class="dual-panel-grid" style="grid-column:1/-1">
        <div class="card">
            <div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:8px">
                <h2 style="margin-bottom:0">实盘下单监控</h2>
                {% if focus_order_id %}
                <a href="/order_focus?order_id={{ focus_order_id }}" class="btn-del" style="text-decoration:none;background:#1f3a5a;color:#8fc8ff">放大查看K线(100根)</a>
                {% else %}
                <span class="btn-del" style="background:#2f2f44;color:#9aa;cursor:not-allowed">暂无持仓可放大</span>
                {% endif %}
            </div>
            {% include order_rule_tips_tpl %}
            <form id="add-order-form" action="/add_order" method="post" class="form-row" data-risk-percent="{{ risk_percent }}">
                {% from 'trade_policy_fields.html' import trade_policy_symbol, trade_policy_direction with context %}
                {{ trade_policy_symbol('symbol', 'order-symbol') }}
                {{ trade_policy_direction('direction', 'order-direction') }}
                <select id="sltp-mode" name="sltp_mode">
                    <option value="fixed_rr" selected>止盈止损:固定盈亏比</option>
                    <option value="price">止盈止损:价格模式</option>
                    <option value="pct">止盈止损:百分比模式</option>
                </select>
                {% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
                {{ order_entry_type_fields() }}
                {% from 'order_leverage_fields.html' import order_leverage_fields with context %}
                {{ order_leverage_fields() }}
                {% if not intraday_discipline %}
                <label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
                    <input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
                </label>
                <span id="order-time-close-wrap" class="order-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
                    <label style="display:inline-flex;align-items:center;gap:4px;margin:0;cursor:pointer">
                        <input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb"> 时间平仓
                    </label>
                    <select name="time_close_hours" id="order-time-close-hours" title="持仓满该时长后自动平仓">
                        <option value="1">1h</option>
                        <option value="2">2h</option>
                        <option value="4" selected>4h</option>
                    </select>
                </span>
                {% else %}
                <input type="hidden" name="breakeven_enabled" value="0">
                {% endif %}
                <label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
                    <input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
                </label>
                {% from 'symbol_live_price_snippet.html' import symbol_live_price_hint %}
                {{ symbol_live_price_hint('order-symbol-live-price', 'order-symbol', 'order-direction') }}
                <span class="symbol-live-price-note">下单成交价以交易所成交回报为准</span>
                <input id="order-sl" name="sl" step="any" placeholder="止损价格" required>
                <input id="order-fixed-rr" name="fixed_rr" type="number" min="0.01" step="0.01" placeholder="盈亏比(默认1.5)" value="1.5" title="止盈距离=止损距离×盈亏比">
                <input id="order-tp" name="tgt" step="any" placeholder="止盈价格" style="display:none">
                <input id="order-sl-pct" name="sl_pct" type="number" min="0.01" step="0.01" placeholder="止损%" style="display:none">
                <input id="order-tp-pct" name="tp_pct" type="number" min="0.01" step="0.01" placeholder="止盈%" style="display:none">
                <button type="submit">{{ open_position_button_label }}</button>
            </form>
            {% include 'order_plan_preview_bar.html' %}
        </div>
        <div class="card">
            <h2 style="margin-bottom:8px">实时持仓</h2>
            {% if ui_orphan_recovery_enabled %}
            {% if not order and orphan_live_positions %}
            {% set o = orphan_live_positions[0] %}
            <div id="orphan-position-recover" class="orphan-recover-banner" style="display:block;margin-bottom:10px;padding:10px 12px;background:#2a2210;border:1px solid #6b5420;border-radius:6px;font-size:.9rem;color:#e8d5a8">
                检测到交易所仍有 <strong>{{ o.symbol }}</strong> {{ '空' if o.direction == 'short' else '多' }}仓,但本地监控已中断(误同步时可能无交易记录).
                {% if o.recoverable_monitor_id %}
                <button type="button" class="pos-entrust-btn" onclick="recoverLivePosition({{ o.recoverable_monitor_id }})">恢复监控{% if o.plan_stop_loss and o.plan_take_profit %}并挂止盈止损{% endif %}</button>
                {% else %}
                未找到可恢复的监控记录,需在服务器数据库处理.
                {% endif %}
            </div>
            {% else %}
            <div id="orphan-position-recover" class="orphan-recover-banner" style="display:none;margin-bottom:10px;padding:10px 12px;background:#2a2210;border:1px solid #6b5420;border-radius:6px;font-size:.9rem;color:#e8d5a8"></div>
            {% endif %}
            {% endif %}
            <div class="panel-scroll pos-list pos-list-live">
                {% for o in order %}
                <div class="pos-card" id="order-row-{{ o.id }}"
                    data-monitor-id="{{ o.id }}"
                    data-symbol="{{ o.symbol }}"
                    data-direction="{{ o.direction }}"
                    data-plan-sl="{% if o.stop_loss %}{{ price_fmt(o.symbol, o.stop_loss) }}{% endif %}"
                    data-plan-tp="{% if o.take_profit %}{{ price_fmt(o.symbol, o.take_profit) }}{% endif %}"
                    data-entry="{% if o.trigger_price %}{{ price_fmt(o.symbol, o.trigger_price) }}{% endif %}">
                    <div class="pos-card-head">
                        <div class="pos-card-symbol">
                            <strong>{{ o.exchange_symbol or o.symbol }}</strong>
                            {% if o.time_close_enabled %}
                            <span class="pos-symbol-time-close pos-meta-on pos-time-close-meta" id="order-time-close-wrap-{{ o.id }}"
                                data-close-at-ms="{{ o.time_close_at_ms or '' }}">
                                <span class="pos-time-close-label">时间平仓 {{ o.time_close_hours or '' }}h</span>
                                · <span class="pos-time-close-cd" id="order-time-close-cd-{{ o.id }}">--:--:--</span>
                            </span>
                            {% endif %}
                            {% include 'force_close_order_badge.html' %}
                            <span class="pos-side-badge {{ 'pos-side-long' if o.direction == 'long' else 'pos-side-short' }}">{{ '做多' if o.direction == 'long' else '做空' }}</span>
                        </div>
                        <div class="pos-head-actions">
                            {% if not intraday_discipline %}
                            <button type="button" class="pos-entrust-btn" onclick="openTpslEntrustModal({{ o.id }})">委托</button>
                            <a href="/del_order/{{ o.id }}" class="pos-close-btn" onclick="return confirm('删除会触发手动平仓,继续?')">平仓</a>
                            {% endif %}
                        </div>
                    </div>
                    <div class="pos-meta">
                        <span class="pos-meta-item">来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %}</span>
                        <span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% elif intraday_discipline %}开仓: —{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
                        <span class="pos-meta-item">风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %}</span>
                        <span class="pos-meta-item" id="order-latest-risk-wrap-{{ o.id }}" style="display:none">最新风险: —</span>
                        <span class="pos-meta-item {% if not intraday_discipline %}{% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}{% endif %}">
                            {% if intraday_discipline %}
                            {% elif o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
                        </span>
                        <span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
                    </div>
                    <div class="pos-grid">
                        <div class="pos-cell">
                            <span class="pos-label">成交价</span>
                            <span class="pos-value">{{ price_fmt(o.symbol, o.trigger_price) }}</span>
                        </div>
                        <div class="pos-cell">
                            <span class="pos-label">止损</span>
                            <span class="pos-value" id="order-plan-sl-{{ o.id }}">{{ price_fmt(o.symbol, o.stop_loss) if o.stop_loss else '—' }}</span>
                        </div>
                        <div class="pos-cell">
                            <span class="pos-label">止盈</span>
                            <span class="pos-value" id="order-plan-tp-{{ o.id }}">{{ price_fmt(o.symbol, o.take_profit) if o.take_profit else '—' }}</span>
                        </div>
                        <div class="pos-cell">
                            <span class="pos-label">盈亏比</span>
                            <span class="pos-value" id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %}</span>
                        </div>
                        <div class="pos-cell">
                            <span class="pos-label">张数</span>
                            <span class="pos-value" id="order-contracts-{{ o.id }}">{% if o.order_amount is not none %}{{ '%.2f'|format(o.order_amount) }}{% else %}—{% endif %}</span>
                        </div>
                        <div class="pos-cell">
                            <span class="pos-label">盈利金额</span>
                            <span class="pos-value pos-tp-profit" id="order-tp-profit-{{ o.id }}">—</span>
                        </div>
                        <div class="pos-cell">
                            <span class="pos-label">标记价</span>
                            <span class="pos-value" id="order-price-{{ o.id }}">-</span>
                        </div>
                        <div class="pos-cell">
                            <span class="pos-label">浮盈亏</span>
                            <span class="pos-value" id="order-pnl-{{ o.id }}">-</span>
                        </div>
                    </div>
                    <div class="pos-footer">
                        <span>保证金: <span id="order-ex-margin-{{ o.id }}">-</span></span>
                        <span>计划基数: {{ funds_fmt(o.margin_capital) if o.margin_capital is not none else '-' }}U</span>
                        <span>杠杆: {{ o.leverage or '-' }}x</span>
                        <span>仓位占比: {{ o.position_ratio if o.position_ratio is not none else '-' }}%</span>
                        <span>开仓时间: {{ (o.opened_at or '-')[:16] }}</span>
                        <span>持仓时长: <span class="order-hold-duration" id="order-hold-duration-{{ o.id }}" data-order-opened-ms="{{ o.opened_at_ms or '' }}">—</span></span>
                    </div>
                    <div class="pos-ex-orders">
                        <div class="pos-ex-orders-title">交易所止盈止损</div>
                        <div class="pos-ex-order-row">
                            <span class="pos-ex-order-main" id="ex-sl-text-{{ o.id }}">止损:加载中…</span>
                            {% if not intraday_discipline %}
                            <button type="button" class="pos-ex-cancel-btn" id="ex-sl-cancel-{{ o.id }}" disabled onclick="cancelExchangeTpsl({{ o.id }}, 'sl')">撤单</button>
                            {% endif %}
                        </div>
                        <div class="pos-ex-order-row">
                            <span class="pos-ex-order-main" id="ex-tp-text-{{ o.id }}">止盈:加载中…</span>
                            {% if not intraday_discipline %}
                            <button type="button" class="pos-ex-cancel-btn" id="ex-tp-cancel-{{ o.id }}" disabled onclick="cancelExchangeTpsl({{ o.id }}, 'tp')">撤单</button>
                            {% endif %}
                        </div>
                    </div>
                </div>
                {% else %}
                <div class="pos-empty">暂无持仓</div>
                {% endfor %}
            </div>
        </div>

        <div id="tpsl-modal" class="tpsl-modal-backdrop" onclick="if(event.target===this)closeTpslEntrustModal()">
            <div class="tpsl-modal" onclick="event.stopPropagation()">
                <h3 id="tpsl-modal-title">挂止盈止损</h3>
                <p style="font-size:.78rem;color:#8892b0;margin:0 0 10px">将先撤销该合约已有 TP/SL,再按下列价格重挂.</p>
                <div class="form-row">
                    <select id="tpsl-modal-mode" onchange="toggleTpslModalMode()">
                        <option value="price">价格模式</option>
                        <option value="pct">百分比模式</option>
                    </select>
                </div>
                <div class="form-row">
                    <input id="tpsl-modal-sl" step="any" placeholder="止损价格">
                    <input id="tpsl-modal-tp" step="any" placeholder="止盈价格">
                </div>
                <div class="form-row">
                    <input id="tpsl-modal-sl-pct" type="number" min="0.01" step="0.01" placeholder="止损%" style="display:none">
                    <input id="tpsl-modal-tp-pct" type="number" min="0.01" step="0.01" placeholder="止盈%" style="display:none">
                </div>
                <div class="tpsl-modal-actions">
                    <button type="button" class="tpsl-modal-cancel" onclick="closeTpslEntrustModal()">取消</button>
                    <button type="button" class="tpsl-modal-submit" onclick="submitTpslEntrust()">先撤后挂</button>
                </div>
            </div>
        </div>

        </div>
        {% elif page in ('strategy', 'strategy_trend', 'strategy_roll') %}
        {% include 'strategy_trading_page.html' %}
        {% elif page == 'strategy_records' %}
        {% include 'strategy_records_page.html' %}
        {% elif page == 'options' %}
        {% include 'options_panel.html' %}
        {% elif page == 'options_review' %}
        {% include 'options_review_panel.html' %}
        {% elif page == 'hedge_plan' %}
        {% include 'hedge_plan_panel.html' %}
        {% endif %}



        {% if page == 'records' %}
        {% include 'records_panel.html' %}
        {% endif %}

    {% if page == 'env_config' %}
    {% include 'env_config_panel.html' %}
    {% endif %}

    {% if page == 'risk_policy' %}
    {% include 'risk_policy_panel.html' %}
    {% endif %}

    {% if page == 'settings' %}
    {% include 'settings_panel.html' %}
    {% endif %}

    {% if page == 'stats' %}
    <div class="card stats-card full" id="stats-card">
        <div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
            <h2 style="margin-bottom:0">数据统计</h2>
            <button type="button" class="stats-toggle" id="stats-toggle-btn" onclick="toggleStatsCard()">折叠</button>
        </div>
        <div class="stats-content" id="stats-content">
            <div class="sub" style="margin-bottom:12px;color:#8892b0;font-size:.82rem">
                统计分析按<strong>北京时间 {{ stats_bundle.stats_reset_hour }}:00</strong>切日计入(与顶栏 UTC 列表窗无关).历史总开仓(累计):
                <strong style="color:#cfd3ef">{{ stats_bundle.total_opens_all }}</strong> 次
            </div>
            <div class="form-row" style="margin-bottom:14px;align-items:center">
                <label style="display:flex;align-items:center;gap:8px;font-size:.88rem;color:#cfd3ef">
                    统计品类
                    <select id="stats-segment-select" onchange="switchStatsSegment()" style="min-width:200px">
                        {% for seg in stats_bundle.segments %}
                        <option value="{{ seg.key }}">{{ seg.title }}</option>
                        {% endfor %}
                    </select>
                </label>
            </div>
            {% for seg in stats_bundle.segments %}
            <div class="stats-segment-block stats-segment-panel" data-stats-segment="{{ seg.key }}"{% if not loop.first %} style="display:none"{% endif %}>
                <div class="stats-period-tabs" role="tablist" aria-label="统计周期">
                    <button type="button" class="stats-period-tab active" data-stats-period="day" role="tab" aria-selected="true" onclick="switchStatsPeriod('day')">日统计</button>
                    <button type="button" class="stats-period-tab" data-stats-period="week" role="tab" aria-selected="false" onclick="switchStatsPeriod('week')">周统计</button>
                    <button type="button" class="stats-period-tab" data-stats-period="month" role="tab" aria-selected="false" onclick="switchStatsPeriod('month')">月统计</button>
                </div>
                {{ period_stats_pane("day", seg.day) }}
                {{ period_stats_pane("week", seg.week) }}
                {{ period_stats_pane("month", seg.month) }}
            </div>
            {% endfor %}
        </div>
    </div>
    {% endif %}
</div>

<div class="modal" id="imgModal" onclick="closeModal()">
    <img id="bigImg" src="" alt="screenshot">
</div>
<div class="detail-modal" id="detailModal" onclick="closeDetailModal(event)">
    <div class="panel" onclick="event.stopPropagation()">
        <div class="panel-head">
            <div class="panel-title" id="detailTitle">详情</div>
            <div class="panel-actions">
                <button type="button" class="panel-fs" onclick="expandDetailToFullscreen()">全屏</button>
                <button type="button" class="panel-close" onclick="forceCloseDetailModal()">关闭</button>
            </div>
        </div>
        <div class="panel-body" id="detailBody"></div>
        <div id="detailImages" class="journal-detail-images" style="display:none"></div>
        <img id="detailImage" class="panel-image" src="" alt="detail-image" style="display:none" onclick="showImage(this.src)">
    </div>
</div>

<script src="/static/instance_ui.js?v=10"></script>
<script src="/static/journal_upload_slots.js?v=3"></script>
<script src="/static/instance_records_mobile.js?v=2"></script>
<script src="/static/time_close_ui.js?v=3"></script>
<script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script>
<script>
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
const ORDER_ENTRY_MODEL_CATEGORIES = {{ entry_model_categories | tojson }};
const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }};
</script>
<script src="/static/order_entry_model.js?v=5"></script>
<script src="/static/manual_order_rr_preview.js?v=5"></script>
<script src="/static/symbol_live_price.js?v=2"></script>
<script src="/static/strategy_roll.js?v=6"></script>
<script src="/static/instance_stats.js?v=4"></script>
<script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ORDER_TYPE_OPTIONS = {{ order_type_options | tojson }};

function validateJournalEntryReason(){
    const form = document.getElementById("journal-form");
    if(!form) return true;
    const orderSel = form.querySelector('[name="order_type"]');
    if(!orderSel || !orderSel.value){
        alert("请选择下单类型");
        return false;
    }
    const sel = form.querySelector('[name="entry_reason"]');
    if(!sel || !sel.value){
        alert("请选择开仓类型");
        return false;
    }
    return true;
}
function showImage(src){document.getElementById("bigImg").src=src;document.getElementById("imgModal").style.display="flex";}
function closeModal(){document.getElementById("imgModal").style.display="none";}
function setDetailModalFullscreen(on){
    const modal = document.getElementById("detailModal");
    if(modal){ modal.classList.toggle("fullscreen", !!on); }
}
function forceCloseDetailModal(){
    const modal = document.getElementById("detailModal");
    if(modal){ modal.style.display = "none"; modal.classList.remove("fullscreen"); }
}
function closeDetailModal(e){if(e.target && e.target.id==="detailModal"){forceCloseDetailModal();}}
function expandDetailToFullscreen(){ setDetailModalFullscreen(true); }
function toggleReviewCardFullscreen(){
    const card = document.getElementById("review-card");
    if(!card) return;
    const on = !card.classList.contains("is-fullscreen");
    card.classList.toggle("is-fullscreen", on);
    document.body.classList.toggle("review-card-fullscreen-open", on);
    const btn = document.getElementById("review-card-fs-btn");
    if(btn){ btn.textContent = on ? "退出全屏" : "全屏"; }
}
document.addEventListener("keydown", function(e){
    if(e.key !== "Escape") return;
    const card = document.getElementById("review-card");
    if(card && card.classList.contains("is-fullscreen")){ toggleReviewCardFullscreen(); }
});
function setAiReviewMarkdown(el, rawText){
    if(!el) return;
    if(window.AiReviewRender && AiReviewRender.setElementMarkdown){
        AiReviewRender.setElementMarkdown(el, rawText || "");
    } else {
        el.classList.remove("ai-result-md");
        el.innerText = rawText || "";
    }
}
function setDetailBodyPlain(text){
    const body = document.getElementById("detailBody");
    if(!body) return;
    body.classList.remove("md-review");
    body.innerText = text || "";
}
function setDetailBodyMarkdown(text){
    if(window.InstanceUI && InstanceUI.clearDetailActions) InstanceUI.clearDetailActions();
    const body = document.getElementById("detailBody");
    if(!body) return;
    body.classList.remove("trade-record-detail-wrap", "journal-detail-meta");
    if(window.AiReviewRender && AiReviewRender.setElementMarkdown){
        body.classList.add("md-review");
        AiReviewRender.setElementMarkdown(body, text || "");
    } else {
        setDetailBodyPlain(text);
    }
}
function openAiInlineResultFullscreen(title, elementId){
    const el = document.getElementById(elementId || "daily_result");
    const text = (window.AiReviewRender && AiReviewRender.getElementMarkdown)
        ? String(AiReviewRender.getElementMarkdown(el) || "").trim()
        : String((el && el.innerText) || "").trim();
    if(!text){ alert("暂无内容"); return; }
    document.getElementById("detailTitle").innerText = title || "AI复盘";
    setDetailBodyMarkdown(text);
    const imgEl = document.getElementById("detailImage");
    imgEl.src = "";
    imgEl.style.display = "none";
    setDetailModalFullscreen(true);
    document.getElementById("detailModal").style.display = "flex";
}

const journalCache = {};
const reviewCache = {};
window.journalCache = journalCache;
window.reviewCache = reviewCache;

function formatJournalExitOneLine(o){
    const t = (o.early_exit_trigger || "").trim();
    const n = (o.early_exit_note || "").trim();
    if(t === "手动平仓") return n || (o.exit_reason || "").trim() || "无";
    if(t) return t;
    return (o.exit_reason || "").trim() || (o.early_exit_reason || "").trim() || "无";
}

function openJournalDetail(id){
    InstanceUI.openJournalDetailModal(id, journalCache, formatJournalExitOneLine);
}

function openReviewDetail(id, fullscreen){
    const r = reviewCache[id];
    if(!r){ return; }
    document.getElementById("detailTitle").innerText = `${r.review_type === "daily" ? "日复盘" : "周复盘"}｜${r.target_date || "-"}`;
    setDetailBodyMarkdown(r.content || "");
    const imgEl = document.getElementById("detailImage");
    imgEl.src = "";
    imgEl.style.display = "none";
    setDetailModalFullscreen(!!fullscreen);
    document.getElementById("detailModal").style.display = "flex";
}

function deleteJournal(id){
    if(!confirm("确定删除该交易复盘记录?")) return;
    fetch(`/delete_journal/${id}`,{method:"POST"}).then(()=>loadJournals());
}

function deleteReview(id){
    if(!confirm("确定删除该AI复盘?")) return;
    fetch(`/delete_review/${id}`,{method:"POST"}).then(()=>loadReviews());
}

function deleteTradeRecord(id){
    if(!confirm("确定删除这条交易记录?")) return;
    fetch(`/delete_trade_record/${id}`,{method:"POST"})
    .then(r=>r.json())
    .then(data=>{
        if(data && data.ok){
            const row = document.getElementById(`trade-row-${id}`);
            if(row){ row.remove(); return; }
        }
        window.location.href = `${window.location.pathname}?_ts=${Date.now()}`;
    })
    .catch(()=>{ window.location.href = `${window.location.pathname}?_ts=${Date.now()}`; });
}

function normalizeBeijingDatetimeString(v){
    const raw = String(v || "").trim().replace("T"," ");
    const m = raw.match(/^(\d{4}-\d{2}-\d{2})[ ](\d{2}:\d{2})(:\d{2})?/);
    if(!m) return "";
    const sec = m[3] ? m[3].slice(1,3) : "00";
    return `${m[1]} ${m[2]}:${sec}`;
}

function toggleReviewMode(){
    if(window.InstanceTheme && typeof InstanceTheme.syncReviewEditButtons === "function"){
        InstanceTheme.syncReviewEditButtons();
        return;
    }
    const on = !!(document.getElementById("review-mode-toggle") || {}).checked;
    document.querySelectorAll(".review-edit-btn").forEach(btn=>{
        btn.disabled = !on;
    });
}

function editTradeRecordReview(t){
    if(!t) return;
    const pickEntryReason = function(){
        if(window.InstanceUI && typeof InstanceUI.promptReviewEntryReason === "function"){
            return InstanceUI.promptReviewEntryReason(JOURNAL_ENTRY_REASON_OPTIONS, t.effective_entry_reason || "");
        }
        const entryHint = "开仓类型(下拉选项之一,留空=不改该项)";
        return Promise.resolve(prompt(entryHint, String(t.effective_entry_reason || "")));
    };
    const opened = prompt("开仓时间(YYYY-MM-DD HH:MM:SS)", normalizeBeijingDatetimeString(t.opened_at || ""));
    if(opened === null) return;
    const closed = prompt("平仓时间(YYYY-MM-DD HH:MM:SS)", normalizeBeijingDatetimeString(t.closed_at || ""));
    if(closed === null) return;
    const stopLoss = prompt("止损价格(核对后用于统计)", formatPriceForInput(t.stop_loss));
    if(stopLoss === null) return;
    const takeProfit = prompt("止盈价格(核对后用于统计)", formatPriceForInput(t.take_profit));
    if(takeProfit === null) return;
    const pnl = prompt("最终盈亏(可手工核对后填写)", String(t.pnl_amount ?? ""));
    if(pnl === null) return;
    const result = prompt("结果(止盈/止损/保本止盈/移动止盈/手动平仓/强制清仓/时间平仓)", String(t.result || ""));
    if(result === null) return;
    const note = prompt("备注(可空)", String(t.miss_reason || "")) ?? "";
    pickEntryReason().then(function(entryIn){
        if(entryIn === null) return;
    const payload = {
        id: t.id,
        reviewed_opened_at: normalizeBeijingDatetimeString(opened),
        reviewed_closed_at: normalizeBeijingDatetimeString(closed),
        reviewed_stop_loss: stopLoss,
        reviewed_take_profit: takeProfit,
        reviewed_pnl_amount: pnl,
        reviewed_result: String(result || "").trim(),
        reviewed_miss_reason: String(note || "").trim()
    };
    const entryTrim = String(entryIn || "").trim();
    if(entryTrim) payload.reviewed_entry_reason = entryTrim;
    fetch("/api/trade_record_review_update",{
        method:"POST",
        headers:{"Content-Type":"application/json"},
        body: JSON.stringify(payload)
    })
    .then(r=>r.json().then(d=>({status:r.status, data:d})))
    .then(({status,data})=>{
        if(status >= 400 || !data.ok){
            alert((data && data.msg) || "核对保存失败");
            return;
        }
        alert(`核对已保存:持仓分钟=${data.hold_minutes} 实际RR=${data.actual_rr ?? "-"}`);
        window.location.href = `${window.location.pathname}?_ts=${Date.now()}`;
    })
    .catch(()=>alert("核对保存请求失败"));
    });
}


function deleteKeyMonitor(id){
    if(!confirm("删除该关键位?将写入下方历史并刷新页面.")) return;
    fetch(`/delete_key_monitor/${id}`,{method:"POST"})
    .then(r=>r.json())
    .then(data=>{
        window.location.href = `${window.location.pathname}?_ts=${Date.now()}`;
    })
    .catch(()=>{ window.location.href = `${window.location.pathname}?_ts=${Date.now()}`; });
}

function deleteKeyHistory(id){
    if(!confirm("确定删除这条关键位历史?")) return;
    fetch(`/delete_key_history/${id}`,{method:"POST"})
    .then(r=>r.json())
    .then(()=>{
        window.location.href = `${window.location.pathname}?_ts=${Date.now()}`;
    })
    .catch(()=>{ window.location.href = `${window.location.pathname}?_ts=${Date.now()}`; });
}

function listWindowQueryString(){
    const presetEl = document.getElementById("win-preset-select");
    const preset = (presetEl && presetEl.value) || new URLSearchParams(window.location.search).get("win_preset") || "utc_this_month";
    const q = new URLSearchParams(window.location.search);
    q.set("win_preset", preset);
    if(preset === "custom"){
        const fromEl = document.getElementById("win-from-utc");
        const toEl = document.getElementById("win-to-utc");
        if(fromEl && fromEl.value) q.set("from_utc", fromEl.value.replace("T", " ") + ":00");
        else q.delete("from_utc");
        if(toEl && toEl.value) q.set("to_utc", toEl.value.replace("T", " ") + ":00");
        else q.delete("to_utc");
    } else {
        q.delete("from_utc");
        q.delete("to_utc");
    }
    return q.toString();
}

function toggleListWindowCustom(){
    const preset = document.getElementById("win-preset-select");
    const box = document.getElementById("win-custom-range");
    if(!preset || !box) return;
    box.style.display = preset.value === "custom" ? "" : "none";
}

function applyListWindow(){
    const qs = listWindowQueryString();
    const path = window.location.pathname || "/trade";
    window.location.href = qs ? (path + "?" + qs) : path;
}

function attachListWindowToExports(){
    const qs = listWindowQueryString();
    if(!qs) return;
    document.querySelectorAll('.export-bar a[href^="/export/trade_records"], .export-bar a[href^="/export/key_monitor_history"]').forEach(a=>{
        const base = a.getAttribute("href").split("?")[0];
        a.setAttribute("href", base + "?" + qs);
    });
}

function loadJournals(){
    const qs = listWindowQueryString();
    fetch("/api/journals" + (qs ? "?" + qs : "")).then(r=>r.json()).then(data=>{
        Object.keys(journalCache).forEach(k=>delete journalCache[k]);
        data.forEach(o=>{ journalCache[o.id] = o; });
        const box = document.getElementById("journal-list");
        if(box){
            const html = InstanceUI.renderJournalListHtml(data);
            box.innerHTML = html || "<div class='journal-empty-msg'>暂无数据</div>";
        }
    });
}

function loadReviews(){
    const qs = listWindowQueryString();
    fetch("/api/reviews" + (qs ? "?" + qs : "")).then(r=>r.json()).then(data=>{
        Object.keys(reviewCache).forEach(k=>delete reviewCache[k]);
        let html="";
        data.forEach(r=>{
            reviewCache[r.id] = r;
            const preview = (r.content || "").replace(/\s+/g, " ").trim();
            const shortText = preview.length > 90 ? `${preview.slice(0, 90)}...` : preview;
            html += `<div class="entry">
                <div><strong>${r.review_type === "daily" ? "日复盘" : "周复盘"}</strong> | ${r.target_date}</div>
                <div style="font-size:12px;color:#9aa">${r.created_at || ""}</div>
                <div style="margin-top:4px;color:#c9d2ff">${shortText || "(空)"}</div>
                <div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:6px">
                    <button type="button" class="btn-del" style="border:none;cursor:pointer;background:#1f3a5a;color:#8fc8ff" onclick="openReviewDetail('${r.id}', false)">查看</button>
                    <button type="button" class="btn-del" style="border:none;cursor:pointer;background:#1f3a5a;color:#8fc8ff" onclick="openReviewDetail('${r.id}', true)">全屏</button>
                    <a class="btn-del" style="text-decoration:none;background:#1f3a5a;color:#8fc8ff" href="/export/review_md/${r.id}">导出MD</a>
                    <button type="button" class="btn-del" onclick="deleteReview('${r.id}')">删除</button>
                </div>
            </div>`;
        });
        const box = document.getElementById("review-list");
        if(box){ box.innerHTML = html || "<div class='entry'>暂无数据</div>"; }
    });
}

function genDaily(){
    if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
    const d = document.getElementById("day_date").value;
    if(!d){alert("请选择日期");return;}
    if(window.AiReviewRender && AiReviewRender.setGenerating){
        AiReviewRender.setGenerating({
            wrapId:"daily_result_wrap",
            elId:"daily_result",
            btnId:"gen-daily-btn",
            message:"生成日复盘中,请稍候…(AI 分析可能需要 1～3 分钟)",
            btnLabel:"日复盘生成中…"
        });
    }
    const ac = new AbortController();
    const timer = setTimeout(()=>ac.abort(), 360000);
    fetch("/ai_daily_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`date=${encodeURIComponent(d)}`,signal:ac.signal})
    .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
    .then(data=>{
        if(!data || data.result == null) throw new Error("返回数据为空");
        const el=document.getElementById("daily_result");
        const wrap=document.getElementById("daily_result_wrap");
        setAiReviewMarkdown(el, data.result);
        if(wrap){ wrap.style.display="block"; }
        else if(el){ el.style.display="block"; }
        loadReviews();
    })
    .catch(err=>{
        const el=document.getElementById("daily_result");
        if(el && el.classList.contains("is-loading")){
            el.classList.remove("is-loading","ai-result-md");
            el.innerText = err.name === "AbortError"
                ? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS."
                : "生成失败,请重试.";
        }
        alert("生成日复盘失败:"+(err.message||err));
    })
    .finally(()=>{
        clearTimeout(timer);
        if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-daily-btn");
    });
}

function genWeekly(){
    if(window.AiReviewRender && AiReviewRender.isGenerating && AiReviewRender.isGenerating()) return;
    const s=document.getElementById("week_start").value;
    const e=document.getElementById("week_end").value;
    if(!s || !e){alert("请选择起止日期");return;}
    if(window.AiReviewRender && AiReviewRender.setGenerating){
        AiReviewRender.setGenerating({
            wrapId:"weekly_result_wrap",
            elId:"weekly_result",
            btnId:"gen-weekly-btn",
            message:"生成周复盘中,请稍候…(AI 分析可能需要 1～3 分钟)",
            btnLabel:"周复盘生成中…"
        });
    }
    const ac = new AbortController();
    const timer = setTimeout(()=>ac.abort(), 360000);
    fetch("/ai_weekly_review",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`start_date=${encodeURIComponent(s)}&end_date=${encodeURIComponent(e)}`,signal:ac.signal})
    .then(r=>{ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); })
    .then(data=>{
        if(!data || data.result == null) throw new Error("返回数据为空");
        const el=document.getElementById("weekly_result");
        const wrap=document.getElementById("weekly_result_wrap");
        setAiReviewMarkdown(el, data.result);
        if(wrap){ wrap.style.display="block"; }
        else if(el){ el.style.display="block"; }
        loadReviews();
    })
    .catch(err=>{
        const el=document.getElementById("weekly_result");
        if(el && el.classList.contains("is-loading")){
            el.classList.remove("is-loading","ai-result-md");
            el.innerText = err.name === "AbortError"
                ? "生成超时(>6分钟),请检查 OPENAI_MODEL 是否与网关已启用模型一致,或增大 AI_REVIEW_TIMEOUT_SECONDS."
                : "生成失败,请重试.";
        }
        alert("生成周复盘失败:"+(err.message||err));
    })
    .finally(()=>{
        clearTimeout(timer);
        if(window.AiReviewRender && AiReviewRender.clearGenerating) AiReviewRender.clearGenerating("gen-weekly-btn");
    });
}

function exportDailyBundleMd(){
    const d = document.getElementById("day_date").value;
    if(!d){ alert("请先选择日期"); return; }
    const url = `/export/reviews_md_bundle?review_type=daily&target_date=${encodeURIComponent(d)}`;
    window.location.href = url;
}

function exportWeeklyBundleMd(){
    const s = document.getElementById("week_start").value;
    const e = document.getElementById("week_end").value;
    if(!s || !e){ alert("请先选择周起止日期"); return; }
    const target = `${s}~${e}`;
    const url = `/export/reviews_md_bundle?review_type=weekly&target_date=${encodeURIComponent(target)}`;
    window.location.href = url;
}

function setJournalField(name, value){
    const form = document.getElementById("journal-form");
    const el = form ? form.querySelector(`[name="${name}"]`) : null;
    if(!el) return;
    if(typeof value === "undefined" || value === null) return;
    el.value = String(value);
}

const EARLY_EXIT_TRIGGERS = new Set(["止盈","保本止盈","移动止盈","时间平仓","强制清仓","手动平仓","止损","其他"]);
const KEY_ENTRY_REASON_BY_SIGNAL = {
    "箱体突破": "关键位箱体突破",
    "收敛突破": "关键位收敛突破",
    "斐波回调0.618": "关键位斐波0.618",
    "斐波回调0.786": "关键位斐波0.786",
    "假突破": "关键位假突破"
};

function splitLegacyEarlyExitReason(raw){
    const s = String(raw || "").trim();
    if(!s) return { trigger: "", note: "" };
    const sep = s.indexOf("｜");
    if(sep > -1){
        const a = s.slice(0, sep).trim();
        const b = s.slice(sep + 1).trim();
        if(EARLY_EXIT_TRIGGERS.has(a)){
            return { trigger: a, note: b };
        }
    }
    if(EARLY_EXIT_TRIGGERS.has(s)){
        return { trigger: s, note: "" };
    }
    return { trigger: "", note: s };
}

function normalizeDatetimeLocal(v){
    const raw = String(v || "").trim();
    if(!raw) return "";
    const m = raw.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/);
    if(m) return `${m[1]}T${m[2]}`;
    return raw;
}

function toDatetimeLocalFromBeijing(v){
    const raw = String(v || "").trim();
    if(!raw) return "";
    const m = raw.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/);
    if(m) return `${m[1]}T${m[2]}`;
    return "";
}

function coinFromSymbol(symbol){
    const s = String(symbol || "").trim().toUpperCase();
    if(!s) return "";
    if(s.includes("/")) return s.split("/")[0];
    if(s.includes("-")) return s.split("-")[0];
    if(s.endsWith("USDT")) return s.slice(0, -4);
    return s;
}

/** 输入框/备注用价格:去掉浮点尾数,按量级保留有效小数(与后端 price_fmt 兜底一致) */
function formatPriceForInput(val){
    if(val === null || val === undefined || val === "") return "";
    const v = Number(val);
    if(!Number.isFinite(v)) return String(val);
    const av = Math.abs(v);
    let d;
    if(av >= 10000) d = 2;
    else if(av >= 100) d = 3;
    else if(av >= 1) d = 4;
    else if(av >= 0.01) d = 6;
    else if(av >= 0.0001) d = 8;
    else d = 10;
    let text = v.toFixed(d);
    if(text.includes(".")) text = text.replace(/\.?0+$/, "");
    return text;
}

function calcExpectedRrFromTrade(t){
    const entry = Number(t.trigger_price);
    const sl = Number(t.stop_loss);
    const tp = Number(t.take_profit);
    if(!Number.isFinite(entry) || !Number.isFinite(sl) || !Number.isFinite(tp)) return "";
    if(entry <= 0 || sl <= 0 || tp <= 0) return "";
    const direction = (t.direction || "long").toLowerCase();
    let risk = 0;
    let reward = 0;
    if(direction === "short"){
        risk = sl - entry;
        reward = entry - tp;
    } else {
        risk = entry - sl;
        reward = tp - entry;
    }
    if(risk <= 0 || reward <= 0) return "";
    return (reward / risk).toFixed(2);
}

function fillJournalFromTrade(t){
    if(!t){ return; }
    setJournalField("open_datetime", toDatetimeLocalFromBeijing(t.opened_at));
    setJournalField("close_datetime", toDatetimeLocalFromBeijing(t.closed_at));
    setJournalField("coin", coinFromSymbol(t.symbol));
    setJournalField("tf", "5m");
    setJournalField("pnl", (t.pnl_amount === null || typeof t.pnl_amount === "undefined") ? "" : String(t.pnl_amount));
    const rr = calcExpectedRrFromTrade(t);
    setJournalField("expect_rr", rr);
    let realRr = rr;
    const riskAmount = Number(t.risk_amount);
    const pnlAmount = Number(t.pnl_amount);
    if(Number.isFinite(riskAmount) && riskAmount > 0 && Number.isFinite(pnlAmount)){
        realRr = (pnlAmount / riskAmount).toFixed(2);
    }
    setJournalField("real_rr", realRr);
    const riskHint = document.getElementById("risk-amount-hint");
    if(riskHint){ riskHint.value = (Number.isFinite(riskAmount) && riskAmount > 0) ? String(riskAmount) : ""; }
    const entryPx = formatPriceForInput(t.trigger_price);
    const slPx = formatPriceForInput(t.stop_loss);
    const tpPx = formatPriceForInput(t.take_profit);
    const entryHint = document.getElementById("entry-price-hint");
    if(entryHint){ entryHint.value = entryPx; }
    const stopHint = document.getElementById("stop-loss-hint");
    if(stopHint){ stopHint.value = slPx; }
    const dirHint = document.getElementById("direction-hint");
    if(dirHint){ dirHint.value = t.direction || "long"; }
    const dirNorm = String(t.direction || "long").toLowerCase() === "short" ? "short" : "long";
    setJournalField("direction", dirNorm);
    setJournalField("early_exit_trigger", "");
    setJournalField("early_exit_note", "");
    const kst = String(t.key_signal_type || "").trim();
    const mt = String(t.monitor_type || "").trim();
    const orderType = (function(){
        if(mt === "趋势回调" && JOURNAL_ORDER_TYPE_OPTIONS.includes("趋势回调")) return "趋势回调";
        if(mt === "顺势加仓" && JOURNAL_ORDER_TYPE_OPTIONS.includes("顺势加仓")) return "顺势加仓";
        if(mt === "关键位监控" || mt.includes("关键位")) return "关键位监控";
        return "下单监控";
    })();
    if(JOURNAL_ORDER_TYPE_OPTIONS.includes(orderType)){
        setJournalField("order_type", orderType);
    } else {
        setJournalField("order_type", "");
    }
    if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
        setJournalField("entry_reason", t.effective_entry_reason);
    } else {
        const erFromKey = KEY_ENTRY_REASON_BY_SIGNAL[kst] || "";
        if(erFromKey && JOURNAL_ENTRY_REASON_OPTIONS.includes(erFromKey)){
            setJournalField("entry_reason", erFromKey);
        } else {
            setJournalField("entry_reason", "");
        }
    }
    const er = String(t.result || "").trim();
    const exitTrigMap = { 止盈: "止盈", 保本止盈: "保本止盈", 移动止盈: "移动止盈", 时间平仓: "时间平仓", 强制清仓: "强制清仓", 手动平仓: "手动平仓", 止损: "止损" };
    if(exitTrigMap[er]) setJournalField("early_exit_trigger", exitTrigMap[er]);
    const note = `来自交易记录自动填充:${t.symbol || "-"} ${t.direction || "-"} | 入场:${entryPx || "-"} 止损:${slPx || "-"} 止盈:${tpPx || "-"} | 下单类型:${orderType || t.monitor_type || "-"}`;
    setJournalField("note", note);
    const form = document.getElementById("journal-form");
    if(form && typeof form.scrollIntoView === "function"){
        form.scrollIntoView({behavior:"smooth", block:"start"});
    }
    recomputeJournalRealRr();
    if(typeof syncEarlyExitNoteRequired === "function") syncEarlyExitNoteRequired();
}

function recomputeJournalRealRr(){
    const form = document.getElementById("journal-form");
    if(!form) return;
    const pnlEl = form.querySelector('[name="pnl"]');
    const rrEl = form.querySelector('[name="real_rr"]');
    const riskHint = document.getElementById("risk-amount-hint");
    if(!pnlEl || !rrEl || !riskHint) return;
    const pnl = Number(String(pnlEl.value || "").trim());
    const risk = Number(String(riskHint.value || "").trim());
    if(Number.isFinite(pnl) && Number.isFinite(risk) && risk > 0){
        rrEl.value = (pnl / risk).toFixed(2);
    }
}


function toggleStatsCard(){
    const card = document.getElementById("stats-card");
    const btn = document.getElementById("stats-toggle-btn");
    if(!card || !btn) return;
    const collapsed = card.classList.toggle("collapsed");
    btn.innerText = collapsed ? "展开" : "折叠";
}

attachListWindowToExports();
toggleListWindowCustom();
initStatsSegmentFromUrl();
if(document.getElementById("journal-list")) loadJournals();
if(document.getElementById("review-list")) loadReviews();
const reviewToggle = document.getElementById("review-mode-toggle");
if(reviewToggle){
    reviewToggle.addEventListener("change", toggleReviewMode);
    reviewToggle.addEventListener("input", toggleReviewMode);
    toggleReviewMode();
}
if(window.InstanceTheme && typeof InstanceTheme.initReviewEditModeSync === "function"){
    InstanceTheme.initReviewEditModeSync();
}
const journalForm = document.getElementById("journal-form");
if(journalForm){
    const pnlInput = journalForm.querySelector('[name="pnl"]');
    if(pnlInput){
        pnlInput.addEventListener("input", recomputeJournalRealRr);
        pnlInput.addEventListener("change", recomputeJournalRealRr);
    }
    const earlyTrig = journalForm.querySelector('[name="early_exit_trigger"]');
    const earlyNote = journalForm.querySelector('[name="early_exit_note"]');
    function syncEarlyExitNoteRequired(){
        if(!earlyTrig || !earlyNote) return;
        if(earlyTrig.value === "手动平仓"){
            earlyNote.setAttribute("required", "required");
            earlyNote.placeholder = "手工平仓须说明原因(必填)";
        } else {
            earlyNote.removeAttribute("required");
            earlyNote.placeholder = "离场补充(仅手工平仓必填)";
        }
    }
    window.syncEarlyExitNoteRequired = syncEarlyExitNoteRequired;
    if(earlyTrig){
        earlyTrig.addEventListener("change", syncEarlyExitNoteRequired);
        syncEarlyExitNoteRequired();
    }
}

if(window.TimeCloseUI){
    TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
}
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
setTimeout(() => {
    if(document.getElementById("journal-list")) loadJournals();
    if(document.getElementById("review-list")) loadReviews();
}, 300);


const MANUAL_MIN_PLANNED_RR = {{ manual_min_planned_rr }};
const MANUAL_FIXED_RR_DEFAULT = 1.5;
const FIXED_RR_LS_KEY = "manualFixedRr";
function loadFixedRrPref(){
    try{
        const raw = localStorage.getItem(FIXED_RR_LS_KEY);
        const el = document.getElementById("order-fixed-rr");
        if(!el || raw == null || raw === "") return;
        const v = Number(raw);
        if(Number.isFinite(v) && v > 0) el.value = raw;
    }catch(_){}
}
function saveFixedRrPref(){
    try{
        const el = document.getElementById("order-fixed-rr");
        if(el && el.value) localStorage.setItem(FIXED_RR_LS_KEY, el.value);
    }catch(_){}
}
function calcTpFromFixedRr(direction, entry, sl, rr){
    const e = Number(entry), s = Number(sl), r = Number(rr);
    if(!Number.isFinite(e) || !Number.isFinite(s) || !Number.isFinite(r) || r <= 0) return null;
    if(direction === "short"){
        if(s <= e) return null;
        return e - (s - e) * r;
    }
    if(s >= e) return null;
    return e + (e - s) * r;
}
function refreshOrderTpPreview(entryPx){
    if(window.ManualOrderRrPreview) ManualOrderRrPreview.schedule();
}
function calcClientRr(direction, entry, sl, tp){
    const e = Number(entry), s = Number(sl), t = Number(tp);
    if(!Number.isFinite(e) || !Number.isFinite(s) || !Number.isFinite(t)) return null;
    if(direction === 'short'){
        if(s <= e || t >= e) return null;
        return (e - t) / (s - e);
    }
    if(s >= e || t <= e) return null;
    return (t - e) / (e - s);
}
function calcClientRrFromPct(slPct, tpPct){
    const sl = Number(slPct), tp = Number(tpPct);
    if(!Number.isFinite(sl) || !Number.isFinite(tp) || sl <= 0 || tp <= 0) return null;
    return tp / sl;
}
function rejectManualOrderRr(rr){
    if(rr !== null && rr >= MANUAL_MIN_PLANNED_RR) return false;
    alert(`计划盈亏比 ${rr === null ? '无效' : rr.toFixed(2)}:1 低于最低要求 ${MANUAL_MIN_PLANNED_RR}:1,已阻止人工下单.`);
    return true;
}

let tpslEntrustMonitorId = null;
function formatExTpslLine(role, slot){
    const label = role === 'sl' ? '止损' : '止盈';
    if(!slot || !slot.order_id) return label + ':未挂单';
    const px = slot.trigger_display || slot.trigger_price || '-';
    const amt = slot.amount != null && !Number.isNaN(Number(slot.amount)) ? ` 数量 ${Number(slot.amount)}` : '';
    return `${label}:触发 ${px}${amt}`;
}
function paintExchangeTpslRow(orderId, tpsl){
    const data = tpsl || {};
    const slText = document.getElementById(`ex-sl-text-${orderId}`);
    const tpText = document.getElementById(`ex-tp-text-${orderId}`);
    const slBtn = document.getElementById(`ex-sl-cancel-${orderId}`);
    const tpBtn = document.getElementById(`ex-tp-cancel-${orderId}`);
    const intraday = (document.body && document.body.getAttribute("data-intraday-discipline")) === "1";
    if(slText) slText.innerText = formatExTpslLine('sl', data.sl);
    if(tpText) tpText.innerText = formatExTpslLine('tp', data.tp);
    if(!intraday){
        if(slBtn) slBtn.disabled = !(data.sl && data.sl.order_id);
        if(tpBtn) tpBtn.disabled = !(data.tp && data.tp.order_id);
    }
}
function toggleTpslModalMode(){
    const mode = (document.getElementById('tpsl-modal-mode')||{}).value || 'price';
    const pct = mode === 'pct';
    ['tpsl-modal-sl','tpsl-modal-tp'].forEach(id=>{ const el=document.getElementById(id); if(el) el.style.display=pct?'none':''; });
    ['tpsl-modal-sl-pct','tpsl-modal-tp-pct'].forEach(id=>{ const el=document.getElementById(id); if(el) el.style.display=pct?'':'none'; });
}
function openTpslEntrustModal(orderId){
    const card = document.getElementById(`order-row-${orderId}`);
    if(!card) return;
    tpslEntrustMonitorId = orderId;
    const slEl = document.getElementById('tpsl-modal-sl');
    const tpEl = document.getElementById('tpsl-modal-tp');
    if(slEl) slEl.value = formatPriceForInput(card.getAttribute('data-plan-sl') || '');
    if(tpEl) tpEl.value = formatPriceForInput(card.getAttribute('data-plan-tp') || '');
    const modeEl = document.getElementById('tpsl-modal-mode');
    if(modeEl) modeEl.value = 'price';
    toggleTpslModalMode();
    const title = document.getElementById('tpsl-modal-title');
    if(title) title.innerText = `挂止盈止损 · ${card.getAttribute('data-symbol')||''}`;
    const modal = document.getElementById('tpsl-modal');
    if(modal) modal.classList.add('open');
}
function closeTpslEntrustModal(){
    tpslEntrustMonitorId = null;
    const modal = document.getElementById('tpsl-modal');
    if(modal) modal.classList.remove('open');
}
function submitTpslEntrust(){
    const orderId = tpslEntrustMonitorId;
    if(!orderId) return;
    const mode = (document.getElementById('tpsl-modal-mode')||{}).value || 'price';
    const body = { sltp_mode: mode };
    if(mode === 'pct'){
        body.sl_pct = Number((document.getElementById('tpsl-modal-sl-pct')||{}).value);
        body.tp_pct = Number((document.getElementById('tpsl-modal-tp-pct')||{}).value);
    }else{
        body.sl = (document.getElementById('tpsl-modal-sl')||{}).value;
        body.tp = (document.getElementById('tpsl-modal-tp')||{}).value;
    }
    fetch(`/api/order/${orderId}/place_tpsl`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) })
        .then(r=>r.json()).then(data=>{
            if(!data.ok){ alert(data.msg || '委托失败'); return; }
            alert(data.msg || '已提交');
            closeTpslEntrustModal();
            if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl);
            paintPlanTpslDisplay(orderId, data);
            paintLatestRiskDisplay(orderId, data);
            paintContractsDisplay(orderId, data);
            paintTpProfitDisplay(orderId, data);
            const rrEl = document.getElementById(`order-rr-${orderId}`);
            if(rrEl){
                const rr = data.display_rr_ratio != null && data.display_rr_ratio !== "" ? data.display_rr_ratio : data.planned_rr;
                rrEl.innerText = formatRrRatio(rr);
            }
            refreshPriceSnapshotConditional();
        }).catch(()=>alert('委托请求失败'));
}
function cancelExchangeTpsl(orderId, role){
    const label = role === 'sl' ? '止损' : '止盈';
    if(!confirm(`确认撤销交易所${label}委托?(不会平仓)`)) return;
    fetch(`/api/order/${orderId}/cancel_tpsl`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ role }) })
        .then(r=>r.json()).then(data=>{
            if(!data.ok){ alert(data.msg || '撤单失败'); return; }
            if(data.exchange_tpsl) paintExchangeTpslRow(orderId, data.exchange_tpsl);
            else refreshPriceSnapshotConditional();
        }).catch(()=>alert('撤单请求失败'));
}

function allowManualOrderSubmit(form){
    form.dataset.rrOk = "1";
    if(window.FormSubmitGuard){
        if(FormSubmitGuard.isLocked(form)){
            FormSubmitGuard.setSubmitLabel(form, "开仓提交中…");
        } else {
            FormSubmitGuard.lock(form, "开仓提交中…");
        }
    }
    form.submit();
}

let latestAvailableUsdt = null;
const lastPriceMap = {};

function formatSigned(v, digits=2){
    if(v === null || typeof v === "undefined" || Number.isNaN(Number(v))) return "-";
    const n = Number(v);
    const sign = n > 0 ? "+" : "";
    return `${sign}${n.toFixed(digits)}`;
}

function formatRrRatio(rr){
    if(rr === null || typeof rr === "undefined") return "-:1";
    const n = Number(rr);
    if(Number.isNaN(n)) return "-:1";
    const body = Number.isInteger(n) ? String(n) : String(parseFloat(n.toFixed(2)));
    return `${body}:1`;
}

function resolveSlBreakevenSecured(orderId, snap){
    if(!snap) return false;
    const et = snap.exchange_tpsl;
    const slSlot = et && et.sl ? et.sl : null;
    let slPx = NaN;
    if(slSlot){
        const raw = slSlot.trigger_price != null ? slSlot.trigger_price : slSlot.trigger_display;
        slPx = raw != null && raw !== "" ? Number(raw) : NaN;
    }
    if(!Number.isFinite(slPx) || slPx <= 0){
        slPx = snap.stop_loss != null && snap.stop_loss !== "" ? Number(snap.stop_loss) : NaN;
    }
    let entry = snap.avg_entry_price != null && snap.avg_entry_price !== "" ? Number(snap.avg_entry_price) : NaN;
    let direction = snap.direction ? String(snap.direction).toLowerCase() : "";
    const card = document.getElementById(`order-row-${orderId}`);
    if(card){
        if(!direction) direction = String(card.getAttribute("data-direction") || "long").toLowerCase();
        if(!Number.isFinite(entry)){
            const ent = card.getAttribute("data-entry");
            if(ent){
                const n = Number(String(ent).replace(/,/g, ""));
                if(Number.isFinite(n) && n > 0) entry = n;
            }
        }
    }
    if(Number.isFinite(slPx) && Number.isFinite(entry) && entry > 0){
        if(direction === "short") return slPx <= entry;
        return slPx >= entry;
    }
    return snap.sl_breakeven_secured === true || snap.sl_breakeven_secured === 1;
}
function paintBreakevenBadge(orderId, snap){
    const wrap = document.getElementById(`order-be-wrap-${orderId}`);
    if(!wrap) return;
    const secured = (snap && typeof snap === "object") ? resolveSlBreakevenSecured(orderId, snap) : !!snap;
    wrap.style.display = secured ? "inline-flex" : "none";
}
function paintPlanTpslDisplay(orderId, snap){
    if(!snap) return;
    const card = document.getElementById(`order-row-${orderId}`);
    const slEl = document.getElementById(`order-plan-sl-${orderId}`);
    const tpEl = document.getElementById(`order-plan-tp-${orderId}`);
    const slRaw = snap.stop_loss_raw != null && snap.stop_loss_raw !== "" ? snap.stop_loss_raw : snap.stop_loss;
    const tpRaw = snap.take_profit_raw != null && snap.take_profit_raw !== "" ? snap.take_profit_raw : snap.take_profit;
    const slDisp = snap.stop_loss_display || (slRaw != null && slRaw !== "" ? formatPriceForInput(slRaw) : null);
    const tpDisp = snap.take_profit_display || (tpRaw != null && tpRaw !== "" ? formatPriceForInput(tpRaw) : null);
    if(slEl) slEl.innerText = slDisp || "—";
    if(tpEl) tpEl.innerText = tpDisp || "—";
    if(card){
        if(slRaw != null && slRaw !== "") card.setAttribute("data-plan-sl", formatPriceForInput(slRaw));
        else if(slDisp) card.setAttribute("data-plan-sl", slDisp);
        if(tpRaw != null && tpRaw !== "") card.setAttribute("data-plan-tp", formatPriceForInput(tpRaw));
        else if(tpDisp) card.setAttribute("data-plan-tp", tpDisp);
    }
}
function paintLatestRiskDisplay(orderId, snap){
    const wrap = document.getElementById(`order-latest-risk-wrap-${orderId}`);
    if(!wrap) return;
    const v = snap && snap.latest_risk_amount;
    const n = v != null && v !== "" ? Number(v) : NaN;
    if(Number.isFinite(n)){
        wrap.style.display = "inline-flex";
        wrap.textContent = `最新风险: ${n.toFixed(2)}U`;
    } else {
        wrap.style.display = "none";
    }
}
function paintContractsDisplay(orderId, snap){
    const el = document.getElementById(`order-contracts-${orderId}`);
    if(!el || !snap) return;
    const v = snap.contracts != null && snap.contracts !== "" ? snap.contracts : snap.order_amount;
    const n = v != null && v !== "" ? Number(v) : NaN;
    el.innerText = Number.isFinite(n) ? n.toFixed(2) : "—";
}
function paintTpProfitDisplay(orderId, snap){
    const el = document.getElementById(`order-tp-profit-${orderId}`);
    if(!el) return;
    const v = snap && snap.reward_at_tp_usdt;
    const n = v != null && v !== "" ? Number(v) : NaN;
    if(Number.isFinite(n)){
        el.innerText = `${n.toFixed(2)}U`;
        el.classList.add("pos-tp-profit");
    } else {
        el.innerText = "—";
        el.classList.remove("pos-tp-profit");
    }
}

function paintPriceTrend(el, key, value){
    if(!el) return;
    const prev = lastPriceMap[key];
    el.classList.remove("price-up","price-down","price-flat");
    if(typeof prev === "number"){
        if(value > prev) el.classList.add("price-up");
        else if(value < prev) el.classList.add("price-down");
        else el.classList.add("price-flat");
    } else {
        el.classList.add("price-flat");
    }
    lastPriceMap[key] = value;
}

function renderOrphanRecoverBanner(orphans){
    const el = document.getElementById("orphan-position-recover");
    if(!el) return;
    const liveCards = document.querySelectorAll(".pos-list-live .pos-card");
    if(liveCards.length > 0 || !orphans || !orphans.length){
        el.style.display = "none";
        el.innerHTML = "";
        return;
    }
    const o = orphans[0];
    const dir = o.direction === "short" ? "空" : "多";
    const mid = o.recoverable_monitor_id;
    let html = `检测到交易所仍有 <strong>${o.symbol}</strong> ${dir}仓,但本地监控已中断(误同步时可能无交易记录).`;
    if(mid){
        const tpslHint = (o.plan_stop_loss && o.plan_take_profit) ? "并挂止盈止损" : "";
        html += ` <button type="button" class="pos-entrust-btn" onclick="recoverLivePosition(${mid})">恢复监控${tpslHint}</button>`;
    } else {
        html += " 未找到可恢复的监控记录,需在服务器数据库处理.";
    }
    el.innerHTML = html;
    el.style.display = "block";
}

async function recoverLivePosition(monitorId){
    const withTpsl = confirm("确认恢复本地实时监控?若原计划有止盈止损,将尝试重新挂到交易所.");
    if(!withTpsl) return;
    try{
        const res = await fetch("/api/recover_live_position", {
            method: "POST",
            headers: {"Content-Type": "application/json"},
            body: JSON.stringify({monitor_id: monitorId, place_tpsl: true})
        });
        const data = await res.json();
        alert(data.msg || (data.ok ? "已恢复" : "失败"));
        if(data.ok) location.reload();
    }catch(e){
        alert("恢复失败:" + e);
    }
}

function refreshPriceSnapshot(){
    fetch("/api/price_snapshot").then(r=>r.json()).then(data=>{
        const updatedEl = document.getElementById("price-last-updated");
        if(data.updated_at && updatedEl){
            updatedEl.innerText = data.updated_at;
        }
        if(data.force_close && window.TimeCloseUI && TimeCloseUI.paintForceCloseHeader){
            TimeCloseUI.paintForceCloseHeader(data.force_close);
        }
        (data.key_prices || []).forEach(k=>{
            const pEl = document.getElementById(`key-price-${k.id}`);
            if(pEl){
                pEl.innerText = k.price_display || (Number.isFinite(Number(k.price)) ? Number(k.price).toFixed(6) : "-");
                paintPriceTrend(pEl, `k-${k.id}`, Number(k.price));
            }
            const upEl = document.getElementById(`key-up-diff-${k.id}`);
            if(upEl){
                upEl.innerText = `${formatSigned(k.upper_diff, 4)} (${formatSigned(k.upper_pct, 2)}%)`;
            }
            const lowEl = document.getElementById(`key-low-diff-${k.id}`);
            if(lowEl){
                lowEl.innerText = `${formatSigned(k.lower_diff, 4)} (${formatSigned(k.lower_pct, 2)}%)`;
            }
            const gateEl = document.getElementById(`key-gate-${k.id}`);
            if(gateEl){
                gateEl.innerText = k.gate_summary || "-";
                gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f";
            }
            const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
            if(gateMetricEl){
                gateMetricEl.innerText = k.gate_metrics || "";
            }
        });
        (data.order_prices || []).forEach(o=>{
            const pEl = document.getElementById(`order-price-${o.id}`);
            if(pEl){
                const hasMark = (()=>{ const x = o.exchange_mark_price; if(x===null||x===undefined||x==="")return false; const n=Number(x); return !Number.isNaN(n); })();
                let disp = "";
                if(hasMark && o.exchange_mark_price_display){
                    disp = o.exchange_mark_price_display;
                } else if(o.price_display){
                    disp = o.price_display;
                } else {
                    const px = hasMark ? Number(o.exchange_mark_price) : Number(o.price);
                    disp = Number.isFinite(px) ? px.toFixed(6) : "-";
                }
                pEl.innerText = disp;
                const pxNum = hasMark ? Number(o.exchange_mark_price) : Number(o.price);
                paintPriceTrend(pEl, `o-${o.id}`, Number.isFinite(pxNum) ? pxNum : px);
            }
            const exM = document.getElementById(`order-ex-margin-${o.id}`);
            if(exM){
                const mv = o.exchange_initial_margin;
                const mn = (mv === null || mv === undefined || mv === "") ? NaN : Number(mv);
                if(!Number.isNaN(mn)){
                    exM.innerText = `${mn.toFixed(2)}U`;
                } else {
                    const prc = (typeof data.positions_raw_count === "number") ? data.positions_raw_count : null;
                    exM.innerText = (prc === 0) ? "无仓数据" : "-";
                }
            }
            const pnlEl = document.getElementById(`order-pnl-${o.id}`);
            if(pnlEl){
                pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`;
                pnlEl.classList.remove("price-up","price-down","price-flat");
                if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up");
                else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down");
                else pnlEl.classList.add("price-flat");
            }
            const rrEl = document.getElementById(`order-rr-${o.id}`);
            if(rrEl){
                const rr = o.display_rr_ratio != null && o.display_rr_ratio !== "" ? o.display_rr_ratio : o.rr_ratio;
                rrEl.innerText = formatRrRatio(rr);
            }
            paintLatestRiskDisplay(o.id, o);
            paintContractsDisplay(o.id, o);
            paintTpProfitDisplay(o.id, o);
            paintBreakevenBadge(o.id, o);
            if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
            paintPlanTpslDisplay(o.id, o);
            if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
            if(window.TimeCloseUI) TimeCloseUI.paintOrderForceClose(o);
        });
{% if ui_orphan_recovery_enabled %}
        renderOrphanRecoverBanner(data.orphan_live_positions);
        {% endif %}
    }).catch(()=>{});
}

function refreshOrderDefaults(){
    const symbolEl = document.getElementById("order-symbol");
    const directionEl = document.getElementById("order-direction");
    if(!symbolEl || !directionEl){ return; }
    const symbol = (symbolEl.value || "").trim();
    const direction = directionEl.value || "long";
    if(!symbol || !direction){ return; }
    fetch(`/api/order_defaults?symbol=${encodeURIComponent(symbol)}&direction=${encodeURIComponent(direction)}`)
    .then(r=>r.json())
    .then(data=>{
        if(!data.ok){ return; }
        if(data.leverage){
            if(typeof paintOrderLeverageHint === "function"){
                paintOrderLeverageHint(data.leverage);
            } else {
                const levEl = document.getElementById("order-leverage");
                if(levEl) levEl.value = data.leverage;
            }
        }
        if(typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null){
            latestAvailableUsdt = Number(data.available_trading_usdt);
            const fullEl = document.getElementById("use-full-margin");
            const marginEl = document.getElementById("order-margin");
            if(fullEl && marginEl && fullEl.checked){
                const m = Math.max(latestAvailableUsdt * {{ full_margin_buffer_ratio }}, 0).toFixed(2);
                marginEl.value = m;
            }
        }
        const px = data.last_price || data.price;
        if(px) refreshOrderTpPreview(px);
        if(window.ManualOrderRrPreview) ManualOrderRrPreview.schedule();
    }).catch(()=>{});
}

function paintRealtimePnl(v){
    const nodes = document.querySelectorAll('[data-funds-field="realtime-pnl"]');
    if(!nodes.length) return;
    if(v === null || v === undefined || Number.isNaN(Number(v))){
        nodes.forEach((pnlEl) => {
            pnlEl.innerText = "—";
            pnlEl.classList.remove("pnl-pos", "pnl-neg");
        });
        return;
    }
    const n = Number(v);
    const sign = n > 0 ? "+" : "";
    const text = `${sign}${n.toFixed(2)}U`;
    nodes.forEach((pnlEl) => {
        pnlEl.innerText = text;
        pnlEl.classList.toggle("pnl-pos", n > 0);
        pnlEl.classList.toggle("pnl-neg", n < 0);
    });
}
let lastRealtimePnl = null;
function updateRealtimePnl(v){
    if(v != null && !Number.isNaN(Number(v))){
        lastRealtimePnl = Number(v);
        paintRealtimePnl(v);
        return;
    }
    if(lastRealtimePnl != null) return;
    paintRealtimePnl(v);
}
function sumOrdersFloatPnl(orders){
    if(!orders || !orders.length) return null;
    let total = 0, found = false;
    orders.forEach(o=>{
        if(o.float_pnl != null && !Number.isNaN(Number(o.float_pnl))){
            total += Number(o.float_pnl);
            found = true;
        }
    });
    return found ? total : null;
}
function combineRealtimeFloatPnl(perpTotal, optionsTotal){
    let total = 0, found = false;
    [perpTotal, optionsTotal].forEach(v=>{
        if(v != null && !Number.isNaN(Number(v))){
            total += Number(v);
            found = true;
        }
    });
    return found ? total : null;
}
function paintRealtimePnlFromSnapshot(data){
    if(!data) return;
    const perp = data.order_prices && data.order_prices.length
        ? sumOrdersFloatPnl(data.order_prices)
        : null;
    const combined = combineRealtimeFloatPnl(perp, data.options_unrealized_pnl);
    if(combined !== null || perp !== null || data.options_unrealized_pnl != null){
        paintRealtimePnl(combined);
    }
}

function formatOptionsFundingLabel(usdc, usdt) {
    const parts = [];
    if (usdc !== null && usdc !== undefined && Number(usdc) > 0) parts.push(`${Number(usdc).toFixed(2)} USDC`);
    if (usdt !== null && usdt !== undefined && Number(usdt) > 0) parts.push(`${Number(usdt).toFixed(2)} USDT`);
    return parts.length ? parts.join(" · ") : "—";
}

function setFundsFieldText(field, text){
    if(text == null || text === "") return;
    document.querySelectorAll(`[data-funds-field="${field}"]`).forEach((el) => {
        el.innerText = text;
    });
}
function accountSnapshotFundingMissing(data){
    if(!data || typeof data !== "object") return true;
    const hasFunding = data.funding_usdt != null && data.funding_usdt !== "";
    const hasTotal = data.total_funds != null && data.total_funds !== "";
    const hasTrading = data.current_capital != null && data.current_capital !== "";
    return !hasFunding && !hasTotal && !hasTrading;
}
let accountSnapshotRetryCount = 0;
function applyAccountSnapshot(data){
    if(!data || typeof data !== "object") return;
    if(data.funding_usdt != null && data.funding_usdt !== ""){
        setFundsFieldText("total-capital", `${Number(data.funding_usdt).toFixed(2)}U`);
    }
    if(data.total_funds != null && data.total_funds !== ""){
        setFundsFieldText("total-funds", `${Number(data.total_funds).toFixed(2)}U`);
    }
    if(data.current_capital != null && data.current_capital !== "" && !Number.isNaN(Number(data.current_capital))){
        setFundsFieldText("current-capital", `${Number(data.current_capital).toFixed(2)}U`);
    }
    if(data.options_funding_usdc != null || data.options_funding_usdt != null){
        const optFunding = formatOptionsFundingLabel(data.options_funding_usdc, data.options_funding_usdt);
        setFundsFieldText("options-funding-usdc", optFunding);
    }
    if(data.options_trading_usdc != null || data.options_trading_usdt != null){
        const optTrading = formatOptionsFundingLabel(data.options_trading_usdc, data.options_trading_usdt);
        setFundsFieldText("options-trading-usdc", optTrading);
    }
    if(typeof data.unrealized_pnl !== "undefined"){
        updateRealtimePnl(data.unrealized_pnl);
    }
    if(typeof data.total !== "undefined" && data.total !== null){
        setFundsFieldText("stat-total", String(data.total));
    }
    if(typeof data.rate !== "undefined" && data.rate !== null){
        setFundsFieldText("stat-rate", `${Number(data.rate)}%`);
    }
    if(typeof data.profit_loss_ratio !== "undefined"){
        setFundsFieldText(
            "stat-pl-ratio",
            data.profit_loss_ratio != null && data.profit_loss_ratio !== ""
                ? String(data.profit_loss_ratio)
                : "—"
        );
    }
    if(typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null){
        latestAvailableUsdt = Number(data.available_trading_usdt);
    }
    if(data.risk_status){
        document.querySelectorAll("#account-risk-badge").forEach((badge) => {
            if(window.AccountRiskBadge){
                AccountRiskBadge.applyToElement(badge, data.risk_status);
            }else{
                const st = data.risk_status.status || "normal";
                badge.className = "risk-status-badge risk-status-" + st;
                badge.innerText = data.risk_status.status_label || "正常";
                badge.title = data.risk_status.reason || "";
            }
        });
    }
    if(data.force_close && window.TimeCloseUI && TimeCloseUI.paintForceCloseHeader){
        TimeCloseUI.paintForceCloseHeader(data.force_close);
    }
    let canTradeText = "可开仓";
    if(!data.can_trade){
        const parts = [];
        if(data.risk_status && data.risk_status.can_trade === false && data.risk_status.reason){
            parts.push(data.risk_status.reason);
        }
        if((data.active_count||0) >= (data.max_active_positions||{{ max_active_positions }})) parts.push(`持仓 ${data.active_count}/${data.max_active_positions}`);
        const hard = Number(data.daily_open_hard_limit != null ? data.daily_open_hard_limit : {{ daily_open_hard_limit }});
        const opens = Number(data.opens_today);
        if(hard > 0 && !Number.isNaN(opens) && opens >= hard) parts.push(`本交易日开仓 ${opens}/${hard} 已达上限`);
        if(data.open_guard_blocks_now) parts.push(`未到北京时间 ${data.reset_hour||{{ reset_hour }}}:00`);
        canTradeText = parts.length ? `不可开仓(${parts.join(";")})` : "不可开仓";
    }
    const opensToday = Number(data.opens_today);
    const hardLim = Number(data.daily_open_hard_limit != null ? data.daily_open_hard_limit : {{ daily_open_hard_limit }});
    const alertLim = Number(data.daily_open_alert_threshold != null ? data.daily_open_alert_threshold : {{ daily_open_alert_threshold }});
    const openCntTxt = !Number.isNaN(opensToday)
        ? `本交易日开仓 ${opensToday}${hardLim > 0 ? ` / 硬上限 ${hardLim}` : ""}(AI 提醒 ${alertLim})`
        : "";
    const tip = document.getElementById("order-rule-tip");
    const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt.toFixed(2)}U` : "";
    if(tip){
        tip.innerText = `规则:最多 ${data.max_active_positions || {{ max_active_positions }}} 仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x;${openCntTxt ? openCntTxt + ";" : ""}${canTradeText}${avail};人工开仓盈亏比不得低于 {{ manual_min_planned_rr }}:1`;
    }
    const allowEl = document.getElementById("allow-open-before-reset");
    const guardStatus = document.getElementById("open-guard-status");
    const resetH = data.reset_hour != null ? data.reset_hour : {{ reset_hour }};
    if(allowEl && typeof data.open_guard_enabled !== "undefined"){
        allowEl.checked = !data.open_guard_enabled;
    }
    if(guardStatus && typeof data.open_guard_enabled !== "undefined"){
        guardStatus.innerText = data.open_guard_enabled
            ? `已限制:${resetH}:00 前不可开仓`
            : `已放开:${resetH}:00 前允许开仓`;
    }
}
function refreshAccountSnapshot(opts){
    const options = opts || {};
    const qs = options.force ? "?force=1" : "";
    fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{
        applyAccountSnapshot(data);
        if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){
            accountSnapshotRetryCount += 1;
            setTimeout(() => refreshAccountSnapshot({ silent: true, force: accountSnapshotRetryCount >= 2 }), 1200 * accountSnapshotRetryCount);
        }else if(!accountSnapshotFundingMissing(data)){
            accountSnapshotRetryCount = 0;
        }
    }).catch(()=>{
        if(!options.silent && accountSnapshotRetryCount < 3){
            accountSnapshotRetryCount += 1;
            setTimeout(() => refreshAccountSnapshot({ silent: true }), 1200 * accountSnapshotRetryCount);
        }
    });
}

{% if ui_open_guard_enabled %}
const allowOpenBeforeResetEl = document.getElementById("allow-open-before-reset");
if(allowOpenBeforeResetEl){
    allowOpenBeforeResetEl.addEventListener("change", function(){
        const allow = !!this.checked;
        fetch("/api/settings/open_guard", {
            method: "POST",
            headers: {"Content-Type": "application/json"},
            body: JSON.stringify({enabled: !allow}),
        }).then(r=>r.json()).then(data=>{
            if(!data.ok){ alert(data.msg || "保存失败"); return; }
            refreshAccountSnapshot();
        }).catch(()=>alert("保存失败"));
    });
}
{% endif %}
const orderSymbolEl = document.getElementById("order-symbol");
const orderDirectionEl = document.getElementById("order-direction");
const fullMarginEl = document.getElementById("use-full-margin");
if(orderSymbolEl) {
    orderSymbolEl.addEventListener("change", refreshOrderDefaults);
    orderSymbolEl.addEventListener("input", refreshOrderDefaults);
}
if(orderDirectionEl) orderDirectionEl.addEventListener("change", refreshOrderDefaults);
if(fullMarginEl){
    fullMarginEl.addEventListener("change", function(){
        const marginEl = document.getElementById("order-margin");
        if(marginEl && this.checked && latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)){
            marginEl.value = Math.max(latestAvailableUsdt * {{ full_margin_buffer_ratio }}, 0).toFixed(2);
        }
    });
}

const sltpModeEl = document.getElementById("sltp-mode");
function toggleSltpMode(){
    const mode = sltpModeEl ? sltpModeEl.value : "fixed_rr";
    const slEl = document.getElementById("order-sl");
    const tpEl = document.getElementById("order-tp");
    const fixedRrEl = document.getElementById("order-fixed-rr");
    const slPctEl = document.getElementById("order-sl-pct");
    const tpPctEl = document.getElementById("order-tp-pct");
    if(!slEl || !tpEl || !slPctEl || !tpPctEl){ return; }
    const pct = mode === "pct";
    const fixed = mode === "fixed_rr";
    slEl.style.display = pct ? "none" : "";
    tpEl.style.display = (pct || fixed) ? "none" : "";
    if(fixedRrEl) fixedRrEl.style.display = fixed ? "" : "none";
    slEl.required = !pct;
    tpEl.required = !pct && !fixed;
    if(fixedRrEl) fixedRrEl.required = fixed;
    slPctEl.style.display = pct ? "" : "none";
    tpPctEl.style.display = pct ? "" : "none";
    slPctEl.required = pct;
    tpPctEl.required = pct;
    refreshOrderTpPreview();
    if(window.ManualOrderRrPreview) ManualOrderRrPreview.schedule();
}
if(sltpModeEl){
    sltpModeEl.addEventListener("change", toggleSltpMode);
    loadFixedRrPref();
    toggleSltpMode();
}
if(window.ManualOrderRrPreview){
    ManualOrderRrPreview.wire({ minRr: MANUAL_MIN_PLANNED_RR });
}
["order-sl","order-fixed-rr","order-direction"].forEach(function(id){
    const el = document.getElementById(id);
    if(el) el.addEventListener("input", function(){ refreshOrderTpPreview(); });
    if(el) el.addEventListener("change", function(){ refreshOrderTpPreview(); });
});

refreshAccountSnapshot();
if (window.AccountRiskBadge) AccountRiskBadge.startTicker();
const _journalFormEl = document.getElementById("journal-form");
if(_journalFormEl){
    _journalFormEl.addEventListener("submit", function(ev){
        if(!validateJournalEntryReason()) ev.preventDefault();
    });
}

const addOrderForm = document.getElementById("add-order-form");
if(addOrderForm){
    addOrderForm.addEventListener("submit", function(ev){
        if(addOrderForm.dataset.rrOk === "1"){
            addOrderForm.dataset.rrOk = "0";
            return;
        }
        ev.preventDefault();
        if(window.FormSubmitGuard && FormSubmitGuard.isLocked(addOrderForm)) return;
        const direction = (document.getElementById("order-direction")||{}).value || "long";
        const mode = (document.getElementById("sltp-mode")||{}).value || "fixed_rr";
        const symbol = ((document.getElementById("order-symbol")||{}).value || "").trim();
        if(mode === "fixed_rr"){
            saveFixedRrPref();
            const rr = Number((document.getElementById("order-fixed-rr")||{}).value);
            if(!Number.isFinite(rr) || rr <= 0){
                alert("请填写正数盈亏比");
                return;
            }
            if(window.FormSubmitGuard) FormSubmitGuard.lock(addOrderForm, "校验盈亏比…");
            if(rejectManualOrderRr(rr)){
                if(window.FormSubmitGuard) FormSubmitGuard.unlock(addOrderForm);
                return;
            }
            allowManualOrderSubmit(addOrderForm);
            return;
        }
        if(mode === "pct"){
            if(window.FormSubmitGuard) FormSubmitGuard.lock(addOrderForm, "校验盈亏比…");
            const rr = calcClientRrFromPct(
                (document.getElementById("order-sl-pct")||{}).value,
                (document.getElementById("order-tp-pct")||{}).value
            );
            if(rejectManualOrderRr(rr)){
                if(window.FormSubmitGuard) FormSubmitGuard.unlock(addOrderForm);
                return;
            }
            allowManualOrderSubmit(addOrderForm);
            return;
        }
        const sl = Number((document.getElementById("order-sl")||{}).value);
        const tp = Number((document.getElementById("order-tp")||{}).value);
        let entry = sl;
        if(window.FormSubmitGuard) FormSubmitGuard.lock(addOrderForm, "校验盈亏比…");
        if(!symbol){
            if(rejectManualOrderRr(calcClientRr(direction, entry, sl, tp))){
                if(window.FormSubmitGuard) FormSubmitGuard.unlock(addOrderForm);
                return;
            }
            allowManualOrderSubmit(addOrderForm);
            return;
        }
        fetch(`/api/order_defaults?symbol=${encodeURIComponent(symbol)}&direction=${encodeURIComponent(direction)}`)
            .then(r=>r.json())
            .then(data=>{
                const px = data.last_price || data.price;
                if(px) entry = Number(px);
                if(rejectManualOrderRr(calcClientRr(direction, entry, sl, tp))){
                    if(window.FormSubmitGuard) FormSubmitGuard.unlock(addOrderForm);
                    return;
                }
                allowManualOrderSubmit(addOrderForm);
            })
            .catch(()=>{
                alert("无法校验盈亏比,请稍后重试");
                if(window.FormSubmitGuard) FormSubmitGuard.unlock(addOrderForm);
            });
    });
}

refreshOrderDefaults();
refreshPriceSnapshotConditional();
setInterval(refreshAccountSnapshot, {{ balance_refresh_seconds * 1000 }});
function refreshPriceSnapshotConditional(){
    const page = document.body.getAttribute("data-page") || "";
    fetch("/api/price_snapshot").then(r=>r.json()).then(data=>{
        const updatedEl = document.getElementById("price-last-updated");
        if(data.updated_at && updatedEl) updatedEl.innerText = data.updated_at;
        if(data.force_close && window.TimeCloseUI && TimeCloseUI.paintForceCloseHeader){
            TimeCloseUI.paintForceCloseHeader(data.force_close);
        }
        if(page === "key_monitor"){
            (data.key_prices || []).forEach(k=>{
                const pEl = document.getElementById(`key-price-${k.id}`);
                if(pEl){ pEl.innerText = k.price_display || (Number.isFinite(Number(k.price)) ? Number(k.price).toFixed(6) : "-"); paintPriceTrend(pEl, `k-${k.id}`, Number(k.price)); }
                const upEl = document.getElementById(`key-up-diff-${k.id}`);
                if(upEl) upEl.innerText = `${formatSigned(k.upper_diff, 4)} (${formatSigned(k.upper_pct, 2)}%)`;
                const lowEl = document.getElementById(`key-low-diff-${k.id}`);
                if(lowEl) lowEl.innerText = `${formatSigned(k.lower_diff, 4)} (${formatSigned(k.lower_pct, 2)}%)`;
                const gateEl = document.getElementById(`key-gate-${k.id}`);
                if(gateEl){ gateEl.innerText = k.gate_summary || "-"; gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f"; }
                const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
                if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
                if(typeof paintKeyMonitorSummary === "function") paintKeyMonitorSummary(k.id, k);
            });
        }
        if(page === "trade"){
            (data.order_prices || []).forEach(o=>{
                const pEl = document.getElementById(`order-price-${o.id}`);
                if(pEl){
                    const hasMark = (()=>{ const x = o.exchange_mark_price; if(x===null||x===undefined||x==="")return false; const n=Number(x); return !Number.isNaN(n); })();
                    let disp = "";
                    if(hasMark && o.exchange_mark_price_display) disp = o.exchange_mark_price_display;
                    else if(o.price_display) disp = o.price_display;
                    else { const px = hasMark ? Number(o.exchange_mark_price) : Number(o.price); disp = Number.isFinite(px) ? px.toFixed(6) : "-"; }
                    pEl.innerText = disp;
                    const pxNum = hasMark ? Number(o.exchange_mark_price) : Number(o.price);
                    paintPriceTrend(pEl, `o-${o.id}`, Number.isFinite(pxNum) ? pxNum : px);
                }
                const exM = document.getElementById(`order-ex-margin-${o.id}`);
                if(exM){
                    const mv = o.exchange_initial_margin;
                    const mn = (mv === null || mv === undefined || mv === "") ? NaN : Number(mv);
                    if(!Number.isNaN(mn)) exM.innerText = `${mn.toFixed(2)}U`;
                    else { const prc = (typeof data.positions_raw_count === "number") ? data.positions_raw_count : null; exM.innerText = (prc === 0) ? "无仓数据" : "-"; }
                }
                const pnlEl = document.getElementById(`order-pnl-${o.id}`);
                if(pnlEl){
                    pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`;
                    pnlEl.classList.remove("price-up","price-down","price-flat");
                    if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up");
                    else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down");
                    else pnlEl.classList.add("price-flat");
                }
                const rrEl = document.getElementById(`order-rr-${o.id}`);
                if(rrEl) rrEl.innerText = formatRrRatio(o.rr_ratio);
                paintLatestRiskDisplay(o.id, o);
                paintContractsDisplay(o.id, o);
                paintTpProfitDisplay(o.id, o);
                paintBreakevenBadge(o.id, o);
                paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
                paintPlanTpslDisplay(o.id, o);
                if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
                if(window.TimeCloseUI) TimeCloseUI.paintOrderForceClose(o);
                const holdEl = document.getElementById(`order-hold-duration-${o.id}`);
                if(holdEl && o.opened_at_ms != null && o.opened_at_ms !== ""){
                    holdEl.setAttribute("data-order-opened-ms", String(o.opened_at_ms));
                }
            });
            tickOrderHoldDurations();
{% if ui_orphan_recovery_enabled %}
            renderOrphanRecoverBanner(data.orphan_live_positions);
{% endif %}
        }
        if(data.order_prices && data.order_prices.length){
            paintRealtimePnlFromSnapshot(data);
        } else if (typeof data.options_unrealized_pnl !== "undefined") {
            paintRealtimePnlFromSnapshot(data);
        }
    }).catch(()=>{});
}
function formatLiveHoldDurationFromMs(openedMs, nowMs){
    if(openedMs == null || openedMs === "" || !Number.isFinite(Number(openedMs))) return "—";
    const ms = Number(openedMs);
    const now = (nowMs != null) ? nowMs : Date.now();
    let sec = Math.floor((now - ms) / 1000);
    if(sec < 0) sec = 0;
    if(sec <= 0) return "0分钟";
    const d = Math.floor(sec / 86400); sec %= 86400;
    const h = Math.floor(sec / 3600); sec %= 3600;
    const m = Math.floor(sec / 60);
    const parts = [];
    if(d) parts.push(`${d}天`);
    if(h) parts.push(`${h}小时`);
    if(m || !parts.length) parts.push(`${m}分钟`);
    return parts.join("");
}
function tickOrderHoldDurations(){
    const now = Date.now();
    document.querySelectorAll(".order-hold-duration[data-order-opened-ms]").forEach(el=>{
        const ms = Number(el.getAttribute("data-order-opened-ms"));
        if(!Number.isFinite(ms) || ms <= 0) return;
        el.textContent = formatLiveHoldDurationFromMs(ms, now);
    });
}
setInterval(tickOrderHoldDurations, 1000);
tickOrderHoldDurations();
setInterval(refreshPriceSnapshotConditional, {{ price_refresh_seconds * 1000 }});
</script>
<script src="/static/records_review_page.js?v=2"></script>
<script src="/static/options_expiry_countdown.js?v=1"></script>
<script src="/static/instance_dashboard.js?v=3"></script>
<script>
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
{% if page == 'dashboard' %}
document.addEventListener("DOMContentLoaded", function () {
  if (window.InstanceDashboard) InstanceDashboard.init(true);
});
{% endif %}
</script>
<script src="/static/instance_settings_prefs.js?v=14"></script>
</body>
</html>