#!/usr/bin/env python3 """mofin_collect.py — MoFin 数据采集链 每轮盯盘 cron 前运行,顺序执行: 0. 策略新鲜度强制检查(代码级约束:推荐前必须先检查策略是否最新) 1. market_watch — 拉90个行业板块数据(9:30前跳过,市场未开) 2. trend_detector — 检测17种信号(依赖板块数据,同跳) 3. mofin_news — 搜新闻+小果分析 4. stock_quote — 所有持仓最新行情(CRITICAL: LLM唯一价格源) """ import subprocess, sys, time, json from pathlib import Path from datetime import datetime BASE = Path(__file__).parent.parent if "hermes" in str(Path(__file__).resolve()) else Path(__file__).parent now = datetime.now() market_open = (now.hour >= 9 and now.minute >= 30) or now.hour >= 10 # ── 步骤0: 策略新鲜度强制检查(代码级约束)── # 在LLM看到任何数据前,先确保所有持仓策略是新鲜的 # 策略过期(>4h未更新) → 创建TODO触发重评 + 注入STALE标记到LLM context # LLM看到STALE标记:禁止对该股给出任何操作建议 print("--- strategy_freshness_check ---", flush=True) try: sys.path.insert(0, str(BASE)) from mofin_db import get_conn conn = get_conn() cur = conn.cursor() # 读所有活跃持仓及其最新策略更新时间 rows = cur.execute(""" SELECT h.code, h.name, h.price, h.position_pct, hs.stop_loss, hs.take_profit, hs.entry_low, hs.entry_high, hs.created_at, hs.action FROM holdings h LEFT JOIN ( SELECT code, stop_loss, take_profit, entry_low, entry_high, created_at, action, ROW_NUMBER() OVER (PARTITION BY code ORDER BY id DESC) AS rn FROM holding_strategies ) hs ON h.code = hs.code AND hs.rn = 1 WHERE h.is_active = 1 ORDER BY h.code """).fetchall() stale_count = 0 fresh_count = 0 no_strategy_count = 0 for r in rows: code = r["code"] name = r["name"] last_update = r["created_at"] has_strategy = last_update is not None if has_strategy: try: last_dt = datetime.fromisoformat(last_update) hours_stale = (now - last_dt).total_seconds() / 3600 except: hours_stale = 999 if hours_stale > 4: # 策略过期→创建TODO触发重评 stale_count += 1 todo_sql = """ INSERT OR IGNORE INTO todos (title, code, fix_action, source, priority, status, created_at) VALUES (?, ?, 'reassess_strategy', 'freshness_check', 'high', 'pending', ?) """ cur.execute(todo_sql, ( f"策略过期需重评: {code} {name} ({hours_stale:.0f}h未更新)", code, now.isoformat() )) print(f" ⚠️ STALE {code} {name}: {hours_stale:.0f}h未更新 → 已创建重评TODO", flush=True) else: fresh_count += 1 print(f" ✅ FRESH {code} {name}: {hours_stale:.1f}h前更新", flush=True) else: no_strategy_count += 1 print(f" ❌ NO_STRATEGY {code} {name}: 无策略记录", flush=True) conn.commit() conn.close() total = len(rows) print(f"策略检查完成: {total}只持仓, {fresh_count}新鲜, {stale_count}过期(已创TODO), {no_strategy_count}无策略", flush=True) if stale_count > 0 or no_strategy_count > 0: print(f"⚠️ LLM注意: 有{stale_count + no_strategy_count}只持仓策略不是最新状态, 禁止对这些股给出操作建议", flush=True) except Exception as e: print(f"WARN: strategy_freshness_check跳过 ({e})", flush=True) # 步骤1-3: 行业/新闻数据 SCRIPTS = [] if market_open: SCRIPTS.append(("market_watch.py", 60)) SCRIPTS.append(("trend_detector.py", 60)) else: print(f"[{now.strftime('%H:%M')}] 市场未开盘(9:30),跳过板块采集", flush=True) SCRIPTS.append(("mofin_news.py", 50)) for script, timeout in SCRIPTS: path = BASE / script if not path.exists(): path = Path("/home/hmo/MoFin") / script print(f"--- {script} ---", flush=True) start = time.time() try: result = subprocess.run( [sys.executable, str(path)], capture_output=True, text=True, timeout=timeout ) elapsed = time.time() - start if result.returncode == 0: print(f"OK ({elapsed:.0f}s)", flush=True) if result.stdout.strip(): for line in result.stdout.strip().split("\n")[-3:]: print(f" {line}", flush=True) else: print(f"FAIL ({elapsed:.0f}s): {result.stderr[:200]}", flush=True) except subprocess.TimeoutExpired: print(f"TIMEOUT ({timeout}s)", flush=True) except Exception as e: print(f"ERROR: {e}", flush=True) # ── 步骤4: 个股行情注入(唯一权威价格源)── # 所有持仓最新行情,注入到 LLM context # LLM 禁止自行调用原始API解析价格 PRICE_SCRIPT = BASE / "stock_quote.py" if not PRICE_SCRIPT.exists(): PRICE_SCRIPT = Path("/home/hmo/MoFin/scripts/stock_quote.py") if PRICE_SCRIPT.exists(): print("--- stock_quote.py ---", flush=True) try: result = subprocess.run( [sys.executable, str(PRICE_SCRIPT), "--all-holdings"], capture_output=True, text=True, timeout=30 ) if result.returncode == 0 and result.stdout.strip(): lines = [l for l in result.stdout.strip().split("\n") if l.strip()] print(f"OK ({len(lines)}只持仓)", flush=True) for line in lines[:50]: print(f" {line}", flush=True) else: print(f"WARN: stock_quote stderr={result.stderr[:100]}", flush=True) except Exception as e: print(f"WARN: stock_quote skipped ({e})", flush=True) else: print("WARN: stock_quote.py not found", flush=True) print("采集链完成", flush=True)