#!/usr/bin/env python3 # -*- coding: utf-8 -*- """strategy_executor.py — 策略执行调度器(2026-08-13 温区自适应核心) 核心(老莫要求):根据当前温区激活策略,调度对应的选股/买卖/重评任务。 温区自适应数据流: regime_tracker(K=5 平滑温区) + temp_band(温度) + regime_perf(实测) → strategy_router(当前温区激活策略 strategy_weights.json) → strategy_executor(按激活策略调度对应 scanner/监控/重评) 调度逻辑: - 读 strategy_weights.json 的 active(当前温区激活策略集合) - 对每个激活策略,调度其对应的选股扫描器(scanner) - 非激活策略的 scanner 不跑(温区休眠) 策略→scanner 映射: v_weak → mr_scanner.py (choppy/trend_down 超跌确认) v_oversold → predictive_oversold_scanner.py(trend_down 预测超跌反弹) s2_panic → s2_scanner.py (trend_down 恐慌买超跌) v_mr/v_mr_sel → mr_scanner.py (复用 v_weak 扫描器,同家族) v_next4/v8.1 → leader_scanner.py (trend_up 龙头识别) 用法:cron 每30分钟盘中跑(替代各 scanner 分散 cron) python3 strategy_executor.py """ import sys import json import subprocess from pathlib import Path from datetime import datetime # ── 消息通道统一路由(broadcast/xmpp by delivery) ── try: from messenger import install_stdio_hook as _msh _msh() except Exception: pass _SCRIPT_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(_SCRIPT_DIR)) sys.path.insert(0, "/home/hmo/MoFin") WEIGHTS_FILE = "/home/hmo/MoFin/data/strategy_weights.json" LOG = "/home/hmo/MoFin/gateway/logs/strategy_executor.log" # 策略 → 选股扫描器映射 STRATEGY_SCANNER = { "v_weak": "mr_scanner.py", "v_oversold": "predictive_oversold_scanner.py", "p_oversold": "predictive_oversold_scanner.py", "s2_panic": "s2_scanner.py", "v_mr": "mr_scanner.py", "v_mr_sel": "mr_scanner.py", "v_mr2": "mr_scanner.py", "v_mr3": "mr_scanner.py", "v_mr4": "mr_scanner.py", "v_lurk_v1": "mr_scanner.py", "v_lurk_v2": "mr_scanner.py", "v_lurk_v3": "mr_scanner.py", "v_next": "leader_scanner.py", "v_next4": "leader_scanner.py", "v8.1": "leader_scanner.py", "v_next5": "leader_scanner.py", "v_combo": "leader_scanner.py", "b_td1_v3": "b_td1_v3_scanner.py", "s2_panic_v2": "s2_panic_v2_scanner.py", } def log(msg): line = f"[{datetime.now().isoformat(timespec='seconds')}] {msg}" print(line, flush=True) try: Path(LOG).parent.mkdir(parents=True, exist_ok=True) with open(LOG, "a", encoding="utf-8") as f: f.write(line + "\n") except Exception: pass def load_active(): """读当前温区激活策略""" try: p = Path(WEIGHTS_FILE) if p.exists(): d = json.loads(p.read_text(encoding="utf-8")) return d.get("active") or [], d.get("state", "unknown") except Exception as e: log(f"加载 weights 失败: {e}") return [], "unknown" def is_trading_time(): """盘中时段检查(9:30-11:30 / 13:00-15:00 连续竞价)""" now = datetime.now() t = now.strftime("%H:%M") wd = now.weekday() if wd >= 5: return False return ("09:30" <= t <= "11:30") or ("13:00" <= t <= "15:00") def run_scanner(scanner_name): """执行一个扫描器(带超时防挂 + returncode 检查 + 完整 stderr 落盘) 2026-08-17 知微提单 t_b27ad65f 辅修: 1. returncode 检查——scanner 崩溃(exit!=0)记 ✗,不再误报「✓完成」 2. 超时 600→480s(每扫描器预算,避免挤占 cron harness 600s 总预算) 3. 完整 stderr 落盘(截断 200→1000 字符,便于追根因) """ script = _SCRIPT_DIR / scanner_name if not script.exists(): log(f" ⚠️ {scanner_name} 不存在,跳过") return False log(f" ▶ 执行 {scanner_name}") try: r = subprocess.run( [sys.executable, str(script)], capture_output=True, text=True, timeout=480, cwd=str(_SCRIPT_DIR), ) out = (r.stdout or "").strip()[-500:] # 尾部(含完成/命中汇总) err = (r.stderr or "").strip()[-1000:] # 完整 stderr(截断1000) if r.returncode == 0: log(f" ✓ {scanner_name} 完成(rc=0): {out}") if err: log(f" ⚠️ {scanner_name} stderr(rc=0): {err}") return True else: log(f" ✗ {scanner_name} 崩溃(rc={r.returncode}): {out}") log(f" ✗ {scanner_name} stderr: {err}") return False except subprocess.TimeoutExpired as e: # 超时:子进程可能孤儿化,尝试终止进程组 try: if e.stdout or e.stderr: pass except Exception: pass log(f" ⏱ {scanner_name} 超时(480s)跳过——检查孤儿进程") return False except Exception as e: log(f" ✗ {scanner_name} 异常: {e}") return False def main(): if not is_trading_time(): print(f"[SKIP] {datetime.now().strftime('%H:%M')} 非连续竞价时段", flush=True) return 0 active, regime = load_active() log(f"温区: {regime} | 激活策略: {active}") # 去重:同一 scanner 只跑一次(多策略映射同一扫描器) scanners = set() for strat in active: sc = STRATEGY_SCANNER.get(strat) if sc: scanners.add(sc) log(f"调度扫描器: {sorted(scanners)}") if not scanners: log(" 无激活策略的扫描器(当前温区无对应选股任务)") return 0 for sc in sorted(scanners): run_scanner(sc) log("调度完成") return 0 if __name__ == "__main__": sys.exit(main())