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

138 lines
5.6 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 -*-
"""leader_scanner.py — MoFin 龙头识别策略(bull_trend 适用,2026-08-13 落地)
核心逻辑(12维方法论 + 龙头识别低波动优化版理念):
- 适用状态:bull_trendabove_ma20 + rsi>55 + adx>20
- 入场:龙头股回调到 MA20 附近(趋势中的健康回调,不追高)
- 12维条件(复用 stock_indicators 已有字段,无拍脑袋):
1. 个股 close > MA20(趋势向上)
2. dist_ma20 ∈ [-3%, +2%](回调到 MA20 附近,不追高)
3. 行业强势(sector_above_ma20=1 或 sector_ret20>0,需 sector 数据)
4. 市值/流动性过滤(mcap_q > 0.3,龙头非小盘)
5. RSI ∈ [45, 65](强势但未超买)
6. 量能配合(vol_ratio > 0.8,非极度缩量)
- 出场:跌破 MA20 或达到上方压力位
- RR:压力位(前高/筹码阻力)/ 支撑位(MA20)计算
注意:这是初版框架,需 MoFin 引擎验证达标(年化≥13.2%)才正式上线
"""
import os
import sqlite3
import json
from datetime import datetime
from pathlib import Path
DB = "/home/hmo/MoFin/data/mofin.db"
OUT = Path("/home/hmo/MoFin/data/leader_signals.json")
def load_market_state():
p = Path("/home/hmo/MoFin/data/market_state.json")
if p.exists():
return json.loads(p.read_text(encoding="utf-8"))
return {"state": "neutral"}
def scan_leaders():
"""扫描龙头股回调买点(bull_trend 状态)"""
ms = load_market_state()
state = ms.get("state", "neutral")
if state != "bull_trend":
print(f"当前状态 {state},非 bull_trend,不扫描龙头(避免追高)")
return []
c = sqlite3.connect(DB)
# 最新交易日
row = c.execute("SELECT MAX(date) FROM stock_indicators").fetchone()
if not row or not row[0]:
c.close()
return []
latest = row[0]
print(f"扫描日期: {latest}")
# 龙头条件(12维)
rows = c.execute(
"""SELECT code, ma20, rsi, dist_ma20, mcap_q, pe_q, vol_ratio, trend_aligned
FROM stock_indicators
WHERE date=? AND ma20 IS NOT NULL AND rsi IS NOT NULL""",
(latest,)
).fetchall()
c.close()
signals = []
for r in rows:
code, ma20, rsi, dist_ma20, mcap_q, pe_q, vol_ratio, trend_aligned = r
# 条件1: 趋势向上(close > MA20 → dist_ma20 > 0,或接近)
if dist_ma20 is None or dist_ma20 < -3 or dist_ma20 > 2:
continue
# 条件2: RSI 强势未超买
if rsi < 45 or rsi > 65:
continue
# 条件3: 市值/流动性(龙头非小盘,mcap_q > 0.3
if mcap_q is not None and mcap_q < 0.3:
continue
# 条件4: 量能配合
if vol_ratio is not None and vol_ratio < 0.8:
continue
# 条件5: 趋势共振(trend_aligned=1
if trend_aligned != 1:
continue
signals.append({
"code": code, "ma20": ma20, "rsi": rsi,
"dist_ma20": dist_ma20, "mcap_q": mcap_q, "pe_q": pe_q,
"vol_ratio": vol_ratio, "entry_reason": "龙头回调MA20",
})
# 按 dist_ma20 排序(最接近 MA20 的优先)
signals.sort(key=lambda x: abs(x["dist_ma20"]))
print(f"龙头信号: {len(signals)} 只")
for s in signals[:5]:
print(f" {s['code']}: dist_ma20={s['dist_ma20']:.1f}% rsi={s['rsi']:.1f} mcap_q={s['mcap_q']}")
return signals
def main():
signals = scan_leaders()
# 2026-08-18 补写 candidatestrend_up 激活策略真正进入选股管道)
_strategy_tag = os.environ.get("LEADER_STRATEGY", "v_next")
try:
_c = sqlite3.connect(DB, timeout=30)
_c.execute("PRAGMA busy_timeout=30000")
_now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
_ins = 0
for s in signals:
code = str(s["code"])
_price = s.get("ma20") or 0 # 用 MA20 附近作为参考价
if _price <= 0:
continue
_sl = round(_price * 0.92, 2) # 跌破 MA20 容忍 8%
_tp = round(_price * 1.15, 2) # +15% 压力位
_mid = (_price * 0.98 + _price) / 2
_rr = round((_tp - _mid) / (_mid - _sl), 2) if _mid > _sl else 0
_en = f"{_price*0.98:.2f}~{_price:.2f}"
try:
_c.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 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, _strategy_tag,
f"龙头回调MA20: rsi={s.get('rsi')} dist={s.get('dist_ma20')}% mcap_q={s.get('mcap_q')}",
_en, _sl, _tp, _rr, _strategy_tag))
_ins += 1
except Exception:
pass
_c.commit(); _c.close()
print(f"写入 candidates: {_ins} 只 (策略 {_strategy_tag})")
except Exception as e:
print(f"candidates 写入失败: {e}")
out = {
"state": load_market_state().get("state", "neutral"),
"signals": signals,
"count": len(signals),
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
OUT.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
print(f"leader_signals.json 写入")
if __name__ == "__main__":
main()