Add hub usage help page with settings nav toggle.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-11 10:02:07 +08:00
parent 843d87422d
commit 9f0a30a040
12 changed files with 484 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
"""中控「使用说明」:读取 manual_trading_hub/docs/help 下的 MD."""
from __future__ import annotations
from pathlib import Path
from typing import Any
from lib.hub.hub_strategy_lib import render_markdown_html
from lib.paths import REPO_ROOT
HELP_SECTIONS: tuple[dict[str, str], ...] = (
{"key": "quickstart", "label": "快速开始", "title": "快速开始", "md_file": "01-quickstart.md"},
{"key": "hub-nav", "label": "中控导航", "title": "中控导航说明", "md_file": "02-hub-nav.md"},
{"key": "monitor", "label": "监控区", "title": "监控区与实例入口", "md_file": "03-monitor.md"},
{"key": "instance", "label": "实例页面", "title": "实例页导航说明", "md_file": "04-instance.md"},
{"key": "settings", "label": "设置说明", "title": "设置与配置说明", "md_file": "05-settings.md"},
)
def _help_dir() -> Path:
return REPO_ROOT / "manual_trading_hub" / "docs" / "help"
def _section_meta(key: str) -> dict[str, str]:
k = (key or "").strip().lower()
for item in HELP_SECTIONS:
if item["key"] == k:
return item
raise KeyError(key)
def _md_path(section_key: str) -> Path:
return _help_dir() / _section_meta(section_key)["md_file"]
def help_meta_payload() -> dict[str, Any]:
sections = [{"key": s["key"], "label": s["label"], "title": s["title"]} for s in HELP_SECTIONS]
return {"ok": True, "sections": sections}
def load_help_payload(section_key: str) -> dict[str, Any]:
key = (section_key or "").strip().lower()
meta = _section_meta(key)
md_path = _md_path(key)
md_text = md_path.read_text(encoding="utf-8") if md_path.is_file() else ""
return {
"ok": True,
"section_key": key,
"label": meta["label"],
"title": meta["title"],
"md_source": str(md_path.relative_to(REPO_ROOT)).replace("\\", "/"),
"content_html": render_markdown_html(md_text),
}
@@ -0,0 +1,34 @@
# 快速开始
## 系统是什么
**中控**聚合三所(币安 / OKX / Gate)的持仓、委托、关键位与趋势计划,并提供资金曲线、行情、复盘与 AI 教练。**实际下单、关键位配置、策略执行、交易复盘**在各交易所**实例网页**完成。
```
浏览器 → 中控(默认 :5100
├─ 监控区 / 资金 / 行情 / 内照明心 …
└─ 点击「下单 / 期权」→ 实例页(内嵌或新标签)
实例 Flask(币安 :5001 · Gate :5000 · OKX :5004
```
## 日常推荐流程
1. 打开 **监控区**,查看三所持仓、浮盈亏、关键位与趋势计划。
2. 需要操作某所时,点该卡片 **「下单」**(或 **「期权」**)进入实例页。
3. 复盘与统计:中控 **内照明心**、**数据看板**,或实例 **交易记录与复盘**
4. 事前计划:中控 **开仓计划**;策略 playbook:中控 **策略说明**(与实例「策略交易」不同,见下文)。
## 三账户默认对应
| 账户 | 实例端口 | 常见能力 |
|------|----------|----------|
| 币安 | 5001 | 关键位 + 趋势 |
| OKX | 5004 | 关键位 + 趋势 + **期权**(需在设置勾选「监控期权」) |
| Gate | 5000 | 关键位 + 趋势 |
## 两个容易混淆的名称
| 名称 | 在哪里 | 是什么 |
|------|--------|--------|
| **策略说明** | 中控顶栏 | 各所策略文档与开仓检查清单 |
| **策略交易** | 实例顶栏 | 趋势回调 / 顺势加仓等自动化功能 |
@@ -0,0 +1,22 @@
# 中控导航说明
顶栏各页面用途如下(可在 **系统设置 → 显示与导航** 中隐藏不需要的 Tab)。
| 导航 | 用途 |
|------|------|
| **资金概况** | 总资金曲线、分户权益、回撤与 24h 变化 |
| **开仓计划** | 事前写下计划、跟踪进行中、统计历史胜率 |
| **监控区** | **核心操作台**:三所持仓卡片、全平/撤单、关键位与趋势计划摘要 |
| **策略说明** | 三所策略 playbook + 开仓检查清单(非系统操作手册) |
| **使用说明** | 本页:中控与实例怎么用 |
| **行情区** | K 线、指标、画线;可从持仓跳转带币种 |
| **计算器** | 趋势回调 / 滚仓张数与盈亏测算(手动填价) |
| **内照明心** | 复盘语录、归档交易、永久 5m K 线 |
| **数据看板** | 当日 KPI、分户卡片、SSE 刷新 |
| **AI 教练** | 交易教练对话、监管推送(需配置密钥) |
| **系统日志** | 中控与三实例 PM2 日志(排错用,非部署说明) |
| **系统设置** | 中控密码、导航显示、交易所地址、监控能力勾选 |
## 默认首页
登录后默认进入 **监控区**`/monitor`)。
@@ -0,0 +1,43 @@
# 监控区与实例入口
## 监控卡片里有什么
每张交易所卡片通常包含:
- **资金行**:资金账户 / 交易账户 / 浮动盈亏(可在设置关闭)
- **永续持仓**:表格摘要;点击标题栏 **进入全屏** 可看完整持仓卡片
- **OKX 期权**(勾选「监控期权」后):永续与期权分块;全屏时期权也以卡片展示(与实例期权页字段一致,只读)
- **关键位 / 下单监控 / 趋势回调 / 顺势加仓**:只读摘要(数据来自实例)
## 全屏模式
点击卡片标题栏(或移动端卡片主体)进入 **全屏**
- 永续:每币种一张持仓卡,可 **委托 / 平仓**(非日内纪律模式)
- 期权(OKX):只读卡片,含权利金、标记价、买盘深度等
- 下方:关键位、下单监控、趋势与滚仓区块
`Esc`**返回监控** 退出全屏。
## 打开实例(SSO
监控卡片或全屏顶栏按钮:
| 按钮 | 进入实例页 | 说明 |
|------|------------|------|
| **打开实例** | 实盘下单 | 新浏览器标签 |
| **下单** | 实盘下单 | 中控内 iframe |
| **监控位** | 关键位监控 | |
| **复盘** | 交易记录与复盘 | |
| **期权** | 期权页 | 仅 OKX 且勾选监控期权 |
实例地址在 **系统设置 → 交易所** 配置 `flask_url`;未配置时不会出现上述按钮。
## 常见操作
| 操作 | 位置 |
|------|------|
| 紧急全平 | 卡片 **全平**(日内纪律账户可能禁用) |
| 改止盈止损 | 持仓行 **委托** 或全屏卡片 |
| 撤条件单 | 监控区条件单列表 |
| 停止趋势计划 | 趋势回调区块 **停止 / 保本** 等 |
@@ -0,0 +1,30 @@
# 实例页导航说明
从监控区 **下单 / 打开实例** 进入后,实例顶栏常见 Tab 如下(部分可在实例 **系统设置 → 导航显示** 中隐藏)。
| Tab | 用途 |
|-----|------|
| **关键位监控** | 配置 5m 门禁关键位,可选自动下单 |
| **实盘下单** | 人工下单、下单监控、预估盈亏比 |
| **策略交易** | 趋势回调、顺势加仓计划(自动化) |
| **策略交易记录** | 上述策略的执行历史 |
| **交易记录与复盘** | 平仓记录、日记、AI 复盘 |
| **统计分析** | 按周期汇总盈亏 |
| **期权** | OKX 期权链、持仓、多档平仓(OKX 且已启用) |
| **风控说明** | 只读展示当前风控相关 env |
| **env配置** | 修改运行参数(中文标签) |
| **系统设置** | 实例登录密码、导航 Tab 开关等 |
## 与中控的分工
| 在中控做 | 在实例做 |
|----------|----------|
| 看三所持仓汇总、全平 | 下单、改单、平仓 |
| 看关键位 / 趋势摘要 | 新建 / 修改关键位与策略 |
| 内照明心、数据看板 | 交易记录详情、日记 |
| 开仓计划 | 策略交易执行 |
| OKX 期权只读监控 | 期权开仓、多档平仓 |
## iframe 内操作
在中控 iframe 打开实例时,顶栏有 **返回监控 / 刷新 / 新标签打开**,无需重复登录。
@@ -0,0 +1,38 @@
# 设置与配置说明
## 三层配置,不要混用
| 层级 | 入口 | 管什么 |
|------|------|--------|
| **中控系统设置** | 中控 `/settings` | 中控密码、顶栏显示、交易所 URL、监控能力(关键位/趋势/期权)、宏观日历、备份 |
| **实例系统设置** | 实例 `/settings` | 实例登录密码、实例顶栏 Tab 显示 |
| **实例 env配置** | 实例 `/env_config` | 交易参数(止损比例、风控开关等) |
改 env 后通常需 **重启对应实例 PM2** 才完全生效;具体字段含义见实例 env 页说明或仓库 `docs/env配置说明.md`(无需在中控内阅读)。
## 中控 · 显示与导航
可隐藏不常用的顶栏 Tab(**监控区**、**系统设置** 无法隐藏)。
## 中控 · 交易所
每项需配置:
- **flask_url**:实例 HTTP 地址(中控聚合与打开实例用)
- **agent_url**:子代理地址(持仓与全平)
- **capabilities**:勾选 **关键位 / 趋势 / 期权** 决定监控区展示哪些块
## 实例 · 导航显示
固定保留:**关键位监控、实盘下单、系统设置**。其余 Tab 可按需开关。
## 使用向常见问题
**监控卡片没有「下单」按钮**
→ 检查该所 `flask_url` 是否填写且实例可访问。
**OKX 看不到期权**
→ 中控设置勾选「监控期权」,且实例已启用期权模块。
**策略说明 vs 策略交易**
→ 前者在中控,是文档;后者在实例,是自动化功能。
+16
View File
@@ -89,6 +89,7 @@ from lib.hub.hub_entry_plan_lib import (
meta_payload as entry_plan_meta_payload,
update_entry_plan,
)
from lib.hub.hub_help_lib import help_meta_payload, load_help_payload
from lib.hub.hub_strategy_lib import (
build_export_html,
build_print_html,
@@ -983,6 +984,7 @@ def root_redirect():
@app.get("/funds")
@app.get("/ai")
@app.get("/strategy")
@app.get("/help")
@app.get("/logs")
@app.get("/settings")
def shell_pages():
@@ -1094,6 +1096,7 @@ class SettingsDisplayBody(BaseModel):
show_nav_ai: bool = True
show_nav_calculator: bool = True
show_nav_strategy: bool = True
show_nav_help: bool = True
show_nav_logs: bool = True
@@ -3275,6 +3278,19 @@ def api_strategy_meta():
return strategy_meta_payload()
@app.get("/api/help/meta")
def api_help_meta():
return help_meta_payload()
@app.get("/api/help/{section_key}")
def api_help_section(section_key: str):
try:
return load_help_payload(section_key.strip().lower())
except KeyError:
return JSONResponse({"ok": False, "msg": "unknown section"}, status_code=404)
@app.get("/api/system-logs/meta")
def api_system_logs_meta():
return system_logs_meta()
+1
View File
@@ -29,6 +29,7 @@ DEFAULT_DISPLAY = {
"show_nav_ai": True,
"show_nav_calculator": True,
"show_nav_strategy": True,
"show_nav_help": True,
"show_nav_logs": True,
}
+76
View File
@@ -8652,6 +8652,82 @@ body.funds-fullscreen-open {
}
}
/* ── 使用说明 ── */
.help-toolbar {
margin-bottom: 12px;
min-height: 1.2em;
}
.help-layout {
display: grid;
grid-template-columns: minmax(180px, 220px) minmax(0, 1fr);
gap: 16px;
align-items: start;
}
.help-toc-card {
padding: 16px 14px 18px;
position: sticky;
top: 72px;
}
.help-toc-title {
margin: 0 0 12px;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--text-soft);
text-transform: uppercase;
}
.help-toc-nav {
display: flex;
flex-direction: column;
gap: 4px;
}
.help-toc-item {
display: block;
width: 100%;
text-align: left;
padding: 8px 10px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--text-soft);
font-size: 0.88rem;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.help-toc-item:hover {
background: var(--surface-2, rgba(255, 255, 255, 0.04));
color: var(--text);
}
.help-toc-item.is-active {
background: var(--accent-soft, rgba(59, 130, 246, 0.12));
color: var(--accent, #3b82f6);
font-weight: 600;
}
.help-doc-card {
padding: 18px 20px 20px;
min-width: 0;
}
@media (max-width: 960px) {
.help-layout {
grid-template-columns: 1fr;
}
.help-toc-card {
position: static;
}
.help-toc-nav {
flex-direction: row;
flex-wrap: wrap;
}
.help-toc-item {
width: auto;
flex: 1 1 auto;
}
.help-doc-card .strategy-doc-body {
max-height: none;
}
}
/* ── 监控页:永续 / 期权分块 ── */
.hub-monitor-block {
margin-bottom: 4px;
+18
View File
@@ -41,6 +41,10 @@
return displayPref("show_nav_strategy", true);
}
function showNavHelpPref() {
return displayPref("show_nav_help", true);
}
function showNavLogsPref() {
return displayPref("show_nav_logs", true);
}
@@ -54,6 +58,7 @@
const navAi = document.getElementById("nav-ai");
const navCalc = document.getElementById("nav-calculator");
const navStrategy = document.getElementById("nav-strategy");
const navHelp = document.getElementById("nav-help");
const navLogs = document.getElementById("nav-logs");
if (navFunds) navFunds.classList.toggle("nav-hidden", d.show_nav_funds === false);
if (navDash) navDash.classList.toggle("nav-hidden", d.show_nav_dashboard === false);
@@ -62,6 +67,7 @@
if (navAi) navAi.classList.toggle("nav-hidden", d.show_nav_ai === false);
if (navCalc) navCalc.classList.toggle("nav-hidden", d.show_nav_calculator === false);
if (navStrategy) navStrategy.classList.toggle("nav-hidden", d.show_nav_strategy === false);
if (navHelp) navHelp.classList.toggle("nav-hidden", d.show_nav_help === false);
if (navLogs) navLogs.classList.toggle("nav-hidden", d.show_nav_logs === false);
}
@@ -73,6 +79,7 @@
if (page === "ai") return showNavAiPref();
if (page === "calculator") return showNavCalculatorPref();
if (page === "strategy") return showNavStrategyPref();
if (page === "help") return showNavHelpPref();
if (page === "logs") return showNavLogsPref();
return true;
}
@@ -87,6 +94,7 @@
const aiCb = document.getElementById("pref-show-nav-ai");
const calcCb = document.getElementById("pref-show-nav-calculator");
const strategyCb = document.getElementById("pref-show-nav-strategy");
const helpCb = document.getElementById("pref-show-nav-help");
const logsCb = document.getElementById("pref-show-nav-logs");
if (pnlCb) pnlCb.checked = d.show_account_pnl !== false;
if (fundsCb) fundsCb.checked = d.show_nav_funds !== false;
@@ -96,6 +104,7 @@
if (aiCb) aiCb.checked = d.show_nav_ai !== false;
if (calcCb) calcCb.checked = d.show_nav_calculator !== false;
if (strategyCb) strategyCb.checked = d.show_nav_strategy !== false;
if (helpCb) helpCb.checked = d.show_nav_help !== false;
if (logsCb) logsCb.checked = d.show_nav_logs !== false;
syncNavVisibility(data);
}
@@ -1208,6 +1217,7 @@
if (p.includes("funds")) return "funds";
if (p.includes("plan")) return "plan";
if (p.includes("calculator")) return "calculator";
if (p.includes("help")) return "help";
if (p.includes("strategy")) return "strategy";
if (p.includes("logs")) return "logs";
if (p.includes("market")) return "market";
@@ -1222,6 +1232,7 @@
if (page === "funds") return "page-funds";
if (page === "plan") return "page-plan";
if (page === "calculator") return "page-calculator";
if (page === "help") return "page-help";
if (page === "strategy") return "page-strategy";
if (page === "logs") return "page-logs";
if (page === "market") return "page-market";
@@ -1283,6 +1294,11 @@
} else if (window.hubStrategyPage && window.hubStrategyPage.destroy) {
window.hubStrategyPage.destroy();
}
if (page === "help" && window.hubHelpPage) {
window.hubHelpPage.init();
} else if (window.hubHelpPage && window.hubHelpPage.destroy) {
window.hubHelpPage.destroy();
}
if (page === "logs" && window.hubLogsPage) {
window.hubLogsPage.init();
} else if (window.hubLogsPage && window.hubLogsPage.destroy) {
@@ -4706,6 +4722,7 @@
const aiCb = document.getElementById("pref-show-nav-ai");
const calcCb = document.getElementById("pref-show-nav-calculator");
const strategyCb = document.getElementById("pref-show-nav-strategy");
const helpCb = document.getElementById("pref-show-nav-help");
const logsCb = document.getElementById("pref-show-nav-logs");
const supEnabled = document.getElementById("supervisor-enabled");
const supProg = document.getElementById("supervisor-wechat-program");
@@ -4727,6 +4744,7 @@
show_nav_ai: aiCb ? !!aiCb.checked : true,
show_nav_calculator: calcCb ? !!calcCb.checked : true,
show_nav_strategy: strategyCb ? !!strategyCb.checked : true,
show_nav_help: helpCb ? !!helpCb.checked : true,
show_nav_logs: logsCb ? !!logsCb.checked : true,
},
supervisor: {
+125
View File
@@ -0,0 +1,125 @@
/**
* 使用说明:中控 docs/help MD 章节.
*/
(function () {
const page = document.getElementById("page-help");
if (!page) return;
const tocEl = document.getElementById("help-toc-nav");
const statusEl = document.getElementById("help-load-status");
const docBody = document.getElementById("help-doc-body");
const docSource = document.getElementById("help-doc-source");
let sectionsMeta = [];
let activeKey = "quickstart";
let cache = {};
let bound = false;
async function apiFetch(url) {
const r = await fetch(url, { credentials: "same-origin" });
const data = await r.json();
if (!r.ok || !data.ok) throw new Error((data && data.msg) || r.statusText || "请求失败");
return data;
}
function esc(s) {
return String(s ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function sectionFromHash() {
const h = (window.location.hash || "").replace(/^#/, "").trim().toLowerCase();
if (!h) return null;
return sectionsMeta.some((s) => s.key === h) ? h : null;
}
function setHash(key) {
const next = `#${key}`;
if (window.location.hash !== next) {
history.replaceState(null, "", `/help${next}`);
}
}
function renderToc() {
if (!tocEl) return;
tocEl.innerHTML = sectionsMeta
.map(
(s) =>
`<button type="button" class="help-toc-item${s.key === activeKey ? " is-active" : ""}" data-key="${esc(s.key)}">${esc(s.label)}</button>`
)
.join("");
tocEl.querySelectorAll(".help-toc-item").forEach((btn) => {
btn.addEventListener("click", () => {
const key = btn.getAttribute("data-key");
if (!key || key === activeKey) return;
activeKey = key;
setHash(key);
renderToc();
void loadSection(key);
});
});
}
function renderSection(data) {
if (docBody) docBody.innerHTML = data.content_html || "";
if (docSource) {
docSource.textContent = data.md_source ? `来源: ${data.md_source}` : "";
}
if (statusEl) statusEl.textContent = "";
}
async function loadSection(key) {
if (cache[key]) {
renderSection(cache[key]);
return;
}
if (statusEl) statusEl.textContent = "加载中…";
try {
const data = await apiFetch(`/api/help/${encodeURIComponent(key)}`);
cache[key] = data;
renderSection(data);
} catch (err) {
if (statusEl) statusEl.textContent = "";
if (docBody) docBody.innerHTML = `<p class="muted">${esc(err.message || "加载失败")}</p>`;
}
}
async function loadMeta() {
const data = await apiFetch("/api/help/meta");
sectionsMeta = data.sections || [];
const fromHash = sectionFromHash();
if (fromHash) activeKey = fromHash;
else if (sectionsMeta.length && !sectionsMeta.some((s) => s.key === activeKey)) {
activeKey = sectionsMeta[0].key;
}
renderToc();
await loadSection(activeKey);
if (!sectionFromHash() && activeKey) setHash(activeKey);
}
function bindOnce() {
if (bound) return;
bound = true;
window.addEventListener("hashchange", () => {
const key = sectionFromHash();
if (!key || key === activeKey) return;
activeKey = key;
renderToc();
void loadSection(key);
});
}
window.hubHelpPage = {
init() {
bindOnce();
void loadMeta().catch((err) => {
if (statusEl) statusEl.textContent = "";
if (docBody) docBody.innerHTML = `<p class="muted">${esc(err.message || "加载失败")}</p>`;
});
},
destroy() {},
};
})();
+28
View File
@@ -52,6 +52,7 @@
<a href="/plan" id="nav-plan">开仓计划</a>
<a href="/monitor" id="nav-monitor">监控区</a>
<a href="/strategy" id="nav-strategy">策略说明</a>
<a href="/help" id="nav-help">使用说明</a>
<a href="/market" id="nav-market">行情区</a>
<a href="/calculator" id="nav-calculator">计算器</a>
<a href="/archive" id="nav-archive">内照明心</a>
@@ -890,6 +891,28 @@
</div>
</div>
<div id="page-help" class="page hidden">
<div class="page-head">
<div>
<h1><span class="head-tag">HLP</span> 使用说明</h1>
<p class="page-desc">中控与实例 · 日常操作指南(不含部署说明)</p>
</div>
</div>
<div class="help-toolbar">
<span id="help-load-status" class="toolbar-meta"></span>
</div>
<div class="help-layout">
<aside class="card help-toc-card">
<h3 class="help-toc-title">目录</h3>
<nav id="help-toc-nav" class="help-toc-nav" aria-label="使用说明目录"></nav>
</aside>
<section class="card help-doc-card">
<div id="help-doc-body" class="strategy-doc-body prose"></div>
<p id="help-doc-source" class="strategy-doc-source"></p>
</section>
</div>
</div>
<div id="page-logs" class="page hidden">
<div class="page-head hub-logs-page-head">
<div>
@@ -1010,6 +1033,10 @@
<input type="checkbox" id="pref-show-nav-strategy" checked />
顶栏显示「策略说明」
</label>
<label class="chk-label settings-display-chk">
<input type="checkbox" id="pref-show-nav-help" checked />
顶栏显示「使用说明」
</label>
<label class="chk-label settings-display-chk">
<input type="checkbox" id="pref-show-nav-logs" checked />
顶栏显示「系统日志」
@@ -1233,6 +1260,7 @@
<script src="/assets/funds.js?v=20260707-hub-options-funds"></script>
<script src="/assets/dashboard.js?v=20260708-options-expiry-cd"></script>
<script src="/assets/strategy.js?v=3"></script>
<script src="/assets/help.js?v=1"></script>
<script src="/assets/logs.js?v=1"></script>
<script src="/assets/ai_review_render.js?v=3"></script>
<script src="/assets/time_close_ui.js?v=3"></script>