#!/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()