Files
MoFin/deploy/profile-scripts/hk_scanner.py
T

156 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
# ── 消息通道统一路由(broadcast/xmpp by delivery) ──
try:
from messenger import install_stdio_hook as _msh
_msh()
except Exception:
pass
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()