diff --git a/manual_trading_hub/README.md b/manual_trading_hub/README.md
index 2192799..97ca041 100644
--- a/manual_trading_hub/README.md
+++ b/manual_trading_hub/README.md
@@ -1,8 +1,8 @@
# 手工交易多账户中控(manual_trading_hub)
-> **完整操作说明见 [使用说明.md](./使用说明.md)**(监控区 / 下单区 / 系统设置、鉴权、四所能力与故障排查)。
+> **完整操作说明见 [使用说明.md](./使用说明.md)**(监控区 / 系统设置、鉴权、四所能力与故障排查)。
-本目录提供多账户 **监控 + 下单转发 + 紧急全平**。各 `crypto_monitor_*` 仅需注册 `hub_bridge`(见使用说明);策略与复盘仍在各实例网页。
+本目录提供多账户 **监控 + 紧急全平**。人工下单、关键位、趋势回调请在各 `crypto_monitor_*` 实例网页操作;策略与复盘仍在各实例。
---
diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py
index 8d1fa45..3a0b9d8 100644
--- a/manual_trading_hub/hub.py
+++ b/manual_trading_hub/hub.py
@@ -1,6 +1,6 @@
"""
-多账户交易中控:监控区 / 下单区 / 系统设置。
-转发至各 crypto_monitor_* 的 /api/hub/* 与子代理 /status。
+多账户交易中控:监控区 / 系统设置。
+聚合各实例监控数据与子代理 /status;下单请在各实例网页操作。
"""
from __future__ import annotations
@@ -9,7 +9,7 @@ import os
from pathlib import Path
import httpx
-from fastapi import Body, FastAPI, HTTPException, Query, Request
+from fastapi import Body, FastAPI, HTTPException, Request
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
@@ -114,12 +114,18 @@ def root_redirect():
@app.get("/monitor")
-@app.get("/trade")
@app.get("/settings")
def shell_pages():
return _shell_page()
+@app.get("/trade")
+def trade_removed_redirect():
+ from fastapi.responses import RedirectResponse
+
+ return RedirectResponse("/monitor", status_code=302)
+
+
@app.get("/api/settings")
def api_get_settings():
return load_settings()
@@ -142,7 +148,7 @@ def api_settings_meta():
return {
"env_disabled_ids": sorted(env_force_disabled_ids()),
"hub_bridge_token_set": bool(HUB_BRIDGE_TOKEN),
- "capability_options": ["order", "key", "trend"],
+ "capability_options": ["key", "trend"],
"public_origin": f"{po[0]}://{po[1]}" if po else None,
"public_origin_hint": (
"未设置 HUB_PUBLIC_ORIGIN 时,复盘链接若为 127.0.0.1,仅服务器本机浏览器可打开"
@@ -182,25 +188,6 @@ async def _fetch_agent_status(client: httpx.AsyncClient, ex: dict) -> dict:
}
-def _exchange_brief(ex: dict | None) -> dict | None:
- if not ex:
- return None
- return {
- "id": ex.get("id"),
- "key": ex.get("key"),
- "name": ex.get("name"),
- }
-
-
-def _form_plain_dict(form) -> dict[str, str]:
- out: dict[str, str] = {}
- for k, v in form.multi_items() if hasattr(form, "multi_items") else form.items():
- if hasattr(v, "read"):
- continue
- out[str(k)] = "" if v is None else str(v)
- return out
-
-
def _parse_http_json_body(r: httpx.Response) -> dict:
text = (r.text or "").strip()
if not text:
@@ -336,90 +323,6 @@ async def api_close_all(body: CloseAllBody | None = Body(default=None)):
return {"results": list(results)}
-@app.get("/api/trade/meta/{exchange_id}")
-async def api_trade_meta(exchange_id: str):
- ex = _find_exchange(exchange_id)
- if not ex or not ex.get("enabled"):
- raise HTTPException(status_code=404, detail="账户未启用")
- async with httpx.AsyncClient() as client:
- meta = await _fetch_flask_json(client, ex, "/api/hub/meta")
- return {"exchange": ex, "meta": meta}
-
-
-@app.post("/api/trade/order/{exchange_id}")
-async def api_trade_order(exchange_id: str, request: Request):
- ex = _find_exchange(exchange_id)
- if not ex or not ex.get("enabled"):
- raise HTTPException(status_code=404, detail="账户未启用")
- try:
- form = _form_plain_dict(await request.form())
- async with httpx.AsyncClient() as client:
- result = await _fetch_flask_json(client, ex, "/api/hub/add_order", "POST", form)
- return {"exchange": _exchange_brief(ex), "result": result}
- except HTTPException:
- raise
- except Exception as e:
- return JSONResponse(
- {"exchange": _exchange_brief(ex), "result": {"ok": False, "messages": [str(e)]}},
- status_code=200,
- )
-
-
-@app.post("/api/trade/key/{exchange_id}")
-async def api_trade_key(exchange_id: str, request: Request):
- ex = _find_exchange(exchange_id)
- if not ex or not ex.get("enabled"):
- raise HTTPException(status_code=404, detail="账户未启用")
- if "key" not in (ex.get("capabilities") or []):
- raise HTTPException(status_code=400, detail="该账户不支持关键位")
- try:
- form = _form_plain_dict(await request.form())
- async with httpx.AsyncClient() as client:
- result = await _fetch_flask_json(client, ex, "/api/hub/add_key", "POST", form)
- return {"exchange": _exchange_brief(ex), "result": result}
- except HTTPException:
- raise
- except Exception as e:
- return JSONResponse(
- {"exchange": _exchange_brief(ex), "result": {"ok": False, "messages": [str(e)]}},
- status_code=200,
- )
-
-
-@app.post("/api/trade/trend/preview/{exchange_id}")
-async def api_trade_trend_preview(exchange_id: str, request: Request):
- ex = _find_exchange(exchange_id)
- if not ex or not ex.get("enabled"):
- raise HTTPException(status_code=404, detail="账户未启用")
- if "trend" not in (ex.get("capabilities") or []):
- raise HTTPException(status_code=400, detail="该账户不支持趋势回调")
- form = await request.form()
- async with httpx.AsyncClient() as client:
- result = await _fetch_flask_json(client, ex, "/api/hub/trend/preview", "POST", dict(form))
- return {"exchange": ex, "result": result}
-
-
-@app.post("/api/trade/trend/execute/{exchange_id}")
-async def api_trade_trend_execute(exchange_id: str, request: Request):
- ex = _find_exchange(exchange_id)
- if not ex or not ex.get("enabled"):
- raise HTTPException(status_code=404, detail="账户未启用")
- form = await request.form()
- async with httpx.AsyncClient() as client:
- result = await _fetch_flask_json(client, ex, "/api/hub/trend/execute", "POST", dict(form))
- return {"exchange": ex, "result": result}
-
-
-@app.get("/api/trade/trend/preview/{exchange_id}/{preview_id}")
-async def api_trade_trend_preview_get(exchange_id: str, preview_id: str):
- ex = _find_exchange(exchange_id)
- if not ex or not ex.get("enabled"):
- raise HTTPException(status_code=404, detail="账户未启用")
- async with httpx.AsyncClient() as client:
- result = await _fetch_flask_json(client, ex, f"/api/hub/trend/preview/{preview_id}")
- return {"exchange": ex, "result": result}
-
-
def main():
import uvicorn
diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css
index 32c8ef5..a42d549 100644
--- a/manual_trading_hub/static/app.css
+++ b/manual_trading_hub/static/app.css
@@ -428,57 +428,7 @@ button:disabled {
white-space: nowrap;
}
-/* —— 下单区 —— */
-.trade-bar {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- align-items: center;
- margin-bottom: 16px;
- padding: 14px 16px;
- background: var(--panel);
- border: 1px solid var(--border);
- border-radius: var(--radius);
-}
-
-.trade-bar label {
- font-size: 12px;
- color: var(--muted);
- margin-right: 6px;
-}
-
-.trade-bar select {
- min-width: 220px;
-}
-
-.tabs {
- display: flex;
- gap: 4px;
- margin-bottom: 16px;
- padding: 4px;
- background: var(--bg-elevated);
- border-radius: var(--radius);
- border: 1px solid var(--border-soft);
- width: fit-content;
- max-width: 100%;
- flex-wrap: wrap;
-}
-
-.tabs button {
- border: none;
- background: transparent;
- padding: 8px 16px;
- border-radius: 7px;
-}
-
-.tabs button.active {
- background: var(--panel);
- color: var(--accent);
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
- border: 1px solid var(--border);
-}
-
-.trade-meta {
+.settings-meta-line {
font-size: 12px;
color: var(--muted);
padding: 10px 14px;
@@ -489,21 +439,6 @@ button:disabled {
line-height: 1.55;
}
-.form-panel.hidden {
- display: none;
-}
-
-.form-panel .card-head strong {
- font-size: 14px;
-}
-
-.form-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: 12px;
- align-items: end;
-}
-
.field {
display: flex;
flex-direction: column;
@@ -522,7 +457,6 @@ button:disabled {
.field input,
.field select,
-.trade-bar select,
.form-row input,
.form-row select {
background: var(--bg);
diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js
index ea90543..f4ec628 100644
--- a/manual_trading_hub/static/app.js
+++ b/manual_trading_hub/static/app.js
@@ -1,8 +1,6 @@
(function () {
const toast = document.getElementById("toast");
let settingsCache = null;
- let tradeMeta = {};
- let trendPreviewId = null;
let monitorTimer = null;
function showToast(msg, isErr) {
@@ -34,7 +32,6 @@
function currentPage() {
const p = window.location.pathname.replace(/\/$/, "") || "/monitor";
- if (p.includes("trade")) return "trade";
if (p.includes("settings")) return "settings";
return "monitor";
}
@@ -49,7 +46,6 @@
});
if (page === "monitor") startMonitorPoll();
else stopMonitorPoll();
- if (page === "trade") initTradePage();
if (page === "settings") loadSettingsUI();
}
@@ -171,13 +167,18 @@
const review = row.review_url
? `复盘`
: "";
+ const flaskOpen = row.flask_url_browser || row.flask_url;
+ const openFlask = flaskOpen
+ ? `实例`
+ : "";
return `
${esc(row.name)}
-
${esc(row.flask_url_browser || row.flask_url || "")}
+
${esc(flaskOpen || "")}
+ ${openFlask}
${review}
@@ -215,164 +216,6 @@
}
}
- function initTradePage() {
- loadSettings().then(() => {
- const sel = document.getElementById("trade-account");
- const prev = sel.value;
- sel.innerHTML = enabledAccounts()
- .map(
- (x) =>
- `
`
- )
- .join("");
- if (prev) sel.value = prev;
- syncTradeTabs();
- loadTradeMeta();
- });
- }
-
- function accountCaps() {
- const id = document.getElementById("trade-account").value;
- const ex = (settingsCache?.exchanges || []).find((x) => String(x.id) === String(id));
- return ex?.capabilities || [];
- }
-
- function syncTradeTabs() {
- const caps = accountCaps();
- document.querySelectorAll(".tabs button").forEach((btn) => {
- const tab = btn.dataset.tab;
- let ok = false;
- if (tab === "order") ok = caps.includes("order");
- if (tab === "key") ok = caps.includes("key");
- if (tab === "trend") ok = caps.includes("trend");
- btn.disabled = !ok;
- btn.style.opacity = ok ? "1" : "0.4";
- });
- let active = document.querySelector(".tabs button.active");
- if (active && active.disabled) {
- const first = [...document.querySelectorAll(".tabs button")].find((b) => !b.disabled);
- if (first) switchTradeTab(first.dataset.tab);
- }
- ["order", "key", "trend"].forEach((t) => {
- document.getElementById("panel-" + t).classList.toggle(
- "hidden",
- !document.querySelector(`.tabs button[data-tab="${t}"]`).classList.contains("active")
- );
- });
- }
-
- function switchTradeTab(tab) {
- document.querySelectorAll(".tabs button").forEach((b) => {
- b.classList.toggle("active", b.dataset.tab === tab);
- });
- ["order", "key", "trend"].forEach((t) => {
- document.getElementById("panel-" + t).classList.toggle("hidden", t !== tab);
- });
- trendPreviewId = null;
- document.getElementById("trend-preview-box").style.display = "none";
- }
-
- async function loadTradeMeta() {
- const id = document.getElementById("trade-account").value;
- if (!id) return;
- try {
- const r = await fetch("/api/trade/meta/" + encodeURIComponent(id));
- const data = await r.json();
- tradeMeta = data.meta?.meta || data.meta || {};
- const el = document.getElementById("trade-meta");
- let txt = "";
- if (tradeMeta.key_gate_rule_text) txt = tradeMeta.key_gate_rule_text;
- else if (tradeMeta.trend_pullback_preview_ttl) {
- txt = `预览 ${tradeMeta.trend_pullback_preview_ttl}s · 补仓 ${tradeMeta.trend_pullback_dca_legs} 档 · 余额偏差 ≤${tradeMeta.trend_preview_max_drift_pct}%`;
- }
- el.textContent = txt;
- el.style.display = txt ? "block" : "none";
- } catch (e) {
- const el = document.getElementById("trade-meta");
- el.textContent = "";
- el.style.display = "none";
- }
- }
-
- async function parseJsonResponse(r) {
- const text = await r.text();
- if (!text) return {};
- try {
- return JSON.parse(text);
- } catch (e) {
- const snippet = text.slice(0, 200);
- throw new Error(
- `HTTP ${r.status} 响应不是 JSON:${snippet}${text.length > 200 ? "…" : ""}`
- );
- }
- }
-
- async function submitForm(path, formEl) {
- const id = document.getElementById("trade-account").value;
- const fd = new FormData(formEl);
- try {
- const r = await fetch(path + encodeURIComponent(id), { method: "POST", body: fd });
- const j = await parseJsonResponse(r);
- const res = j.result || {};
- const msgs =
- (res.messages || []).join("\n") ||
- res.error ||
- res.msg ||
- res.text ||
- JSON.stringify(res, null, 2);
- const failed = res.ok === false || r.status >= 400 || !!res.error || !!res.text;
- showToast(msgs || (failed ? "操作失败" : "已提交"), failed);
- if (res.ok && res.preview) {
- showTrendPreview(res);
- }
- loadTradeMeta();
- } catch (e) {
- showToast(String(e), true);
- }
- }
-
- function showTrendPreview(res) {
- trendPreviewId = res.preview_id;
- const p = res.preview || {};
- const box = document.getElementById("trend-preview-box");
- const levels = (p.grid_levels || [])
- .map((r) => `
| ${r.i} | ${r.price} | ${r.contracts} |
`)
- .join("");
- box.innerHTML = `
-
预览 #${esc(p.id || trendPreviewId)} · ${p.expires_in_sec ?? "?"}s
-
${esc(p.symbol)} ${esc(p.direction)} · ${p.leverage}x · 快照 ${fmt(p.snapshot_available_usdt, 2)} U
-
-
-
-
`;
- box.style.display = "block";
- document.getElementById("btn-trend-exec").onclick = executeTrend;
- }
-
- async function executeTrend() {
- if (!trendPreviewId) {
- showToast("请先生成预览", true);
- return;
- }
- if (!confirm("确认按预览参数实盘下单?")) return;
- const id = document.getElementById("trade-account").value;
- const fd = new FormData();
- fd.set("preview_id", trendPreviewId);
- try {
- const r = await fetch("/api/trade/trend/execute/" + encodeURIComponent(id), {
- method: "POST",
- body: fd,
- });
- const j = await r.json();
- const res = j.result || {};
- showToast((res.messages || []).join("\n") || JSON.stringify(res), !res.ok);
- document.getElementById("trend-preview-box").style.display = "none";
- trendPreviewId = null;
- } catch (e) {
- showToast(String(e), true);
- }
- }
-
async function loadSettingsMetaLine() {
try {
const r = await fetch("/api/settings/meta");
@@ -425,9 +268,8 @@
-
-
-
+
+
-
-
-
下单区
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
系统设置
@@ -231,7 +52,7 @@
HUB_DISABLED_IDS 可强制关闭账户;HUB_BRIDGE_TOKEN 与实例一致,或实例 APP_AUTH_DISABLED=true。
-
+
diff --git a/manual_trading_hub/使用说明.md b/manual_trading_hub/使用说明.md
index 9d6f57a..7d695ae 100644
--- a/manual_trading_hub/使用说明.md
+++ b/manual_trading_hub/使用说明.md
@@ -1,6 +1,6 @@
# 多账户交易中控 — 使用说明
-本文档说明 **manual_trading_hub**(方案 A)的架构、启动方式、三页界面操作与故障排查。中控聚合四所 **监控 + 下单 + 关键位 + 趋势回调(仅 Gate 趋势户)**;**交易复盘**仍进入各实例网页,中控只提供跳转链接。
+本文档说明 **manual_trading_hub** 的架构、启动方式、界面操作与故障排查。中控聚合四所 **持仓/余额/关键位/趋势计划监控 + 紧急全平**;**人工下单、添加关键位、趋势回调、交易复盘** 均在各实例网页操作。
---
@@ -9,17 +9,16 @@
```
浏览器
├─ /monitor 监控区(持仓、关键位、趋势计划、全平)
- ├─ /trade 下单区(人工下单 / 关键位 / 趋势回调)
└─ /settings 系统设置(hub_settings.json)
中控 hub.py(默认 :5100)
├─ HTTP → 子代理 agent.py × N(/status、/emergency/close-all)
- └─ HTTP → 各实例 Flask app.py(/api/hub/*、/api/price_snapshot)
+ └─ HTTP → 各实例 Flask(/api/hub/monitor、/api/price_snapshot 等只读聚合)
```
| 组件 | 职责 | 默认端口(可在设置页改) |
|------|------|-------------------------|
-| **hub.py** | 聚合 UI、转发下单/监控 API、全平 | `5100` |
+| **hub.py** | 聚合 UI、监控 API、全平 | `5100` |
| **agent.py** | 交易所只读状态 + 紧急市价全平 | 币安 `15200`、OKX `15201`、Gate `15202`、Gate趋势 `15203` |
| **crypto_monitor_*.app** | 策略库、关键位、人工单、趋势预览/执行 | 币安 `5001`、Gate `5000`、Gate趋势 `5002`、OKX `5004` |
@@ -139,8 +138,8 @@ python hub.py
浏览器打开:
- 监控区:http://127.0.0.1:5100/monitor
-- 下单区:http://127.0.0.1:5100/trade
- 系统设置:http://127.0.0.1:5100/settings
+- (旧链接 `/trade` 会自动跳转到监控区)
---
@@ -154,7 +153,7 @@ python hub.py
| **机器人持仓** | 来自实例 `/api/hub/monitor` 的 `order_monitors`(active) |
| **关键位** | 仅 `capabilities` 含 `key` 的户;展示门控摘要(`/api/price_snapshot`) |
| **趋势计划** | 仅 Gate 趋势户;`trend_pullback_plans` active |
-| **交易复盘** | 新标签打开该户 `/records`;**中控不做复盘**。链接默认由 `flask_url` 生成;若配置 **`HUB_PUBLIC_ORIGIN`**(如 `http://192.168.x.x`),会把 `127.0.0.1` 换成 Ubuntu 内网 IP,方便局域网其它设备打开 |
+| **实例 / 复盘** | 「实例」打开该户 Flask 首页;「复盘」打开 `/records`。**中控不做下单与复盘编辑**。若配置 **`HUB_PUBLIC_ORIGIN`**,外链会把 `127.0.0.1` 换成内网 IP |
| **关键位** | 来自实例 `/api/hub/monitor` + `/api/price_snapshot`(须 Flask 已启动);无记录或 Flask 未连通时卡片会提示原因;**Gate 趋势户**无关键位 |
| **该户全平** | `POST` 子代理 `/emergency/close-all`,仅平该 API Key 仓位 |
| **全局紧急全平** | 对所有已启用户依次全平(不含 `HUB_DISABLED_IDS` 强制关闭的 id) |
@@ -162,36 +161,11 @@ python hub.py
持仓数据以 **子代理 ccxt** 为准;关键位/趋势/机器人单以 **Flask 数据库** 为准。若 Flask 未启动,卡片仍会显示 agent 持仓,但下方策略信息可能为空或报错。
-### 4.2 下单区 `/trade`
+### 4.2 系统设置 `/settings`
-顶部 **账户下拉** 切换目标所;下方 Tab 根据该户 **能力** 自动启用/禁用:
+**可用**:打开 http://127.0.0.1:5100/settings ,修改表格后点 **保存设置** 即写入 `hub_settings.json`;**重新加载** 从磁盘/默认再读(会重新套用 `HUB_DISABLED_IDS`)。保存后监控区立即使用新 URL/启用状态,**无需重启 hub**。
-#### Tab:人工下单
-
-- 字段与各实例「手工开仓」一致:合约、方向、止盈止损模式(价格/百分比)、趋势单/波段单、杠杆、移动保本、止损/止盈。
-- 提交后中控转发 `POST /api/hub/add_order`,逻辑与在实例网页下单相同(含以损定仓、门控等)。
-- **Gate 趋势户** 同样可使用本 Tab(保留人工下单)。
-
-#### Tab:关键位
-
-- 仅 **币安、Gate 训练、OKX**(capabilities 含 `key`)显示。
-- 类型:箱体突破、收敛突破、斐波回调、关键阻力/支撑等;模式含标准突破、箱体 1R、趋势单自填止盈。
-- 提交转发 `POST /api/hub/add_key`。
-- 页面上方会显示该实例 **关键位门控规则** 文案(来自 `/api/hub/meta`)。
-
-#### Tab:趋势回调
-
-- 仅 **Gate 趋势户**(capabilities 含 `trend`)。
-- 流程:**填写参数 → 生成预览 → 确认执行(实盘)**。
-- 预览转发 `POST /api/hub/trend/preview`;执行须带 `preview_id`,转发 `POST /api/hub/trend/execute`。
-- 预览有效期内展示补仓档位表;过期需重新生成。
-- 做空时「补仓上沿」在 UI 上提示为补仓下沿价(字段名仍为 `add_upper`,与实例一致)。
-
-操作结果在页面底部 **Toast** 显示实例返回的 flash 汇总信息。
-
-### 4.3 系统设置 `/settings`
-
-**可用**:打开 http://127.0.0.1:5100/settings ,修改表格后点 **保存设置** 即写入 `hub_settings.json`;**重新加载** 从磁盘/默认再读(会重新套用 `HUB_DISABLED_IDS`)。保存后监控区、下单区立即使用新 URL/启用状态,**无需重启 hub**。
+**下单与关键位**:请在监控卡片点击「实例」,进入各 `crypto_monitor_*` 网页操作(与中控上线前相同)。
| 列 | 含义 |
|----|------|
@@ -200,7 +174,7 @@ python hub.py
| Flask URL | 实例根地址,如 `http://127.0.0.1:5001` |
| Agent URL | 子代理根地址,如 `http://127.0.0.1:15200` |
| 复盘链接 | 一般为 `{Flask}/records` |
-| 能力 | 勾选 order / key / trend,控制下单区 Tab |
+| 能力 | 勾选「监控关键位」「监控趋势计划」,控制监控卡片展示块 |
| id | 与 `HUB_DISABLED_IDS`、全平 API 路径中的 id 对应 |
- **保存设置**:写入 `hub_settings.json`,重启 hub 后仍生效。
@@ -209,14 +183,14 @@ python hub.py
---
-## 5. 能力矩阵(下单区 Tab)
+## 5. 能力矩阵(监控展示)
-| 账户 | 人工下单 | 关键位 | 趋势回调 |
-|------|:--------:|:------:|:--------:|
-| 币安 | ✓ | ✓ | — |
-| OKX | ✓ | ✓ | — |
-| Gate 训练 | ✓ | ✓ | — |
-| Gate 趋势 | ✓ | — | ✓ |
+| 账户 | 监控关键位 | 监控趋势计划 |
+|------|:----------:|:--------------:|
+| 币安 | ✓ | — |
+| OKX | ✓ | — |
+| Gate 训练 | ✓ | — |
+| Gate 趋势 | — | ✓ |
---
@@ -231,23 +205,13 @@ python hub.py
| GET | `/api/monitor/board` | 监控聚合 |
| POST | `/api/close/{id}` | 单户全平 |
| POST | `/api/close-all` | 全局全平,body 可选 `exclude_ids` |
-| GET | `/api/trade/meta/{id}` | 实例 meta(门控文案等) |
-| POST | `/api/trade/order/{id}` | 人工下单 |
-| POST | `/api/trade/key/{id}` | 添加关键位 |
-| POST | `/api/trade/trend/preview/{id}` | 趋势预览 |
-| POST | `/api/trade/trend/execute/{id}` | 趋势执行 |
-实例侧(需 `X-Hub-Token` 或已登录):
+实例侧(中控只读调用 `/api/hub/monitor` 等;下单请在实例网页):
| 路径 | 说明 |
|------|------|
| `/api/hub/ping` | 连通与能力 |
-| `/api/hub/meta` | 表单规则、预览 TTL 等 |
| `/api/hub/monitor` | 关键位、机器人单、趋势计划 |
-| `/api/hub/add_order` | 人工开仓 |
-| `/api/hub/add_key` | 添加关键位 |
-| `/api/hub/trend/preview` | 趋势预览 |
-| `/api/hub/trend/execute` | 趋势执行 |
---
@@ -282,7 +246,7 @@ python hub.py
## 8. 安全与边界
-1. **中控可下单**:下单区操作会真实调用交易所逻辑,与在实例网页操作等价,请确认账户与参数。
+1. **中控不下单**:开仓、关键位、趋势回调仅在各实例网页操作。
2. **全平为市价减仓**:监控区全平不可撤销,操作前二次确认。
3. **子代理建议只监听 127.0.0.1**,不要对局域网暴露 API Key 通道。
4. **公网暴露 hub**:请防火墙限制 `5100`,或 `HUB_HOST=127.0.0.1` + `HUB_TRUST_LAN=0`。
@@ -297,11 +261,9 @@ python hub.py
|------|----------|------|
| 监控卡片「子代理不可用」 | agent 未启动或端口错 | 检查 Agent URL、启动 agent |
| 无关键位/趋势信息 | Flask 未启动或令牌错误 | 启动 app.py;核对 `HUB_BRIDGE_TOKEN` |
-| 下单返回 401 | 令牌不一致 | 四实例与中控设相同 `HUB_BRIDGE_TOKEN` |
| 全平 401 | 子代理 `CONTROL_TOKEN` 与中控不一致 | 中控用 `X-Control-Token` 转发,需与 agent 一致 |
| OKX 始终灰色 | `HUB_DISABLED_IDS=1` | 清空该环境变量并在设置页启用 |
-| 趋势预览成功但执行失败 | 预览过期 | 重新「生成预览」再执行 |
-| 关键位 Tab 灰色 | 该户 capabilities 无 `key` | 正常;Gate 趋势户无关键位 |
+| 无关键位块 | 该户 capabilities 无 `key` | 正常;Gate 趋势户无关键位 |
| 局域网无法打开中控 | 防火墙 / `HUB_TRUST_LAN=0` | 放行端口或恢复默认信任私网 |
手动探测实例桥接:
@@ -318,8 +280,8 @@ Invoke-WebRequest -Uri "http://127.0.0.1:5001/api/hub/ping" -Headers @{"X-Hub-To
早期中控 **仅监控 + 全平**,使用环境变量 `HUB_AGENTS` 列表。当前版本改为:
- **hub_settings.json**(或内置默认)管理四所 URL 与能力;
-- **三页 UI**:监控 / 下单 / 设置;
-- 通过 **hub_bridge** 调用实例下单 API。
+- **两页 UI**:监控 / 设置;
+- 通过 **hub_bridge** 只读聚合监控数据。
子代理 `agent.py` 仍负责持仓与全平;`HUB_AGENTS` 环境变量在新版 hub 中 **不再使用**(以设置文件为准)。
@@ -345,8 +307,8 @@ pm2 save && pm2 startup
1. 启动四所 **agent** + **Flask**(OKX 按需)。
2. 启动 **hub.py**,打开监控区确认持仓与关键位门控正常。
-3. 在下单区选账户 → 人工单 / 关键位 / 趋势(按能力)。
-4. 复盘、导出记录 → 点击监控卡片「复盘」进入对应实例。
+3. 开仓、关键位、趋势 → 点击监控卡片「实例」进入对应 Flask。
+4. 复盘、导出记录 → 点击「复盘」进入 `/records`。
5. 异常行情 → 单户全平或全局紧急全平。
如有新交易所,在 **系统设置** 添加一行并勾选能力,无需修改 hub.py 源码(需该所有 Flask 注册 `hub_bridge` 且 agent 已部署)。
diff --git a/manual_trading_hub/部署文档.md b/manual_trading_hub/部署文档.md
index 40fcbdb..3bfac85 100644
--- a/manual_trading_hub/部署文档.md
+++ b/manual_trading_hub/部署文档.md
@@ -1,6 +1,6 @@
# 多账户交易中控 — 部署文档(含 PM2)
-本文档说明在 **Ubuntu / Linux** 上部署 **manual_trading_hub**(监控区、下单区、系统设置)的推荐步骤。功能与界面操作见 **《使用说明.md》**;环境变量说明见 **`.env.example`** 与各 `crypto_monitor_*` 的 `.env.example`。
+本文档说明在 **Ubuntu / Linux** 上部署 **manual_trading_hub**(监控区、系统设置)的推荐步骤。功能与界面操作见 **《使用说明.md》**;环境变量说明见 **`.env.example`** 与各 `crypto_monitor_*` 的 `.env.example`。
---
@@ -160,7 +160,7 @@ bash scripts/run_hub.sh
1. 打开 **http://127.0.0.1:5100/monitor**(局域网用本机私网 IP)。
2. 已启用账户应显示持仓;Flask 已起时有关键位/趋势信息。
3. **http://127.0.0.1:5100/settings** 保存后生成 `hub_settings.json`。
-4. **http://127.0.0.1:5100/trade** 选账户测试下单(实盘慎用)。
+4. 在各实例 Flask 网页测试下单/关键位(中控仅监控,不下单)。
接口探测: