fix: 全面系统修复 — delivery目标修正 + 脚本同步 + refresh_mtf_cache import修复 + clean_watchlist硬编码修复

修复清单:
- cron delivery修正:10个job从deliver=origin/all改为local,消除推送错误
- refresh_mtf_cache.py:替换strategy_lifecycle.PORTFOLIO_PATH导入为mofin_db直接读DB
- clean_watchlist.py:修复mo_data导入名错误和JSON硬编码路径
- MoFin/scripts/与profile scripts互相补全,双向sync到一致
- price_monitor.py:现金源从portfolio_summary表改为cash_log(Dad确认的现金权威)
- 更新watchlist.json加入000850华茂股份
This commit is contained in:
知微
2026-07-06 14:01:54 +08:00
parent 3e2f0315eb
commit 66962ae190
62 changed files with 23364 additions and 83 deletions
+225
View File
@@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""
pre-flight-check.py — 策略上线前检查清单的自动化部分
检查三项可自动验证的关卡,输出结构化结果供知微在报告中引用。
只检查,不拦报告。不过的项目标记 @preflight:fail。
用法:
python3 pre-flight-check.py [code]
不传 code → 校验版本一致性 + 数据时效 + 全量持仓成本检查
传 code → 只检查单只股票(止损技术位 + R/R + 成本)
依赖:
- /home/hmo/projects/MoFin/data/prompts/registry.json(版本一致性)
- /home/hmo/web-dashboard/data/decisions.json(策略+成本)
- /home/hmo/web-dashboard/data/portfolio.json(持仓数据)
输出格式:
✅ 项目名 — 通过
❌ 项目名 — 失败原因
⚠️ 项目名 — 警告
"""
import json
import sys
from pathlib import Path
from datetime import datetime, timezone
# === 路径 ===
REGISTRY = Path("/home/hmo/projects/MoFin/data/prompts/registry.json")
DECISIONS = Path("/home/hmo/web-dashboard/data/decisions.json")
PORTFOLIO = Path("/home/hmo/web-dashboard/data/portfolio.json")
# === 检查 7:版本一致性 ===
def check_version_consistency():
"""analysis-rules.depends_on.version == strategy-generation.current_version"""
try:
reg = json.loads(REGISTRY.read_text())
except Exception as e:
return ("⚠️ 版本一致性", f"无法读取 registry.json: {e}")
sg = None
ar = None
for p in reg["prompts"]:
if p["id"] == "strategy-generation":
sg = p
if p["id"] == "analysis-rules":
ar = p
if not sg or not ar:
return ("⚠️ 版本一致性", "registry.json 中缺少 strategy-generation 或 analysis-rules")
sg_ver = sg["current_version"]
ar_dep_ver = ar.get("depends_on", {}).get("version", "none")
if sg_ver == ar_dep_ver:
return ("✅ 版本一致性", f"strategy-generation@{sg_ver} == analysis-rules.depends_on@{ar_dep_ver}")
else:
return ("❌ 版本一致性",
f"strategy-generation@{sg_ver} != analysis-rules.depends_on@{ar_dep_ver}"
f"运行 check-prompt-deps.py 修复")
# === 检查 1:数据时效 ===
def check_data_freshness():
"""检查 decisions.json 的更新时间是否在合理范围内"""
try:
mtime = DECISIONS.stat().st_mtime
mtime_dt = datetime.fromtimestamp(mtime, tz=timezone.utc)
now = datetime.now(timezone.utc)
age_minutes = (now - mtime_dt).total_seconds() / 60
except Exception as e:
return ("⚠️ 数据时效", f"无法检查 decisions.json: {e}")
if age_minutes < 60:
return ("✅ 数据时效",
f"decisions.json 更新于 {age_minutes:.0f} 分钟前 ({mtime_dt.strftime('%H:%M')})")
elif age_minutes < 240:
return ("⚠️ 数据时效",
f"decisions.json 已 {age_minutes:.0f} 分钟未更新(可能已收盘),数据视为陈旧")
else:
return ("❌ 数据时效",
f"decisions.json 已 {age_minutes:.0f} 分钟未更新,数据过期,请检查数据管线")
# === 检查 5:成本有效 ===
def check_cost_validity():
"""检查 decisions.json 中所有持仓的成本是否有效"""
try:
dec = json.loads(DECISIONS.read_text())
except Exception as e:
return ("⚠️ 成本有效性", f"无法读取 decisions.json: {e}")
stocks = dec.get("stocks", dec.get("holdings", dec.get("strategies", [])))
if not stocks:
stocks = dec.get("portfolio", [])
issues = []
ok = 0
total = 0
for s in stocks:
# 支持 stocks[] 和 strategies[] 两种结构
cost = s.get("cost") or s.get("actual", {}).get("cost", None)
code = s.get("code") or s.get("symbol", "?")
name = s.get("name", "")
if cost is None or cost == 0:
issues.append(f"{code} {name}: cost={'null' if cost is None else cost}")
else:
ok += 1
total += 1
if not issues:
return ("✅ 成本有效性", f"全部 {total} 条持仓成本有效")
else:
return ("⚠️ 成本有效性",
f"{len(issues)}/{total} 条持仓成本缺失:{''.join(issues[:5])}"
f"{'...' if len(issues) > 5 else ''}")
# === 检查 3(单股):止损技术依据 ===
def check_stop_technical(code):
"""检查单只股票的止损是否基于技术位(仅对单股模式生效)"""
try:
dec = json.loads(DECISIONS.read_text())
except Exception as e:
return ("⚠️ 止损技术位", f"无法读取 decisions.json: {e}")
stocks = dec.get("stocks", dec.get("strategies", []))
for s in stocks:
c = s.get("code", "")
if c.replace(".", "").replace("SZ", "").replace("SH", "").replace("HK", "") == code.replace(".", "").replace("SZ", "").replace("SH", "").replace("HK", ""):
stop = s.get("stop_loss", s.get("actual", {}).get("stop_loss"))
buy_zone = s.get("entry_low", s.get("buy_zone", [None, None]))
if isinstance(buy_zone, list):
entry_low = buy_zone[0]
else:
entry_low = buy_zone
if stop and entry_low and stop < entry_low * 0.95:
return ("⚠️ 止损技术位",
f"{code} 止损{stop} < 买入区下沿{entry_low}×0.95"
f"检查是否用了固定百分比而非技术位")
return ("✅ 止损技术位", f"{code} 止损{stop},需要人工确认是否基于支撑位")
return ("⚠️ 止损技术位", f"代码 {code} 未在 decisions.json 中找到")
# === 检查 4(单股):R/R 达标 ===
def check_rr(code, price=None):
"""检查单只股票的 R/R 是否达标"""
try:
dec = json.loads(DECISIONS.read_text())
except Exception as e:
return ("⚠️ R/R 达标", f"无法读取 decisions.json: {e}")
stocks = dec.get("stocks", dec.get("strategies", []))
for s in stocks:
c = s.get("code", "")
if c.replace(".", "").replace("SZ", "").replace("SH", "").replace("HK", "") == code.replace(".", "").replace("SZ", "").replace("SH", "").replace("HK", ""):
stop = s.get("stop_loss", s.get("actual", {}).get("stop_loss"))
target = s.get("take_profit", s.get("actual", {}).get("take_profit"))
p = price or s.get("price", s.get("current_price"))
if not all([stop, target, p]):
return ("⚠️ R/R 达标", f"{code} 缺少止损/止盈/现价之一")
risk = p - stop
reward = target - p
if risk <= 0:
return ("❌ R/R 达标", f"{code} 现价{p}已低于止损{stop},无R/R可言")
rr = reward / risk
cost = s.get("cost", s.get("actual", {}).get("cost"))
is_new = (cost is None or cost == 0)
min_rr = 2.5 if is_new else 2.0 # 简化处理,弱阻距检测需额外输入
if rr >= min_rr:
return ("✅ R/R 达标", f"{code} R/R={rr:.2f} >= {min_rr}")
else:
return ("❌ R/R 达标", f"{code} R/R={rr:.2f} < {min_rr}")
return ("⚠️ R/R 达标", f"代码 {code} 未在 decisions.json 中找到")
def main():
print("「策略上线前检查」自动化部分")
print(f"检查时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print()
if len(sys.argv) > 1:
# 单股模式
code = sys.argv[1]
price = float(sys.argv[2]) if len(sys.argv) > 2 else None
results = [
check_stop_technical(code),
check_rr(code, price),
]
else:
# 全量模式
results = [
check_version_consistency(),
check_data_freshness(),
check_cost_validity(),
]
for tag, msg in results:
print(f" {tag}{msg}")
# 汇总
fails = sum(1 for tag, _ in results if tag.startswith(""))
warns = sum(1 for tag, _ in results if tag.startswith("⚠️"))
total = len(results)
print()
if fails == 0 and warns == 0:
print(f"{total}/{total} 项通过")
elif fails == 0:
print(f" ⚠️ {warns} 项警告,{total - warns} 项通过")
else:
print(f"{fails} 项失败,{warns} 项警告,{total - fails - warns} 项通过")
if __name__ == "__main__":
main()