Files
crypto_monitor/scripts/build_unified_index.py
T
2026-07-06 02:43:24 +08:00

172 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 = """ <details class="tip-collapse transfer-rule-collapse">
<summary class="tip-collapse-summary">划转规则说明</summary>
<div class="tip-collapse-body rule-tip">
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;将 {{ auto_transfer_to }} 调整至 {{ auto_transfer_amount }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }}<strong>持仓中不划转</strong>并微信通知)
</div>
</details>
<form action="/manual_transfer" method="post" class="form-row">
<input name="amount" type="number" min="0.01" step="0.01" placeholder="手动划转金额U" required>
<select name="from_account">
<option value="funding" {% if auto_transfer_from == 'funding' %}selected{% endif %}>from: funding</option>
<option value="swap" {% if auto_transfer_from == 'swap' %}selected{% endif %}>from: swap</option>
<option value="spot" {% if auto_transfer_from == 'spot' %}selected{% endif %}>from: spot</option>
</select>
<select name="to_account">
<option value="swap" {% if auto_transfer_to == 'swap' %}selected{% endif %}>to: swap</option>
<option value="funding" {% if auto_transfer_to == 'funding' %}selected{% endif %}>to: funding</option>
<option value="spot" {% if auto_transfer_to == 'spot' %}selected{% endif %}>to: spot</option>
</select>
<button type="submit">手动划转</button>
</form>
"""
def main() -> None:
text = SRC.read_text(encoding="utf-8")
# 外链 CSS 替代内联 style
text = re.sub(
r" <style>.*?</style>\n",
' <link rel="stylesheet" href="/static/instance_page.css?v=1">\n',
text,
count=1,
flags=re.DOTALL,
)
# 顶栏:划转 + 可选 open guard
text = text.replace(
' <div class="rule-tip">实时价格更新:<span id="price-last-updated">--</span>(北京时间 UTC+8</div>\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] %}
<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>检测到交易所仍有持仓,本地无对应监控单</strong>
<span style="margin-left:8px">{{ o.exchange_symbol or o.symbol }} · {{ '' if o.direction == 'long' else '' }}</span>
<form action="/recover_orphan_order" method="post" style="display:inline;margin-left:12px">
<input type="hidden" name="symbol" value="{{ o.symbol }}">
<input type="hidden" name="direction" value="{{ o.direction }}">
<button type="submit" style="padding:4px 10px;font-size:.82rem">恢复监控</button>
</form>
</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 %}"""
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()