Files
MoFin/scripts/pre-flight-check.py
T
知微 66962ae190 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华茂股份
2026-07-06 14:01:54 +08:00

226 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()