实例增加实盘说明页(API/账户模式/env),系统设置可关闭导航显示。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5680,6 +5680,15 @@ def system_guide_page():
|
||||
return render_main_page("system_guide")
|
||||
|
||||
|
||||
@app.route("/live_guide")
|
||||
@login_required
|
||||
def live_guide_page():
|
||||
redir = redirect_to_embed_shell_if_enabled("live_guide")
|
||||
if redir is not None:
|
||||
return redir
|
||||
return render_main_page("live_guide")
|
||||
|
||||
|
||||
@app.route("/env_config")
|
||||
@login_required
|
||||
def env_config_page():
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# 交易所实盘说明(OKX)
|
||||
|
||||
本页说明如何把本实例从模拟盘切到真实交易:API 权限、OKX 账户模式、以及实例 `.env` 关键项。币本位期权开仓会先用 USDT 买 ETH/BTC,再开期权,因此 **现货交易权限** 与 **合约模式** 缺一不可。
|
||||
|
||||
## 一、创建 OKX API Key
|
||||
|
||||
1. 登录 OKX → **个人中心 → API** → 创建 API Key。
|
||||
2. 权限务必勾选 **交易(Trade)**。币本位开仓会走现货买币,只开合约/期权权限不够。
|
||||
3. 若有细分权限,请同时打开 **现货(Spot)**。
|
||||
4. 建议开启 **IP 白名单**,填入本实例服务器出口 IP。
|
||||
5. 保存后得到:`API Key` / `Secret Key` / `Passphrase`,填入本实例 env(见下文)。
|
||||
|
||||
常见报错:
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| `50123` This API Key does not have trading permission for the Crypto | Key 无现货/交易权限 | 编辑 Key,勾选交易与现货后重试 |
|
||||
| `未授权` / Bridge 探测失败 | 中控 Token 或客户端未部署接口 | 核对实例 Bridge Token;客户端需已部署 `/api/hub/version` |
|
||||
|
||||
## 二、OKX 账户模式
|
||||
|
||||
交易页 → **账户模式**(首次切换须在网页/App 手动确认)。
|
||||
|
||||
| 账户模式 | 是否推荐 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 仅现货 / 简单模式 | 否 | 无法正常开期权 |
|
||||
| **合约模式(现货和合约)** | **推荐** | 现货用 `cash`,与当前程序一致 |
|
||||
| 跨币种保证金 | 可用但不推荐 | 现货须 `cross`,程序目前按合约模式写 `cash`,会报 `tdMode` 错 |
|
||||
| 组合保证金 | 可用但不推荐 | 同上,现货也须 `cross` |
|
||||
|
||||
推荐流程:先切到 **合约模式** → 再在本实例点「买币并开仓」。
|
||||
|
||||
相关报错:
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| You can't complete this request under your current account mode (`51010`) | 账户模式不支持本次下单 | 改为合约模式 |
|
||||
| Parameter tdMode error | 跨币种/组合下仍用 `cash` 下现货 | 改回合约模式,或等待程序支持按模式自动选 `tdMode` |
|
||||
|
||||
## 三、实例 env 配置
|
||||
|
||||
在顶栏 **env配置**(或改服务器 `.env` 后重启)中设置:
|
||||
|
||||
### 1. 实盘开关与密钥
|
||||
|
||||
- `LIVE_TRADING_ENABLED=true`:允许实盘下单。
|
||||
- `OKX_API_KEY` / `OKX_API_SECRET` / `OKX_API_PASSPHRASE`:上一步创建的密钥(期权与永续共用,不必单独配期权 Key)。
|
||||
- `SIM_DEFAULT_MODE=live`:运行模式切到实盘(顶栏显示 `okx_live`)。也可在运行时由系统把 `trading.mode` 写成 `live`。
|
||||
|
||||
切到实盘后:
|
||||
|
||||
- 顶栏「交易账户 / 总资金」只显示交易所真实余额;未充值时应为 `—` 或 `0`,不会再残留模拟盘数字。
|
||||
- 模拟钱包余额不会带到实盘。
|
||||
|
||||
### 2. 期权相关(币本位)
|
||||
|
||||
- `OKX_OPTIONS_ENABLED=true`:启用期权模块。
|
||||
- `OKX_OPTIONS_MARGIN_MODE=coin`:币本位(用 ETH/BTC 付权利金)。
|
||||
- `OKX_OPTIONS_MAX_ACTIVE_POSITIONS`:期权持仓上限(笔);`0`=不限制;`1`=同时最多 1 个合约(同合约加仓不占新笔数)。
|
||||
- `OKX_OPTIONS_SPOT_BUY_BUFFER`:现货买入相对权利金的缓冲倍数(如 `1.05`)。
|
||||
- 目标平仓门控:`OKX_OPTIONS_CLOSE_GATE_MODE`、`OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN`、`OKX_OPTIONS_CLOSE_HOLD_SECONDS` 等(见 env 页「期权账户」分组)。
|
||||
|
||||
保存后按提示 **保存并重启**(或 PM2 重启),再刷新页面确认顶栏为 `okx_live`。
|
||||
|
||||
## 四、开仓前自检清单
|
||||
|
||||
1. OKX 账户模式 = **合约模式**。
|
||||
2. API Key 有 **交易 + 现货**,IP 白名单正确。
|
||||
3. `.env`:`LIVE_TRADING_ENABLED=true`,密钥已填,模式为 `live`。
|
||||
4. 交易账户有足够 **USDT**(买币)以及开期权所需的标的币/保证金。
|
||||
5. 期权持仓上限、全仓复利等规则符合预期。
|
||||
|
||||
## 五、翻倍离场与指数离场(实盘同样生效)
|
||||
|
||||
后台监控轮询实盘持仓,达标后按买一限价平仓:
|
||||
|
||||
- **翻倍离场**:买一可回收 ≥ 开仓权利金 × (1 + 倍数) 时平仓。
|
||||
- **指数离场**:标的指数到位后触发,但仍须通过「目标平仓门控」(权利金倍数持续若干秒等)。
|
||||
|
||||
开仓时勾选/填写对应出场条件,并保证实例进程在跑。
|
||||
|
||||
## 六、中控远程更新注意
|
||||
|
||||
中控「系统更新 → 客户端远程更新」依赖实例的 `/api/hub/version` 与 `/api/hub/update`。若版本列报「非 JSON / 404」,请先在该实例服务器手动 `git pull` 并重启一次,再在中控刷新版本。
|
||||
@@ -16,6 +16,7 @@
|
||||
records: "/records",
|
||||
stats: "/stats",
|
||||
risk_policy: "/risk_policy",
|
||||
live_guide: "/live_guide",
|
||||
system_guide: "/system_guide",
|
||||
env_config: "/env_config",
|
||||
settings: "/settings",
|
||||
@@ -361,6 +362,7 @@
|
||||
const hideTopBar =
|
||||
tab === "settings" ||
|
||||
tab === "risk_policy" ||
|
||||
tab === "live_guide" ||
|
||||
tab === "system_guide" ||
|
||||
tab === "env_config" ||
|
||||
tab === "options_review";
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
records: "/records",
|
||||
stats: "/stats",
|
||||
risk_policy: "/risk_policy",
|
||||
live_guide: "/live_guide",
|
||||
system_guide: "/system_guide",
|
||||
env_config: "/env_config",
|
||||
settings: "/settings",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"options-review": "show_nav_options_review",
|
||||
options_review: "show_nav_options_review",
|
||||
risk_policy: "show_nav_risk_policy",
|
||||
live_guide: "show_nav_live_guide",
|
||||
system_guide: "show_nav_system_guide",
|
||||
env_config: "show_nav_env_config",
|
||||
};
|
||||
@@ -87,6 +88,7 @@
|
||||
"options-review": "show_nav_options_review",
|
||||
options_review: "show_nav_options_review",
|
||||
risk_policy: "show_nav_risk_policy",
|
||||
live_guide: "show_nav_live_guide",
|
||||
system_guide: "show_nav_system_guide",
|
||||
env_config: "show_nav_env_config",
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
||||
"show_nav_records": False, # 永续交易记录与复盘已移除
|
||||
"show_nav_stats": False,
|
||||
"show_nav_risk_policy": True,
|
||||
"show_nav_live_guide": True,
|
||||
"show_nav_system_guide": False,
|
||||
"show_nav_env_config": True,
|
||||
"show_nav_options": True,
|
||||
@@ -38,6 +39,7 @@ DISPLAY_LABELS: dict[str, str] = {
|
||||
"show_nav_records": "交易记录与复盘",
|
||||
"show_nav_stats": "统计分析",
|
||||
"show_nav_risk_policy": "风控说明",
|
||||
"show_nav_live_guide": "实盘说明",
|
||||
"show_nav_system_guide": "系统说明",
|
||||
"show_nav_env_config": "env配置",
|
||||
"show_nav_options": "期权",
|
||||
@@ -59,6 +61,7 @@ NAV_TAB_ALLOWED: dict[str, str] = {
|
||||
"records": "show_nav_records",
|
||||
"stats": "show_nav_stats",
|
||||
"risk_policy": "show_nav_risk_policy",
|
||||
"live_guide": "show_nav_live_guide",
|
||||
"system_guide": "show_nav_system_guide",
|
||||
"env_config": "show_nav_env_config",
|
||||
"options": "show_nav_options",
|
||||
@@ -122,6 +125,7 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
|
||||
"show_nav_account_ledger",
|
||||
"show_nav_key_monitor",
|
||||
"show_nav_risk_policy",
|
||||
"show_nav_live_guide",
|
||||
"show_nav_system_guide",
|
||||
"show_nav_env_config",
|
||||
"show_nav_options",
|
||||
|
||||
@@ -17,6 +17,7 @@ EMBED_TABS: tuple[str, ...] = (
|
||||
"options",
|
||||
"options_review",
|
||||
"risk_policy",
|
||||
"live_guide",
|
||||
"system_guide",
|
||||
"env_config",
|
||||
"settings",
|
||||
@@ -34,6 +35,7 @@ PATH_TO_EMBED_TAB: dict[str, str] = {
|
||||
"/options/review": "options_review",
|
||||
"/hedge-plan": "options", # 对冲计划已移除,旧链接落到期权
|
||||
"/risk_policy": "risk_policy",
|
||||
"/live_guide": "live_guide",
|
||||
"/system_guide": "system_guide",
|
||||
"/env_config": "env_config",
|
||||
"/settings": "settings",
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""实例「实盘说明」:加载 Markdown,生成 h2 目录与带锚点正文."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from lib.common.markdown_html_lib import render_markdown_html
|
||||
from lib.paths import REPO_ROOT
|
||||
|
||||
|
||||
def live_guide_md_path() -> Path:
|
||||
return REPO_ROOT / "docs" / "实盘说明.md"
|
||||
|
||||
|
||||
def _slugify(text: str) -> str:
|
||||
raw = re.sub(r"<[^>]+>", "", text or "")
|
||||
raw = re.sub(r"\s+", "-", raw.strip())
|
||||
raw = re.sub(r"[^\w\u4e00-\u9fff\-]+", "", raw)
|
||||
return raw[:80] or "section"
|
||||
|
||||
|
||||
def _inject_h2_ids(html: str) -> tuple[str, list[dict[str, str]]]:
|
||||
toc: list[dict[str, str]] = []
|
||||
used: dict[str, int] = {}
|
||||
|
||||
def repl(m: re.Match[str]) -> str:
|
||||
inner = m.group(1)
|
||||
base = _slugify(inner)
|
||||
n = used.get(base, 0) + 1
|
||||
used[base] = n
|
||||
hid = base if n == 1 else f"{base}-{n}"
|
||||
toc.append({"id": hid, "title": re.sub(r"<[^>]+>", "", inner).strip()})
|
||||
return f'<h2 id="{escape(hid)}">{inner}</h2>'
|
||||
|
||||
out = re.sub(r"<h2>(.*?)</h2>", repl, html, flags=re.I | re.S)
|
||||
return out, toc
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def _load_payload_cached(mtime_ns: int, path_str: str) -> dict[str, Any]:
|
||||
path = Path(path_str)
|
||||
try:
|
||||
md_text = path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
md_text = "# 实盘说明缺失\n\n未找到 `docs/实盘说明.md`。"
|
||||
body = render_markdown_html(md_text)
|
||||
body, toc = _inject_h2_ids(body)
|
||||
return {"html": body, "toc": toc, "mtime_ns": mtime_ns}
|
||||
|
||||
|
||||
def load_live_guide_payload() -> dict[str, Any]:
|
||||
path = live_guide_md_path()
|
||||
try:
|
||||
mtime_ns = path.stat().st_mtime_ns
|
||||
except OSError:
|
||||
mtime_ns = 0
|
||||
return dict(_load_payload_cached(mtime_ns, str(path)))
|
||||
|
||||
|
||||
def live_guide_template_context() -> dict[str, Any]:
|
||||
payload = load_live_guide_payload()
|
||||
return {
|
||||
"live_guide_html": payload.get("html") or "",
|
||||
"live_guide_toc": payload.get("toc") or [],
|
||||
}
|
||||
@@ -153,6 +153,10 @@ def settings_page_context(page: str, *, instance_base_dir: str | None = None, **
|
||||
from lib.instance.instance_system_guide_lib import system_guide_template_context
|
||||
|
||||
return system_guide_template_context()
|
||||
if p == "live_guide":
|
||||
from lib.instance.instance_live_guide_lib import live_guide_template_context
|
||||
|
||||
return live_guide_template_context()
|
||||
if p not in ("settings", "risk_policy", "env_config"):
|
||||
return {}
|
||||
display = kwargs.pop("display", None)
|
||||
|
||||
@@ -143,6 +143,10 @@
|
||||
{% include 'system_guide_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% if page == 'live_guide' %}
|
||||
{% include 'live_guide_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% if page == 'settings' %}
|
||||
{% include 'settings_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
{% if display.show_nav_risk_policy %}
|
||||
<a href="/risk_policy" data-embed-tab="risk_policy" class="{% if initial_tab == 'risk_policy' %}active{% endif %}">风控说明</a>
|
||||
{% endif %}
|
||||
{% if display.get('show_nav_live_guide', true) %}
|
||||
<a href="/live_guide" data-embed-tab="live_guide" class="{% if initial_tab == 'live_guide' %}active{% endif %}">实盘说明</a>
|
||||
{% endif %}
|
||||
<a href="/system_guide" data-embed-tab="system_guide" class="{% if initial_tab == 'system_guide' %}active{% endif %}"{% if not display.show_nav_system_guide %} style="display:none"{% endif %}>系统说明</a>
|
||||
{% if display.show_nav_env_config %}
|
||||
<a href="/env_config" data-embed-tab="env_config" class="{% if initial_tab == 'env_config' %}active{% endif %}">env配置</a>
|
||||
@@ -63,7 +66,7 @@
|
||||
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
||||
|
||||
{% include 'instance_header_panel.html' %}
|
||||
{% if initial_tab not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options_review') and include_transfer_block %}
|
||||
{% if initial_tab not in ('settings', 'risk_policy', 'live_guide', 'system_guide', 'env_config', 'options_review') and include_transfer_block %}
|
||||
{% include 'instance_top_bar.html' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -113,6 +116,9 @@
|
||||
{% if display.show_nav_risk_policy %}
|
||||
<a href="/risk_policy" data-embed-tab="risk_policy">风控说明</a>
|
||||
{% endif %}
|
||||
{% if display.get('show_nav_live_guide', true) %}
|
||||
<a href="/live_guide" data-embed-tab="live_guide">实盘说明</a>
|
||||
{% endif %}
|
||||
<a href="/system_guide" data-embed-tab="system_guide"{% if not display.show_nav_system_guide %} style="display:none"{% endif %}>系统说明</a>
|
||||
{% if display.show_nav_env_config %}
|
||||
<a href="/env_config" data-embed-tab="env_config">env配置</a>
|
||||
|
||||
@@ -180,6 +180,9 @@
|
||||
{% if display.show_nav_risk_policy %}
|
||||
<a href="/risk_policy" data-embed-tab="risk_policy" class="{% if page == 'risk_policy' %}active{% endif %}">风控说明</a>
|
||||
{% endif %}
|
||||
{% if display.get('show_nav_live_guide', true) %}
|
||||
<a href="/live_guide" data-embed-tab="live_guide" class="{% if page == 'live_guide' %}active{% endif %}">实盘说明</a>
|
||||
{% endif %}
|
||||
<a href="/system_guide" data-embed-tab="system_guide" class="{% if page == 'system_guide' %}active{% endif %}"{% if not display.show_nav_system_guide %} style="display:none"{% endif %}>系统说明</a>
|
||||
{% if display.show_nav_env_config %}
|
||||
<a href="/env_config" data-embed-tab="env_config" class="{% if page == 'env_config' %}active{% endif %}">env配置</a>
|
||||
@@ -191,7 +194,7 @@
|
||||
{% with msg=get_flashed_messages() %}{% if msg %}<div class="flash">{{ msg[0] }}</div>{% endif %}{% endwith %}
|
||||
|
||||
{% include 'instance_header_panel.html' %}
|
||||
{% if page not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options', 'options_review') %}
|
||||
{% if page not in ('settings', 'risk_policy', 'live_guide', 'system_guide', 'env_config', 'options', 'options_review') %}
|
||||
{% include 'instance_top_bar.html' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -385,6 +388,10 @@
|
||||
{% include 'system_guide_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% if page == 'live_guide' %}
|
||||
{% include 'live_guide_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% if page == 'settings' %}
|
||||
{% include 'settings_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
{# 实盘说明: docs/实盘说明.md + h2 目录 #}
|
||||
<div class="system-guide-page full">
|
||||
<div class="card system-guide-card">
|
||||
<div class="system-guide-head">
|
||||
<h2 style="margin:0">实盘说明</h2>
|
||||
<p class="muted" style="margin:6px 0 0;font-size:.85rem">OKX API、账户模式与实例 env。可在系统设置 → 导航显示中关闭顶栏入口。</p>
|
||||
</div>
|
||||
<div class="system-guide-layout">
|
||||
{% if live_guide_toc %}
|
||||
<aside class="system-guide-toc" aria-label="章节目录">
|
||||
<div class="system-guide-toc-title">目录</div>
|
||||
<nav>
|
||||
{% for item in live_guide_toc %}
|
||||
<a href="#{{ item.id }}">{{ item.title }}</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</aside>
|
||||
{% endif %}
|
||||
<article class="system-guide-body prose">
|
||||
{{ live_guide_html|safe }}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.system-guide-page { grid-column: 1 / -1; }
|
||||
.system-guide-card { padding: 16px 18px 28px; }
|
||||
.system-guide-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(160px, 220px) minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
margin-top: 14px;
|
||||
align-items: start;
|
||||
}
|
||||
.system-guide-toc {
|
||||
position: sticky;
|
||||
top: 8px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(127,127,127,.25);
|
||||
border-radius: 8px;
|
||||
background: rgba(127,127,127,.06);
|
||||
max-height: calc(100vh - 120px);
|
||||
overflow: auto;
|
||||
}
|
||||
.system-guide-toc-title {
|
||||
font-size: .78rem;
|
||||
font-weight: 600;
|
||||
opacity: .75;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.system-guide-toc a {
|
||||
display: block;
|
||||
font-size: .84rem;
|
||||
line-height: 1.35;
|
||||
padding: 5px 0;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
opacity: .9;
|
||||
}
|
||||
.system-guide-toc a:hover { opacity: 1; text-decoration: underline; }
|
||||
.system-guide-body { min-width: 0; line-height: 1.65; font-size: .92rem; }
|
||||
.system-guide-body h1 { font-size: 1.35rem; margin: 0 0 12px; }
|
||||
.system-guide-body h2 { font-size: 1.12rem; margin: 22px 0 10px; padding-top: 4px; scroll-margin-top: 12px; }
|
||||
.system-guide-body h3 { font-size: 1rem; margin: 16px 0 8px; }
|
||||
.system-guide-body p, .system-guide-body li { margin: 0 0 8px; }
|
||||
.system-guide-body ul, .system-guide-body ol { padding-left: 1.35em; margin: 0 0 10px; }
|
||||
.system-guide-body table { border-collapse: collapse; width: 100%; margin: 10px 0 14px; font-size: .86rem; }
|
||||
.system-guide-body th, .system-guide-body td {
|
||||
border: 1px solid rgba(127,127,127,.35);
|
||||
padding: 7px 9px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.system-guide-body code {
|
||||
font-family: ui-monospace, Consolas, monospace;
|
||||
font-size: .86em;
|
||||
padding: 1px 4px;
|
||||
border-radius: 4px;
|
||||
background: rgba(127,127,127,.12);
|
||||
}
|
||||
.system-guide-body hr { border: 0; border-top: 1px solid rgba(127,127,127,.28); margin: 18px 0; }
|
||||
@media (max-width: 820px) {
|
||||
.system-guide-layout { grid-template-columns: 1fr; }
|
||||
.system-guide-toc { position: static; max-height: none; }
|
||||
.system-guide-toc nav { display: flex; flex-wrap: wrap; gap: 4px 12px; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user