diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html deleted file mode 100644 index dab4642..0000000 --- a/crypto_monitor_binance/templates/index.html +++ /dev/null @@ -1,2124 +0,0 @@ - - - - - - - - - - - - - - - - - {{ exchange_display }} · 加密货币 | 交易监控复盘系统 - - - - - -{% macro period_stats(title, s) %} -
-

{{ title }}

-
{{ s.range_label }}
-
-
开单次数
{{ s.opens_count }}
-
平仓笔数
{{ s.closed_count }}
-
胜率
{% if s.win_rate_pct is not none %}{{ s.win_rate_pct }}%{% else %}-{% endif %}
-
净盈亏(U)
{{ funds_fmt(s.net_pnl_u) }}
-
亏损额合计(U)
{{ funds_fmt(s.loss_sum_u) }}
-
单笔最大亏损(U)
{% if s.max_single_loss is not none %}{{ funds_fmt(s.max_single_loss) }}{% else %}-{% endif %}
-
单笔最大盈利(U)
{% if s.max_single_profit is not none %}{{ funds_fmt(s.max_single_profit) }}{% else %}-{% endif %}
-
最大回撤(U)
{{ funds_fmt(s.max_drawdown_u) }}
-
当前连续亏损笔数
{{ s.consecutive_losses }}
-
最长连续亏损(交易日)
{{ s.max_loss_streak_days }} 天
-
期内最大亏损日
{% if s.worst_day %}{{ s.worst_day }}({{ funds_fmt(s.worst_day_pnl) }}U){% else %}-{% endif %}
-
-
-{% endmacro %} -
-
-

加密货币|交易监控 + AI复盘一体化

-
-
{{ exchange_display }}
- {% if trade_policy.badge_text %} - {{ trade_policy.badge_text }} - {% endif %} - {% include 'force_close_header_badge.html' %} - {{ risk_status.status_label|default('正常') }} -
- - -
-
-
-
- 关键位监控 - 实盘下单 - 策略交易 - 策略交易记录 - 交易记录与复盘 - 统计分析 -
- {% with msg=get_flashed_messages() %}{% if msg %}
{{ msg[0] }}
{% endif %}{% endwith %} - -
- 列表筛选(UTC,默认当日):{{ list_window.label }} - - - - - - - 统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日 -
-
- 数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列,复盘单独导出): - 交易记录 - 复盘记录 - 关键位(当前) - 关键位历史 -
-
-
交易所
{{ exchange_display }}
-
总交易
{{ total }}
-
胜率
{{ rate }}%
-
资金账户(USDT)
{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}
-
交易日
{{ trading_day }}
-
当日资金(交易账户)
{{ funds_fmt(current_capital) }}U
-
-
实时价格更新:--(北京时间 UTC+8)
- -
- {% if page == 'key_monitor' %} - {% include 'key_monitor_panel.html' %} - {% elif page == 'trade' %} -
-
-
-

实盘下单监控

- {% if focus_order_id %} - 放大查看K线(100根) - {% else %} - 暂无持仓可放大 - {% endif %} -
- {% include 'order_monitor_rule_tips_binance.html' %} -
- 划转规则说明 -
- 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;将 {{ auto_transfer_to }} 调整至 {{ auto_transfer_amount }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }};持仓中不划转并微信通知) -
-
-
- - - - -
-
- {% 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') }} - - {% 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() }} - - - - - - - {% from 'symbol_live_price_snippet.html' import symbol_live_price_hint %} - {{ symbol_live_price_hint('order-symbol-live-price', 'order-symbol', 'order-direction') }} - 下单成交价以交易所成交回报为准 - - - - - - -
- {% include 'order_plan_preview_bar.html' %} -
-
-

实时持仓

- {% if not order and orphan_live_positions %} - {% set o = orphan_live_positions[0] %} -
- 检测到交易所仍有 {{ o.symbol }} {{ '空' if o.direction == 'short' else '多' }}仓,但本地监控已中断(误同步时可能无交易记录)。 - {% if o.recoverable_monitor_id %} - - {% else %} - 未找到可恢复的监控记录,需在服务器数据库处理。 - {% endif %} -
- {% else %} - - {% endif %} -
- {% for o in order %} -
-
-
- {{ o.exchange_symbol or o.symbol }} - {% if o.time_close_enabled %} - - 时间平仓 {{ o.time_close_hours or '' }}h - · --:--:-- - - {% endif %} - {% include 'force_close_order_badge.html' %} - {{ '做多' if o.direction == 'long' else '做空' }} -
-
- - 平仓 -
-
-
- 来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} - {% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %} - 风险: {% 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 %} - - - {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} - - -
-
-
- 成交价 - {{ price_fmt(o.symbol, o.trigger_price) }} -
-
- 止损 - {{ price_fmt(o.symbol, o.stop_loss) if o.stop_loss else '—' }} -
-
- 止盈 - {{ price_fmt(o.symbol, o.take_profit) if o.take_profit else '—' }} -
-
- 盈亏比 - {% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %} -
-
- 张数 - {% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %} -
-
- 标记价 - - -
-
- 浮盈亏 - - -
-
- -
-
交易所止盈止损
-
- 止损:加载中… - -
-
- 止盈:加载中… - -
-
-
- {% else %} -
暂无持仓
- {% endfor %} -
-
- -
-
-

挂止盈止损

-

将先撤销该合约已有 TP/SL,再按下列价格重挂。

-
- -
-
- - -
-
- - -
-
- - -
-
-
- -
- {% elif page in ('strategy', 'strategy_trend', 'strategy_roll') %} - {% include 'strategy_trading_page.html' %} - {% elif page == 'strategy_records' %} - {% include 'strategy_records_page.html' %} - {% endif %} - - - - {% if page == 'records' %} -
-

交易记录

-
- -
-
- - - {% for r in record %} - - {% set pnl_val = (r.pnl_amount or 0)|float %} - - - - - - {% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %} - {% set tp_show = r.effective_take_profit or r.take_profit %} - - - - - - - - {% set pnl_val = (r.effective_pnl_amount or 0)|float %} - - - - - {% endfor %} -
品种类型开仓类型方向成交止损(开仓)止盈基数杠杆持仓分钟开仓时间(北京)平仓时间(北京)盈亏U结果操作
{{ r.symbol }}{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %}{{ r.effective_entry_reason or '-' }}{{ '做多' if r.direction == 'long' else '做空' }}{{ price_fmt(r.symbol, r.trigger_price) }}{{ price_fmt(r.symbol, stop_show) }}{{ price_fmt(r.symbol, tp_show) }}{% if r.margin_capital is not none and r.margin_capital != '' %}{{ funds_fmt(r.margin_capital) }}{% else %}-{% endif %}{{ r.leverage or '-' }}{{ r.effective_hold_minutes or 0 }}{{ (r.effective_opened_at or '-')[:16] }}{{ (r.effective_closed_at or r.created_at or '-')[:16] }}{{ funds_fmt(r.effective_pnl_amount or 0) }}{% if r.display_pnl_source == 'exchange' %}{% elif r.display_pnl_source != 'reviewed' %}{% endif %} - {% set effective_result = r.effective_result %} - {% if effective_result in ["止盈","保本止盈","移动止盈"] %}{{ effective_result }} - {% elif effective_result in ["止损","强制清仓","手动平仓"] %}{{ effective_result }} - {% elif effective_result == "时间平仓" %}{{ effective_result }} - {% else %}{{ effective_result or '-' }}{% endif %} - - - - -
-
-
- -
-

交易复盘记录上传(含截图)

-
- - - - - - {% from 'journal_form_fields.html' import journal_form_fields %} - {{ journal_form_fields(entry_reason_options) }} - {% from 'journal_upload_slots.html' import journal_upload_slots %} - {{ journal_upload_slots() }} -
- - - - - - - - - -
-
双周期上下排列;截止=平仓时间:开仓前背景至平仓;截止=当前时间:最近 N 根至此刻(可看平仓后走势);标注开仓、平仓与止损位
-
- - - - - - -
- - -
-
- -
-
-

AI复盘(按交易记录)

- -
-
- - - - - - - -
- - -
-
- 交易复盘记录 -
-
-
- AI历史复盘 -
-
-
-
-
-
- {% endif %} - - - {% if page == 'stats' %} -
-
-

数据统计

- -
-
-
- 统计分析按北京时间 {{ stats_bundle.stats_reset_hour }}:00切日计入(与顶栏 UTC 列表窗无关)。历史总开仓(累计): - {{ stats_bundle.total_opens_all }} 次 -
-
- -
- {% for seg in stats_bundle.segments %} - - {% endfor %} -
-
- {% endif %} - - - -
-
-
-
详情
-
- - -
-
-
- - -
-
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/crypto_monitor_gate/templates/login.html b/crypto_monitor_gate/templates/login.html deleted file mode 100644 index 8b6ea41..0000000 --- a/crypto_monitor_gate/templates/login.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - 登录 · {{ exchange_display }} - - - - -
-
- - -
-
- -
-

交易监控系统登录

-

交易所:{{ exchange_display }}

- {% with messages = get_flashed_messages() %} - {% if messages %} -
{{ messages[0] }}
- {% endif %} - {% endwith %} -
-
- - -
-
- - -
- -
-
- - diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index d65e808..bec05d0 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -6375,7 +6375,7 @@ def login(): return redirect("/") else: flash("账号或密码错误") - return render_template("login.html") + return render_template("login.html", exchange_display=EXCHANGE_DISPLAY_NAME) @app.route("/logout") def logout(): @@ -7185,6 +7185,7 @@ def order_focus(): selected_order=selected, default_timeframe=KLINE_TIMEFRAME, price_refresh_seconds=PRICE_REFRESH_SECONDS, + exchange_display=EXCHANGE_DISPLAY_NAME, ) diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html deleted file mode 100644 index f10ae06..0000000 --- a/crypto_monitor_okx/templates/index.html +++ /dev/null @@ -1,2100 +0,0 @@ - - - - - - - - - - - - - - - - - {{ exchange_display }} · 加密货币 | 交易监控复盘系统 - - - - - -{% macro period_stats(title, s) %} -
-

{{ title }}

-
{{ s.range_label }}
-
-
开单次数
{{ s.opens_count }}
-
平仓笔数
{{ s.closed_count }}
-
胜率
{% if s.win_rate_pct is not none %}{{ s.win_rate_pct }}%{% else %}-{% endif %}
-
净盈亏(U)
{{ funds_fmt(s.net_pnl_u) }}
-
亏损额合计(U)
{{ funds_fmt(s.loss_sum_u) }}
-
单笔最大亏损(U)
{% if s.max_single_loss is not none %}{{ funds_fmt(s.max_single_loss) }}{% else %}-{% endif %}
-
单笔最大盈利(U)
{% if s.max_single_profit is not none %}{{ funds_fmt(s.max_single_profit) }}{% else %}-{% endif %}
-
最大回撤(U)
{{ funds_fmt(s.max_drawdown_u) }}
-
当前连续亏损笔数
{{ s.consecutive_losses }}
-
最长连续亏损(交易日)
{{ s.max_loss_streak_days }} 天
-
期内最大亏损日
{% if s.worst_day %}{{ s.worst_day }}({{ funds_fmt(s.worst_day_pnl) }}U){% else %}-{% endif %}
-
-
-{% endmacro %} -
-
-

加密货币|交易监控 + AI复盘一体化

-
-
{{ exchange_display }}
- {% if trade_policy.badge_text %} - {{ trade_policy.badge_text }} - {% endif %} - {% include 'force_close_header_badge.html' %} - {{ risk_status.status_label|default('正常') }} -
- - -
-
-
-
- 关键位监控 - 实盘下单 - 策略交易 - 策略交易记录 - 交易记录与复盘 - 统计分析 -
- {% with msg=get_flashed_messages() %}{% if msg %}
{{ msg[0] }}
{% endif %}{% endwith %} - -
- 列表筛选(UTC,默认当日):{{ list_window.label }} - - - - - - - 统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日 -
-
- 数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列,复盘单独导出): - 交易记录 - 复盘记录 - 关键位(当前) - 关键位历史 -
-
-
交易所
{{ exchange_display }}
-
总交易
{{ total }}
-
胜率
{{ rate }}%
-
资金账户(USDT)
{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}
-
交易日
{{ trading_day }}
-
当日资金(交易账户)
{{ funds_fmt(current_capital) }}U
-
-
实时价格更新:--(北京时间 UTC+8)
-
- - - {% if open_guard_enabled %}已限制:{{ reset_hour }}:00 前不可开仓{% else %}已放开:{{ reset_hour }}:00 前允许开仓{% endif %} - -
- -
- {% if page == 'key_monitor' %} - {% include 'key_monitor_panel.html' %} - {% elif page == 'trade' %} -
-
-
-

实盘下单监控

- {% if focus_order_id %} - 放大查看K线(100根) - {% else %} - 暂无持仓可放大 - {% endif %} -
- {% include 'order_monitor_rule_tips_okx.html' %} -
- 划转规则说明 -
- 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;将 {{ auto_transfer_to }} 调整至 {{ auto_transfer_amount }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }};持仓中不划转并微信通知) -
-
-
- - - - -
-
- {% 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') }} - - {% 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() }} - - - - - - - {% from 'symbol_live_price_snippet.html' import symbol_live_price_hint %} - {{ symbol_live_price_hint('order-symbol-live-price', 'order-symbol', 'order-direction') }} - 下单成交价以交易所成交回报为准 - - - - - - -
- {% include 'order_plan_preview_bar.html' %} -
-
-

实时持仓

-
- {% for o in order %} -
-
-
- {{ o.exchange_symbol or o.symbol }} - {% if o.time_close_enabled %} - - 时间平仓 {{ o.time_close_hours or '' }}h - · --:--:-- - - {% endif %} - {% include 'force_close_order_badge.html' %} - {{ '做多' if o.direction == 'long' else '做空' }} -
-
- - 平仓 -
-
-
- 来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} - {% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %} - 风险: {% 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 %} - - - {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} - - -
-
-
- 成交价 - {{ price_fmt(o.symbol, o.trigger_price) }} -
-
- 止损 - {{ price_fmt(o.symbol, o.stop_loss) if o.stop_loss else '—' }} -
-
- 止盈 - {{ price_fmt(o.symbol, o.take_profit) if o.take_profit else '—' }} -
-
- 盈亏比 - {% if o.rr_ratio is not none %}{{ '%g'|format(o.rr_ratio) }}:1{% else %}-:1{% endif %} -
-
- 张数 - {% if o.order_amount is not none %}{{ '%g'|format(o.order_amount) }}{% else %}—{% endif %} -
-
- 标记价 - - -
-
- 浮盈亏 - - -
-
- -
-
交易所止盈止损
-
- 止损:加载中… - -
-
- 止盈:加载中… - -
-
-
- {% else %} -
暂无持仓
- {% endfor %} -
-
- -
-
-

挂止盈止损

-

将先撤销该合约已有 TP/SL,再按下列价格重挂。

-
- -
-
- - -
-
- - -
-
- - -
-
-
- -
- {% elif page in ('strategy', 'strategy_trend', 'strategy_roll') %} - {% include 'strategy_trading_page.html' %} - {% elif page == 'strategy_records' %} - {% include 'strategy_records_page.html' %} - {% endif %} - - - - {% if page == 'records' %} -
-

交易记录

-
- -
-
- - - {% for r in record %} - - {% set pnl_val = (r.pnl_amount or 0)|float %} - - - - - - {% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %} - {% set tp_show = r.effective_take_profit or r.take_profit %} - - - - - - - - {% set pnl_val = (r.effective_pnl_amount or 0)|float %} - - - - - {% endfor %} -
品种类型开仓类型方向成交止损(开仓)止盈基数杠杆持仓分钟开仓时间(北京)平仓时间(北京)盈亏U结果操作
{{ r.symbol }}{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %}{{ r.effective_entry_reason or '-' }}{{ '做多' if r.direction == 'long' else '做空' }}{{ price_fmt(r.symbol, r.trigger_price) }}{{ price_fmt(r.symbol, stop_show) }}{{ price_fmt(r.symbol, tp_show) }}{% if r.margin_capital is not none and r.margin_capital != '' %}{{ funds_fmt(r.margin_capital) }}{% else %}-{% endif %}{{ r.leverage or '-' }}{{ r.effective_hold_minutes or 0 }}{{ (r.effective_opened_at or '-')[:16] }}{{ (r.effective_closed_at or r.created_at or '-')[:16] }}{{ funds_fmt(r.effective_pnl_amount or 0) }}{% if r.display_pnl_source == 'exchange' %}{% elif r.display_pnl_source != 'reviewed' %}{% endif %} - {% set effective_result = r.effective_result %} - {% if effective_result in ["止盈","保本止盈","移动止盈"] %}{{ effective_result }} - {% elif effective_result in ["止损","强制清仓","手动平仓"] %}{{ effective_result }} - {% elif effective_result == "时间平仓" %}{{ effective_result }} - {% else %}{{ effective_result or '-' }}{% endif %} - - - - -
-
-
- -
-

交易复盘记录上传(含截图)

-
- - - - - - {% from 'journal_form_fields.html' import journal_form_fields %} - {{ journal_form_fields(entry_reason_options) }} - {% from 'journal_upload_slots.html' import journal_upload_slots %} - {{ journal_upload_slots() }} -
- - - - - - - - - -
-
双周期上下排列;截止=平仓时间:开仓前背景至平仓;截止=当前时间:最近 N 根至此刻(可看平仓后走势);标注开仓、平仓与止损位
-
- - - - - - -
- - -
-
- -
-
-

AI复盘(按交易记录)

- -
-
- - - - - - - -
- - -
-
- 交易复盘记录 -
-
-
- AI历史复盘 -
-
-
-
-
-
- {% endif %} - - - {% if page == 'stats' %} -
-
-

数据统计

- -
-
-
- 统计分析按北京时间 {{ stats_bundle.stats_reset_hour }}:00切日计入(与顶栏 UTC 列表窗无关)。历史总开仓(累计): - {{ stats_bundle.total_opens_all }} 次 -
-
- -
- {% for seg in stats_bundle.segments %} - - {% endfor %} -
-
- {% endif %} - - - -
-
-
-
详情
-
- - -
-
-
- - -
-
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/crypto_monitor_okx/templates/login.html b/crypto_monitor_okx/templates/login.html deleted file mode 100644 index 39ec256..0000000 --- a/crypto_monitor_okx/templates/login.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - 系统登录 - - - - -
-
- - -
-
- -
-

交易监控系统登录

- {% with messages = get_flashed_messages() %} - {% if messages %} -
{{ messages[0] }}
- {% endif %} - {% endwith %} -
-
- - -
-
- - -
- -
-
- - diff --git a/lib/instance/instance_embed_lib.py b/lib/instance/instance_embed_lib.py index d5a7de6..036a56a 100644 --- a/lib/instance/instance_embed_lib.py +++ b/lib/instance/instance_embed_lib.py @@ -44,7 +44,16 @@ def order_rule_tips_template(exchange_key: str) -> str: def include_transfer_block(exchange_key: str) -> bool: - return (exchange_key or "").strip().lower() == "gate" + """三所 standalone / embed 壳均在顶栏展示划转区块。""" + return (exchange_key or "").strip().lower() in ORDER_RULE_TIPS_BY_EXCHANGE + + +def ui_open_guard_enabled(exchange_key: str) -> bool: + return (exchange_key or "").strip().lower() == "okx" + + +def ui_orphan_recovery_enabled(exchange_key: str) -> bool: + return (exchange_key or "").strip().lower() == "binance" def path_to_embed_tab(path: str) -> str | None: @@ -144,4 +153,6 @@ def embed_context_extras(exchange_key: str) -> dict: return { "order_rule_tips_tpl": order_rule_tips_template(exchange_key), "include_transfer_block": include_transfer_block(exchange_key), + "ui_open_guard_enabled": ui_open_guard_enabled(exchange_key), + "ui_orphan_recovery_enabled": ui_orphan_recovery_enabled(exchange_key), } diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index c72e03f..e22f494 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -90,7 +90,7 @@
当日资金(交易账户)
{{ funds_fmt(current_capital) }}U
{% if include_transfer_block %} - {% include 'gate_transfer_block.html' %} + {% include 'instance_top_bar.html' %} {% endif %}
diff --git a/crypto_monitor_gate/templates/index.html b/lib/instance/templates/index.html similarity index 82% rename from crypto_monitor_gate/templates/index.html rename to lib/instance/templates/index.html index d83b5f9..d98dc4a 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/lib/instance/templates/index.html @@ -1,3 +1,4 @@ +{# 三所共用 standalone 主页 — 由 scripts/build_unified_index.py 生成,勿手改三所副本 #}  @@ -15,229 +16,7 @@ {{ exchange_display }} · 加密货币 | 交易监控复盘系统 - + @@ -335,7 +114,7 @@
交易日
{{ trading_day }}
当日资金(交易账户)
{{ funds_fmt(current_capital) }}U
- {% include 'gate_transfer_block.html' %} + {% include 'instance_top_bar.html' %}
{% if page == 'key_monitor' %} @@ -351,7 +130,7 @@ 暂无持仓可放大 {% endif %}
- {% include 'order_monitor_rule_tips_gate.html' %} + {% include order_rule_tips_tpl %}
{% from 'trade_policy_fields.html' import trade_policy_symbol, trade_policy_direction with context %} {{ trade_policy_symbol('symbol', 'order-symbol') }} @@ -395,6 +174,21 @@

实时持仓

+ {% if ui_orphan_recovery_enabled %} + {% if not order and orphan_live_positions %} + {% set o = orphan_live_positions[0] %} +
+ 检测到交易所仍有 {{ o.symbol }} {{ '空' if o.direction == 'short' else '多' }}仓,但本地监控已中断(误同步时可能无交易记录)。 + {% if o.recoverable_monitor_id %} + + {% else %} + 未找到可恢复的监控记录,需在服务器数据库处理。 + {% endif %} +
+ {% else %} + + {% endif %} + {% endif %}
{% for o in order %}
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 = `检测到交易所仍有 ${o.symbol} ${dir}仓,但本地监控已中断(误同步时可能无交易记录)。`; + if(mid){ + const tpslHint = (o.plan_stop_loss && o.plan_take_profit) ? "并挂止盈止损" : ""; + html += ` `; + } 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"); @@ -1713,7 +1547,11 @@ function refreshPriceSnapshot(){ 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(()=>{}); } @@ -1781,20 +1619,17 @@ function refreshAccountSnapshot(){ TimeCloseUI.paintForceCloseHeader(data.force_close); } let canTradeText = "可开仓"; - if (!data.can_trade) { + 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); } - const ac = Number(data.active_count || 0); - const max = Number(data.max_active_positions || {{ max_active_positions }}); - if (ac >= max) parts.push(`持仓 ${ac}/${max}`); + 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 (!parts.length) parts.push(`未到北京时间 {{ reset_hour }}:00`); - else parts.push(`或未到北京时间 {{ reset_hour }}:00`); - canTradeText = `不可开仓(${parts.join(";")})`; + 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 }}); @@ -1807,9 +1642,36 @@ function refreshAccountSnapshot(){ 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 前允许开仓`; + } }).catch(()=>{}); } +{% 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"); @@ -2011,6 +1873,9 @@ function refreshPriceSnapshotConditional(){ } }); tickOrderHoldDurations(); +{% if ui_orphan_recovery_enabled %} + renderOrphanRecoverBanner(data.orphan_live_positions); +{% endif %} } }).catch(()=>{}); } diff --git a/lib/instance/templates/instance_top_bar.html b/lib/instance/templates/instance_top_bar.html new file mode 100644 index 0000000..15a221d --- /dev/null +++ b/lib/instance/templates/instance_top_bar.html @@ -0,0 +1,13 @@ +{# 三所统一顶栏:划转 + 手动划转 + 可选整点前开仓开关 #} +{% include 'gate_transfer_block.html' %} +{% if ui_open_guard_enabled %} +
+ + + {% if open_guard_enabled %}已限制:{{ reset_hour }}:00 前不可开仓{% else %}已放开:{{ reset_hour }}:00 前允许开仓{% endif %} + +
+{% endif %} diff --git a/crypto_monitor_binance/templates/login.html b/lib/instance/templates/login.html similarity index 96% rename from crypto_monitor_binance/templates/login.html rename to lib/instance/templates/login.html index 8b6ea41..8e1c5b7 100644 --- a/crypto_monitor_binance/templates/login.html +++ b/lib/instance/templates/login.html @@ -1,136 +1,136 @@ - - - - - - - 登录 · {{ exchange_display }} - - - - - - - - - + + + + + + + 登录 · {{ exchange_display }} + + + + + + + + + diff --git a/lib/strategy/templates/gate_transfer_block.html b/lib/strategy/templates/gate_transfer_block.html index 2c19daf..1a460b5 100644 --- a/lib/strategy/templates/gate_transfer_block.html +++ b/lib/strategy/templates/gate_transfer_block.html @@ -4,7 +4,7 @@ · 划转规则
- 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;将 {{ auto_transfer_to }} 调整至 {{ transfer_amount_fmt }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }};持仓中不划转并微信通知) + 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;将 {{ auto_transfer_to }} 调整至 {{ transfer_amount_fmt|default(funds_fmt(auto_transfer_amount)) }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }};持仓中不划转并微信通知)
diff --git a/scripts/build_embed_fragment.py b/scripts/build_embed_fragment.py index 6ce0833..39bb161 100644 --- a/scripts/build_embed_fragment.py +++ b/scripts/build_embed_fragment.py @@ -1,33 +1,63 @@ -"""Build embed_page_fragment.html from gate index.html.""" +"""Build embed_page_fragment.html from lib/instance/templates/index.html.""" from __future__ import annotations from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -src_lines = (ROOT / "crypto_monitor_gate" / "templates" / "index.html").read_text( - encoding="utf-8" -).splitlines() +SRC = ROOT / "lib" / "instance" / "templates" / "index.html" +OUT = ROOT / "lib" / "instance" / "templates" / "embed_page_fragment.html" -# 1-based line numbers from index.html -macro_body = src_lines[243:262] # {% macro %} … {% endmacro %} -grid_inner = src_lines[328:736] # inside .grid (exclude outer wrapper) -stats_block = src_lines[738:772] +GRID_START = '
' +STATS_START = '
', - *grid_inner, - "
", - *stats_block, -] -out_dir = ROOT / "lib" / "instance" / "templates" -out_dir.mkdir(exist_ok=True) -text = "\n".join(out_lines) + "\n" -text = text.replace( - "{% include 'order_monitor_rule_tips_gate.html' %}", - "{% include order_rule_tips_tpl %}", -) -(out_dir / "embed_page_fragment.html").write_text(text, encoding="utf-8") -print("wrote", out_dir / "embed_page_fragment.html", "lines", len(out_lines)) +def _slice_between(lines: list[str], start: str, end: str | None) -> list[str]: + try: + i = next(idx for idx, line in enumerate(lines) if line == start) + except StopIteration: + raise SystemExit(f"marker not found: {start!r}") + if end is None: + return lines[i:] + try: + j = next(idx for idx, line in enumerate(lines[i + 1 :], i + 1) if line.startswith(end)) + except StopIteration: + raise SystemExit(f"end marker not found: {end!r}") + return lines[i:j] + + +def main() -> None: + lines = SRC.read_text(encoding="utf-8").splitlines() + macro_start = next(i for i, l in enumerate(lines) if l.startswith("{% macro period_stats")) + macro_end = next(i for i, l in enumerate(lines) if l.strip() == "{% endmacro %}") + macro_body = lines[macro_start : macro_end + 1] + + grid_block = _slice_between(lines, GRID_START, STATS_START) + # strip outer .grid wrapper; fragment adds its own + if grid_block and grid_block[0] == GRID_START: + grid_block = grid_block[1:] + if grid_block and grid_block[-1].strip() == "
": + # only remove closing div if it closes .grid (heuristic: last line before stats) + pass + + stats_block = _slice_between(lines, STATS_START, "
") + + out_lines = [ + "{# Hub iframe tab fragment — shared via embed_templates #}", + *macro_body, + '
', + *grid_block, + "
", + *stats_block, + ] + text = "\n".join(out_lines).rstrip() + "\n" + if "order_rule_tips_tpl" not in text: + text = text.replace( + "{% include 'order_monitor_rule_tips_binance.html' %}", + "{% include order_rule_tips_tpl %}", + ) + OUT.write_text(text, encoding="utf-8") + print("wrote", OUT, "lines", len(out_lines)) + + +if __name__ == "__main__": + main() diff --git a/scripts/build_unified_index.py b/scripts/build_unified_index.py new file mode 100644 index 0000000..593bea4 --- /dev/null +++ b/scripts/build_unified_index.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""从 binance index.html 生成三所共用的 lib/instance/templates/index.html。""" +from __future__ import annotations + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SRC = ROOT / "lib" / "instance" / "templates" / "index.html" +OUT = ROOT / "lib" / "instance" / "templates" / "index.html" + +TRANSFER_BLOCK = """
+ 划转规则说明 +
+ 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;将 {{ auto_transfer_to }} 调整至 {{ auto_transfer_amount }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }};持仓中不划转并微信通知) +
+
+ + + + + + +""" + + +def main() -> None: + text = SRC.read_text(encoding="utf-8") + + # 外链 CSS 替代内联 style + text = re.sub( + r" \n", + ' \n', + text, + count=1, + flags=re.DOTALL, + ) + + # 顶栏:划转 + 可选 open guard + text = text.replace( + '
实时价格更新:--(北京时间 UTC+8)
\n', + " {% include 'instance_top_bar.html' %}\n", + ) + + # 规则条动态 include + text = text.replace( + "{% include 'order_monitor_rule_tips_binance.html' %}", + "{% include order_rule_tips_tpl %}", + ) + + # 下单面板内划转块移除(已上移到顶栏) + if TRANSFER_BLOCK in text: + text = text.replace(TRANSFER_BLOCK, "", 1) + + # 孤儿仓恢复 banner + orphan_block = """ {% if not order and orphan_live_positions %} + {% set o = orphan_live_positions[0] %} +
+ 检测到交易所仍有持仓,本地无对应监控单 + {{ o.exchange_symbol or o.symbol }} · {{ '多' if o.direction == 'long' else '空' }} +
+ + + +
+
+ {% else %} + + {% endif %}""" + wrapped = "{% if ui_orphan_recovery_enabled %}\n" + orphan_block + "\n {% endif %}" + text = text.replace(orphan_block, wrapped, 1) + + # refreshAccountSnapshot:采用 OKX 版 open_guard 逻辑 + old_can_trade = """ 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); + } + const ac = Number(data.active_count || 0); + const max = Number(data.max_active_positions || {{ max_active_positions }}); + if (ac >= max) parts.push(`持仓 ${ac}/${max}`); + 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 (!parts.length) parts.push(`未到北京时间 {{ reset_hour }}:00`); + else parts.push(`或未到北京时间 {{ reset_hour }}:00`); + canTradeText = `不可开仓(${parts.join(";")})`; + }""" + new_can_trade = """ 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(";")})` : "不可开仓"; + }""" + text = text.replace(old_can_trade, new_can_trade, 1) + + guard_sync = """ 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 前允许开仓`; + }""" + insert_after = """ 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`; + } + }).catch(()=>{});""" + if guard_sync not in text: + text = text.replace( + insert_after, + insert_after.replace(" }).catch(()=>{});", guard_sync + "\n }).catch(()=>{});"), + 1, + ) + + open_guard_js = """ +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("保存失败")); + }); +} +""" + marker = "const orderSymbolEl = document.getElementById(\"order-symbol\");" + if "allowOpenBeforeResetEl" not in text: + text = text.replace(marker, "{% if ui_open_guard_enabled %}" + open_guard_js + "{% endif %}\n" + marker, 1) + + orphan_fn_guard = "{% if ui_orphan_recovery_enabled %}\n renderOrphanRecoverBanner(data.orphan_live_positions);\n {% endif %}" + text = text.replace( + " renderOrphanRecoverBanner(data.orphan_live_positions);", + orphan_fn_guard, + ) + + header = "{# 三所共用 standalone 主页 — 由 scripts/build_unified_index.py 生成,勿手改三所副本 #}\n" + if not text.startswith("{# 三所共用"): + text = header + text + + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text(text, encoding="utf-8") + print("wrote", OUT, "lines", len(text.splitlines())) + + +if __name__ == "__main__": + main() diff --git a/tests/test_instance_embed_lib.py b/tests/test_instance_embed_lib.py index fc20121..7bc4bcf 100644 --- a/tests/test_instance_embed_lib.py +++ b/tests/test_instance_embed_lib.py @@ -1,7 +1,11 @@ from lib.instance.instance_embed_lib import ( EMBED_TABS, + embed_context_extras, + include_transfer_block, path_to_embed_tab, rewrite_embed_dest, + ui_open_guard_enabled, + ui_orphan_recovery_enabled, ) @@ -24,3 +28,15 @@ def test_embed_tabs_cover_main_nav(): assert "trade" in EMBED_TABS assert "key_monitor" in EMBED_TABS assert "records" in EMBED_TABS + + +def test_embed_context_extras_unified_ui_flags(): + for ex in ("binance", "okx", "gate"): + assert include_transfer_block(ex) is True + assert ui_open_guard_enabled("okx") is True + assert ui_open_guard_enabled("binance") is False + assert ui_orphan_recovery_enabled("binance") is True + assert ui_orphan_recovery_enabled("gate") is False + ctx = embed_context_extras("gate") + assert ctx["order_rule_tips_tpl"] == "order_monitor_rule_tips_gate.html" + assert ctx["include_transfer_block"] is True