feat: instance system settings page with risk docs, export and transfer
顶栏新增系统设置;风控说明读取 .env;数据导出与资金划转迁入三张卡片;顶栏移除导出条与手动划转。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
strategy_records: "/strategy/records",
|
||||
records: "/records",
|
||||
stats: "/stats",
|
||||
settings: "/settings",
|
||||
};
|
||||
|
||||
let navToken = 0;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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] = {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 }} CSV,UTF-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>
|
||||
|
||||
@@ -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 }} CSV,UTF-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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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():
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user