Fix composite margin ratio cap at 50% and add risk guide page with nav toggle.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -231,6 +231,7 @@ def _static_asset_v() -> str:
|
|||||||
"static/css/responsive.css",
|
"static/css/responsive.css",
|
||||||
"static/css/trade.css",
|
"static/css/trade.css",
|
||||||
"static/css/dashboard.css",
|
"static/css/dashboard.css",
|
||||||
|
"static/css/doc.css",
|
||||||
"static/css/base.css",
|
"static/css/base.css",
|
||||||
)
|
)
|
||||||
mtimes = []
|
mtimes = []
|
||||||
@@ -1644,6 +1645,20 @@ def dashboard():
|
|||||||
return render_template("dashboard.html")
|
return render_template("dashboard.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/risk-guide")
|
||||||
|
@login_required
|
||||||
|
@require_nav("risk_guide")
|
||||||
|
def risk_guide():
|
||||||
|
from doc_render import read_doc, render_markdown
|
||||||
|
|
||||||
|
try:
|
||||||
|
_title, raw = read_doc("risk-guide")
|
||||||
|
except FileNotFoundError:
|
||||||
|
flash("文档不存在")
|
||||||
|
return redirect(url_for("positions"))
|
||||||
|
return render_template("risk_guide.html", doc_html=render_markdown(raw))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/dashboard/live")
|
@app.route("/api/dashboard/live")
|
||||||
@login_required
|
@login_required
|
||||||
def api_dashboard_live():
|
def api_dashboard_live():
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ def build_risk_overview(
|
|||||||
"daily_risk_used_pct": daily_risk_used,
|
"daily_risk_used_pct": daily_risk_used,
|
||||||
"limits": {
|
"limits": {
|
||||||
"max_active_positions": max_active_positions(),
|
"max_active_positions": max_active_positions(),
|
||||||
|
"position_mode": "single" if max_active_positions() <= 1 else "multi",
|
||||||
|
"position_mode_label": "单仓模式" if max_active_positions() <= 1 else "多仓模式",
|
||||||
"daily_position_limit": daily_position_limit(),
|
"daily_position_limit": daily_position_limit(),
|
||||||
"daily_trading_risk_pct_limit": daily_trading_risk_pct_limit(),
|
"daily_trading_risk_pct_limit": daily_trading_risk_pct_limit(),
|
||||||
"manual_close_daily_limit": manual_close_daily_limit(),
|
"manual_close_daily_limit": manual_close_daily_limit(),
|
||||||
|
|||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||||
|
# 专有软件 — 未经授权禁止复制、传播、转售。
|
||||||
|
# 详见 LICENSE.zh-CN.txt
|
||||||
|
|
||||||
|
"""将项目 docs 下的 Markdown 转为安全 HTML(无第三方依赖)。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import html
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_DOCS_ROOT = Path(__file__).resolve().parent / "docs"
|
||||||
|
|
||||||
|
ALLOWED_DOCS: dict[str, str] = {
|
||||||
|
"risk-guide": "风控说明.md",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def docs_root() -> Path:
|
||||||
|
return _DOCS_ROOT
|
||||||
|
|
||||||
|
|
||||||
|
def read_doc(slug: str) -> tuple[str, str]:
|
||||||
|
"""返回 (title, raw_markdown)。"""
|
||||||
|
name = ALLOWED_DOCS.get(slug)
|
||||||
|
if not name:
|
||||||
|
raise FileNotFoundError(slug)
|
||||||
|
path = (_DOCS_ROOT / name).resolve()
|
||||||
|
if not path.is_file() or _DOCS_ROOT.resolve() not in path.parents:
|
||||||
|
raise FileNotFoundError(slug)
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
title = name
|
||||||
|
for line in text.splitlines():
|
||||||
|
s = line.strip()
|
||||||
|
if s.startswith("# "):
|
||||||
|
title = s[2:].strip()
|
||||||
|
break
|
||||||
|
return title, text
|
||||||
|
|
||||||
|
|
||||||
|
def _inline(text: str) -> str:
|
||||||
|
s = html.escape(text)
|
||||||
|
s = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", s)
|
||||||
|
s = re.sub(r"`([^`]+)`", r"<code>\1</code>", s)
|
||||||
|
s = re.sub(
|
||||||
|
r"\[([^\]]+)\]\(([^)]+)\)",
|
||||||
|
lambda m: _link_html(m.group(1), m.group(2)),
|
||||||
|
s,
|
||||||
|
)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def _link_html(label: str, href: str) -> str:
|
||||||
|
h = html.escape(href)
|
||||||
|
lbl = _inline(label)
|
||||||
|
if href.startswith(("http://", "https://", "mailto:")):
|
||||||
|
return f'<a href="{h}" target="_blank" rel="noopener noreferrer">{lbl}</a>'
|
||||||
|
if href.endswith(".md") or href.startswith("./"):
|
||||||
|
return f'<span class="doc-xref">{lbl}</span>'
|
||||||
|
return f'<a href="{h}">{lbl}</a>'
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(text: str) -> str:
|
||||||
|
lines = text.splitlines()
|
||||||
|
out: list[str] = []
|
||||||
|
i = 0
|
||||||
|
in_ul = False
|
||||||
|
in_ol = False
|
||||||
|
|
||||||
|
def close_lists() -> None:
|
||||||
|
nonlocal in_ul, in_ol
|
||||||
|
if in_ul:
|
||||||
|
out.append("</ul>")
|
||||||
|
in_ul = False
|
||||||
|
if in_ol:
|
||||||
|
out.append("</ol>")
|
||||||
|
in_ol = False
|
||||||
|
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i]
|
||||||
|
stripped = line.strip()
|
||||||
|
|
||||||
|
if not stripped:
|
||||||
|
close_lists()
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if stripped == "---":
|
||||||
|
close_lists()
|
||||||
|
out.append("<hr>")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if stripped.startswith("|") and stripped.endswith("|"):
|
||||||
|
close_lists()
|
||||||
|
table_lines: list[str] = []
|
||||||
|
while i < len(lines) and lines[i].strip().startswith("|"):
|
||||||
|
table_lines.append(lines[i].strip())
|
||||||
|
i += 1
|
||||||
|
out.append(_render_table(table_lines))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if stripped.startswith("### "):
|
||||||
|
close_lists()
|
||||||
|
out.append(f"<h3>{_inline(stripped[4:])}</h3>")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if stripped.startswith("## "):
|
||||||
|
close_lists()
|
||||||
|
out.append(f"<h2>{_inline(stripped[3:])}</h2>")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if stripped.startswith("# "):
|
||||||
|
close_lists()
|
||||||
|
out.append(f"<h1>{_inline(stripped[2:])}</h1>")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if re.match(r"^[-*]\s+", stripped):
|
||||||
|
if not in_ul:
|
||||||
|
close_lists()
|
||||||
|
out.append("<ul>")
|
||||||
|
in_ul = True
|
||||||
|
item_text = re.sub(r"^[-*]\s+", "", stripped)
|
||||||
|
out.append(f"<li>{_inline(item_text)}</li>")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if re.match(r"^\d+\.\s+", stripped):
|
||||||
|
if not in_ol:
|
||||||
|
close_lists()
|
||||||
|
out.append("<ol>")
|
||||||
|
in_ol = True
|
||||||
|
item_text = re.sub(r"^\d+\.\s+", "", stripped)
|
||||||
|
out.append(f"<li>{_inline(item_text)}</li>")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
close_lists()
|
||||||
|
para = stripped
|
||||||
|
i += 1
|
||||||
|
while i < len(lines):
|
||||||
|
nxt = lines[i].strip()
|
||||||
|
if not nxt or nxt == "---" or nxt.startswith("#") or nxt.startswith("|") or re.match(r"^[-*]\s+", nxt):
|
||||||
|
break
|
||||||
|
para += " " + nxt
|
||||||
|
i += 1
|
||||||
|
out.append(f"<p>{_inline(para)}</p>")
|
||||||
|
|
||||||
|
close_lists()
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def _render_table(rows: list[str]) -> str:
|
||||||
|
if len(rows) < 2:
|
||||||
|
return ""
|
||||||
|
header = [c.strip() for c in rows[0].strip("|").split("|")]
|
||||||
|
body_rows = rows[2:] if len(rows) > 2 and re.match(r"^[\|\s:-]+$", rows[1]) else rows[1:]
|
||||||
|
parts = ["<table class=\"doc-table\">", "<thead><tr>"]
|
||||||
|
for cell in header:
|
||||||
|
parts.append(f"<th>{_inline(cell)}</th>")
|
||||||
|
parts.append("</tr></thead><tbody>")
|
||||||
|
for row in body_rows:
|
||||||
|
cells = [c.strip() for c in row.strip("|").split("|")]
|
||||||
|
parts.append("<tr>")
|
||||||
|
for cell in cells:
|
||||||
|
parts.append(f"<td>{_inline(cell)}</td>")
|
||||||
|
parts.append("</tr>")
|
||||||
|
parts.append("</tbody></table>")
|
||||||
|
return "".join(parts)
|
||||||
+2
-1
@@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
| 板块 | 路径 | 文档 |
|
| 板块 | 路径 | 文档 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 数据看板 | `/dashboard` | [风控说明.md](./风控说明.md) |
|
| 数据看板 | `/dashboard` | [风控说明.md](./风控说明.md)(看板内嵌摘要) |
|
||||||
|
| 风控说明 | `/risk-guide` | [风控说明.md](./风控说明.md)(完整页面) |
|
||||||
| 下单监控 | `/positions` | [ORDER_MONITOR.md](./ORDER_MONITOR.md) |
|
| 下单监控 | `/positions` | [ORDER_MONITOR.md](./ORDER_MONITOR.md) |
|
||||||
| 策略交易 | `/strategy` | [STRATEGY.md](./STRATEGY.md) |
|
| 策略交易 | `/strategy` | [STRATEGY.md](./STRATEGY.md) |
|
||||||
| 开单计划 | `/plans` | [PLANS.md](./PLANS.md) |
|
| 开单计划 | `/plans` | [PLANS.md](./PLANS.md) |
|
||||||
|
|||||||
+6
-6
@@ -43,13 +43,13 @@
|
|||||||
|
|
||||||
## 保证金占用上限
|
## 保证金占用上限
|
||||||
|
|
||||||
| 项 | 配置位置 | 默认值 |
|
| 项 | 配置位置 | 默认值 | 用途 |
|
||||||
|----|----------|--------|
|
|----|----------|--------|------|
|
||||||
| 新开仓上限 | 系统设置 `max_margin_pct` | 30% |
|
| 单仓保证金上限 | 系统设置 `max_margin_pct` | 30% | **新开仓**:拟开 + 已有占用,占权益不得超过此值 |
|
||||||
| 滚仓上限 | 系统设置 `roll_max_margin_pct` | 单独配置 |
|
| 综合保证金上限 | 系统设置 `roll_max_margin_pct` | 50% | **单仓模式**:滚仓/加仓合计上限;**多仓模式**:所有持仓合计上限 |
|
||||||
|
|
||||||
- 新开仓前计算:现有持仓保证金 + 拟开仓位保证金,占权益比例不得超过 `max_margin_pct`。
|
- 看板 **综合保证金占比** 的分母为 **50%(综合上限)**,不是 30%。详见 [风控说明.md](./风控说明.md#保证金占比核心规则)。
|
||||||
- **滚仓/顺势加仓** 使用 `roll_max_margin_pct` 单独收紧手数(见 [STRATEGY.md](./STRATEGY.md))。
|
- 新开仓前仍按 30% 收紧手数;滚仓/多仓合计按 50% 校验(见 [STRATEGY.md](./STRATEGY.md))。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+46
-18
@@ -1,14 +1,35 @@
|
|||||||
# 数据看板 · 风控说明
|
# 风控说明
|
||||||
|
|
||||||
**路径**:`/dashboard`(数据看板)· 风控说明卡片
|
**页面**:`/risk-guide`(顶栏「风控说明」)· 数据看板内嵌卡片同步展示摘要指标
|
||||||
|
|
||||||
本文说明看板 **风控说明** 区域各指标含义、颜色规则及对应配置。全局风控逻辑详见 [RISK.md](./RISK.md)。
|
本文说明账户 **保证金占比**、各风控指标含义、颜色规则及配置来源。全局风控逻辑详见 [RISK.md](./RISK.md)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 状态行(卡片顶部)
|
## 保证金占比(核心规则)
|
||||||
|
|
||||||
顶栏红色/绿色一行文字为 **当前风控结论**,例如:
|
系统设置中有两个保证金上限,默认 **单仓 30%**、**综合 50%**(`max_margin_pct` / `roll_max_margin_pct`)。
|
||||||
|
|
||||||
|
| 模式 | 判定 | 30%(单仓上限) | 50%(综合上限) |
|
||||||
|
|------|------|-----------------|-----------------|
|
||||||
|
| **单仓模式** | `MAX_ACTIVE_POSITIONS = 1` | **单仓保证金上限**:新开仓时,拟开仓位 + 已有持仓占用保证金,占权益不得超过 30% | **滚仓保证金上限**:滚仓/顺势加仓时,总占用保证金占权益不得超过 50% |
|
||||||
|
| **多仓模式** | `MAX_ACTIVE_POSITIONS > 1` | **单仓保证金上限**:每一新开仓仍按 30% 约束该笔/该品种保证金 | **多仓保证金上限**:所有持仓合计占用保证金占权益不得超过 50% |
|
||||||
|
|
||||||
|
### 看板如何展示
|
||||||
|
|
||||||
|
| 看板指标 | 含义 | 对比上限 |
|
||||||
|
|----------|------|----------|
|
||||||
|
| **综合保证金占比** | 当前 **全部持仓** 占用保证金 ÷ 账户权益 | 斜杠后为 **50%**(单仓模式=滚仓上限,多仓模式=多仓上限) |
|
||||||
|
| **单仓保证金上限** | 新开仓单笔/单品种保证金天花板 | 固定显示 **30%**(系统设置) |
|
||||||
|
| **滚仓保证金上限** / **多仓保证金上限** | 单仓模式下为滚仓专用;多仓模式下为合计上限 | 固定显示 **50%**(系统设置) |
|
||||||
|
|
||||||
|
> **示例**:权益 10 万、占用保证金 2.55 万 → 综合保证金占比 **25.55% / 50%**(不是 30%)。新开仓仍受 30% 单仓上限约束;滚仓或多仓合计最高可到 50%。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 状态行(看板卡片顶部)
|
||||||
|
|
||||||
|
顶栏一行文字为 **当前风控结论**,例如:
|
||||||
|
|
||||||
| 显示 | 含义 |
|
| 显示 | 含义 |
|
||||||
|------|------|
|
|------|------|
|
||||||
@@ -34,9 +55,9 @@
|
|||||||
| **冷静期(默认)** | 超限后默认冻结时长 | `.env` → `RISK_COOLING_HOURS_MANUAL`(默认 4h) |
|
| **冷静期(默认)** | 超限后默认冻结时长 | `.env` → `RISK_COOLING_HOURS_MANUAL`(默认 4h) |
|
||||||
| **复盘后冷静** | 填写复盘情绪日记后缩短的冷静期 | `.env` → `RISK_COOLING_HOURS_MANUAL_JOURNAL`(默认 1h) |
|
| **复盘后冷静** | 填写复盘情绪日记后缩短的冷静期 | `.env` → `RISK_COOLING_HOURS_MANUAL_JOURNAL`(默认 1h) |
|
||||||
| **冷静剩余** | 当前冷静期剩余时间 | 运行时计算 |
|
| **冷静剩余** | 当前冷静期剩余时间 | 运行时计算 |
|
||||||
| **综合保证金占比** | 占用保证金占权益比例 / 单仓上限 | 系统设置 `max_margin_pct` |
|
| **综合保证金占比** | 占用保证金占权益 / **综合上限(50%)** | 实时计算 + 系统设置 `roll_max_margin_pct` |
|
||||||
| **单仓保证金上限** | 新开仓允许的保证金占权益上限 | 系统设置 `max_margin_pct`(默认 30%) |
|
| **单仓保证金上限** | 新开仓保证金占权益上限 | 系统设置 `max_margin_pct`(默认 30%) |
|
||||||
| **综合保证金上限** | 滚仓/加仓时允许的更高保证金占比 | 系统设置 `roll_max_margin_pct` |
|
| **滚仓/多仓保证金上限** | 单仓=滚仓上限;多仓=合计上限 | 系统设置 `roll_max_margin_pct`(默认 50%) |
|
||||||
| **计仓模式** | 固定金额(以损定仓)或固定手数 | 系统设置 |
|
| **计仓模式** | 固定金额(以损定仓)或固定手数 | 系统设置 |
|
||||||
| **交易日切** | 统计日重置时刻 | `.env` → `TRADING_DAY_RESET_HOUR`(默认 8:00) |
|
| **交易日切** | 统计日重置时刻 | `.env` → `TRADING_DAY_RESET_HOUR`(默认 8:00) |
|
||||||
|
|
||||||
@@ -53,22 +74,22 @@
|
|||||||
|
|
||||||
### 综合保证金占比
|
### 综合保证金占比
|
||||||
|
|
||||||
显示格式:`已用% / 单仓上限%`
|
显示格式:`已用% / 综合上限%`(综合上限默认 **50%**)
|
||||||
|
|
||||||
| 已用占上限比例 | 已用部分颜色 |
|
| 已用占综合上限比例 | 已用部分颜色 |
|
||||||
|----------------|--------------|
|
|--------------------|--------------|
|
||||||
| < 85% | **绿色**(安全) |
|
| < 85% | **绿色**(安全) |
|
||||||
| 85% ~ 100% | **琥珀色**(接近上限) |
|
| 85% ~ 100% | **琥珀色**(接近上限) |
|
||||||
| ≥ 100% | **红色**(已达或超过单仓上限) |
|
| ≥ 100% | **红色**(已达或超过综合上限) |
|
||||||
|
|
||||||
斜杠后的 **上限数值** 为 **蓝色**,与「单仓保证金上限」一致。
|
斜杠后的 **50%** 为 **琥珀色**,与「滚仓/多仓保证金上限」一致。
|
||||||
|
|
||||||
### 单仓保证金上限 / 综合保证金上限
|
### 单仓 / 综合保证金上限
|
||||||
|
|
||||||
| 指标 | 数值颜色 |
|
| 指标 | 数值颜色 |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
| 单仓保证金上限 | **蓝色**(新开仓保证金天花板) |
|
| 单仓保证金上限(30%) | **蓝色** |
|
||||||
| 综合保证金上限 | **琥珀色**(滚仓/加仓专用,通常高于单仓上限) |
|
| 滚仓/多仓保证金上限(50%) | **琥珀色** |
|
||||||
|
|
||||||
### 持仓方向(持仓信息、平仓记录)
|
### 持仓方向(持仓信息、平仓记录)
|
||||||
|
|
||||||
@@ -79,10 +100,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 导航与设置
|
||||||
|
|
||||||
|
- 顶栏 **风控说明** 即本页(`/risk-guide`),内容由 `docs/风控说明.md` 同步渲染。
|
||||||
|
- 可在 **系统设置 → 导航显示** 中关闭「风控说明」入口;关闭后顶栏隐藏,直接访问 URL 将跳回下单监控。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 与全局风控的关系
|
## 与全局风控的关系
|
||||||
|
|
||||||
- 看板 **实时展示** 账户风控状态;下单前各板块仍调用 `assert_can_open()` 做相同校验。
|
- 看板 **实时展示** 账户风控状态;下单前各板块仍调用 `assert_can_open()` 做相同校验。
|
||||||
- **日持仓限制**、**日交易风险** 为新增维度,与「同时持仓上限」「冷静期」并列生效,任一超限即禁止新开仓。
|
- **日持仓限制**、**日交易风险** 与「同时持仓上限」「冷静期」并列生效,任一超限即禁止新开仓。
|
||||||
- **综合保证金占比** 使用 CTP 柜台权益与占用保证金实时计算;断线时可能短暂显示 `—`。
|
- **综合保证金占比** 使用 CTP 柜台权益与占用保证金实时计算;断线时可能短暂显示 `—`。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -92,6 +120,6 @@
|
|||||||
| 文档 | 内容 |
|
| 文档 | 内容 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| [RISK.md](./RISK.md) | 全局账户风控规则与 env 变量 |
|
| [RISK.md](./RISK.md) | 全局账户风控规则与 env 变量 |
|
||||||
| [SETTINGS.md](./SETTINGS.md) | 保证金上限、计仓模式等系统设置 |
|
| [SETTINGS.md](./SETTINGS.md) | 保证金上限、计仓模式、导航开关 |
|
||||||
| [ORDER_MONITOR.md](./ORDER_MONITOR.md) | 下单监控顶栏风控状态 |
|
| [ORDER_MONITOR.md](./ORDER_MONITOR.md) | 下单监控顶栏风控状态 |
|
||||||
| [INDEX.md](./INDEX.md) | 文档总索引 |
|
| [INDEX.md](./INDEX.md) | 文档总索引 |
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from typing import Callable
|
|||||||
# 可在系统设置中开关的导航项
|
# 可在系统设置中开关的导航项
|
||||||
NAV_TOGGLES: dict[str, str] = {
|
NAV_TOGGLES: dict[str, str] = {
|
||||||
"dashboard": "数据看板",
|
"dashboard": "数据看板",
|
||||||
|
"risk_guide": "风控说明",
|
||||||
"fees": "手续费配置",
|
"fees": "手续费配置",
|
||||||
"plans": "开单计划",
|
"plans": "开单计划",
|
||||||
"market": "行情K线",
|
"market": "行情K线",
|
||||||
|
|||||||
@@ -87,6 +87,18 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dash-risk-doc-link {
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-risk-doc-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.dash-risk-doc-ref code {
|
.dash-risk-doc-ref code {
|
||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
}
|
}
|
||||||
@@ -161,12 +173,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-risk-value .risk-margin-cap-inline {
|
.dashboard-risk-value .risk-margin-cap-inline {
|
||||||
color: #5eb8ff;
|
color: #c4a035;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] .dashboard-risk-value .risk-margin-cap-inline {
|
[data-theme="light"] .dashboard-risk-value .risk-margin-cap-inline {
|
||||||
color: #1d4ed8;
|
color: #b45309;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-risk-grid .stat-item {
|
.dashboard-risk-grid .stat-item {
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/* Copyright (c) 2025-2026 马建军. All rights reserved. 详见 LICENSE.zh-CN.txt */
|
||||||
|
|
||||||
|
.doc-page {
|
||||||
|
max-width: 52rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
line-height: 1.65;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h1 {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
padding-bottom: 0.45rem;
|
||||||
|
border-bottom: 1px solid var(--table-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h2 {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
margin: 1.35rem 0 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h3 {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
margin: 1rem 0 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content p {
|
||||||
|
margin: 0.45rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--table-border);
|
||||||
|
margin: 1.1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content ul,
|
||||||
|
.doc-content ol {
|
||||||
|
margin: 0.45rem 0 0.65rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content li {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content code {
|
||||||
|
font-size: 0.84em;
|
||||||
|
padding: 0.08rem 0.32rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--card-inner);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content a {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content .doc-xref {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content .doc-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0.65rem 0 0.85rem;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content .doc-table th,
|
||||||
|
.doc-content .doc-table td {
|
||||||
|
padding: 0.42rem 0.55rem;
|
||||||
|
border: 1px solid var(--table-border);
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content .doc-table th {
|
||||||
|
background: var(--card-inner);
|
||||||
|
color: var(--text-title);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content strong {
|
||||||
|
color: var(--text-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.doc-content {
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
.doc-content .doc-table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -196,6 +196,10 @@
|
|||||||
}
|
}
|
||||||
var marginPct = risk.margin_pct_used;
|
var marginPct = risk.margin_pct_used;
|
||||||
var maxMarginPct = lim.max_margin_pct;
|
var maxMarginPct = lim.max_margin_pct;
|
||||||
|
var rollMaxPct = lim.roll_max_margin_pct;
|
||||||
|
var isSingleMode = lim.position_mode === 'single'
|
||||||
|
|| (maxPos != null && Number(maxPos) <= 1);
|
||||||
|
var compositeCapLabel = isSingleMode ? '滚仓保证金上限' : '多仓保证金上限';
|
||||||
|
|
||||||
if (riskReasonEl) {
|
if (riskReasonEl) {
|
||||||
var reason = st.reason || (enabled ? '可新开仓' : '风控已关闭');
|
var reason = st.reason || (enabled ? '可新开仓' : '风控已关闭');
|
||||||
@@ -227,7 +231,7 @@
|
|||||||
{ label: '冷静剩余', value: fmtRemainSec(st.freeze_remaining_sec) },
|
{ label: '冷静剩余', value: fmtRemainSec(st.freeze_remaining_sec) },
|
||||||
{
|
{
|
||||||
label: '综合保证金占比',
|
label: '综合保证金占比',
|
||||||
valueHtml: riskMarginPctHtml(marginPct, maxMarginPct),
|
valueHtml: riskMarginPctHtml(marginPct, rollMaxPct),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '单仓保证金上限',
|
label: '单仓保证金上限',
|
||||||
@@ -235,8 +239,8 @@
|
|||||||
valueClass: 'risk-cap-single',
|
valueClass: 'risk-cap-single',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '综合保证金上限',
|
label: compositeCapLabel,
|
||||||
value: lim.roll_max_margin_pct != null ? fmtNum(lim.roll_max_margin_pct) + '%' : '—',
|
value: rollMaxPct != null ? fmtNum(rollMaxPct) + '%' : '—',
|
||||||
valueClass: 'risk-cap-roll',
|
valueClass: 'risk-cap-roll',
|
||||||
},
|
},
|
||||||
{ label: '计仓模式', value: sizingDetail },
|
{ label: '计仓模式', value: sizingDetail },
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
<nav class="site-nav" id="site-nav">
|
<nav class="site-nav" id="site-nav">
|
||||||
<a href="{{ url_for('positions') }}" class="{% if request.endpoint in ('positions', 'trade_page', 'recommend_page') %}active{% endif %}">下单监控</a>
|
<a href="{{ url_for('positions') }}" class="{% if request.endpoint in ('positions', 'trade_page', 'recommend_page') %}active{% endif %}">下单监控</a>
|
||||||
{% if nav_items.dashboard %}<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint == 'dashboard' %}active{% endif %}">数据看板</a>{% endif %}
|
{% if nav_items.dashboard %}<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint == 'dashboard' %}active{% endif %}">数据看板</a>{% endif %}
|
||||||
|
{% if nav_items.risk_guide %}<a href="{{ url_for('risk_guide') }}" class="{% if request.endpoint == 'risk_guide' %}active{% endif %}">风控说明</a>{% endif %}
|
||||||
{% if nav_items.strategy %}<a href="{{ url_for('strategy_page') }}" class="{% if request.endpoint in ('strategy_page', 'strategy_records_page') %}active{% endif %}">策略交易</a>{% endif %}
|
{% if nav_items.strategy %}<a href="{{ url_for('strategy_page') }}" class="{% if request.endpoint in ('strategy_page', 'strategy_records_page') %}active{% endif %}">策略交易</a>{% endif %}
|
||||||
{% if nav_items.plans %}<a href="{{ url_for('plans') }}" class="{% if request.endpoint == 'plans' %}active{% endif %}">开单计划</a>{% endif %}
|
{% if nav_items.plans %}<a href="{{ url_for('plans') }}" class="{% if request.endpoint == 'plans' %}active{% endif %}">开单计划</a>{% endif %}
|
||||||
<a href="{{ url_for('keys') }}" class="{% if request.endpoint == 'keys' %}active{% endif %}">关键位监控</a>
|
<a href="{{ url_for('keys') }}" class="{% if request.endpoint == 'keys' %}active{% endif %}">关键位监控</a>
|
||||||
|
|||||||
@@ -35,7 +35,11 @@
|
|||||||
<div class="card dashboard-section dashboard-risk-card">
|
<div class="card dashboard-section dashboard-risk-card">
|
||||||
<h2 class="dashboard-risk-heading">
|
<h2 class="dashboard-risk-heading">
|
||||||
风控说明
|
风控说明
|
||||||
|
{% if nav_items.risk_guide %}
|
||||||
|
<a class="dash-risk-doc-link" href="{{ url_for('risk_guide') }}">完整说明</a>
|
||||||
|
{% else %}
|
||||||
<span class="text-muted dash-risk-doc-ref">· 详见 <code>docs/风控说明.md</code></span>
|
<span class="text-muted dash-risk-doc-ref">· 详见 <code>docs/风控说明.md</code></span>
|
||||||
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="dashboard-risk-reason" id="dash-risk-reason">加载中…</p>
|
<p class="dashboard-risk-reason" id="dash-risk-reason">加载中…</p>
|
||||||
<div class="stat-grid stat-grid-summary dashboard-risk-grid" id="dash-risk-grid"></div>
|
<div class="stat-grid stat-grid-summary dashboard-risk-grid" id="dash-risk-grid"></div>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}风控说明 - 国内期货 · 交易复盘系统{% endblock %}
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/doc.css') }}?v={{ asset_v }}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="card doc-page">
|
||||||
|
<article class="doc-content">
|
||||||
|
{{ doc_html|safe }}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -218,11 +218,11 @@
|
|||||||
<input name="fixed_amount" type="number" step="1" min="1" value="{{ fixed_amount }}">
|
<input name="fixed_amount" type="number" step="1" min="1" value="{{ fixed_amount }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>保证金占用上限(%)</label>
|
<label>单仓保证金上限(%)</label>
|
||||||
<input name="max_margin_pct" type="number" step="1" min="1" max="100" value="{{ max_margin_pct }}">
|
<input name="max_margin_pct" type="number" step="1" min="1" max="100" value="{{ max_margin_pct }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>滚仓保证金占用上限(%)</label>
|
<label>综合保证金上限(%)</label>
|
||||||
<input name="roll_max_margin_pct" type="number" step="1" min="1" max="100" value="{{ roll_max_margin_pct }}">
|
<input name="roll_max_margin_pct" type="number" step="1" min="1" max="100" value="{{ roll_max_margin_pct }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@@ -236,8 +236,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary" style="margin-top:.75rem">保存交易设置</button>
|
<button type="submit" class="btn-primary" style="margin-top:.75rem">保存交易设置</button>
|
||||||
<p class="hint" style="margin-top:.75rem;margin-bottom:0">
|
<p class="hint" style="margin-top:.75rem;margin-bottom:0">
|
||||||
开仓保证金上限用于开仓校验与品种最大手数估算(默认 30%)。固定金额计仓时<strong>先按止损算手数,再按保证金上限收紧</strong>。
|
单仓保证金上限(默认 30%)用于<strong>新开仓</strong>校验与最大手数估算;综合保证金上限(默认 50%)在单仓模式下为滚仓合计上限、多仓模式下为全部持仓合计上限。固定金额计仓时<strong>先按止损算手数,再按单仓上限收紧</strong>。
|
||||||
滚仓保证金上限为滚仓后<strong>总持仓</strong>占用上限(默认 50%,可在下方修改)。
|
|
||||||
<strong>移动保本</strong>:达 1R 后止损移至开仓价 ± N 跳。
|
<strong>移动保本</strong>:达 1R 后止损移至开仓价 ± N 跳。
|
||||||
<strong>挂单超时</strong>:限价开仓未成交时,超过设定分钟数自动向柜台撤单(1~60 分钟)。
|
<strong>挂单超时</strong>:限价开仓未成交时,超过设定分钟数自动向柜台撤单(1~60 分钟)。
|
||||||
<span class="text-muted">{{ small_account_margin_rec.label }}。</span>
|
<span class="text-muted">{{ small_account_margin_rec.label }}。</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user