MoFin 初始提交
完整数据采集+分析管道: - market_watch.py:90行业板块采集(同花顺/东方财富) - 市场精选推荐 cron:全市场分析+候选池+星级推荐 - price_monitor.py:持仓/自选高频价格监控 - refresh_mtf_cache.py:多周期K线缓存 - 策略评估/知识萃取管道 文档:docs/ 含完整需求+架构设计 注意:尚未配置 git remote,笑笑接手后自行配置
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
"""MoFin 提示词管理器 — 集中管理所有知微用到的提示词
|
||||
|
||||
功能:
|
||||
1. 提示词注册表 — 所有提示词分类管理
|
||||
2. 版本管理 — 每次修改记录版本号/变更日志/内容
|
||||
3. 策略关联 — 每只股票的策略关联生成它的提示词版本
|
||||
4. 统计分析 — 哪个版本的提示词产生的策略最有效
|
||||
"""
|
||||
|
||||
from .registry import (list_prompts, get_prompt, add_prompt, update_prompt,
|
||||
delete_prompt, add_version, set_active_version,
|
||||
get_version_history, get_categories, get_version_content)
|
||||
from .tracking import record_strategy_generation, get_strategy_version_stats, \
|
||||
get_associations_for_stock, get_associations_for_prompt_version, \
|
||||
get_current_strategy_prompt_version
|
||||
from .analytics import analyze_prompt_version_effectiveness, \
|
||||
get_prompt_version_comparison, generate_report
|
||||
|
||||
__all__ = ["list_prompts", "get_prompt", "add_prompt", "add_version",
|
||||
"set_active_version", "get_version_history", "get_categories",
|
||||
"record_strategy_generation", "get_strategy_version_stats",
|
||||
"get_prompt_version_comparison", "generate_report"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,186 @@
|
||||
"""策略→提示词版本分析引擎
|
||||
|
||||
核心功能:将策略评估结果按提示词版本聚合,计算每个版本的准确率。
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .tracking import load_associations, get_associations_for_prompt_version
|
||||
from .registry import get_prompt, get_version_history
|
||||
|
||||
PROJECT_DIR = Path("/home/hmo/projects/MoFin")
|
||||
DECISIONS_PATH = PROJECT_DIR / "data" / "decisions.json"
|
||||
ACCURACY_PATH = PROJECT_DIR / "data" / "accuracy_stats.json"
|
||||
EVAL_PATH = PROJECT_DIR / "data" / "evaluation.json"
|
||||
|
||||
|
||||
def _load_json(path, default=None):
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return {} if default is None else default
|
||||
|
||||
|
||||
def analyze_prompt_version_effectiveness() -> dict:
|
||||
"""按提示词版本聚合策略评估结果
|
||||
|
||||
关联 decisions.json 中的 evaluation 字段和 associations.json 中的版本记录,
|
||||
计算出每个提示词版本的:
|
||||
- 生成的策略总数
|
||||
- 达到止盈数(成功)
|
||||
- 跌破止损数(失败)
|
||||
- 待验证数
|
||||
- 盈亏比平均值
|
||||
"""
|
||||
# 加载数据
|
||||
decisions = _load_json(DECISIONS_PATH, {"decisions": []})
|
||||
associations = load_associations().get("associations", [])
|
||||
|
||||
# 建立 code → 最新关联的映射
|
||||
code_to_pv = {} # code -> {prompt_id, version}
|
||||
for a in associations:
|
||||
code = a.get("code")
|
||||
if code and code not in code_to_pv:
|
||||
code_to_pv[code] = {
|
||||
"prompt_id": a.get("prompt_id"),
|
||||
"version": a.get("prompt_version"),
|
||||
}
|
||||
|
||||
# 按 prompt_id@version 分组统计
|
||||
version_stats = {}
|
||||
|
||||
for d in decisions.get("decisions", []):
|
||||
code = d.get("code")
|
||||
if not code:
|
||||
continue
|
||||
|
||||
pv = code_to_pv.get(code)
|
||||
if not pv:
|
||||
continue
|
||||
|
||||
key = f"{pv['prompt_id']}@{pv['version']}"
|
||||
if key not in version_stats:
|
||||
version_stats[key] = {
|
||||
"prompt_id": pv["prompt_id"],
|
||||
"version": pv["version"],
|
||||
"total": 0,
|
||||
"take_profit_hit": 0,
|
||||
"stop_loss_hit": 0,
|
||||
"in_entry_zone": 0,
|
||||
"pending": 0,
|
||||
"avg_rr": 0.0,
|
||||
"rr_sum": 0.0,
|
||||
"rr_count": 0,
|
||||
"stocks": [],
|
||||
}
|
||||
|
||||
vs = version_stats[key]
|
||||
vs["total"] += 1
|
||||
vs["stocks"].append(code)
|
||||
|
||||
# 从 evaluation 中读取状态
|
||||
evals = d.get("evaluation", [])
|
||||
for ev in evals:
|
||||
if isinstance(ev, dict) and ev.get("phase") == 1:
|
||||
theo = ev.get("theoretical", {})
|
||||
status = theo.get("status", "")
|
||||
if status == "take_profit_hit":
|
||||
vs["take_profit_hit"] += 1
|
||||
elif status == "stop_loss_hit":
|
||||
vs["stop_loss_hit"] += 1
|
||||
elif status == "in_entry_zone":
|
||||
vs["in_entry_zone"] += 1
|
||||
else:
|
||||
vs["pending"] += 1
|
||||
|
||||
# 盈亏比
|
||||
rr = d.get("rr_ratio")
|
||||
if rr is not None and isinstance(rr, (int, float)):
|
||||
vs["rr_sum"] += rr
|
||||
vs["rr_count"] += 1
|
||||
|
||||
# 计算平均盈亏比和成功率
|
||||
for key, vs in version_stats.items():
|
||||
if vs["rr_count"] > 0:
|
||||
vs["avg_rr"] = round(vs["rr_sum"] / vs["rr_count"], 2)
|
||||
|
||||
total_outcome = vs["take_profit_hit"] + vs["stop_loss_hit"]
|
||||
if total_outcome > 0:
|
||||
vs["success_rate"] = round(vs["take_profit_hit"] / total_outcome * 100, 1)
|
||||
else:
|
||||
vs["success_rate"] = None
|
||||
|
||||
del vs["rr_sum"]
|
||||
|
||||
return version_stats
|
||||
|
||||
|
||||
def get_prompt_version_comparison() -> dict:
|
||||
"""生成版本对比报告"""
|
||||
version_stats = analyze_prompt_version_effectiveness()
|
||||
|
||||
# 补充每个版本的标签信息
|
||||
for key, vs in version_stats.items():
|
||||
prompt = get_prompt(vs["prompt_id"])
|
||||
if prompt:
|
||||
for v in prompt.versions:
|
||||
if v.version == vs["version"]:
|
||||
vs["label"] = v.label
|
||||
vs["changelog"] = v.changelog
|
||||
vs["tags"] = v.tags
|
||||
break
|
||||
if "label" not in vs:
|
||||
vs["label"] = vs["version"]
|
||||
|
||||
return version_stats
|
||||
|
||||
|
||||
def generate_report() -> str:
|
||||
"""生成版本有效性报告文本"""
|
||||
comparison = get_prompt_version_comparison()
|
||||
|
||||
lines = []
|
||||
lines.append("📊 提示词版本有效性分析 | " + datetime.now().strftime("%Y-%m-%d"))
|
||||
lines.append("")
|
||||
|
||||
if not comparison:
|
||||
lines.append("暂无数据 — 请先运行策略生成和评估后重试")
|
||||
return "\n".join(lines)
|
||||
|
||||
# 按 prompt_id 分组显示
|
||||
by_prompt = {}
|
||||
for key, vs in comparison.items():
|
||||
pid = vs["prompt_id"]
|
||||
by_prompt.setdefault(pid, []).append(vs)
|
||||
|
||||
for pid, versions in sorted(by_prompt.items()):
|
||||
prompt = get_prompt(pid)
|
||||
lines.append(f"\n## {prompt.name if prompt else pid}")
|
||||
lines.append(f" {prompt.description if prompt else ''}")
|
||||
lines.append("")
|
||||
|
||||
# 按版本号排序
|
||||
versions.sort(key=lambda x: x["version"])
|
||||
|
||||
header = f" {'版本':<10} {'标签':<20} {'策略数':<8} {'止盈':<8} {'止损':<8} {'成功率':<10} {'平均R/R':<10}"
|
||||
lines.append(header)
|
||||
lines.append(" " + "-" * len(header))
|
||||
|
||||
for vs in versions:
|
||||
sr = f"{vs['success_rate']}%" if vs['success_rate'] is not None else "-"
|
||||
label = vs.get("label", "")[:18]
|
||||
lines.append(
|
||||
f" {vs['version']:<10} {label:<20} "
|
||||
f"{vs['total']:<8} {vs['take_profit_hit']:<8} "
|
||||
f"{vs['stop_loss_hit']:<8} {sr:<10} "
|
||||
f"{vs['avg_rr']:<10}"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("注:数据来自 decisions.json evaluation + associations.json")
|
||||
return "\n".join(lines)
|
||||
@@ -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>
|
||||
"""
|
||||
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/env python3
|
||||
"""初始化提示词注册表 — 录入所有现存提示词版本"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from prompt_manager.registry import add_prompt, add_version, list_prompts
|
||||
from prompt_manager.models import PromptDef, PromptVersion
|
||||
|
||||
now = "2026-06-12T16:00:00" # 使用最近修改时间
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 1. 策略生成规则 — 最核心的提示词
|
||||
# ═══════════════════════════════════════════
|
||||
strategy_gen = PromptDef(
|
||||
id="strategy-generation",
|
||||
name="策略生成规则",
|
||||
description="用于生成买入区/止损/止盈的技术面策略规则集,嵌入 strategy_lifecycle.py 的 reassess_strategy() 函数",
|
||||
category="strategy",
|
||||
locations=[
|
||||
"/home/hmo/web-dashboard/strategy_lifecycle.py",
|
||||
"/home/hmo/projects/MoFin/src/strategy_lifecycle.py",
|
||||
"finance/price-range-monitor SKILL.md",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-09T08:00:00",
|
||||
updated_at=now,
|
||||
current_version="v2.4",
|
||||
)
|
||||
|
||||
add_prompt(strategy_gen)
|
||||
|
||||
# v1 — 初始机械百分比
|
||||
add_version("strategy-generation", PromptVersion(
|
||||
version="v1",
|
||||
label="初始机械百分比",
|
||||
created_at="2026-06-09T08:00:00",
|
||||
changelog="初始版本,基于固定百分比(±5~10%)计算买入区/止损/止盈,无技术面支撑",
|
||||
content="""策略生成规则 v1(初始机械百分比)
|
||||
|
||||
止损 = 成本 × 0.85(-15%)
|
||||
止盈 = 成本 × 1.20(+20%)
|
||||
买入区 = 现价 × 0.90 ~ 现价 × 1.05
|
||||
|
||||
无技术面分析,纯百分比计算。
|
||||
""",
|
||||
status="deprecated",
|
||||
tags=["机械百分比"],
|
||||
))
|
||||
|
||||
# v2 — 技术面支撑压力位 v1
|
||||
add_version("strategy-generation", PromptVersion(
|
||||
version="v2",
|
||||
label="技术面支撑压力位 v1",
|
||||
created_at="2026-06-11T10:00:00",
|
||||
changelog="从机械百分比改为基于 technical_analysis.py 的支撑/压力位计算,止损放强支撑,止盈放强压力",
|
||||
content="""策略生成规则 v2(技术面支撑压力位 v1)
|
||||
|
||||
1. 止损 = 强支撑(strong_support),约5-8%跌幅
|
||||
2. 止盈 = 强压力(strong_resist)
|
||||
3. 买入区 = 弱支撑(ws) ~ 弱压力(wr)
|
||||
4. 新买入/已持仓统一策略
|
||||
5. 无R/R校验
|
||||
""",
|
||||
status="deprecated",
|
||||
tags=["技术面", "支撑压力"],
|
||||
))
|
||||
|
||||
# v2.1 — R/R 校验 + 最小波幅
|
||||
add_version("strategy-generation", PromptVersion(
|
||||
version="v2.1",
|
||||
label="技术面 + R/R 校验",
|
||||
created_at="2026-06-12T10:00:00",
|
||||
changelog="新增R/R≥2.0校验+4%最小波幅保护。修复比亚迪A单日振幅±0.5%导致R/R=0.8的问题。R/R不满足时尝试多级阻力位上调止盈",
|
||||
content="""策略生成规则 v2.1(技术面 + R/R 校验)
|
||||
|
||||
1. 止损 = 强支撑(新买入用弱支撑)
|
||||
2. 止盈 = 强压力(多级阻力位尝试满足R/R)
|
||||
3. 买入区 = 弱支撑~弱支撑×1.05
|
||||
4. R/R ≥ 2.0 校验(新买入推荐)
|
||||
5. 4% 最小波幅保护(单日振幅<2%时)
|
||||
6. 盈亏比不满足时:弱压→强压 逐级尝试
|
||||
""",
|
||||
status="deprecated",
|
||||
tags=["技术面", "R/R", "最小波幅"],
|
||||
))
|
||||
|
||||
# v2.2 — 止损三级分离 + 移动止损
|
||||
add_version("strategy-generation", PromptVersion(
|
||||
version="v2.2",
|
||||
label="止损三级分离 + 移动止损",
|
||||
created_at="2026-06-13T10:00:00",
|
||||
changelog="止损分三级(新买入/已持仓/深套)。新买入用弱支撑,已持仓用强支撑,深套取强撑/85%最低。盈利>5%启用移动止损保护利润",
|
||||
content="""策略生成规则 v2.2(止损三级分离 + 移动止损)
|
||||
|
||||
| 场景 | 止损位置 | 逻辑 |
|
||||
|------|---------|------|
|
||||
| 新买入(cost=0) | 弱支撑(weak_support) | 入场失败小亏走人 |
|
||||
| 已持仓(profit≥-20%) | 强支撑(strong_support) | 趋势坏了才走 |
|
||||
| 深套(profit<-20%) | min(强支撑, 价×0.85) | 不轻易割 |
|
||||
|
||||
盈利>5%:取 max(弱支撑, 成本线, 现价×0.95) 移动止损
|
||||
|
||||
买入区 R/R 约束:新买入≥1.5,已持仓≥1.0
|
||||
买入区宽度收紧:只围绕弱支撑,不扩展到弱压力
|
||||
""",
|
||||
status="deprecated",
|
||||
tags=["技术面", "R/R", "移动止损", "三级止损"],
|
||||
))
|
||||
|
||||
# v2.3 — 买入区 R/R 约束 + 买入时机模型
|
||||
add_version("strategy-generation", PromptVersion(
|
||||
version="v2.3",
|
||||
label="买入区 R/R 约束 + 时机四象限",
|
||||
created_at="2026-06-13T16:00:00",
|
||||
changelog="买入区自身增加R/R约束(entry_high满足1:1.5),新增买入时机四象限模型(放量跌不入/缩量回踩入/放量突破追/缩量反弹不追),趋势位置检测扩展有效区间",
|
||||
content="""策略生成规则 v2.3
|
||||
|
||||
买入区 R/R 约束:
|
||||
- entry_high ≤ (target + min_rr × stop) / (1 + min_rr)
|
||||
- 新买入 min_rr=1.5,已持仓 min_rr=1.0
|
||||
- 坍缩保护:R/R约束导致买入区消失时标记"不建议"
|
||||
|
||||
买入时机四象限:
|
||||
| 场景 | 操作 |
|
||||
|------|------|
|
||||
| ①放量跌入买入区 | ❌ 不买 |
|
||||
| ②缩量回踩弱支撑+放量反弹 | ✅ 买入 |
|
||||
| ③放量突破压力位 | ✅ 追买 |
|
||||
| ④缩量反弹到压力位 | ❌ 警惕 |
|
||||
|
||||
趋势位置检测:股价>80%分位或<20%分位时自动扩展有效区间到价×8%
|
||||
""",
|
||||
status="deprecated",
|
||||
tags=["技术面", "R/R", "买入时机", "趋势位置"],
|
||||
))
|
||||
|
||||
# v2.4 — 当前版本(R/R阈值差异化 + 止损最小距离保护)
|
||||
add_version("strategy-generation", PromptVersion(
|
||||
version="v2.4",
|
||||
label="R/R阈值差异化 + 止损最小距离",
|
||||
created_at="2026-06-13T18:00:00",
|
||||
changelog="R/R阈值差异化(新买入≥1.5/2.0/2.0三级,已持仓≥0.5/1.5两级),止损最小距离3%保护(正常持仓不被短线波动触发),买入区坍缩逻辑优化",
|
||||
content="""策略生成规则 v2.4(当前活跃版本)
|
||||
|
||||
R/R阈值差异化:
|
||||
| 场景 | 阈值 |
|
||||
|------|------|
|
||||
| 新买入 <1.5 | ❌ 不建议买入 |
|
||||
| 新买入 1.5~2.0 | ⚠️ 谨慎买入 |
|
||||
| 新买入 ≥2.0 | ✅ 正常 |
|
||||
| 已持仓 <0.5 | ⚠️ 盈亏比极低 |
|
||||
| 已持仓 0.5~1.5 | ⚠️ 不建议加仓 |
|
||||
| 已持仓 ≥1.5 | ✅ 无标记 |
|
||||
|
||||
止损最小距离:现价到止损≥3%(非深套场景)
|
||||
买入区坍缩:R/R约束导致entry_high<entry_low时,坍缩到[价×0.99, 价×1.01]
|
||||
|
||||
其他规则继承 v2.3。
|
||||
""",
|
||||
status="active",
|
||||
tags=["技术面", "R/R阈值差异化", "止损最小距离"],
|
||||
))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 2. 快速盯盘
|
||||
# ═══════════════════════════════════════════
|
||||
quick_scan = PromptDef(
|
||||
id="quick-scan",
|
||||
name="快速盯盘",
|
||||
description="交易时段每15分钟跑一次的盘中行情监控报告 prompt",
|
||||
category="scan",
|
||||
locations=[
|
||||
"cron job: 62a2ba59f7ff",
|
||||
"finance/price-range-monitor SKILL.md",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-10T09:00:00",
|
||||
updated_at=now,
|
||||
current_version="v3",
|
||||
)
|
||||
|
||||
add_prompt(quick_scan)
|
||||
|
||||
add_version("quick-scan", PromptVersion(
|
||||
version="v1",
|
||||
label="初始版本",
|
||||
created_at="2026-06-10T09:00:00",
|
||||
changelog="初始版本,包含三段式报告格式",
|
||||
content="快速盯盘 v1:三段式报告格式,每15分钟运行。包含重点推荐操作/风险关注/其余持仓。",
|
||||
status="deprecated",
|
||||
tags=["三段式"],
|
||||
))
|
||||
add_version("quick-scan", PromptVersion(
|
||||
version="v2",
|
||||
label="字数限制+数据纪律",
|
||||
created_at="2026-06-11T10:00:00",
|
||||
changelog="新增≤300字严格限制,要求查新闻原因(>±3%),禁止模糊词",
|
||||
content="快速盯盘 v2:字数≤300字,涨跌>±3%必须查新闻,禁止模糊词(可关注/可考虑/建议观察)。",
|
||||
status="deprecated",
|
||||
tags=["字数限制", "数据纪律"],
|
||||
))
|
||||
add_version("quick-scan", PromptVersion(
|
||||
version="v3",
|
||||
label="买入时机+唯一动词",
|
||||
created_at="2026-06-12T14:00:00",
|
||||
changelog="新增买入时机四象限要求,每只推荐必须带唯一动作动词+数量+价格,仓位必写",
|
||||
content="快速盯盘 v3(当前):三段式≤300字,每只推荐必须带唯一动作动词+数量+价格,仓位%必写,禁止选择题。买入时机四象限:放量跌不入/缩量回踩入/放量突破追/缩量反弹不追。",
|
||||
status="active",
|
||||
tags=["唯一动词", "买入时机"],
|
||||
))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 3. 策略评估日报
|
||||
# ═══════════════════════════════════════════
|
||||
eval_daily = PromptDef(
|
||||
id="evaluation-daily",
|
||||
name="策略评估日报",
|
||||
description="每日21:00自动运行的策略评估+反馈闭环 prompt",
|
||||
category="evaluation",
|
||||
locations=[
|
||||
"cron job: 9d1236d8a07f",
|
||||
"finance/strategy-evaluation SKILL.md",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-09T21:00:00",
|
||||
updated_at=now,
|
||||
current_version="v2",
|
||||
)
|
||||
|
||||
add_prompt(eval_daily)
|
||||
|
||||
add_version("evaluation-daily", PromptVersion(
|
||||
version="v1",
|
||||
label="初始版本",
|
||||
created_at="2026-06-09T21:00:00",
|
||||
changelog="初始版本,运行strategy_evaluator.py并输出评估报告",
|
||||
content="策略评估 v1:运行评估脚本 → 读取 evaluation.json → 输出双维度评估报告。",
|
||||
status="deprecated",
|
||||
tags=["双维度评估"],
|
||||
))
|
||||
add_version("evaluation-daily", PromptVersion(
|
||||
version="v2",
|
||||
label="反馈闭环+信号识别",
|
||||
created_at="2026-06-11T21:00:00",
|
||||
changelog="新增反馈闭环机制:识别6种策略信号(买入区从未触发/频繁止损/价格远超止盈/理论实际差距/连续正确/连续错误),自动生成调整建议",
|
||||
content="""策略评估 v2(当前):
|
||||
1. 运行 strategy_evaluator.py
|
||||
2. 读 evaluation.json + accuracy_stats.json
|
||||
3. 识别6种信号并生成调整建议
|
||||
4. 固化经验到 knowledge-log
|
||||
5. 无变化输出 SILENT 抑制推送
|
||||
""",
|
||||
status="active",
|
||||
tags=["反馈闭环", "信号识别"],
|
||||
))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 4. 知识萃取
|
||||
# ═══════════════════════════════════════════
|
||||
knowledge = PromptDef(
|
||||
id="knowledge-extraction",
|
||||
name="知识萃取",
|
||||
description="每日16:30从当日分析中提炼可复用知识,写入 analyst-knowledge-log.md",
|
||||
category="knowledge",
|
||||
locations=[
|
||||
"cron job: e27e2e92ed80",
|
||||
"finance/analyst-knowledge SKILL.md",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-11T16:30:00",
|
||||
updated_at=now,
|
||||
current_version="v1",
|
||||
)
|
||||
|
||||
add_prompt(knowledge)
|
||||
|
||||
add_version("knowledge-extraction", PromptVersion(
|
||||
version="v1",
|
||||
label="初始版本",
|
||||
created_at="2026-06-11T16:30:00",
|
||||
changelog="初始版本,从 decisions.json 和 evaluation.json 中提炼经验写入知识日志",
|
||||
content="""知识萃取 v1(当前):
|
||||
1. 读 decisions.json 的 changelog 和 evaluation
|
||||
2. 读当天分析输出的 anomaly 信号
|
||||
3. 提炼 1-3 条可复用知识
|
||||
4. 写入 /home/hmo/Obsidian/knowledge/finance/analyst-knowledge-log.md
|
||||
5. 长期有效的规律 → memory add
|
||||
""",
|
||||
status="active",
|
||||
tags=["知识沉淀"],
|
||||
))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 5. 持仓复查
|
||||
# ═══════════════════════════════════════════
|
||||
review = PromptDef(
|
||||
id="portfolio-review",
|
||||
name="持仓复查",
|
||||
description="每周四20:00进行全面持仓基本面+技术面复查",
|
||||
category="review",
|
||||
locations=[
|
||||
"cron job: 5dde4e1a42ce",
|
||||
"finance/price-range-monitor SKILL.md",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-11T20:00:00",
|
||||
updated_at=now,
|
||||
current_version="v1",
|
||||
)
|
||||
|
||||
add_prompt(review)
|
||||
|
||||
add_version("portfolio-review", PromptVersion(
|
||||
version="v1",
|
||||
label="初始版本",
|
||||
created_at="2026-06-11T20:00:00",
|
||||
changelog="初始版本,逐个过所有持仓的营收/利润/PE/PB/ROE/技术面/研报/新闻",
|
||||
content="""持仓复查 v1(当前):
|
||||
1. 所有持仓个股逐个过:营收趋势、利润、利润率、PE/PB/ROE/负债率
|
||||
2. 技术面:支撑位、压力位、均线形态
|
||||
3. 最新研报目标价
|
||||
4. 近期重大新闻/催化剂
|
||||
5. 标记异常信号
|
||||
""",
|
||||
status="active",
|
||||
tags=["全面复查"],
|
||||
))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 6. 系统健康检查
|
||||
# ═══════════════════════════════════════════
|
||||
health = PromptDef(
|
||||
id="system-health-check",
|
||||
name="系统健康检查",
|
||||
description="每日9:00检查MoFin所有核心组件是否正常运行",
|
||||
category="health",
|
||||
locations=[
|
||||
"cron job: 37c02f4d7df9",
|
||||
"cron job: 88d6753bde11",
|
||||
"/home/hmo/web-dashboard/system_health_check.py",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-09T09:00:00",
|
||||
updated_at=now,
|
||||
current_version="v2",
|
||||
)
|
||||
|
||||
add_prompt(health)
|
||||
|
||||
add_version("system-health-check", PromptVersion(
|
||||
version="v1",
|
||||
label="初始版本",
|
||||
created_at="2026-06-09T09:00:00",
|
||||
changelog="初始版本,检查进程/端口/数据文件",
|
||||
content="健康检查 v1:检查 mofin-dashboard/xmpp-zhiwei/ejabberd 进程,8899/5222/8643端口,数据文件存在性。",
|
||||
status="deprecated",
|
||||
tags=["进程", "端口"],
|
||||
))
|
||||
add_version("system-health-check", PromptVersion(
|
||||
version="v2",
|
||||
label="18项全面检查",
|
||||
created_at="2026-06-10T09:00:00",
|
||||
changelog="扩展为18项检查:进程+端口+数据文件+价格事件+策略评估+建议记录+cron jobs+数据新鲜度",
|
||||
content="""健康检查 v2(当前):18项检查
|
||||
- 进程:mofin-dashboard, xmpp-zhiwei, ejabberd
|
||||
- 端口:8899, 5222, 8643
|
||||
- 数据:portfolio/watchlist/decisions/market/price_events/evaluation/accuracy_stats
|
||||
- 活动:价格事件数/策略评估数/建议记录数
|
||||
- Cron: 两个profile
|
||||
- 数据新鲜度:文件更新时间
|
||||
""",
|
||||
status="active",
|
||||
tags=["18项检查"],
|
||||
))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 7. 报告格式规范
|
||||
# ═══════════════════════════════════════════
|
||||
format_rules = PromptDef(
|
||||
id="report-format",
|
||||
name="报告格式规范",
|
||||
description="所有分析报告的三段式输出格式规则,嵌入 price-range-monitor SKILL.md 和 cron 提示词",
|
||||
category="format",
|
||||
locations=[
|
||||
"finance/price-range-monitor SKILL.md",
|
||||
"finance/price-range-monitor/references/report-format-final-2026-06-10.md",
|
||||
"/home/hmo/Obsidian/knowledge/finance/zhiwei-analysis-rules.md",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-10T14:00:00",
|
||||
updated_at=now,
|
||||
current_version="v3",
|
||||
)
|
||||
|
||||
add_prompt(format_rules)
|
||||
|
||||
add_version("report-format", PromptVersion(
|
||||
version="v1",
|
||||
label="初始格式",
|
||||
created_at="2026-06-10T14:00:00",
|
||||
changelog="初始三段式格式:重点推荐操作(≤3)/风险关注(≤3)/其余持仓",
|
||||
content="三段式 v1:【重点推荐操作】≤3只 / 【风险关注】≤3只 / 【其余持仓】一行概括",
|
||||
status="deprecated",
|
||||
tags=["三段式"],
|
||||
))
|
||||
add_version("report-format", PromptVersion(
|
||||
version="v2",
|
||||
label="字数限制+仓位必写",
|
||||
created_at="2026-06-11T16:00:00",
|
||||
changelog="新增≤800字限制,仓位%必写(现→建议),技术面四个数字必写,禁止模糊词",
|
||||
content="三段式 v2:≤800字,仓位%必写,技术面四数字(强阻/弱阻/强撑/弱撑)必写,禁止模糊词/选择题。",
|
||||
status="deprecated",
|
||||
tags=["字数限制", "仓位"],
|
||||
))
|
||||
add_version("report-format", PromptVersion(
|
||||
version="v3",
|
||||
label="唯一动词+理由可验证",
|
||||
created_at="2026-06-12T16:00:00",
|
||||
changelog="每只推荐必须带唯一动作动词+数量+价格,理由必须可验证(用户原话:我要验证你是不是按我说的逻辑定策略)",
|
||||
content="""三段式 v3(当前):
|
||||
1. 【重点推荐操作】≤3只,理由不重复
|
||||
2. 每只:仓位(现→建议) + 技术面四数字 + 操作(唯一动词+数量+价) + 可验证理由
|
||||
3. 【风险关注】≤3只,距止损%+原因
|
||||
4. 【其余持仓】一行带过
|
||||
5. 全文≤600字
|
||||
6. 禁止:可关注/可考虑/建议观察/择机/试试
|
||||
7. A股在前港股在后
|
||||
""",
|
||||
status="active",
|
||||
tags=["唯一动词", "理由可验证"],
|
||||
))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 8. 分析规则
|
||||
# ═══════════════════════════════════════════
|
||||
analysis_rules = PromptDef(
|
||||
id="analysis-rules",
|
||||
name="分析规则",
|
||||
description="单股/行业分析流程规则:数据源纪律、A+H股处理、异常处理、买入时机四象限",
|
||||
category="analysis",
|
||||
locations=[
|
||||
"finance/price-range-monitor SKILL.md",
|
||||
"/home/hmo/Obsidian/knowledge/finance/analysis-rules.md",
|
||||
"/home/hmo/Obsidian/knowledge/finance/zhiwei-analysis-rules.md",
|
||||
],
|
||||
versions=[],
|
||||
created_at="2026-06-09T10:00:00",
|
||||
updated_at=now,
|
||||
current_version="v2",
|
||||
)
|
||||
|
||||
add_prompt(analysis_rules)
|
||||
|
||||
add_version("analysis-rules", PromptVersion(
|
||||
version="v1",
|
||||
label="初始规则",
|
||||
created_at="2026-06-09T10:00:00",
|
||||
changelog="初始分析规则:腾讯API数据源、港股字段映射、涨跌>3%查新闻",
|
||||
content="分析规则 v1:腾讯API港股字段映射,涨跌>±3%必须查新闻,A+H股价差正常。",
|
||||
status="deprecated",
|
||||
tags=["数据源"],
|
||||
))
|
||||
add_version("analysis-rules", PromptVersion(
|
||||
version="v2",
|
||||
label="日期纪律+预检查清单",
|
||||
created_at="2026-06-12T16:00:00",
|
||||
changelog="新增日期铁律(分析前先date)、预分析自查清单(时间戳/交易日/策略数据)、A股优先于港股",
|
||||
content="""分析规则 v2(当前):
|
||||
1. 日期纪律:分析前先 date 确认日期星期
|
||||
2. 数据源:腾讯API主(港股),新浪补充(A股昨收)
|
||||
3. 预检查:时间戳→交易日→策略数据
|
||||
4. A股优先于港股
|
||||
5. 涨跌>±3%查新闻
|
||||
6. A+H价差正常
|
||||
7. 买入时机四象限
|
||||
""",
|
||||
status="active",
|
||||
tags=["日期纪律", "预检查"],
|
||||
))
|
||||
|
||||
|
||||
print("✅ 提示词注册表初始化完成!")
|
||||
print(f" 共注册 {len(list_prompts())} 个提示词")
|
||||
for p in list_prompts():
|
||||
print(f" - {p['id']}: {p['name']} ({len(p['versions'])} 个版本)")
|
||||
@@ -0,0 +1,115 @@
|
||||
"""提示词管理数据模型"""
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 枚举/常量
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
PROMPT_CATEGORIES = {
|
||||
"strategy": "策略生成 — 制定买入区/止损/止盈的规则集",
|
||||
"scan": "快速盯盘 — 交易时段行情监控报告",
|
||||
"evaluation": "策略评估 — 每日/每周策略效果评估",
|
||||
"knowledge": "知识萃取 — 经验沉淀到知识日志",
|
||||
"review": "持仓复查 — 定期全面持仓分析",
|
||||
"health": "系统健康检查 — 每日开盘前检查",
|
||||
"format": "报告格式规范 — 三段式输出格式规则",
|
||||
"analysis": "分析规则 — 单股/行业分析流程",
|
||||
}
|
||||
|
||||
VERSION_STATUS = {
|
||||
"active": "当前使用中",
|
||||
"deprecated": "已弃用(不再使用)",
|
||||
"archived": "已归档(历史版本,仅保留记录)",
|
||||
"experimental": "实验版本(临时测试)",
|
||||
}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 数据类
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
@dataclass
|
||||
class PromptVersion:
|
||||
"""单个提示词版本"""
|
||||
version: str # v1, v2, v2.1 ...
|
||||
label: str # 人类可读的名称
|
||||
created_at: str # ISO datetime
|
||||
changelog: str # 变更说明
|
||||
content: str # 提示词完整内容(或摘要)
|
||||
content_path: str = "" # 提示词文件路径(完整内容存储处)
|
||||
author: str = "知微" # 修改者
|
||||
status: str = "active" # active/deprecated/archived/experimental
|
||||
tags: list = field(default_factory=list) # 标签:如 "技术面", "R/R阈值"
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d):
|
||||
return cls(**d)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PromptDef:
|
||||
"""一条提示词定义"""
|
||||
id: str # 唯一标识,如 "strategy-generation"
|
||||
name: str # 显示名称
|
||||
description: str # 用途说明
|
||||
category: str # 分类
|
||||
locations: list # 存放位置(文件路径、cron job ID等)
|
||||
versions: list # PromptVersion 列表
|
||||
created_at: str # 首次创建时间
|
||||
updated_at: str # 最近更新时间
|
||||
current_version: str # 当前活跃版本号
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"category": self.category,
|
||||
"locations": self.locations,
|
||||
"versions": [v.to_dict() for v in self.versions],
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at,
|
||||
"current_version": self.current_version,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d):
|
||||
versions = [PromptVersion.from_dict(v) for v in d.get("versions", [])]
|
||||
return cls(
|
||||
id=d["id"],
|
||||
name=d["name"],
|
||||
description=d.get("description", ""),
|
||||
category=d.get("category", ""),
|
||||
locations=d.get("locations", []),
|
||||
versions=versions,
|
||||
created_at=d.get("created_at", ""),
|
||||
updated_at=d.get("updated_at", ""),
|
||||
current_version=d.get("current_version", ""),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StrategyLink:
|
||||
"""策略→提示词版本关联记录"""
|
||||
code: str # 股票代码
|
||||
name: str # 股票名称
|
||||
prompt_id: str # 提示词ID
|
||||
prompt_version: str # 提示词版本号
|
||||
strategy_action: str # 生成的策略文本
|
||||
generated_at: str # 生成时间
|
||||
evaluation_result: Optional[dict] = None # 评估结果(后续补充)
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d):
|
||||
return cls(**d)
|
||||
@@ -0,0 +1,194 @@
|
||||
"""提示词注册表 — 所有提示词的CRUD和版本管理"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .models import PromptDef, PromptVersion, PROMPT_CATEGORIES
|
||||
|
||||
# 数据文件路径
|
||||
DATA_DIR = Path("/home/hmo/projects/MoFin/data/prompts")
|
||||
REGISTRY_PATH = DATA_DIR / "registry.json"
|
||||
VERSIONS_DIR = DATA_DIR / "versions"
|
||||
|
||||
|
||||
def ensure_dirs():
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
VERSIONS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def load_registry() -> dict:
|
||||
"""加载注册表"""
|
||||
ensure_dirs()
|
||||
if not REGISTRY_PATH.exists():
|
||||
return {"prompts": [], "updated_at": datetime.now().isoformat()}
|
||||
try:
|
||||
return json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return {"prompts": [], "updated_at": datetime.now().isoformat()}
|
||||
|
||||
|
||||
def save_registry(data: dict):
|
||||
ensure_dirs()
|
||||
data["updated_at"] = datetime.now().isoformat()
|
||||
REGISTRY_PATH.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# CRUD
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
def list_prompts(category: str = None) -> list:
|
||||
"""列出所有提示词,可选按分类过滤"""
|
||||
reg = load_registry()
|
||||
prompts = []
|
||||
for p in reg.get("prompts", []):
|
||||
if category and p.get("category") != category:
|
||||
continue
|
||||
prompts.append(dict(PromptDef.from_dict(p).to_dict()))
|
||||
return prompts
|
||||
|
||||
|
||||
def get_prompt(prompt_id: str) -> Optional[PromptDef]:
|
||||
"""获取单个提示词"""
|
||||
reg = load_registry()
|
||||
for p in reg.get("prompts", []):
|
||||
if p["id"] == prompt_id:
|
||||
return PromptDef.from_dict(p)
|
||||
return None
|
||||
|
||||
|
||||
def add_prompt(prompt_def: PromptDef):
|
||||
"""添加新提示词"""
|
||||
reg = load_registry()
|
||||
# 检查是否已存在
|
||||
for i, p in enumerate(reg.get("prompts", [])):
|
||||
if p["id"] == prompt_def.id:
|
||||
reg["prompts"][i] = prompt_def.to_dict()
|
||||
break
|
||||
else:
|
||||
reg.setdefault("prompts", []).append(prompt_def.to_dict())
|
||||
save_registry(reg)
|
||||
|
||||
|
||||
def update_prompt(prompt_id: str, updates: dict):
|
||||
"""更新提示词元数据"""
|
||||
reg = load_registry()
|
||||
for p in reg.get("prompts", []):
|
||||
if p["id"] == prompt_id:
|
||||
for k, v in updates.items():
|
||||
if k not in ("id", "versions", "created_at"):
|
||||
p[k] = v
|
||||
break
|
||||
save_registry(reg)
|
||||
|
||||
|
||||
def delete_prompt(prompt_id: str):
|
||||
"""删除提示词"""
|
||||
reg = load_registry()
|
||||
reg["prompts"] = [p for p in reg.get("prompts", []) if p["id"] != prompt_id]
|
||||
save_registry(reg)
|
||||
|
||||
|
||||
def get_version_content(prompt_id: str, version: str) -> Optional[str]:
|
||||
"""读取版本内容文件"""
|
||||
content_path = VERSIONS_DIR / f"{prompt_id}-{version}.md"
|
||||
if content_path.exists():
|
||||
return content_path.read_text(encoding="utf-8")
|
||||
# 回退:从版本记录的 content 字段读取
|
||||
prompt = get_prompt(prompt_id)
|
||||
if prompt:
|
||||
for v in prompt.versions:
|
||||
if v.version == version:
|
||||
return v.content
|
||||
return None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 版本管理
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
def add_version(prompt_id: str, version: PromptVersion):
|
||||
"""添加新版本到提示词"""
|
||||
prompt = get_prompt(prompt_id)
|
||||
if not prompt:
|
||||
raise ValueError(f"提示词 '{prompt_id}' 不存在")
|
||||
|
||||
# 检查版本号是否已存在
|
||||
for i, v in enumerate(prompt.versions):
|
||||
if v.version == version.version:
|
||||
prompt.versions[i] = version
|
||||
break
|
||||
else:
|
||||
prompt.versions.append(version)
|
||||
|
||||
# 保存版本内容到独立文件
|
||||
content_path = VERSIONS_DIR / f"{prompt_id}-{version.version}.md"
|
||||
content_path.write_text(version.content, encoding="utf-8")
|
||||
|
||||
# 更新 content_path 只存路径,content 字段只存摘要
|
||||
version.content_path = str(content_path)
|
||||
version.content = version.content[:200] + "..." if len(version.content) > 200 else version.content
|
||||
|
||||
# 设置当前版本
|
||||
prompt.current_version = version.version
|
||||
prompt.updated_at = datetime.now().isoformat()
|
||||
|
||||
# 保存
|
||||
add_prompt(prompt)
|
||||
|
||||
|
||||
def set_active_version(prompt_id: str, version_str: str):
|
||||
"""切换当前活跃版本"""
|
||||
prompt = get_prompt(prompt_id)
|
||||
if not prompt:
|
||||
raise ValueError(f"提示词 '{prompt_id}' 不存在")
|
||||
|
||||
# 验证版本存在
|
||||
version_exists = any(v.version == version_str for v in prompt.versions)
|
||||
if not version_exists:
|
||||
raise ValueError(f"版本 '{version_str}' 不存在于 '{prompt_id}'")
|
||||
|
||||
# 将所有版本标记为非活跃,目标版本标记为活跃
|
||||
for v in prompt.versions:
|
||||
if v.version == version_str:
|
||||
v.status = "active"
|
||||
elif v.status == "active":
|
||||
v.status = "deprecated"
|
||||
|
||||
prompt.current_version = version_str
|
||||
prompt.updated_at = datetime.now().isoformat()
|
||||
add_prompt(prompt)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 分类信息
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
def get_categories() -> dict:
|
||||
"""获取所有分类"""
|
||||
return dict(PROMPT_CATEGORIES)
|
||||
|
||||
|
||||
def get_version_history(prompt_id: str) -> list:
|
||||
"""获取某个提示词的所有版本历史"""
|
||||
prompt = get_prompt(prompt_id)
|
||||
if not prompt:
|
||||
return []
|
||||
history = []
|
||||
for v in prompt.versions:
|
||||
history.append({
|
||||
"version": v.version,
|
||||
"label": v.label,
|
||||
"status": v.status,
|
||||
"created_at": v.created_at,
|
||||
"changelog": v.changelog,
|
||||
"tags": v.tags,
|
||||
"is_current": v.version == prompt.current_version,
|
||||
})
|
||||
return sorted(history, key=lambda x: x["version"])
|
||||
@@ -0,0 +1,122 @@
|
||||
"""策略→提示词版本关联追踪
|
||||
|
||||
当 strategy_lifecycle.py 生成策略时,记录当前使用的提示词版本,
|
||||
将每只股票的策略与生成它的提示词版本关联起来。
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .models import StrategyLink
|
||||
from .registry import get_prompt
|
||||
|
||||
DATA_DIR = Path("/home/hmo/projects/MoFin/data/prompts")
|
||||
ASSOCIATIONS_PATH = DATA_DIR / "associations.json"
|
||||
|
||||
|
||||
def load_associations() -> dict:
|
||||
"""加载关联记录"""
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
if not ASSOCIATIONS_PATH.exists():
|
||||
return {"associations": [], "updated_at": datetime.now().isoformat()}
|
||||
try:
|
||||
return json.loads(ASSOCIATIONS_PATH.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return {"associations": [], "updated_at": datetime.now().isoformat()}
|
||||
|
||||
|
||||
def save_associations(data: dict):
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
data["updated_at"] = datetime.now().isoformat()
|
||||
ASSOCIATIONS_PATH.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def get_current_strategy_prompt_version() -> dict:
|
||||
"""获取当前活跃的策略生成提示词版本
|
||||
|
||||
返回 {prompt_id, version, label} 或 None
|
||||
"""
|
||||
prompt = get_prompt("strategy-generation")
|
||||
if not prompt:
|
||||
return None
|
||||
for v in prompt.versions:
|
||||
if v.version == prompt.current_version:
|
||||
return {
|
||||
"prompt_id": prompt.id,
|
||||
"version": v.version,
|
||||
"label": v.label,
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def record_strategy_generation(code: str, name: str, strategy_action: str) -> StrategyLink:
|
||||
"""记录策略生成事件
|
||||
|
||||
由 strategy_lifecycle.py 在生成策略时调用
|
||||
"""
|
||||
pv = get_current_strategy_prompt_version()
|
||||
if not pv:
|
||||
# 如果没有提示词版本记录,创建一个默认记录
|
||||
pv = {"prompt_id": "strategy-generation", "version": "unknown", "label": "未知版本"}
|
||||
|
||||
link = StrategyLink(
|
||||
code=code,
|
||||
name=name,
|
||||
prompt_id=pv["prompt_id"],
|
||||
prompt_version=pv["version"],
|
||||
strategy_action=strategy_action[:500], # 截断保护
|
||||
generated_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
data = load_associations()
|
||||
data.setdefault("associations", []).append(link.to_dict())
|
||||
|
||||
# 只保留最近的10000条记录
|
||||
if len(data["associations"]) > 10000:
|
||||
data["associations"] = data["associations"][-10000:]
|
||||
|
||||
save_associations(data)
|
||||
return link
|
||||
|
||||
|
||||
def get_associations_for_stock(code: str, max_results: int = 10) -> list:
|
||||
"""获取某只股票的所有策略关联记录"""
|
||||
data = load_associations()
|
||||
records = [a for a in data.get("associations", []) if a["code"] == code]
|
||||
return sorted(records, key=lambda x: x.get("generated_at", ""), reverse=True)[:max_results]
|
||||
|
||||
|
||||
def get_associations_for_prompt_version(prompt_id: str, version: str) -> list:
|
||||
"""获取某个提示词版本生成的所有策略"""
|
||||
data = load_associations()
|
||||
return [a for a in data.get("associations", [])
|
||||
if a.get("prompt_id") == prompt_id and a.get("prompt_version") == version]
|
||||
|
||||
|
||||
def get_strategy_version_stats() -> dict:
|
||||
"""统计每个提示词版本生成的策略数量"""
|
||||
data = load_associations()
|
||||
stats = {}
|
||||
for a in data.get("associations", []):
|
||||
key = f"{a.get('prompt_id', '?')}@{a.get('prompt_version', '?')}"
|
||||
if key not in stats:
|
||||
stats[key] = {
|
||||
"prompt_id": a.get("prompt_id"),
|
||||
"version": a.get("prompt_version"),
|
||||
"count": 0,
|
||||
"stocks": set(),
|
||||
"last_generated": "",
|
||||
}
|
||||
stats[key]["count"] += 1
|
||||
stats[key]["stocks"].add(a.get("code"))
|
||||
if a.get("generated_at", "") > stats[key]["last_generated"]:
|
||||
stats[key]["last_generated"] = a.get("generated_at", "")
|
||||
# 转换 set 为 list 以便 JSON 序列化
|
||||
for k in stats:
|
||||
stats[k]["stocks"] = sorted(stats[k]["stocks"])
|
||||
return stats
|
||||
Reference in New Issue
Block a user