MoFin 初始提交

完整数据采集+分析管道:
- market_watch.py:90行业板块采集(同花顺/东方财富)
- 市场精选推荐 cron:全市场分析+候选池+星级推荐
- price_monitor.py:持仓/自选高频价格监控
- refresh_mtf_cache.py:多周期K线缓存
- 策略评估/知识萃取管道

文档:docs/ 含完整需求+架构设计
注意:尚未配置 git remote,笑笑接手后自行配置
This commit is contained in:
知微 (MoFin)
2026-06-20 12:04:21 +08:00
commit aa0f740381
950 changed files with 189006 additions and 0 deletions
+382
View File
@@ -0,0 +1,382 @@
"""Dashboard 提示词管理视图 —— 集成到 MoFin Web Dashboard
注册 API 路由和前端页面。
"""
import json
from pathlib import Path
from datetime import datetime
from .registry import (
list_prompts, get_prompt, add_prompt, update_prompt,
add_version, set_active_version, get_version_history, get_categories,
)
from .tracking import (
get_strategy_version_stats, get_associations_for_stock,
)
from .analytics import (
get_prompt_version_comparison, generate_report,
)
from .models import PromptDef, PromptVersion
def register_routes(server):
"""在主 server.py 中调用,注册所有 /api/prompts/* 路由
用法:
from prompt_manager.dashboard_views import register_routes
register_routes(app) # app = Flask instance
"""
# ═══════════════════════════════════════════
# API: 提示词列表
# ═══════════════════════════════════════════
@server.route("/api/prompts")
def api_prompts_list():
from flask import request, jsonify
category = request.args.get("category")
prompts = list_prompts(category)
return jsonify({
"prompts": prompts,
"categories": get_categories(),
})
# ═══════════════════════════════════════════
# API: 单个提示词详情
# ═══════════════════════════════════════════
@server.route("/api/prompts/<prompt_id>")
def api_prompt_detail(prompt_id):
from flask import jsonify
from prompt_manager.registry import get_version_content
prompt = get_prompt(prompt_id)
if not prompt:
return jsonify({"error": "not found"}), 404
# 从文件中加载完整内容(registry.json 只存了摘要)
prompt_dict = prompt.to_dict()
for v in prompt_dict.get("versions", []):
if v.get("content_path"):
full = get_version_content(prompt_id, v["version"])
if full:
v["content"] = full
else:
# 回退:从文件直接读取
cp = v["content_path"]
try:
with open(cp, encoding="utf-8") as f:
v["content"] = f.read()
except:
pass # 保持摘要
history = get_version_history(prompt_id)
return jsonify({
"prompt": prompt_dict,
"version_history": history,
})
# ═══════════════════════════════════════════
# API: 新增提示词
# ═══════════════════════════════════════════
@server.route("/api/prompts", methods=["POST"])
def api_prompt_create():
from flask import request, jsonify
data = request.get_json()
if not data or "id" not in data:
return jsonify({"error": "缺少 id"}), 400
now = datetime.now().isoformat()
prompt = PromptDef(
id=data["id"],
name=data.get("name", data["id"]),
description=data.get("description", ""),
category=data.get("category", ""),
locations=data.get("locations", []),
versions=[],
created_at=now,
updated_at=now,
current_version="",
)
add_prompt(prompt)
return jsonify({"status": "ok", "prompt": prompt.to_dict()})
# ═══════════════════════════════════════════
# API: 添加版本
# ═══════════════════════════════════════════
@server.route("/api/prompts/<prompt_id>/versions", methods=["POST"])
def api_prompt_add_version(prompt_id):
from flask import request, jsonify
data = request.get_json()
if not data or "version" not in data:
return jsonify({"error": "缺少 version"}), 400
now = datetime.now().isoformat()
version = PromptVersion(
version=data["version"],
label=data.get("label", data["version"]),
created_at=now,
changelog=data.get("changelog", ""),
content=data.get("content", ""),
content_path="",
author=data.get("author", "知微"),
status=data.get("status", "active"),
tags=data.get("tags", []),
)
try:
add_version(prompt_id, version)
return jsonify({"status": "ok"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
# ═══════════════════════════════════════════
# API: 切换活跃版本
# ═══════════════════════════════════════════
@server.route("/api/prompts/<prompt_id>/activate", methods=["POST"])
def api_prompt_activate(prompt_id):
from flask import request, jsonify
data = request.get_json()
if not data or "version" not in data:
return jsonify({"error": "缺少 version"}), 400
try:
set_active_version(prompt_id, data["version"])
return jsonify({"status": "ok"})
except ValueError as e:
return jsonify({"error": str(e)}), 400
# ═══════════════════════════════════════════
# API: 版本统计
# ═══════════════════════════════════════════
@server.route("/api/prompts/stats")
def api_prompt_stats():
from flask import jsonify
version_stats = get_strategy_version_stats()
return jsonify(version_stats)
# ═══════════════════════════════════════════
# API: 版本有效性对比
# ═══════════════════════════════════════════
@server.route("/api/prompts/effectiveness")
def api_prompt_effectiveness():
from flask import jsonify
comparison = get_prompt_version_comparison()
return jsonify(comparison)
# ═══════════════════════════════════════════
# API: 生成分析报告(文本)
# ═══════════════════════════════════════════
@server.route("/api/prompts/report")
def api_prompt_report():
from flask import jsonify
report = generate_report()
return jsonify({"report": report})
# ═══════════════════════════════════════════
# API: 获取某只股票的策略关联记录
# ═══════════════════════════════════════════
@server.route("/api/prompts/associations/<code>")
def api_prompt_associations(code):
from flask import jsonify
records = get_associations_for_stock(code)
return jsonify({"code": code, "records": records})
# ═══════════════════════════════════════════
# 前端页面模板
# ═══════════════════════════════════════════
def get_dashboard_tab_html():
"""返回提示词管理 Tab 的 HTML,嵌入到 Dashboard 的 <div id="prompt-manager-content"> 中"""
return """\
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">📝 提示词管理</h5>
<div>
<button class="btn btn-sm btn-outline-primary" onclick="loadPromptReport()">📊 版本有效性报告</button>
</div>
</div>
<div class="card-body" id="prompt-manager-body">
<div class="text-center text-muted py-4">加载中...</div>
</div>
</div>
</div>
</div>
<!-- 版本详情 Modal -->
<div class="modal fade" id="promptVersionModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="versionModalTitle">版本详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="versionModalBody"></div>
</div>
</div>
</div>
<!-- 报告 Modal -->
<div class="modal fade" id="promptReportModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">提示词版本有效性报告</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="promptReportBody" style="white-space: pre-wrap; font-family: monospace;"></div>
</div>
</div>
</div>
<script>
function loadPrompts() {
const body = document.getElementById('prompt-manager-body');
body.innerHTML = '<div class="text-center text-muted py-4">加载中...</div>';
Promise.all([
fetch('/api/prompts').then(r => r.json()),
fetch('/api/prompts/effectiveness').then(r => r.json())
]).then(([data, effectiveness]) => {
const prompts = data.prompts || [];
const categories = data.categories || {};
let html = '';
// 分类导航
const catSet = {};
prompts.forEach(p => { catSet[p.category] = true; });
html += '<div class="mb-3">';
html += '<span class="badge bg-secondary me-1" style="cursor:pointer" onclick="filterPrompts(\'\')">全部</span>';
Object.keys(catSet).sort().forEach(cat => {
html += `<span class="badge bg-info me-1" style="cursor:pointer" onclick="filterPrompts('${cat}')">${categories[cat] || cat}</span>`;
});
html += '</div>';
// 提示词列表
if (prompts.length === 0) {
html += '<div class="alert alert-info">还没有提示词记录,请先初始化注册表。</div>';
} else {
html += '<div class="table-responsive"><table class="table table-sm table-hover">';
html += '<thead><tr><th>提示词名称</th><th>分类</th><th>当前版本</th><th>版本数</th><th>策略数</th><th>成功率</th><th>操作</th></tr></thead><tbody>';
prompts.forEach(p => {
const verCount = (p.versions || []).length;
const currentVer = p.current_version || '-';
const eff = effectiveness[`${p.id}@${currentVer}`] || {};
const sr = eff.success_rate !== undefined ? eff.success_rate + '%' : '-';
const total = eff.total || 0;
html += `<tr data-category="${p.category}">
<td><strong>${p.name}</strong><br><small class="text-muted">${p.id}</small></td>
<td><span class="badge bg-light text-dark">${categories[p.category] || p.category}</span></td>
<td><span class="badge bg-primary">${currentVer}</span></td>
<td>${verCount}</td>
<td>${total}</td>
<td>${sr}</td>
<td>
<button class="btn btn-sm btn-outline-info" onclick="showPromptVersions('${p.id}')">版本</button>
<button class="btn btn-sm btn-outline-secondary" onclick="showPromptDetail('${p.id}')">详情</button>
</td>
</tr>`;
});
html += '</tbody></table></div>';
}
body.innerHTML = html;
}).catch(err => {
body.innerHTML = `<div class="alert alert-danger">加载失败: ${err.message}</div>`;
});
}
function filterPrompts(category) {
const rows = document.querySelectorAll('#prompt-manager-body table tbody tr');
rows.forEach(row => {
if (!category || row.dataset.category === category) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
function showPromptVersions(promptId) {
fetch(`/api/prompts/${promptId}`)
.then(r => r.json())
.then(data => {
const prompt = data.prompt || {};
const history = data.version_history || [];
let html = `<h6>${prompt.name} — 版本历史</h6>`;
html += '<table class="table table-sm"><thead><tr><th>版本</th><th>标签</th><th>状态</th><th>时间</th><th>变更说明</th></tr></thead><tbody>';
history.forEach(h => {
const statusBadge = h.is_current ? 'bg-success' :
h.status === 'deprecated' ? 'bg-warning text-dark' : 'bg-secondary';
html += `<tr>
<td><code>${h.version}</code></td>
<td>${h.label}</td>
<td><span class="badge ${statusBadge}">${h.is_current ? '当前' : h.status}</span></td>
<td><small>${h.created_at}</small></td>
<td><small>${h.changelog}</small></td>
</tr>`;
});
html += '</tbody></table>';
document.getElementById('versionModalTitle').textContent = prompt.name + ' - 版本历史';
document.getElementById('versionModalBody').innerHTML = html;
new bootstrap.Modal(document.getElementById('promptVersionModal')).show();
});
}
function showPromptDetail(promptId) {
fetch(`/api/prompts/${promptId}`)
.then(r => r.json())
.then(data => {
const prompt = data.prompt || {};
// 获取当前版本内容
const currentVer = prompt.current_version;
const currentVerData = (prompt.versions || []).find(v => v.version === currentVer);
let html = `<h6>${prompt.name}</h6>`;
html += `<p class="text-muted">${prompt.description || '无描述'}</p>`;
html += `<p><strong>ID:</strong> ${prompt.id} | <strong>分类:</strong> ${prompt.category} | <strong>当前版本:</strong> ${currentVer}`;
html += ` | <strong>位置:</strong> ${(prompt.locations || []).join(', ') || '-'}</p>`;
if (currentVerData) {
html += '<hr><h6>当前版本内容摘要</h6>';
html += `<pre style="max-height: 300px; overflow-y: auto; background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px;">${currentVerData.content || '无内容'}</pre>`;
}
document.getElementById('versionModalTitle').textContent = prompt.name + ' - 详情';
document.getElementById('versionModalBody').innerHTML = html;
new bootstrap.Modal(document.getElementById('promptVersionModal')).show();
});
}
function loadPromptReport() {
fetch('/api/prompts/report')
.then(r => r.json())
.then(data => {
document.getElementById('promptReportBody').textContent = data.report || '暂无数据';
new bootstrap.Modal(document.getElementById('promptReportModal')).show();
});
}
// 页面加载后自动加载
if (document.getElementById('prompt-manager-body')) {
loadPrompts();
}
</script>
"""