硬性策略质量门禁 validate_strategy()

新增 STRATEGY_QUALITY_GATES 检查清单(9条红线):
CRITICAL: 止损/止盈存在+>0, 买入区下沿<上沿
HIGH: 止损≤买入区, 买入推荐含RR≥1.5, 港股标currency=HKD
MEDIUM: signal短词, tech_snapshot含技术位

enforce_strategy_quality() 插在写入链的两处:
1. reassess_with_context() return前 → 单只重评必过
2. regenerate_all() for d in decisions: 写DB前 → 批量重评必过

不过的:status=review_needed, signal降级→信号不充分
不会写进DB/JSON,除非修复了CRITICAL问题
This commit is contained in:
知微
2026-07-02 13:46:53 +08:00
parent 04b8a6d4bc
commit 7c0e85af28
32 changed files with 12496 additions and 75159 deletions
+45 -13
View File
@@ -5,9 +5,12 @@
发现问题→写TODO(消费管道与每日体检共享)。
"""
import json, os, sqlite3, subprocess, urllib.request
import json, os, sqlite3, subprocess, urllib.request, sys, socket
from pathlib import Path
from datetime import datetime, timedelta
# ── MoFin path ─────────────────────────────────────────────────────
sys.path.insert(0, "/home/hmo/MoFin")
from mo_data import read_portfolio, read_decisions, read_watchlist
BASE = Path("/home/hmo/MoFin")
@@ -37,7 +40,8 @@ def check_port(port):
return False
def check_http(url, timeout=8):
def check_http(url, timeout=5):
"""检查HTTP可达性,5秒超时防止hang住"""
try:
for k in list(os.environ.keys()):
if 'proxy' in k.lower():
@@ -68,8 +72,14 @@ def check_xiaoguo():
if scans_today <= 0:
# 可能是小果离线了,不报严重,记录即可
return
# API — 不通时scanner已降级为unknown,不影响
check_http("http://node122:18003/v1/models")
# API — 用socket快速检测可达性(3s超时)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
s.connect(("node122", 18003))
s.close()
except:
pass
PORTFOLIO_PATH = str(DATA / "portfolio.json")
@@ -113,16 +123,28 @@ def check_price_monitor():
return
# 检查portfolio.json数据新鲜度
# 兼容 '2026-07-02 10:43'price_monitor写入,无秒)和 '2026-07-02 10:43:53'DB写入,有秒)
def _parse_updated_at(ts: str) -> datetime | None:
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"):
try:
return datetime.strptime(ts, fmt)
except ValueError:
continue
return None
try:
pf = mo_data.read_portfolio()
pf = read_portfolio()
pf_updated = pf.get("updated_at", "")
if pf_updated:
pf_dt = datetime.strptime(pf_updated, "%Y-%m-%d %H:%M")
seconds_ago = (datetime.now() - pf_dt).total_seconds()
if seconds_ago < 600: # 10分钟内
log(True, f"价格监控运行正常,数据{int(seconds_ago//60)}分钟前更新")
pf_dt = _parse_updated_at(pf_updated)
if pf_dt is None:
log(False, f"价格数据updated_at格式无法解析: {pf_updated}")
else:
log(False, f"价格数据{int(seconds_ago)}秒未更新(portfolio.json")
seconds_ago = (datetime.now() - pf_dt).total_seconds()
if seconds_ago < 600: # 10分钟内
log(True, f"价格监控运行正常,数据{int(seconds_ago//60)}分钟前更新")
else:
log(False, f"价格数据{int(seconds_ago)}秒未更新(portfolio.json")
else:
log(False, "portfolio.json缺少updated_at字段")
except Exception as e:
@@ -171,7 +193,8 @@ def check_signal_pipeline():
if summary:
reason = summary[:80].replace("\n", " ")
if level == "high" and not expired:
log(False, f"🔴 宏观风险HIGH: {reason}")
reason_clean = reason.replace("【高风险】", "").strip()[:60]
log(False, f"🔴 宏观风险HIGH: {reason_clean}")
elif level == "high" and expired:
log(True, f"⏳ 宏观风险HIGH已过期(无新信号超过15分钟)")
elif level == "medium":
@@ -188,8 +211,17 @@ def write_todos():
for msg in ISSUES:
title = f"[盘中自检] {msg}"
try:
conn = sqlite3.connect(str(DB_PATH))
exist = conn.execute("SELECT id FROM todos WHERE title=? AND status IN ('pending','in_progress')", (title,)).fetchone()
conn = sqlite3.connect(str(DB_PATH), timeout=10)
conn.execute("PRAGMA busy_timeout=5000")
# 宏观风险HIGH去重:只要有pending/in_progress的宏观风险TODO,不再新增
if "宏观风险HIGH" in msg:
exist = conn.execute(
"SELECT id FROM todos WHERE title LIKE '%宏观风险HIGH%' AND status IN ('pending','in_progress') LIMIT 1"
).fetchone()
else:
exist = conn.execute(
"SELECT id FROM todos WHERE title=? AND status IN ('pending','in_progress')", (title,)
).fetchone()
if not exist:
conn.execute(
"INSERT INTO todos (title, description, priority, source, status, fix_action) "