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

129 lines
5.3 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.
import time
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""b_td1_v3_scanner.py — B组超跌·原池优选 实盘扫描器(v4 纯 SQL2026-08-17
架构铁律(老莫):数据使用层不采集、不计算指标——指标是数据加工层的事。
采集层 daily_kline_collector → stock_daily(原始K线)
加工层 factor_engine(17:05 cron) → stock_indicatorsbias60/rsi/dist_lo20/r5f/mcap_q/pe_q 全算好)
使用层 本扫描器 → 只 SQL 查询,零计算零网络
stock_indicators 覆盖 3972只(08-14)close 需联表 stock_daily 取。
"""
import sys, sqlite3
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
TOP_N = 5
def load_regime():
try:
from regime_gate import get_current_regime
rg = get_current_regime()
if rg and rg.get("regime") != "unknown":
return rg.get("regime")
except Exception:
pass
try:
conn = sqlite3.connect(str(DB_PATH), timeout=5)
r = conn.execute("SELECT regime FROM market_regime WHERE market='a' ORDER BY date DESC LIMIT 1").fetchone()
conn.close()
return r[0] if r else "unknown"
except Exception:
return "unknown"
def _sql_scan():
"""纯 SQLstock_indicators(加工层指标) JOIN stock_daily(close) 最新完整日,池条件过滤"""
conn = sqlite3.connect(str(DB_PATH), timeout=10)
day = conn.execute(
"SELECT MAX(date) FROM stock_indicators WHERE date < date('now','localtime')").fetchone()[0]
if not day:
conn.close()
return []
rows = conn.execute(
"SELECT i.code, sd.close, i.bias60, i.rsi, i.dist_lo20, i.r5f, i.mcap_q, i.pe_q "
"FROM stock_indicators i JOIN stock_daily sd ON sd.code=i.code AND sd.date=i.date "
"WHERE i.date=? AND i.mcap_q<0.3 AND i.pe_q<0.3 "
"AND i.bias60<-20 AND i.rsi<40 AND i.dist_lo20>5", (day,)).fetchall()
conn.close()
hits = []
for code, close, b60, rsi, dist, r5, mq, pq in rows:
if not close or close <= 0:
continue
sc = 0
sc += 40 if b60 < -30 else 32 if b60 < -20 else 20
sc += 30 if rsi < 30 else 24 if rsi < 40 else 14
if r5 is not None:
sc += 10 if r5 < -25 else 7 if r5 < -15 else 4
hits.append((code, code, {
"price": close, "bias60": round(b60, 2), "rsi": round(rsi, 2),
"dist_lo20": round(dist, 2), "ret5": round(r5 or 0, 2),
"mcap_q": mq, "score": sc, "target": round(close * 1.15, 2),
"stop_loss": round(close * 0.92, 2), "date": day,
}))
hits.sort(key=lambda x: -x[2]["score"])
return hits
def main():
import argparse
from datetime import datetime
ap = argparse.ArgumentParser()
ap.add_argument("--force", action="store_true")
ap.add_argument("--top", type=int, default=TOP_N)
args = ap.parse_args()
regime = load_regime()
print(f"[b_td1_v3] {datetime.now().strftime('%H:%M')} 扫描开始 温区={regime}", flush=True)
if not args.force and regime not in ("trend_down", "choppy"):
print(f" 温区 {regime} 非超跌池主战场,跳过", flush=True)
return
hits = _sql_scan()
if not hits:
print(" SQL扫描无命中(最新完整日无满足池条件的小市值超跌股)", flush=True)
return
hits = hits[: args.top]
print(f" SQL扫描命中 {len(hits)} 只(score降序前{args.top}", flush=True)
conn = sqlite3.connect(str(DB_PATH), timeout=10)
inserted = 0
for code, name, sig in hits:
reasons = (f"dist_lo20={sig['dist_lo20']}% bias60={sig['bias60']}% "
f"rsi={sig['rsi']} ret5={sig['ret5']}% mcap_q={sig['mcap_q']} score={sig['score']}")
_mid_v = (sig['price'] * 0.98 + sig['price']) / 2
_rr_v = round((sig['target'] - _mid_v) / (_mid_v - sig['stop_loss']), 2) if _mid_v > sig['stop_loss'] > 0 else 0
# 2026-08-18 database is locked 重试(整点调度撞锁致崩溃,工单P1)
import random as _rd
_w_ok = False
for _try in range(4):
try:
conn.execute(
"INSERT INTO candidates (code, name, sector, reason, entry_range, stop_loss, target, rr, source_strategy, 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, rr=excluded.rr, source_strategy=excluded.source_strategy",
(code, code, "b_td1_v3", reasons,
f"{sig['price']*0.98:.2f}~{sig['price']:.2f}", sig["stop_loss"], sig["target"], _rr_v, "b_td1_v3"))
_w_ok = True
break
except Exception as _e:
if "locked" in str(_e).lower():
time.sleep(0.4 * (2 ** _try) + _rd.uniform(0, 0.2))
else:
raise
if _w_ok:
inserted += 1
print(f" 🟢 {code} {code}{sig['price']} score={sig['score']} {reasons}", flush=True)
conn.commit()
conn.close()
print(f" ✅ 新增 {inserted} 只 b_td1_v3 候选", flush=True)
if __name__ == "__main__":
main()