#!/usr/bin/env python3 # -*- coding: utf-8 -*- """hk_scanner.py — 港股通组合策略扫描器(按温区调度,2026-08-14) 基于港股 12 维面板(/tmp/panel_12d_hk.pkl,每日盘后构建),按当前温区选策略扫描: trend_up → hk_pe_mom(低PE+小市值+行业动量) trend_down → hk_mr1(深度超卖,trend_down 连续>5天时暂停) choppy → hk_pe_oversold(低PE+超卖) 数据:面板为日频(盘后构建,含收盘因子)。盘中扫描用最近可用面板日。 输出:candidates 表(sector='hk_pe_mom'/'hk_mr1'/'hk_pe_oversold') """ import json import sqlite3 import sys import time from pathlib import Path from datetime import datetime from hk_strategies import HK_STRATEGIES, strategies_for_regime DB_PATH = Path("/home/hmo/MoFin/data/mofin.db") PANEL = "/tmp/panel_12d_hk.pkl" TD_GUARD = 5 # trend_down 连续>5天暂停超卖(组合级防守) def get_hk_regime(): """港股当前温区(smoothed markets.hk,回退 market_regime 表)""" try: p = Path("/home/hmo/MoFin/data/market_regime_smoothed.json") if p.exists(): d = json.loads(p.read_text(encoding="utf-8")) mk = (d.get("markets") or {}).get("hk") or {} if mk.get("current_regime"): return mk["current_regime"] except Exception: pass try: conn = sqlite3.connect(str(DB_PATH), timeout=5) conn.execute("PRAGMA busy_timeout=30000") # 2026-08-18 整点撞锁等待 row = conn.execute( "SELECT regime FROM market_regime WHERE market='hk' ORDER BY date DESC LIMIT 1").fetchone() conn.close() return row[0] if row else "unknown" except Exception: return "unknown" def trend_down_streak(): """trend_down 连续天数(防守用)""" try: conn = sqlite3.connect(str(DB_PATH), timeout=5) conn.execute("PRAGMA busy_timeout=30000") # 2026-08-18 整点撞锁等待 rows = conn.execute( "SELECT date, regime FROM market_regime WHERE market='hk' ORDER BY date DESC LIMIT 30").fetchall() conn.close() except Exception: return 0 run = 0 for _, reg in rows: if reg == "trend_down": run += 1 else: break return run def scan_panel(strat): """用面板最新日扫描策略信号。返回命中股票列表""" import pandas as pd panel = pd.read_pickle(PANEL) latest = panel["date"].max() p = panel[panel["date"] == latest] e = strat["entry"] cond = pd.Series(True, index=p.index) if "pe_q_max" in e: cond &= p["pe_q"] < e["pe_q_max"] if "mcap_q_max" in e: cond &= p["mcap_q"] < e["mcap_q_max"] if "sec_ret20_min" in e: cond &= p["sec_ret20"] > e["sec_ret20_min"] if "rsi_max" in e: cond &= p["rsi"] < e["rsi_max"] if "bias60_max" in e: cond &= p["bias60"] < e["bias60_max"] if "vol_ratio_min" in e: cond &= p["vol_ratio"] > e["vol_ratio_min"] if "rsi_delta_min" in e: cond &= p["rsi"] - p.groupby("code")["rsi"].transform(lambda x: x.shift(1).fillna(0)) * 0 >= e["rsi_delta_min"] hits = p[cond] return latest, hits[["code", "close"]].to_dict("records") def main(): print(f"[hk_scanner] {datetime.now().strftime('%H:%M:%S')} 港股组合扫描", flush=True) regime = get_hk_regime() td_streak = trend_down_streak() print(f" 港股温区: {regime} (trend_down连续{td_streak}天)", flush=True) # 按温区选策略 versions = strategies_for_regime(regime) if not versions: print(f" {regime} 温区无激活策略,跳过", flush=True) return # trend_down 连续>N天防守:暂停超卖策略 if regime == "trend_down" and td_streak > TD_GUARD: versions = [v for v in versions if v != "hk_mr1"] print(f" ⚠ trend_down连续{td_streak}天>守卫{TD_GUARD},暂停 hk_mr1(防守)", flush=True) print(f" 激活策略: {versions}", flush=True) conn = sqlite3.connect(str(DB_PATH), timeout=5) conn.execute("PRAGMA busy_timeout=30000") # 2026-08-18 整点撞锁等待 inserted = 0 for v in versions: strat = HK_STRATEGIES[v] try: latest, hits = scan_panel(strat) except Exception as ex: print(f" {v} 扫描失败: {ex}", flush=True) continue print(f" {v}({strat['name']}): 命中 {len(hits)} 只(面板日 {latest})", flush=True) for h in hits[:10]: code = h["code"] price = h["close"] ex = strat["exit"] exists = conn.execute( "SELECT code FROM candidates WHERE code=? AND (promoted IS NULL OR promoted=0)", (code,)).fetchone() if exists: continue conn.execute( "INSERT INTO candidates (code, name, sector, reason, entry_range, stop_loss, target, created_at) " "VALUES (?,?,?,?,?,?,?,datetime('now','localtime')) " "ON CONFLICT(code) DO UPDATE SET name=excluded.name, sector=excluded.sector, " "reason=excluded.reason, entry_range=excluded.entry_range, " "stop_loss=excluded.stop_loss, target=excluded.target", (code, code, v, f"{v}({regime}温区,{strat['summary'][:30]})", f"{round(price*0.97,2)}~{round(price*1.02,2)}", round(price*(1-ex['sl_pct']), 2), round(price*(1+ex['tp_pct']), 2))) inserted += 1 print(f" 🟢 {code} 价{price} → {v}", flush=True) conn.commit() conn.close() print(f" ✅ 新增 {inserted} 只港股候选", flush=True) if __name__ == "__main__": main()