feat: instance system settings page with risk docs, export and transfer

顶栏新增系统设置;风控说明读取 .env;数据导出与资金划转迁入三张卡片;顶栏移除导出条与手动划转。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 01:26:38 +08:00
parent d7fa596e68
commit d87081205d
16 changed files with 503 additions and 27 deletions
+17 -2
View File
@@ -7239,6 +7239,7 @@ def render_main_page(page="trade", embed_mode=None):
orphan_live_positions = list_orphan_live_positions(conn)
conn.close()
from lib.instance.instance_embed_lib import embed_context_extras
from lib.instance.instance_settings_lib import settings_page_context
template_ctx = dict(
page=page,
@@ -7315,6 +7316,14 @@ def render_main_page(page="trade", embed_mode=None):
kline_timeframe=KLINE_TIMEFRAME,
**strategy_extra,
**embed_context_extras("binance"),
**settings_page_context(
page,
exchange_key="binance",
exchange_display=EXCHANGE_DISPLAY_NAME,
risk_status=risk_status,
trade_policy=TRADE_POLICY,
data_export_version=3,
),
**force_close_template_context(
FORCE_CLOSE_ENABLED,
FORCE_CLOSE_BJ_HOUR,
@@ -7358,6 +7367,12 @@ def stats_page():
return render_main_page("stats")
@app.route("/settings")
@login_required
def settings_page():
return render_main_page("settings")
@app.route("/api/account_snapshot")
@login_required
def api_account_snapshot():
@@ -9615,7 +9630,7 @@ def manual_transfer():
amount = float(request.form.get("amount", "0"))
except Exception:
flash("划转金额格式错误")
return redirect("/")
return redirect("/settings")
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
@@ -9630,7 +9645,7 @@ def manual_transfer():
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
else:
flash(f"手动划转失败:{msg}")
return redirect("/")
return redirect("/settings")
def _journal_ai_chart_builder(row):
+17 -2
View File
@@ -7033,6 +7033,7 @@ def render_main_page(page="trade", embed_mode=None):
)
conn.close()
from lib.instance.instance_embed_lib import embed_context_extras
from lib.instance.instance_settings_lib import settings_page_context
template_ctx = dict(
page=page,
@@ -7118,6 +7119,14 @@ def render_main_page(page="trade", embed_mode=None):
has_active_positions=bool(order_list),
),
**embed_context_extras("gate"),
**settings_page_context(
page,
exchange_key="gate",
exchange_display=EXCHANGE_DISPLAY_NAME,
risk_status=risk_status,
trade_policy=TRADE_POLICY,
data_export_version=3,
),
)
if embed_mode == "fragment":
return render_template("embed_page_fragment.html", **template_ctx)
@@ -7173,6 +7182,12 @@ def stats_page():
return render_main_page("stats")
@app.route("/settings")
@login_required
def settings_page():
return render_main_page("settings")
@app.route("/api/account_snapshot")
@login_required
def api_account_snapshot():
@@ -9487,7 +9502,7 @@ def manual_transfer():
amount = float(request.form.get("amount", "0"))
except Exception:
flash("划转金额格式错误")
return redirect("/")
return redirect("/settings")
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
@@ -9502,7 +9517,7 @@ def manual_transfer():
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
else:
flash(f"手动划转失败:{msg}")
return redirect(request.referrer or "/trade")
return redirect("/settings")
def _journal_ai_chart_builder(row):
+17 -2
View File
@@ -6583,6 +6583,7 @@ def render_main_page(page="trade", embed_mode=None):
)
conn.close()
from lib.instance.instance_embed_lib import embed_context_extras
from lib.instance.instance_settings_lib import settings_page_context
template_ctx = dict(
page=page,
@@ -6661,6 +6662,14 @@ def render_main_page(page="trade", embed_mode=None):
exchange_pnl_sync=exchange_pnl_sync,
**strategy_extra,
**embed_context_extras("okx"),
**settings_page_context(
page,
exchange_key="okx",
exchange_display=EXCHANGE_DISPLAY_NAME,
risk_status=risk_status,
trade_policy=TRADE_POLICY,
data_export_version=3,
),
**force_close_template_context(
FORCE_CLOSE_ENABLED,
FORCE_CLOSE_BJ_HOUR,
@@ -6717,6 +6726,12 @@ def stats_page():
return render_main_page("stats")
@app.route("/settings")
@login_required
def settings_page():
return render_main_page("settings")
@app.route("/api/account_snapshot")
@login_required
def api_account_snapshot():
@@ -8999,7 +9014,7 @@ def manual_transfer():
amount = float(request.form.get("amount", "0"))
except Exception:
flash("划转金额格式错误")
return redirect("/")
return redirect("/settings")
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
@@ -9014,7 +9029,7 @@ def manual_transfer():
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
else:
flash(f"手动划转失败:{msg}")
return redirect("/")
return redirect("/settings")
def _journal_ai_chart_builder(row):
+1
View File
@@ -9,6 +9,7 @@
strategy_records: "/strategy/records",
records: "/records",
stats: "/stats",
settings: "/settings",
};
let navToken = 0;
+133
View File
@@ -1917,3 +1917,136 @@ html[data-theme="light"] .journal-detail-img-thumb {
background: #eef2f7;
}
/* ── 系统设置页 ── */
.settings-page {
margin-top: 12px;
}
.settings-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
align-items: start;
}
.settings-card h2 {
margin: 0 0 10px;
font-size: 1.05rem;
}
.settings-card-desc,
.settings-env-hint,
.settings-transfer-auto {
color: var(--muted, #8892b0);
font-size: 0.82rem;
line-height: 1.5;
margin: 0 0 12px;
}
.settings-live-status {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
font-size: 0.88rem;
margin: 0 0 10px;
}
.settings-status-reason,
.settings-policy-note {
color: var(--muted, #8892b0);
font-size: 0.8rem;
}
.settings-section {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid var(--border-soft, #2a3150);
}
.settings-section h3 {
margin: 0 0 8px;
font-size: 0.92rem;
color: #cfd3ef;
}
.settings-kv {
margin: 0;
}
.settings-kv-row {
display: grid;
grid-template-columns: 9.5em 1fr;
gap: 8px 12px;
padding: 6px 0;
font-size: 0.82rem;
border-bottom: 1px dashed rgba(136, 146, 176, 0.15);
}
.settings-kv-row:last-child {
border-bottom: none;
}
.settings-kv-row dt {
margin: 0;
color: #9aa3c7;
}
.settings-kv-row dd {
margin: 0;
}
.settings-kv-value {
color: #e8ecff;
font-weight: 600;
}
.settings-kv-note {
display: block;
margin-top: 2px;
color: #8892b0;
font-size: 0.75rem;
font-weight: 400;
}
.settings-link-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.settings-export-link {
display: block;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid var(--border-soft, #2a3150);
background: var(--inset-surface, #12151f);
color: #8fc8ff;
text-decoration: none;
font-size: 0.88rem;
}
.settings-export-link:hover {
border-color: #3d4f7a;
background: #1a2030;
}
.settings-transfer-form {
margin-top: 10px;
}
html[data-theme="light"] .settings-section {
border-top-color: #d0dae4;
}
html[data-theme="light"] .settings-section h3,
html[data-theme="light"] .settings-kv-value {
color: #142232;
}
html[data-theme="light"] .settings-export-link {
background: #fff;
border-color: #b8c8d8;
color: #1d4f8c;
}
+2 -1
View File
@@ -36,10 +36,11 @@ def embed_render_plan(page: str, embed_mode: str | None) -> EmbedRenderPlan:
)
is_shell = embed_mode == "shell"
is_strategy = page in EMBED_STRATEGY_PAGES
is_settings = page == "settings"
return EmbedRenderPlan(
exchange_capitals=is_shell,
records_rows=page == "records",
records_summary=is_shell and page != "records",
records_summary=is_shell and page != "records" and not is_settings,
key_history=page == "key_monitor",
key_list=page in ("key_monitor", "trade") or is_strategy,
orders=page == "trade" or is_strategy,
+2
View File
@@ -17,6 +17,7 @@ EMBED_TABS: tuple[str, ...] = (
"strategy_records",
"records",
"stats",
"settings",
)
PATH_TO_EMBED_TAB: dict[str, str] = {
@@ -29,6 +30,7 @@ PATH_TO_EMBED_TAB: dict[str, str] = {
"/strategy/records": "strategy_records",
"/records": "records",
"/stats": "stats",
"/settings": "settings",
}
ORDER_RULE_TIPS_BY_EXCHANGE: dict[str, str] = {
+162
View File
@@ -0,0 +1,162 @@
"""实例「系统设置」页:从 .env 汇总风控说明(三所共用)。"""
from __future__ import annotations
import os
from typing import Any, Optional
from lib.key_monitor.key_auto_order_lib import load_key_auto_order_enabled
from lib.trade.account_risk_lib import (
cooling_hours_manual,
cooling_hours_manual_journal,
manual_close_daily_limit,
max_active_positions_from_env,
mood_issues_daily_freeze_enabled,
risk_control_enabled,
)
from lib.trade.position_sizing_lib import is_full_margin_mode, load_position_sizing_mode, mode_label_zh
from lib.trade.trade_policy_lib import TradePolicy
def _env_bool(key: str, default: bool = False) -> bool:
raw = (os.getenv(key) or "").strip().lower()
if not raw:
return default
return raw in ("1", "true", "yes", "on")
def _env_float(key: str, default: float) -> float:
try:
return float(os.getenv(key, str(default)))
except (TypeError, ValueError):
return default
def _env_int(key: str, default: int) -> int:
try:
return int(os.getenv(key, str(default)))
except (TypeError, ValueError):
return default
def _row(label: str, value: str, note: str = "") -> dict[str, str]:
return {"label": label, "value": value, "note": note}
def _on_off(enabled: bool) -> str:
return "开启" if enabled else "关闭"
def build_instance_settings_view(
*,
exchange_key: str,
exchange_display: str,
risk_status: Optional[dict[str, Any]] = None,
trade_policy: Optional[TradePolicy] = None,
data_export_version: int = 3,
) -> dict[str, Any]:
rs = risk_status or {}
sizing_mode = load_position_sizing_mode()
key_auto = load_key_auto_order_enabled()
reset_hour = _env_int("TRADING_DAY_RESET_HOUR", 8)
hard_limit = _env_int("DAILY_OPEN_HARD_LIMIT", 0)
alert_threshold = _env_int("DAILY_OPEN_ALERT_THRESHOLD", 5)
force_close_on = _env_bool("FORCE_CLOSE_ENABLED", False)
force_close_hour = _env_int("FORCE_CLOSE_BJ_HOUR", 0)
auto_transfer_on = _env_bool("AUTO_TRANSFER_ENABLED", False)
sections: list[dict[str, Any]] = []
sections.append(
{
"title": "交易执行",
"rows": [
_row("最大同时持仓", str(max_active_positions_from_env())),
_row("计仓模式", mode_label_zh(sizing_mode)),
_row("以损定仓风险%", f"{_env_float('RISK_PERCENT', 2):g}%"),
_row("人工最低盈亏比", f">= {_env_float('MANUAL_MIN_PLANNED_RR', 1.4):g}:1"),
_row(
"交易日切点",
f"北京时间 {reset_hour}:00",
"新交易日统计与部分开仓限制以此为准",
),
_row(
"单日开仓提醒",
f"{alert_threshold}",
"达次数推送企业微信,不拦单",
),
_row(
"单日开仓硬上限",
str(hard_limit) if hard_limit > 0 else "未启用",
"达上限后禁止一切新开仓直至下一交易日" if hard_limit > 0 else "",
),
],
}
)
sections.append(
{
"title": "账户冷静期",
"rows": [
_row("风控总开关", _on_off(risk_control_enabled())),
_row("手动平仓冷静", f"{cooling_hours_manual():g} 小时"),
_row("复盘后冷静", f"{cooling_hours_manual_journal():g} 小时", "手动平仓且填写说明后可缩短"),
_row("日手动平仓上限", f"{manual_close_daily_limit()}", "超限当日冻结"),
_row(
"复盘情绪日冻结",
_on_off(mood_issues_daily_freeze_enabled()),
"复盘勾选心态标签可触发当日冻结",
),
],
}
)
key_rows = [
_row("关键位自动单", _on_off(key_auto)),
_row("关键位最低盈亏比", f"> {_env_float('KEY_AUTO_MIN_PLANNED_RR', 1.5):g}:1"),
]
if is_full_margin_mode(sizing_mode):
key_rows.append(
_row(
"全仓模式",
"仅触价类自动单",
"箱体/斐波等自动开仓在全仓下禁用",
)
)
sections.append({"title": "关键位与自动单", "rows": key_rows})
if force_close_on or (exchange_key or "").strip().lower() == "gate":
sections.append(
{
"title": "整点强制清仓",
"rows": [
_row("强制清仓", _on_off(force_close_on)),
_row("执行时刻", f"北京时间 {force_close_hour}:00 起 15 分钟内"),
],
}
)
policy_note = ""
if trade_policy and getattr(trade_policy, "badge_text", ""):
policy_note = str(trade_policy.badge_text)
return {
"exchange_display": exchange_display,
"risk_status_label": str(rs.get("status_label") or "正常"),
"risk_status_reason": str(rs.get("reason") or "").strip(),
"can_trade": bool(rs.get("can_trade", True)),
"trade_policy_note": policy_note,
"sections": sections,
"data_export_version": int(data_export_version),
"show_transfer": (exchange_key or "").strip().lower() in ("gate", "binance", "okx"),
"auto_transfer_enabled": auto_transfer_on,
"auto_transfer_bj_hour": _env_int("AUTO_TRANSFER_BJ_HOUR", 8),
"auto_transfer_amount": _env_float("AUTO_TRANSFER_AMOUNT", 30),
"auto_transfer_from": (os.getenv("AUTO_TRANSFER_FROM") or "funding").strip(),
"auto_transfer_to": (os.getenv("AUTO_TRANSFER_TO") or "swap").strip(),
}
def settings_page_context(page: str, **kwargs: Any) -> dict[str, Any]:
if (page or "").strip() != "settings":
return {}
return {"instance_settings": build_instance_settings_view(**kwargs)}
@@ -390,6 +390,9 @@
</div>
{% endif %}
</div>
{% 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">
+4 -10
View File
@@ -7,7 +7,7 @@
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
<link rel="stylesheet" href="/static/instance_page.css?v=3">
<link rel="stylesheet" href="/static/instance_theme.css?v=53">
<link rel="stylesheet" href="/static/instance_theme.css?v=54">
<script src="/static/account_risk_badge.js?v=4"></script>
<meta name="theme-color" content="#0b0d14">
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
@@ -56,6 +56,7 @@
{% endif %}
<a href="/records" data-embed-tab="records" class="{% if initial_tab == 'records' %}active{% endif %}">交易记录与复盘</a>
<a href="/stats" data-embed-tab="stats" class="{% if initial_tab == 'stats' %}active{% endif %}">统计分析</a>
<a href="/settings" data-embed-tab="settings" class="{% if initial_tab == 'settings' %}active{% endif %}">系统设置</a>
</nav>
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
@@ -76,13 +77,6 @@
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
</div>
<div class="export-bar instance-desktop-only">
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSVUTF-8;交易记录含开仓类型列,复盘单独导出):</span>
<a href="/export/trade_records">交易记录</a>
<a href="/export/journal_entries">复盘记录</a>
<a href="/export/key_monitors">关键位(当前)</a>
<a href="/export/key_monitor_history">关键位历史</a>
</div>
<div class="stat-box instance-desktop-only">
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
<div class="stat-item"><div class="label">总交易</div><div class="value" id="stat-total">{{ total }}</div></div>
@@ -91,7 +85,7 @@
<div class="stat-item"><div class="label">交易日</div><div class="value">{{ trading_day }}</div></div>
<div class="stat-item"><div class="label">当日资金(交易账户)</div><div class="value" id="current-capital">{{ funds_fmt(current_capital) }}U</div></div>
</div>
{% if include_transfer_block %}
{% if initial_tab != 'settings' and include_transfer_block %}
{% include 'instance_top_bar.html' %}
{% endif %}
@@ -135,6 +129,6 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
<script src="/static/strategy_roll.js?v=6"></script>
<script src="/static/key_monitor_form.js?v=2"></script>
{% include 'embed_boot_scripts.html' %}
<script src="/static/instance_embed.js?v=6"></script>
<script src="/static/instance_embed.js?v=7"></script>
</body>
</html>
+10 -8
View File
@@ -17,7 +17,7 @@
<link rel="manifest" href="/static/icons/manifest.webmanifest">
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
<link rel="stylesheet" href="/static/instance_page.css?v=1">
<link rel="stylesheet" href="/static/instance_theme.css?v=53">
<link rel="stylesheet" href="/static/instance_theme.css?v=54">
</head>
<body
@@ -81,9 +81,11 @@
{% endif %}
<a href="/records" class="{% if page == 'records' %}active{% endif %}">交易记录与复盘</a>
<a href="/stats" class="{% if page == 'stats' %}active{% endif %}">统计分析</a>
<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 %}
{% if page != 'settings' %}
<div class="list-window-bar">
<span style="color:#cfd3ef">列表筛选(<strong>UTC</strong>,默认当日):{{ list_window.label }}</span>
<label>预设
@@ -101,13 +103,6 @@
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
</div>
<div class="export-bar instance-desktop-only">
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSVUTF-8;交易记录含开仓类型列,复盘单独导出):</span>
<a href="/export/trade_records">交易记录</a>
<a href="/export/journal_entries">复盘记录</a>
<a href="/export/key_monitors">关键位(当前)</a>
<a href="/export/key_monitor_history">关键位历史</a>
</div>
<div class="stat-box instance-desktop-only">
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
<div class="stat-item"><div class="label">总交易</div><div class="value">{{ total }}</div></div>
@@ -116,7 +111,10 @@
<div class="stat-item"><div class="label">交易日</div><div class="value">{{ trading_day }}</div></div>
<div class="stat-item"><div class="label">当日资金(交易账户)</div><div class="value" id="current-capital">{{ funds_fmt(current_capital) }}U</div></div>
</div>
{% endif %}
{% if page != 'settings' %}
{% include 'instance_top_bar.html' %}
{% endif %}
<div class="grid">
{% if page == 'key_monitor' %}
@@ -506,6 +504,10 @@
{% endif %}
</div>
{% 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">
+4 -2
View File
@@ -1,5 +1,7 @@
{# 三所统一顶栏:划转 + 手动划转 + 可选整点前开仓开关 #}
{% include 'gate_transfer_block.html' %}
{# 三所统一顶栏:实时价 + 可选整点前开仓开关(划转已移至系统设置) #}
<div class="rule-tip instance-price-bar">
实时价格更新:<span id="price-last-updated">--</span>(北京时间 UTC+8
</div>
{% if ui_open_guard_enabled %}
<div class="rule-tip" id="open-guard-bar" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;color:#cfd3ef">
@@ -0,0 +1,25 @@
{# 系统设置 · 资金划转(三所共用) #}
<div class="settings-transfer-panel">
<p class="rule-tip settings-transfer-auto">
自动划转 <strong>{{ '开启' if auto_transfer_enabled else '关闭' }}</strong>
每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong> 起该整点小时内尝试;
账簿按 <strong>UTC 自然日</strong> 去重;
<code>{{ auto_transfer_to }}</code> 调整至 <strong>{{ transfer_amount_fmt|default(funds_fmt(auto_transfer_amount)) }}U</strong>
不足从 <code>{{ auto_transfer_from }}</code> 划入、超出划回 <code>{{ auto_transfer_from }}</code>
<strong>持仓中不划转</strong>并微信通知。
</p>
<form action="/manual_transfer" method="post" class="form-row gate-transfer-form settings-transfer-form">
<input name="amount" type="number" min="0.01" step="0.01" placeholder="手动划转金额 U" required>
<select name="from_account" aria-label="划出账户">
<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" aria-label="划入账户">
<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>
</div>
@@ -0,0 +1,61 @@
{# 系统设置:风控说明 · 数据导出 · 资金划转 #}
<div class="settings-page">
<div class="settings-cards-grid">
<div class="card settings-card">
<h2>风控说明</h2>
<p class="settings-live-status">
当前账户状态:
<span class="risk-status-badge risk-status-{{ risk_status.status|default('normal') }}">
{{ instance_settings.risk_status_label }}
</span>
{% if instance_settings.risk_status_reason %}
<span class="settings-status-reason">{{ instance_settings.risk_status_reason }}</span>
{% endif %}
</p>
{% if instance_settings.trade_policy_note %}
<p class="settings-policy-note">账户限制:{{ instance_settings.trade_policy_note }}</p>
{% endif %}
<p class="settings-env-hint">以下参数读取自本实例 <code>.env</code>,修改后需重启进程生效。</p>
{% for section in instance_settings.sections %}
<div class="settings-section">
<h3>{{ section.title }}</h3>
<dl class="settings-kv">
{% for row in section.rows %}
<div class="settings-kv-row">
<dt>{{ row.label }}</dt>
<dd>
<span class="settings-kv-value">{{ row.value }}</span>
{% if row.note %}
<span class="settings-kv-note">{{ row.note }}</span>
{% endif %}
</dd>
</div>
{% endfor %}
</dl>
</div>
{% endfor %}
</div>
<div class="card settings-card">
<h2>数据导出</h2>
<p class="settings-card-desc">
CSV · UTF-8 · v{{ instance_settings.data_export_version }}。
交易记录含开仓类型列;复盘单独导出。
</p>
<div class="settings-link-list">
<a class="settings-export-link" href="/export/trade_records">交易记录</a>
<a class="settings-export-link" href="/export/journal_entries">复盘记录</a>
<a class="settings-export-link" href="/export/key_monitors">关键位(当前)</a>
<a class="settings-export-link" href="/export/key_monitor_history">关键位历史</a>
</div>
</div>
{% if instance_settings.show_transfer %}
<div class="card settings-card">
<h2>资金划转</h2>
<p class="settings-card-desc">手动在资金账户与交易账户之间划转 USDT;自动划转规则见下方说明。</p>
{% include 'instance_transfer_panel.html' %}
</div>
{% endif %}
</div>
</div>
+2
View File
@@ -28,6 +28,8 @@ def test_embed_tabs_cover_main_nav():
assert "trade" in EMBED_TABS
assert "key_monitor" in EMBED_TABS
assert "records" in EMBED_TABS
assert "settings" in EMBED_TABS
assert path_to_embed_tab("/settings") == "settings"
def test_embed_context_extras_unified_ui_flags():
+43
View File
@@ -0,0 +1,43 @@
"""instance_settings_lib 单元测试。"""
from __future__ import annotations
import os
import unittest
from lib.instance.instance_settings_lib import build_instance_settings_view
class InstanceSettingsLibTest(unittest.TestCase):
def test_build_settings_view_sections(self):
view = build_instance_settings_view(
exchange_key="gate",
exchange_display="Gate.io",
risk_status={"status_label": "正常", "can_trade": True},
data_export_version=3,
)
titles = [s["title"] for s in view["sections"]]
self.assertIn("交易执行", titles)
self.assertIn("账户冷静期", titles)
self.assertEqual(view["data_export_version"], 3)
self.assertTrue(view["show_transfer"])
def test_force_close_section_when_enabled(self):
old = os.environ.get("FORCE_CLOSE_ENABLED")
try:
os.environ["FORCE_CLOSE_ENABLED"] = "true"
view = build_instance_settings_view(
exchange_key="gate",
exchange_display="Gate.io",
risk_status={},
)
titles = [s["title"] for s in view["sections"]]
self.assertIn("整点强制清仓", titles)
finally:
if old is None:
os.environ.pop("FORCE_CLOSE_ENABLED", None)
else:
os.environ["FORCE_CLOSE_ENABLED"] = old
if __name__ == "__main__":
unittest.main()