From 7223b54ec40c53f62f0042769c76de0d8c060a85 Mon Sep 17 00:00:00 2001 From: dekun Date: Mon, 6 Jul 2026 03:12:09 +0800 Subject: [PATCH] feat: add hub strategy docs page with MD and checklists Central /strategy page shows per-exchange strategy markdown and read-only entry checklists; nav toggle in settings, print and HTML export per tab. Co-authored-by: Cursor --- docs/strategy/checklists/binance.json | 33 +++ docs/strategy/checklists/gate.json | 38 ++++ docs/strategy/checklists/okx.json | 32 +++ lib/hub/hub_strategy_lib.py | 279 ++++++++++++++++++++++++++ manual_trading_hub/hub.py | 36 +++- manual_trading_hub/requirements.txt | 1 + manual_trading_hub/settings_store.py | 1 + manual_trading_hub/static/app.css | 180 +++++++++++++++++ manual_trading_hub/static/app.js | 18 ++ manual_trading_hub/static/index.html | 41 ++++ manual_trading_hub/static/strategy.js | 168 ++++++++++++++++ tests/test_hub_strategy_lib.py | 39 ++++ 12 files changed, 865 insertions(+), 1 deletion(-) create mode 100644 docs/strategy/checklists/binance.json create mode 100644 docs/strategy/checklists/gate.json create mode 100644 docs/strategy/checklists/okx.json create mode 100644 lib/hub/hub_strategy_lib.py create mode 100644 manual_trading_hub/static/strategy.js create mode 100644 tests/test_hub_strategy_lib.py diff --git a/docs/strategy/checklists/binance.json b/docs/strategy/checklists/binance.json new file mode 100644 index 0000000..6486360 --- /dev/null +++ b/docs/strategy/checklists/binance.json @@ -0,0 +1,33 @@ +{ + "exchange": "binance", + "title": "币安 · 开仓检查清单", + "version": "v0.2", + "groups": [ + { + "title": "账户与方向", + "items": [ + "本账户仅做多,不做空", + "所选币种为大级别多头结构、上方仍有空间", + "计仓模式为以损定仓(risk),关键位自动单已关闭" + ] + }, + { + "title": "开仓类型选型", + "items": [ + "已明确:大分歧A / 大分歧B / 小分歧 三选一", + "大分歧 A/B → 趋势单;小分歧 → 波段单(系统自动)", + "第三次小分歧 → 不做新单", + "小分歧不追突破,仅低吸 / N 字 / 5m 三均线重新多头" + ] + }, + { + "title": "结构与杠杆", + "items": [ + "大分歧A:5m/15m 收敛且不创新低企稳", + "大分歧B:突破已确认,非仅刺破", + "杠杆:BTC/ETH 10x,其它山寨 5x(可选手改但须有理由)", + "止盈止损随行情人工设定,不在此清单量化 RR" + ] + } + ] +} diff --git a/docs/strategy/checklists/gate.json b/docs/strategy/checklists/gate.json new file mode 100644 index 0000000..1d71232 --- /dev/null +++ b/docs/strategy/checklists/gate.json @@ -0,0 +1,38 @@ +{ + "exchange": "gate", + "title": "Gate · BTC 日内 · 开仓检查清单", + "version": "v0.2", + "groups": [ + { + "title": "方向与均线过滤", + "items": [ + "仅交易 BTC,同时仅 1 仓", + "15m 21/55/144 排列清晰(多或空),纠缠则不做", + "1H 方向与 15m 不冲突", + "21 均线关系满足:回踩支撑 / 站稳上方(多)或反弹承压 / 压在下方(空)" + ] + }, + { + "title": "开仓类型 A / B", + "items": [ + "已选定:假破 或 结构突破(二选一)", + "假破:扫流动性后回到结构内,5m N 字 + 15m 顶/底分型齐全", + "结构突破:15m 收盘价站稳关键位,非仅刺破", + "止损带宽 0.4%~1.5%,超出则不做", + "下单前空间至少 1:1,不足则不做" + ] + }, + { + "title": "一日节奏与笔数", + "items": [ + "非周末;在早窗 / 晚窗计划时段内", + "今日笔数未达上限 3,连错未达 2 笔", + "宽幅震荡(S1)时降频或不做", + "0 点前须了结(系统强制清仓);本清单不含手动平仓" + ] + } + ], + "footnotes": [ + "系统:FORCE_CLOSE_ENABLED 开启时,北京时间 0 点自动强制清仓(result=强制清仓)" + ] +} diff --git a/docs/strategy/checklists/okx.json b/docs/strategy/checklists/okx.json new file mode 100644 index 0000000..1b5c568 --- /dev/null +++ b/docs/strategy/checklists/okx.json @@ -0,0 +1,32 @@ +{ + "exchange": "okx", + "title": "OKX · 开仓检查清单", + "version": "v0.2", + "groups": [ + { + "title": "账户与方向", + "items": [ + "已选定做多或做空,且与 4H/大级别方向一致", + "趋势户 profile(非 Gate 日内 BTC/ETH 白名单)", + "开仓类型已从 大分歧A / 大分歧B / 小分歧 中选定" + ] + }, + { + "title": "空侧镜像(若做空)", + "items": [ + "大分歧A:不创新高企稳", + "大分歧B:向下突破确认", + "小分歧:二次探顶 / 倒 N / 5m 空头排列" + ] + }, + { + "title": "纪律与杠杆", + "items": [ + "第三次小分歧不做新单", + "小分歧不追突破", + "杠杆:BTC/ETH 10x,其它 5x", + "止盈止损随行情人工设定,本文档不量化 RR" + ] + } + ] +} diff --git a/lib/hub/hub_strategy_lib.py b/lib/hub/hub_strategy_lib.py new file mode 100644 index 0000000..6cb6e0b --- /dev/null +++ b/lib/hub/hub_strategy_lib.py @@ -0,0 +1,279 @@ +"""中控「策略说明」:读取 docs/strategy MD + checklists JSON。""" + +from __future__ import annotations + +import json +import re +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +from lib.paths import REPO_ROOT + +STRATEGY_EXCHANGES: tuple[str, ...] = ("binance", "okx", "gate") + +STRATEGY_META: dict[str, dict[str, str]] = { + "binance": { + "label": "币安", + "title": "币安·山寨多头趋势", + "md_file": "binance-alt-trend-long.md", + }, + "okx": { + "label": "OKX", + "title": "OKX·多空趋势", + "md_file": "okx-trend-both.md", + }, + "gate": { + "label": "Gate", + "title": "Gate·BTC 日内", + "md_file": "gate-intraday.md", + }, +} + + +def _strategy_dir() -> Path: + return REPO_ROOT / "docs" / "strategy" + + +def _checklist_path(exchange_key: str) -> Path: + return _strategy_dir() / "checklists" / f"{exchange_key.strip().lower()}.json" + + +def _md_path(exchange_key: str) -> Path: + meta = STRATEGY_META.get((exchange_key or "").strip().lower()) + if not meta: + raise KeyError(exchange_key) + return _strategy_dir() / meta["md_file"] + + +def _parse_version(md_text: str) -> str: + m = re.search(r">\s*\*\*状态\*\*[::]\s*(v[\d.]+)", md_text) + if m: + return m.group(1) + m = re.search(r"\|\s*v([\d.]+)\s*\|", md_text) + if m: + return f"v{m.group(1)}" + return "" + + +def render_markdown_html(md_text: str) -> str: + try: + import markdown # type: ignore + + return markdown.markdown( + md_text, + extensions=["tables", "fenced_code", "nl2br", "sane_lists"], + ) + except Exception: + return _simple_md_html(md_text) + + +def _simple_md_html(md_text: str) -> str: + from html import escape + + lines = md_text.replace("\r\n", "\n").replace("\r", "\n").splitlines() + out: list[str] = [] + i = 0 + in_code = False + code_buf: list[str] = [] + list_buf: list[str] = [] + list_ordered = False + + def flush_list() -> None: + nonlocal list_buf, list_ordered + if not list_buf: + return + tag = "ol" if list_ordered else "ul" + out.append(f"<{tag}>") + for item in list_buf: + out.append(f"
  • {_inline_md(item)}
  • ") + out.append(f"") + list_buf = [] + + def flush_code() -> None: + nonlocal code_buf, in_code + if not code_buf: + return + out.append(f"
    {escape(chr(10).join(code_buf))}
    ") + code_buf = [] + in_code = False + + while i < len(lines): + line = lines[i] + if line.strip().startswith("```"): + flush_list() + if in_code: + flush_code() + else: + in_code = True + i += 1 + continue + if in_code: + code_buf.append(line) + i += 1 + continue + if re.match(r"^\s*\|", line) and i + 1 < len(lines) and re.match(r"^\s*\|?\s*[-:| ]+\|", lines[i + 1]): + flush_list() + header = [c.strip() for c in line.strip().strip("|").split("|")] + i += 2 + rows: list[list[str]] = [] + while i < len(lines) and re.match(r"^\s*\|", lines[i]): + rows.append([c.strip() for c in lines[i].strip().strip("|").split("|")]) + i += 1 + out.append("" + "".join(f"" for h in header) + "") + for row in rows: + out.append("" + "".join(f"" for c in row) + "") + out.append("
    {_inline_md(h)}
    {_inline_md(c)}
    ") + continue + if re.match(r"^#{1,3}\s+", line): + flush_list() + m = re.match(r"^(#{1,3})\s+(.*)$", line) + if m: + level = len(m.group(1)) + out.append(f"{_inline_md(m.group(2))}") + i += 1 + continue + if line.strip() == "---": + flush_list() + out.append("
    ") + i += 1 + continue + if line.startswith(">"): + flush_list() + out.append(f"
    {_inline_md(line.lstrip('>').strip())}
    ") + i += 1 + continue + m = re.match(r"^(\d+)\.\s+(.*)$", line.strip()) + if m: + if list_buf and not list_ordered: + flush_list() + list_ordered = True + list_buf.append(m.group(2)) + i += 1 + continue + if re.match(r"^[-*]\s+", line.strip()): + if list_buf and list_ordered: + flush_list() + list_ordered = False + list_buf.append(re.sub(r"^[-*]\s+", "", line.strip())) + i += 1 + continue + if not line.strip(): + flush_list() + i += 1 + continue + flush_list() + out.append(f"

    {_inline_md(line.strip())}

    ") + i += 1 + flush_list() + flush_code() + return "\n".join(out) + + +def _inline_md(text: str) -> str: + from html import escape + + s = escape(text) + s = re.sub(r"`([^`]+)`", r"\1", s) + s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s) + return s + + +def load_checklist(exchange_key: str) -> dict[str, Any]: + path = _checklist_path(exchange_key) + if not path.is_file(): + return {"exchange": exchange_key, "title": "开仓检查清单", "groups": []} + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + return {"exchange": exchange_key, "title": "开仓检查清单", "groups": []} + return data + + +def load_strategy_payload(exchange_key: str) -> dict[str, Any]: + key = (exchange_key or "").strip().lower() + if key not in STRATEGY_META: + raise KeyError(exchange_key) + meta = STRATEGY_META[key] + md_path = _md_path(key) + md_text = md_path.read_text(encoding="utf-8") if md_path.is_file() else "" + checklist = load_checklist(key) + version = _parse_version(md_text) or str(checklist.get("version") or "") + return { + "ok": True, + "exchange_key": key, + "label": meta["label"], + "title": meta["title"], + "version": version, + "md_source": str(md_path.relative_to(REPO_ROOT)).replace("\\", "/"), + "strategy_html": render_markdown_html(md_text), + "checklist": checklist, + } + + +def strategy_meta_payload() -> dict[str, Any]: + tabs = [ + {"key": k, "label": STRATEGY_META[k]["label"], "title": STRATEGY_META[k]["title"]} + for k in STRATEGY_EXCHANGES + ] + return {"ok": True, "exchanges": tabs} + + +def build_export_html(exchange_key: str) -> str: + payload = load_strategy_payload(exchange_key) + key = payload["exchange_key"] + label = payload["label"] + title = payload["title"] + version = payload.get("version") or "" + checklist = payload.get("checklist") or {} + groups = checklist.get("groups") or [] + now = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M") + + checklist_html_parts = [ + f"

    {escape_html(str(checklist.get('title') or '开仓检查清单'))}

    " + ] + for grp in groups: + if not isinstance(grp, dict): + continue + gtitle = escape_html(str(grp.get("title") or "")) + checklist_html_parts.append(f"

    {gtitle}

    ") + checklist_html = "\n".join(checklist_html_parts) + + return f""" + + + +{escape_html(label)} · 策略说明 + + + +

    {escape_html(title)}

    +
    {escape_html(label)} · {escape_html(version)} · 导出 {now}
    +
    +
    {payload.get("strategy_html") or ""}
    +
    {checklist_html}
    +
    + +""" + + +def escape_html(text: str) -> str: + from html import escape + + return escape(text, quote=True) diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index 9115adb..3c4da00 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -78,6 +78,11 @@ from lib.hub.hub_entry_plan_lib import ( meta_payload as entry_plan_meta_payload, update_entry_plan, ) +from lib.hub.hub_strategy_lib import ( + build_export_html, + load_strategy_payload, + strategy_meta_payload, +) from lib.hub.hub_macro_calendar_lib import ( MACRO_EVENT_LABELS, MACRO_EVENT_TYPES, @@ -94,7 +99,7 @@ load_hub_dotenv() import httpx from fastapi import Body, FastAPI, File, Form, HTTPException, Request, UploadFile -from fastapi.responses import FileResponse, JSONResponse +from fastapi.responses import FileResponse, HTMLResponse, JSONResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, Field @@ -805,6 +810,7 @@ def root_redirect(): @app.get("/dashboard") @app.get("/funds") @app.get("/ai") +@app.get("/strategy") @app.get("/settings") def shell_pages(): return _shell_page() @@ -914,6 +920,7 @@ class SettingsDisplayBody(BaseModel): show_nav_archive: bool = True show_nav_ai: bool = True show_nav_calculator: bool = True + show_nav_strategy: bool = True class SupervisorSettingsBody(BaseModel): @@ -2951,6 +2958,33 @@ async def api_archive_sync(): return body +@app.get("/api/strategy/meta") +def api_strategy_meta(): + return strategy_meta_payload() + + +@app.get("/api/strategy/{exchange_key}") +def api_strategy_detail(exchange_key: str): + try: + return load_strategy_payload(exchange_key.strip().lower()) + except KeyError: + return JSONResponse({"ok": False, "msg": "unknown exchange"}, status_code=404) + + +@app.get("/api/strategy/{exchange_key}/export") +def api_strategy_export(exchange_key: str): + key = exchange_key.strip().lower() + try: + html = build_export_html(key) + except KeyError: + return JSONResponse({"ok": False, "msg": "unknown exchange"}, status_code=404) + filename = f"strategy-{key}.html" + return HTMLResponse( + content=html, + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + + @app.get("/api/entry-plans/meta") def api_entry_plans_meta(): init_entry_plan_db() diff --git a/manual_trading_hub/requirements.txt b/manual_trading_hub/requirements.txt index 9698e15..b983bc6 100644 --- a/manual_trading_hub/requirements.txt +++ b/manual_trading_hub/requirements.txt @@ -5,3 +5,4 @@ httpx>=0.27,<1 ccxt>=4.2,<5 PySocks>=1.7,<2 psutil>=5.9,<8 +# 可选:服务端 pip install markdown 后渲染更完整;无则使用内置轻量渲染 diff --git a/manual_trading_hub/settings_store.py b/manual_trading_hub/settings_store.py index 39c3c10..70bf212 100644 --- a/manual_trading_hub/settings_store.py +++ b/manual_trading_hub/settings_store.py @@ -28,6 +28,7 @@ DEFAULT_DISPLAY = { "show_nav_archive": True, "show_nav_ai": True, "show_nav_calculator": True, + "show_nav_strategy": True, } DEFAULT_EXCHANGES = [ diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index d3ce6e9..7d58412 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -7479,3 +7479,183 @@ body.funds-fullscreen-open { } } +/* --- 策略说明 --- */ +.strategy-page-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; +} +.strategy-page-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; +} +.strategy-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 12px; +} +.strategy-tabs { + display: flex; + gap: 8px; + flex-wrap: wrap; +} +.strategy-tab { + padding: 6px 14px; + border-radius: 999px; + border: 1px solid var(--border-soft); + background: var(--surface-2); + color: var(--text-soft); + cursor: pointer; + font-size: 0.88rem; +} +.strategy-tab.is-active { + background: var(--accent-soft); + border-color: var(--accent); + color: var(--text); +} +.strategy-layout { + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); + gap: 14px; + align-items: start; +} +.strategy-col-title { + margin: 0 0 12px; + font-size: 0.95rem; + color: var(--text-soft); +} +.strategy-doc-body { + font-size: 0.88rem; + line-height: 1.55; + color: var(--text); + max-height: min(72vh, 900px); + overflow: auto; + padding-right: 4px; +} +.strategy-doc-body h2 { + font-size: 1rem; + margin: 1.2em 0 0.5em; + color: var(--text); +} +.strategy-doc-body h3 { + font-size: 0.92rem; + margin: 1em 0 0.4em; +} +.strategy-doc-body table { + width: 100%; + border-collapse: collapse; + font-size: 0.82rem; + margin: 8px 0; +} +.strategy-doc-body th, +.strategy-doc-body td { + border: 1px solid var(--border-soft); + padding: 6px 8px; + text-align: left; +} +.strategy-doc-body blockquote { + margin: 8px 0; + padding: 8px 12px; + border-left: 3px solid var(--accent); + color: var(--text-soft); + background: var(--surface-2); +} +.strategy-doc-body pre, +.strategy-doc-body code { + font-size: 0.8rem; +} +.strategy-doc-source { + margin: 12px 0 0; + font-size: 0.75rem; + color: var(--muted); +} +.strategy-checklist-body ul { + list-style: none; + margin: 0; + padding: 0; +} +.strategy-check-group { + margin-bottom: 14px; +} +.strategy-check-group h4 { + margin: 0 0 8px; + font-size: 0.88rem; + color: var(--accent); +} +.strategy-check-group li { + margin: 6px 0; + font-size: 0.86rem; + line-height: 1.45; + display: flex; + gap: 8px; + align-items: flex-start; +} +.strategy-check-box { + flex: 0 0 auto; + font-size: 1rem; + line-height: 1.2; +} +.strategy-checklist-footnotes { + margin: 12px 0 0; + padding-left: 18px; + font-size: 0.78rem; + color: var(--muted); +} +.strategy-checklist-footnotes.hidden { + display: none; +} +.strategy-empty { + color: var(--muted); + font-size: 0.85rem; +} +.strategy-print-header { + margin-bottom: 12px; +} +.strategy-print-header.hidden { + display: none; +} +.strategy-print-meta { + margin: 4px 0 0; + font-size: 0.85rem; + color: var(--muted); +} + +@media (max-width: 960px) { + .strategy-layout { + grid-template-columns: 1fr; + } + .strategy-doc-body { + max-height: none; + } +} + +@media print { + body.hub-strategy-printing .app-bg, + body.hub-strategy-printing .app-header, + body.hub-strategy-printing .no-print, + body.hub-strategy-printing .page:not(#page-strategy) { + display: none !important; + } + body.hub-strategy-printing #page-strategy { + display: block !important; + } + body.hub-strategy-printing .strategy-print-header { + display: block !important; + } + body.hub-strategy-printing .strategy-doc-body { + max-height: none; + overflow: visible; + } + body.hub-strategy-printing .card { + break-inside: avoid; + box-shadow: none; + border: 1px solid #ccc; + } +} + diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 0145e83..bf02ded 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -37,6 +37,10 @@ return displayPref("show_nav_calculator", true); } + function showNavStrategyPref() { + return displayPref("show_nav_strategy", true); + } + function syncNavVisibility(data) { const d = (data && data.display) || {}; const navFunds = document.getElementById("nav-funds"); @@ -45,12 +49,14 @@ const navArchive = document.getElementById("nav-archive"); const navAi = document.getElementById("nav-ai"); const navCalc = document.getElementById("nav-calculator"); + const navStrategy = document.getElementById("nav-strategy"); if (navFunds) navFunds.classList.toggle("nav-hidden", d.show_nav_funds === false); if (navDash) navDash.classList.toggle("nav-hidden", d.show_nav_dashboard === false); if (navPlan) navPlan.classList.toggle("nav-hidden", d.show_nav_plan === false); if (navArchive) navArchive.classList.toggle("nav-hidden", d.show_nav_archive === false); 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); } function pageNavAllowed(page) { @@ -60,6 +66,7 @@ if (page === "archive") return showNavArchivePref(); if (page === "ai") return showNavAiPref(); if (page === "calculator") return showNavCalculatorPref(); + if (page === "strategy") return showNavStrategyPref(); return true; } @@ -72,6 +79,7 @@ const archiveCb = document.getElementById("pref-show-nav-archive"); const aiCb = document.getElementById("pref-show-nav-ai"); const calcCb = document.getElementById("pref-show-nav-calculator"); + const strategyCb = document.getElementById("pref-show-nav-strategy"); if (pnlCb) pnlCb.checked = d.show_account_pnl !== false; if (fundsCb) fundsCb.checked = d.show_nav_funds !== false; if (dashCb) dashCb.checked = d.show_nav_dashboard !== false; @@ -79,6 +87,7 @@ if (archiveCb) archiveCb.checked = d.show_nav_archive !== false; 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; syncNavVisibility(data); } @@ -1158,6 +1167,7 @@ if (p.includes("funds")) return "funds"; if (p.includes("plan")) return "plan"; if (p.includes("calculator")) return "calculator"; + if (p.includes("strategy")) return "strategy"; if (p.includes("market")) return "market"; if (p.includes("/ai")) return "ai"; return "monitor"; @@ -1170,6 +1180,7 @@ if (page === "funds") return "page-funds"; if (page === "plan") return "page-plan"; if (page === "calculator") return "page-calculator"; + if (page === "strategy") return "page-strategy"; if (page === "market") return "page-market"; if (page === "ai") return "page-ai"; return "page-monitor"; @@ -1221,6 +1232,11 @@ } if (page === "funds" && window.hubFundsPage) { window.hubFundsPage.init(); + } + if (page === "strategy" && window.hubStrategyPage) { + window.hubStrategyPage.init(); + } else if (window.hubStrategyPage && window.hubStrategyPage.destroy) { + window.hubStrategyPage.destroy(); } else if (window.hubFundsPage && window.hubFundsPage.destroy) { window.hubFundsPage.destroy(); } @@ -4245,6 +4261,7 @@ const archiveCb = document.getElementById("pref-show-nav-archive"); 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 supEnabled = document.getElementById("supervisor-enabled"); const supProg = document.getElementById("supervisor-wechat-program"); const supWebhook = document.getElementById("supervisor-wechat-webhook"); @@ -4264,6 +4281,7 @@ show_nav_archive: archiveCb ? !!archiveCb.checked : true, show_nav_ai: aiCb ? !!aiCb.checked : true, show_nav_calculator: calcCb ? !!calcCb.checked : true, + show_nav_strategy: strategyCb ? !!strategyCb.checked : true, }, supervisor: { enabled: supEnabled ? !!supEnabled.checked : true, diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 28a5a66..ca7fe76 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -51,6 +51,7 @@ 资金概况 开仓计划 监控区 + 策略说明 行情区 计算器 内照明心 @@ -852,6 +853,41 @@ + +