硬性策略质量门禁 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:
@@ -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) "
|
||||
|
||||
Reference in New Issue
Block a user