feat: 新增文档Tab(📚文档)——开源项目式文档体系(VERSIONS.md运营者版本变更+INDEX.md目录树+docs.json spec),让知微通过文档理解系统变化

This commit is contained in:
hmo
2026-08-11 10:26:00 +08:00
parent 4e324cca8c
commit 7d75d54734
5 changed files with 438 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
# MoFin 文档完整目录(INDEX
> 文档总导航树。按主题分类,每个文档标注用途和阅读对象。
> 维护原则:新增/归档文档时同步更新本目录。
---
## 📖 入门必读(新接入者按顺序读)
| # | 文档 | 内容 | 阅读对象 |
|---|------|------|---------|
| 1 | [README.md](README.md) | 文档中心封面(导航入口)| 所有人 |
| 2 | [VERSIONS.md](VERSIONS.md) | **版本变更记录**(给运营者的详细变更说明)| 知微/老莫 |
| 3 | [portfolio-data-model.md](portfolio-data-model.md) | 核心数据模型:表结构/币种/公式 | 研发 |
| 4 | [DEVELOPMENT_STANDARDS.md](DEVELOPMENT_STANDARDS.md) | 开发规范(代码结构/DB/流程)| 研发 |
| 5 | [QUICKSTART.md](QUICKSTART.md) | 快速操作手册 | 运维 |
---
## 📊 运维参考(日常操作)
| 文档 | 内容 |
|------|------|
| [QUICKSTART.md](QUICKSTART.md) | 快速操作手册 |
| [DEPLOY.md](DEPLOY.md) | 部署指南(本地→Gitea→246 工作流)|
| [DASHBOARD.md](DASHBOARD.md) | Dashboard API 参考 |
| [HEALTH-PIPELINE.md](HEALTH-PIPELINE.md) | 健康监控管线 |
| [doc-audit-20260811.md](doc-audit-20260811.md) | 文档治理核实报告(15 份文档对照系统)|
| [system-audit-20260810.md](system-audit-20260810.md) | 系统彻查报告(含处理状态追踪)|
---
## 📈 策略研究(2026-08 起)
| 文档 | 内容 |
|------|------|
| [strategy_research_methodology.md](strategy_research_methodology.md) | **研究方法论**:由果及因/12维/铁律/支撑压力 |
| [predictive_oversold_strategy.md](predictive_oversold_strategy.md) | **预测超跌反弹策略 v5**(年化 18.57%|
| [deployment-plan-predictive-oversold.md](deployment-plan-predictive-oversold.md) | **部署计划**(整合 MoFin 全流程)|
| [cron-architecture-review-20260811.md](cron-architecture-review-20260811.md) | **Cron 架构审查与重构设计**385 行)|
| [cron-review-poversold-20260811.md](cron-review-poversold-20260811.md) | Cron 梳理(对照 p_oversold 部署)|
| [research/methodology.md](research/methodology.md) | 方法论总纲(铁律 0-5|
| [research/research-log.md](research/research-log.md) | 研究日志 |
| [research/research-scripts.md](research/research-scripts.md) | 研究脚本清单 |
| [research/research-status.md](research/research-status.md) | 策略现状与待办 |
| [research/2026-08-06-low-drawdown-study.md](research/2026-08-06-low-drawdown-study.md) | 低回撤组合研究(3/6/12 定义)|
| [research/2026-08-08-experience-lessons.md](research/2026-08-08-experience-lessons.md) | 研究经验教训 |
---
## ⚙️ 系统机制(核心设计文档)
| 文档 | 内容 |
|------|------|
| [dev-spec.md](dev-spec.md) | **开发规范总纲**(十条红线/双轨同源/自检体系)|
| [DEVELOPMENT_STANDARDS.md](DEVELOPMENT_STANDARDS.md) | 开发规范(代码/DB/流程 v1.1|
| [SELF_GROWTH_SYSTEM.md](SELF_GROWTH_SYSTEM.md) | 自成长系统设计(四层循环)|
| [lifecycle-management.md](lifecycle-management.md) | 对象生命周期管理(信号→候选→自选→持仓)|
| [strategy-review-loop.md](strategy-review-loop.md) | 策略复盘闭环(三层评估)|
| [morning-health-check.md](morning-health-check.md) | 体检机制(8类40项)|
| [zhiwei-ops-discipline.md](zhiwei-ops-discipline.md) | 知微运维纪律(deploy_guard 机制)|
---
## 🗃 历史文档(archive/
旧的设计文档、需求文档、已废弃组件文档。仅供参考。
- 含 SYSTEM_ARCHITECTURE(旧版)、TDX relay、xiaoguo、cron-catalog 等
---
## 📋 一次性文档(decisions/
决策记录(spec/dashboard 引入等)。
---
## 目录维护
- 新增文档 → 按主题加入对应分类
- 归档文档 → 从本目录移除,移 archive/
- 大版本变更 → 同时更新 CHANGELOG-VERSIONS.md
- 本文档与 Dashboard「📚 文档」Tab 联动(Tab 读取本目录树)
+138
View File
@@ -0,0 +1,138 @@
# MoFin 版本变更记录(VERSIONS
> **给运营者(知微)的版本变更说明**。每次系统有改动,都记录在这里,
> 用**你能理解的语言**说明:系统变了什么、为什么变、你需要注意什么。
> 按时间倒序(最新在前)。
---
## 阅读指南
- **每条变更 = 一个完整的改动**(不分大小,统一记录)
- 每条包含:**改了什么**(通俗)+ **为什么改** + **对你的影响**
- 技术细节见 `CHANGELOG.md`(开发者参考)
---
## 2026-08-11 — Cron 架构统一重构
### 改了什么
MoFin 的定时任务系统做了全面梳理:
1. **策略扫描器独立了**——以前"超跌扫描""恐慌买扫描"是被塞进"市场数据采集"里一起跑的,现在它们各自独立调度(9:35 交易日自动运行)
2. **新建了公共代码库**——把各个策略共用的计算逻辑(技术指标、行情数据获取)抽出来,放到公共模块,避免每个策略重复写一遍
3. **清理了冗余任务**——停掉了几个重复/过时的定时任务(如午间重复采集、死掉的旧调度)
4. **新策略扫描器已就绪**——"预测超跌反弹"新策略的扫描代码已写好并验证(有大盘弱势判断,当前大盘不弱所以不会误报)
### 为什么改
之前 cron 系统混乱:策略扫描器嵌在数据采集器里、代码多处重复、死配置残留——这阻碍了"预测超跌反弹"新策略的引入。这次重构让架构清晰,新策略可以顺畅接入。
### 对你的影响
-**定时任务更清晰**:每个任务职责明确,不再有嵌套调用
-**新策略即将上线**:超跌反弹扫描器已就绪,等你批准后即可启用
-**假告警减少**:修了价格监控在集合竞价时段(9:15-9:25)误报暴跌的问题
- ⚠️ **无需你操作**:本次是后台架构调整,不影响你的日常操作
---
## 2026-08-11 — 价格监控竞价期假告警修复
### 改了什么
价格监控原本在**集合竞价时段**(9:15-9:25)也会触发暴跌告警,导致误报(今天 9:17 误报睿创微纳暴跌-20%,实际正常)。
### 为什么改
集合竞价时段的价格是虚拟撮合价,不是真实成交,不该触发告警。
### 对你的影响
- ✅ 集合竞价时段(9:15-9:25)不会再收到暴跌告警
- ✅ 只有连续竞价时段(9:30-11:30、13:00-15:00)才触发告警
- ✅ 之前的假告警(睿创微纳)已确认是误报,实际价格正常
---
## 2026-08-11 — LLM 通道切换
### 改了什么
MoFin 的 AI 调用(知微分析、简报生成等)的 API 通道从 opencode.ai 直连 key6 切换为 key1。
### 为什么改
key6 失效导致 LLM 调用 401 错误,简报/评估停摆。切换 key1 后恢复。
### 对你的影响
- ✅ 知微的日报、简报、策略评估恢复正常
- ✅ 无需操作
---
## 2026-08-10 — Cron 系统审视整理
### 改了什么
暂停了 5 个过时/冗余的定时任务(系统健康检查旧版、自选买入区提醒、300308 盯盘、小果市场筛选),并修复了"gateway 看门狗反噬死循环"(8/3 起导致所有 AI 任务停摆的根因)。
### 对你的影响
- ✅ 所有 AI 驱动的定时任务(简报、评估、复盘)恢复正常
- ✅ 消除了无意义的任务空转
---
## 2026-07-19 — XMPP Bot 断线重连修复
### 改了什么
知微的 XMPP Bot 断线后会无限重连导致崩溃,现已修复(重连失败后最多试一次,不会爆栈)。
### 对你的影响
- ✅ 知微 Bot 断线后会自动恢复,不会彻底挂掉
---
## 2026-07-14 — 价格监控数据库死锁根治
### 改了什么
价格监控写数据库时偶发死锁导致卡死,现已根治(加重试 + 超时保护)。
### 对你的影响
- ✅ 价格监控不再卡死,持仓价格更新更稳定
---
## 2026-07-03 — 数据存储架构升级
### 改了什么
系统数据从 JSON 文件迁移到 SQLite 数据库,统一了数据存储;同时修正了港股/A股的币种处理(港股存港币,汇总时自动转人民币)。
### 对你的影响
- ✅ 数据不再出现 JSON/数据库不一致
- ✅ 港股持仓显示正确(港币计价,汇总自动转人民币)
---
## 2026-07-01 — 统一数据读取
### 改了什么
所有脚本统一通过 mo_data 读取数据,不再各自直接读文件。
### 对你的影响
- ✅ 数据口径一致,不会出现"这个报表对不上那个报表"
---
## 历史版本(详见 CHANGELOG.md
| 日期 | 概要 |
|------|------|
| 2026-07-13 | XMPP Bot 非阻塞 + 深套规则重构 |
| 2026-07-10 | 知微 XMPP Bot 离线修复 |
| 2026-07-09 | 信号堆积修复 + 监控盲区修复 |
| 2026-07-06 | 全面系统修复(知微)|
| 2026-06-30 | 初始架构重构(mo_models 统一数据模型)|
---
## 维护说明
- **每次系统改动,必须更新本文档**(在顶部新增条目)
- **格式**:改了什么 + 为什么改 + 对你的影响
- **面向运营者**:避免技术术语,用知微能理解的语言
- **技术细节**:见 CHANGELOG.md(开发者参考)
> 维护人:Sisyphus(小小莫)
> 原则:让运营者不用看代码,就能完全理解系统每次变化。
+74
View File
@@ -1834,6 +1834,80 @@ def api_evolution_combo():
return jsonify({'error': str(e)}), 500
# ── 📚 文档 Tab 端点(2026-08-11 新增,开源项目式文档体系)──
@app.route("/api/docs/index")
def api_docs_index():
"""文档目录树:扫描 docs/ 返回分类结构(README/VERSIONS/INDEX + 按主题)"""
try:
if not DOCS_DIR.exists():
return jsonify({"ok": False, "error": "docs/ not found"})
index = []
# 核心文档(置顶)
core = ["README.md", "VERSIONS.md", "INDEX.md"]
for f in core:
p = DOCS_DIR / f
if p.exists():
index.append({"path": f, "title": f.replace(".md", ""), "category": "📖 核心", "core": True})
# 按主题分类
cats = {
"运维参考": ["QUICKSTART", "DEPLOY", "DASHBOARD", "HEALTH-PIPELINE", "doc-audit", "system-audit"],
"策略研究": ["strategy_research", "predictive_oversold", "deployment-plan", "cron-", "research/"],
"系统机制": ["dev-spec", "DEVELOPMENT_STANDARDS", "SELF_GROWTH", "lifecycle", "strategy-review", "morning-health", "zhiwei-ops", "portfolio-data-model"],
"决策记录": ["decisions/"],
}
for md in sorted(DOCS_DIR.rglob("*.md")):
rel = str(md.relative_to(DOCS_DIR))
if rel in core or "archive" in rel or "backup" in rel:
continue
cat = "📚 其他"
for cname, kws in cats.items():
if any(kw in rel for kw in kws):
cat = cname
break
index.append({"path": rel, "title": rel.replace(".md", "").replace("/", " / "), "category": cat})
return jsonify({"ok": True, "index": index, "count": len(index)})
except Exception as e:
return jsonify({"ok": False, "error": str(e)[:100]})
@app.route("/api/docs/versions")
def api_docs_versions():
"""版本变更列表:解析 VERSIONS.md 的每个变更条目"""
try:
f = DOCS_DIR / "VERSIONS.md"
if not f.exists():
return jsonify({"ok": False, "error": "VERSIONS.md not found"})
content = f.read_text(encoding="utf-8")
versions = []
# 解析 "## 2026-08-11 — 标题" 格式
import re
parts = re.split(r"^##\s+", content, flags=re.M)
for p in parts:
if not p.strip() or p.startswith("阅读指南") or p.startswith("维护说明"):
continue
lines = p.strip().split("\n", 1)
title = lines[0].strip()
body = lines[1] if len(lines) > 1 else ""
versions.append({"title": title, "body": body[:4000]})
return jsonify({"ok": True, "versions": versions, "count": len(versions)})
except Exception as e:
return jsonify({"ok": False, "error": str(e)[:100]})
@app.route("/api/docs/read")
def api_docs_read():
"""读取指定文档内容(markdown"""
from flask import request
path = request.args.get("path", "")
if not path or ".." in path or path.startswith("/"):
return jsonify({"ok": False, "error": "invalid path"})
f = DOCS_DIR / path
if not f.exists() or not f.suffix == ".md":
return jsonify({"ok": False, "error": "doc not found"})
return jsonify({"ok": True, "content": f.read_text(encoding="utf-8")})
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8899))
print(f"🚀 MoFin Dashboard → http://0.0.0.0:{port}")
+51
View File
@@ -0,0 +1,51 @@
{
"module": "docs",
"version": "1.0",
"purpose": "MoFin 文档体系入口。开源项目式帮助文档:README 封面 + 完整目录 + 版本变更说明。让研发者和运营者(知微)都能通过文档了解系统全貌和最新变化。",
"human_help": {
"title": "📚 文档中心 — 系统帮助与版本变更",
"description": [
"MoFin 文档体系采用开源项目标准组织:README 封面导航 + 完整目录树 + 大版本变更说明。",
"研发者(小小莫/知微)通过文档理解系统各模块;运营者(知微)通过版本变更说明掌握每次大改动的内容和影响。",
"所有文档存放在 docs/ 目录,按主题分类。"
],
"usage": [
"打开 Dashboard 的「📚 文档」Tab,浏览文档目录",
"左侧目录树选择文档,右侧查看内容",
"「版本变更」页签查看每个大版本的改动说明(v1.0 初始架构、v2.0 JSON→DB、v3.0 策略体系、v4.0 自成长、v5.0 纯DB、v6.0 Cron 架构重构)",
"「版本变更」让知微知道每次大改动做了什么、影响什么"
],
"troubleshooting": [
"文档加载失败 → 检查 docs/ 目录权限和 md 文件存在性",
"版本变更空白 → 检查 docs/CHANGELOG-VERSIONS.md 是否存在",
"找不到某文档 → 查看 INDEX.md 完整目录"
]
},
"ai_spec": {
"apis": [
{"method": "GET", "path": "/api/docs/index", "returns": "{ok, index:[{path, title, category}]} — 文档目录树"},
{"method": "GET", "path": "/api/docs/versions", "returns": "{ok, versions:[{version, date, title, summary}]} — 版本变更列表"},
{"method": "GET", "path": "/api/docs/read?path=<path>", "returns": "{ok, content} — 读取指定文档内容(markdown"}
],
"constraints": [
"所有文档必须放在 docs/ 目录,不允许散落",
"每个大版本必须写入 CHANGELOG-VERSIONS.md",
"README.md 是入口封面,必须保持最新导航",
"INDEX.md 是完整目录树,必须反映实际文档结构"
],
"dependencies": [
"docs/ 目录(文档源)",
"CHANGELOG-VERSIONS.md(版本变更源)",
"INDEX.md(目录树源)"
],
"file_layout": {
"docs/README.md": "文档中心封面(导航入口)",
"docs/INDEX.md": "完整文档目录树(按主题分类)",
"docs/CHANGELOG-VERSIONS.md": "大版本变更说明(v1.0/v2.0/.../v6.0",
"docs/GUIDE.md": "系统全貌指南(给研发者的详细说明)",
"docs/<topic>/*.md": "按主题组织的文档"
}
}
}
+92
View File
@@ -71,6 +71,7 @@ body { background: #0f0f13; color: #e2e8f0; }
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="health">🏥 健康</button>
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="principles">📐 开发原则</button>
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="research">📋 研究</button>
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="docs">📚 文档</button>
<a href="/upload" class="tab-btn px-4 py-2 text-sm rounded-lg" style="text-decoration:none;color:#fbbf24;border-color:#fbbf24">📸 上传</a>
</div>
@@ -84,6 +85,7 @@ body { background: #0f0f13; color: #e2e8f0; }
<div id="tab-signals" class="tab-content hidden"></div>
<div id="tab-health" class="tab-content hidden"></div>
<div id="tab-research" class="tab-content hidden"></div>
<div id="tab-docs" class="tab-content hidden"></div>
<div id="tab-principles" class="tab-content hidden">
<div class="flex gap-1 mb-4 bg-slate-900/50 rounded-xl p-1 border border-slate-800/50" id="princBar">
<button class="princ-btn active px-4 py-1.5 text-sm rounded-lg" data-princ="spec">G 规范</button>
@@ -261,6 +263,7 @@ function renderTab(name) {
else if (name === 'principles') renderPrinciples();
else if (name === 'watch') renderWatch();
else if (name === 'research') renderResearch();
else if (name === 'docs') renderDocs();
}
// ── Data Fetching ──
@@ -1926,6 +1929,95 @@ function inMd(t) {
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="text-[#58a6ff]" target="_blank">$1</a>');
}
// ── 📚 文档 Tab2026-08-11 新增,开源项目式文档体系)──
let _docsState = { loaded: false, currentPath: null, view: 'tree' }; // tree | versions | read
async function renderDocs() {
const el = document.getElementById('tab-docs');
if (!el) return;
el.innerHTML =
'<div class="flex gap-2 items-center mb-3 border-b border-slate-800 pb-2">' +
'<button class="doc-nav-btn px-3 py-1.5 text-sm rounded-lg border border-slate-700 ' + (_docsState.view === 'tree' ? 'bg-slate-800 text-blue-400' : 'text-slate-400') + '" onclick="_docsView(\'tree\')">📖 目录</button>' +
'<button class="doc-nav-btn px-3 py-1.5 text-sm rounded-lg border border-slate-700 ' + (_docsState.view === 'versions' ? 'bg-slate-800 text-blue-400' : 'text-slate-400') + '" onclick="_docsView(\'versions\')">📜 版本变更</button>' +
'<span class="text-xs text-slate-500 ml-2">文档中心:帮助文档 + 版本变更说明(面向运营者)</span>' +
'</div>' +
'<div id="docs-body" class="text-sm"></div>';
if (_docsState.view === 'tree') {
await _renderDocsTree();
} else if (_docsState.view === 'versions') {
await _renderDocsVersions();
}
}
async function _docsView(view) {
_docsState.view = view;
await renderDocs();
}
async function _renderDocsTree() {
const body = document.getElementById('docs-body');
const data = await fetchJSON('/api/docs/index');
if (!data.ok) {
body.innerHTML = '<div class="text-red-400">' + (data.error || '加载失败') + '</div>';
return;
}
const byCat = {};
data.index.forEach(d => {
byCat[d.category] = byCat[d.category] || [];
byCat[d.category].push(d);
});
let html = '';
for (const [cat, docs] of Object.entries(byCat)) {
html += '<div class="mb-4">' +
'<div class="text-xs font-semibold text-slate-400 mb-1 uppercase tracking-wide">' + cat + '</div>' +
'<div class="space-y-0.5">' +
docs.map(d =>
'<div class="cursor-pointer hover:bg-slate-800/60 px-2 py-1 rounded text-slate-300" onclick="_docsRead(\'' + d.path + '\')">' +
(d.core ? '⭐ ' : '📄 ') + d.title + '</div>'
).join('') +
'</div></div>';
}
body.innerHTML = html;
}
async function _renderDocsVersions() {
const body = document.getElementById('docs-body');
const data = await fetchJSON('/api/docs/versions');
if (!data.ok) {
body.innerHTML = '<div class="text-red-400">' + (data.error || '加载失败') + '</div>';
return;
}
body.innerHTML = data.versions.map(v => {
const parts = v.title.split('—');
const date = parts[0].trim();
const title = parts.slice(1).join('—').trim() || v.title;
return '<div class="mb-4 border-l-2 border-blue-700 pl-3">' +
'<div class="text-xs text-slate-500">' + date + '</div>' +
'<div class="font-semibold text-slate-200 mb-1">' + title + '</div>' +
'<div class="text-slate-400 whitespace-pre-wrap text-xs leading-relaxed">' + v.body + '</div>' +
'</div>';
}).join('');
}
async function _docsRead(path) {
_docsState.view = 'read';
_docsState.currentPath = path;
const body = document.getElementById('docs-body');
const navBar = body.closest('#tab-docs').querySelector('.flex.gap-2');
// 更新导航显示当前路径
const data = await fetchJSON('/api/docs/read?path=' + encodeURIComponent(path));
if (!data.ok) {
body.innerHTML = '<div class="text-red-400">' + (data.error || '加载失败') + '</div>';
return;
}
body.innerHTML =
'<div class="mb-2">' +
'<button class="text-xs text-blue-400 hover:underline" onclick="_docsView(\'tree\')">← 返回目录</button>' +
'<span class="text-xs text-slate-500 ml-2">' + path + '</span>' +
'</div>' +
'<div class="prose-invert bg-slate-900/50 border border-slate-800 rounded-xl p-4 whitespace-pre-wrap text-sm leading-relaxed text-slate-300 font-mono">' + inMd(data.content) + '</div>';
}
function renderResearch() {
const el = document.getElementById('tab-research');
if (!el) return;