diff --git a/lib/hub/hub_help_lib.py b/lib/hub/hub_help_lib.py new file mode 100644 index 0000000..9456b61 --- /dev/null +++ b/lib/hub/hub_help_lib.py @@ -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), + } diff --git a/manual_trading_hub/docs/help/01-quickstart.md b/manual_trading_hub/docs/help/01-quickstart.md new file mode 100644 index 0000000..3ea2749 --- /dev/null +++ b/manual_trading_hub/docs/help/01-quickstart.md @@ -0,0 +1,34 @@ +# 快速开始 + +## 系统是什么 + +**中控**聚合三所(币安 / OKX / Gate)的持仓、委托、关键位与趋势计划,并提供资金曲线、行情、复盘与 AI 教练。**实际下单、关键位配置、策略执行、交易复盘**在各交易所**实例网页**完成。 + +``` +浏览器 → 中控(默认 :5100) + ├─ 监控区 / 资金 / 行情 / 内照明心 … + └─ 点击「下单 / 期权」→ 实例页(内嵌或新标签) +实例 Flask(币安 :5001 · Gate :5000 · OKX :5004) +``` + +## 日常推荐流程 + +1. 打开 **监控区**,查看三所持仓、浮盈亏、关键位与趋势计划。 +2. 需要操作某所时,点该卡片 **「下单」**(或 **「期权」**)进入实例页。 +3. 复盘与统计:中控 **内照明心**、**数据看板**,或实例 **交易记录与复盘**。 +4. 事前计划:中控 **开仓计划**;策略 playbook:中控 **策略说明**(与实例「策略交易」不同,见下文)。 + +## 三账户默认对应 + +| 账户 | 实例端口 | 常见能力 | +|------|----------|----------| +| 币安 | 5001 | 关键位 + 趋势 | +| OKX | 5004 | 关键位 + 趋势 + **期权**(需在设置勾选「监控期权」) | +| Gate | 5000 | 关键位 + 趋势 | + +## 两个容易混淆的名称 + +| 名称 | 在哪里 | 是什么 | +|------|--------|--------| +| **策略说明** | 中控顶栏 | 各所策略文档与开仓检查清单 | +| **策略交易** | 实例顶栏 | 趋势回调 / 顺势加仓等自动化功能 | diff --git a/manual_trading_hub/docs/help/02-hub-nav.md b/manual_trading_hub/docs/help/02-hub-nav.md new file mode 100644 index 0000000..6209493 --- /dev/null +++ b/manual_trading_hub/docs/help/02-hub-nav.md @@ -0,0 +1,22 @@ +# 中控导航说明 + +顶栏各页面用途如下(可在 **系统设置 → 显示与导航** 中隐藏不需要的 Tab)。 + +| 导航 | 用途 | +|------|------| +| **资金概况** | 总资金曲线、分户权益、回撤与 24h 变化 | +| **开仓计划** | 事前写下计划、跟踪进行中、统计历史胜率 | +| **监控区** | **核心操作台**:三所持仓卡片、全平/撤单、关键位与趋势计划摘要 | +| **策略说明** | 三所策略 playbook + 开仓检查清单(非系统操作手册) | +| **使用说明** | 本页:中控与实例怎么用 | +| **行情区** | K 线、指标、画线;可从持仓跳转带币种 | +| **计算器** | 趋势回调 / 滚仓张数与盈亏测算(手动填价) | +| **内照明心** | 复盘语录、归档交易、永久 5m K 线 | +| **数据看板** | 当日 KPI、分户卡片、SSE 刷新 | +| **AI 教练** | 交易教练对话、监管推送(需配置密钥) | +| **系统日志** | 中控与三实例 PM2 日志(排错用,非部署说明) | +| **系统设置** | 中控密码、导航显示、交易所地址、监控能力勾选 | + +## 默认首页 + +登录后默认进入 **监控区**(`/monitor`)。 diff --git a/manual_trading_hub/docs/help/03-monitor.md b/manual_trading_hub/docs/help/03-monitor.md new file mode 100644 index 0000000..d986534 --- /dev/null +++ b/manual_trading_hub/docs/help/03-monitor.md @@ -0,0 +1,43 @@ +# 监控区与实例入口 + +## 监控卡片里有什么 + +每张交易所卡片通常包含: + +- **资金行**:资金账户 / 交易账户 / 浮动盈亏(可在设置关闭) +- **永续持仓**:表格摘要;点击标题栏 **进入全屏** 可看完整持仓卡片 +- **OKX 期权**(勾选「监控期权」后):永续与期权分块;全屏时期权也以卡片展示(与实例期权页字段一致,只读) +- **关键位 / 下单监控 / 趋势回调 / 顺势加仓**:只读摘要(数据来自实例) + +## 全屏模式 + +点击卡片标题栏(或移动端卡片主体)进入 **全屏**: + +- 永续:每币种一张持仓卡,可 **委托 / 平仓**(非日内纪律模式) +- 期权(OKX):只读卡片,含权利金、标记价、买盘深度等 +- 下方:关键位、下单监控、趋势与滚仓区块 + +按 `Esc` 或 **返回监控** 退出全屏。 + +## 打开实例(SSO) + +监控卡片或全屏顶栏按钮: + +| 按钮 | 进入实例页 | 说明 | +|------|------------|------| +| **打开实例** | 实盘下单 | 新浏览器标签 | +| **下单** | 实盘下单 | 中控内 iframe | +| **监控位** | 关键位监控 | | +| **复盘** | 交易记录与复盘 | | +| **期权** | 期权页 | 仅 OKX 且勾选监控期权 | + +实例地址在 **系统设置 → 交易所** 配置 `flask_url`;未配置时不会出现上述按钮。 + +## 常见操作 + +| 操作 | 位置 | +|------|------| +| 紧急全平 | 卡片 **全平**(日内纪律账户可能禁用) | +| 改止盈止损 | 持仓行 **委托** 或全屏卡片 | +| 撤条件单 | 监控区条件单列表 | +| 停止趋势计划 | 趋势回调区块 **停止 / 保本** 等 | diff --git a/manual_trading_hub/docs/help/04-instance.md b/manual_trading_hub/docs/help/04-instance.md new file mode 100644 index 0000000..de2b29e --- /dev/null +++ b/manual_trading_hub/docs/help/04-instance.md @@ -0,0 +1,30 @@ +# 实例页导航说明 + +从监控区 **下单 / 打开实例** 进入后,实例顶栏常见 Tab 如下(部分可在实例 **系统设置 → 导航显示** 中隐藏)。 + +| Tab | 用途 | +|-----|------| +| **关键位监控** | 配置 5m 门禁关键位,可选自动下单 | +| **实盘下单** | 人工下单、下单监控、预估盈亏比 | +| **策略交易** | 趋势回调、顺势加仓计划(自动化) | +| **策略交易记录** | 上述策略的执行历史 | +| **交易记录与复盘** | 平仓记录、日记、AI 复盘 | +| **统计分析** | 按周期汇总盈亏 | +| **期权** | OKX 期权链、持仓、多档平仓(OKX 且已启用) | +| **风控说明** | 只读展示当前风控相关 env | +| **env配置** | 修改运行参数(中文标签) | +| **系统设置** | 实例登录密码、导航 Tab 开关等 | + +## 与中控的分工 + +| 在中控做 | 在实例做 | +|----------|----------| +| 看三所持仓汇总、全平 | 下单、改单、平仓 | +| 看关键位 / 趋势摘要 | 新建 / 修改关键位与策略 | +| 内照明心、数据看板 | 交易记录详情、日记 | +| 开仓计划 | 策略交易执行 | +| OKX 期权只读监控 | 期权开仓、多档平仓 | + +## iframe 内操作 + +在中控 iframe 打开实例时,顶栏有 **返回监控 / 刷新 / 新标签打开**,无需重复登录。 diff --git a/manual_trading_hub/docs/help/05-settings.md b/manual_trading_hub/docs/help/05-settings.md new file mode 100644 index 0000000..5892461 --- /dev/null +++ b/manual_trading_hub/docs/help/05-settings.md @@ -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 策略交易** +→ 前者在中控,是文档;后者在实例,是自动化功能。 diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index fd8068c..aa7526a 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -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() diff --git a/manual_trading_hub/settings_store.py b/manual_trading_hub/settings_store.py index 65d1e73..270878f 100644 --- a/manual_trading_hub/settings_store.py +++ b/manual_trading_hub/settings_store.py @@ -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, } diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 6bf2244..31d2dc4 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -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; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index ed6232f..03542d7 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -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: { diff --git a/manual_trading_hub/static/help.js b/manual_trading_hub/static/help.js new file mode 100644 index 0000000..05a1d2f --- /dev/null +++ b/manual_trading_hub/static/help.js @@ -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, "&") + .replace(//g, ">") + .replace(/"/g, """); + } + + 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) => + `` + ) + .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 = `
${esc(err.message || "加载失败")}
`; + } + } + + 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 = `${esc(err.message || "加载失败")}
`; + }); + }, + destroy() {}, + }; +})(); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index baa4228..27a2e24 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -52,6 +52,7 @@ 开仓计划 监控区 策略说明 + 使用说明 行情区 计算器 内照明心 @@ -890,6 +891,28 @@ + +