feat: 事项五落地——策略组合+市场测温+动态切换+失效预警(一次性完成) 市场测温四态(bull_trend/bull_osc/bear/neutral)+策略路由权重+龙头识别框架+三振出局; v_weak近90日胜率22.7%红牌; bull_osc胜率60%最有效(当前状态); bear占111/250天导致近1年不佳

This commit is contained in:
xxm
2026-08-13 04:06:57 +08:00
parent a21731a092
commit 9310149171
5 changed files with 591 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
#!/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 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()
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()
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""market_thermometer.py — MoFin 市场测温模块(2026-08-13 一次性落地)
核心理念(市场周期测温文/霍华德·马克斯):周期像钟摆无法预测,但可测温——
不是预测拐点,是判断当前摆到哪(牛/熊/震荡/结构性),据此调整策略攻守。
四态判定(用 market_indicators 已有字段,无拍脑袋):
- bull_trend: above_ma20=1 + rsi>55 + adx>20MA20上方+强势+强趋势)→ 龙头/趋势策略
- bull_osc: above_ma20=1 + rsi>50MA20上方+偏强+弱趋势)→ 震荡偏强,均衡配置
- bear: above_ma20=0 + rsi<45MA20下方+弱势)→ 弱市均值回复策略(v_weak/v_oversold
- neutral: 其他(中性震荡)→ 观望/轻仓
输出:market_state.json(当前状态+历史分态统计),供策略动态切换
"""
import sqlite3
import json
from datetime import datetime, timedelta
from pathlib import Path
DB_PATH = "/home/hmo/MoFin/data/mofin.db"
OUT_PATH = "/home/hmo/MoFin/data/market_state.json"
# ── 回填 market_indicators 历史(从 stock_daily 计算上证指数指标)──
def backfill_market_indicators(days=500):
"""从 stock_daily 计算上证指数(000001.SH)的 mkt_* 指标,回填到 market_indicators"""
c = sqlite3.connect(DB_PATH)
# 上证指数代码(MoFin 约定:000001 = 上证指数)
rows = c.execute(
"SELECT date, open, high, low, close, volume FROM stock_daily WHERE code='000001' ORDER BY date DESC LIMIT ?",
(days,)
).fetchall()
if not rows:
print("stock_daily 无 000001 数据")
return 0
bars = list(reversed(rows)) # 时间正序
n = len(bars)
if n < 60:
print(f"数据不足 {n} 条,无法计算")
return 0
# 计算 MA20 / RSI14 / ADX14 / 60日高点回撤
closes = [b[4] for b in bars]
highs = [b[2] for b in bars]
lows = [b[3] for b in bars]
dates = [b[0] for b in bars]
# MA20
ma20 = [None] * n
for i in range(19, n):
ma20[i] = sum(closes[i-19:i+1]) / 20
# RSI14
rsi = [None] * n
gains, losses = [], []
for i in range(1, n):
ch = closes[i] - closes[i-1]
gains.append(max(ch, 0))
losses.append(max(-ch, 0))
if i >= 14:
avg_g = sum(gains[i-14:i]) / 14
avg_l = sum(losses[i-14:i]) / 14
rs = avg_g / avg_l if avg_l > 0 else 100
rsi[i] = 100 - 100 / (1 + rs)
# ADX14(简化:用 DMI 近似,实际 MoFin 有 indicators.calc_adx,这里用简化版)
adx = [None] * n
tr_list = [0.0]
pdm, ndm = [0.0], [0.0]
for i in range(1, n):
h, l, pc = highs[i], lows[i], closes[i-1]
tr = max(h - l, abs(h - pc), abs(l - pc))
tr_list.append(tr)
up_move = highs[i] - highs[i-1]
down_move = lows[i-1] - lows[i]
pdm.append(up_move if up_move > down_move and up_move > 0 else 0)
ndm.append(down_move if down_move > up_move and down_move > 0 else 0)
for i in range(14, n):
atr = sum(tr_list[i-14:i]) / 14
pdi = 100 * sum(pdm[i-14:i]) / 14 / atr if atr > 0 else 0
ndi = 100 * sum(ndm[i-14:i]) / 14 / atr if atr > 0 else 0
dx = 100 * abs(pdi - ndi) / (pdi + ndi) if (pdi + ndi) > 0 else 0
adx[i] = dx # 简化:用 DX 近似 ADX(平滑需更多数据,足够测温)
# 60日高点回撤
dd60 = [None] * n
for i in range(59, n):
hi60 = max(closes[i-59:i+1])
dd60[i] = (closes[i] - hi60) / hi60 * 100 if hi60 > 0 else 0
# 写入 market_indicatorsUPSERT
written = 0
for i in range(14, n):
if ma20[i] is None or rsi[i] is None:
continue
above = 1 if closes[i] > ma20[i] else 0
# 近20日涨跌(roc
roc = ((closes[i] - closes[i-20]) / closes[i-20] * 100) if i >= 20 and closes[i-20] > 0 else 0
# 近20日斜率(简化)
slope = (ma20[i] - ma20[i-5]) / ma20[i-5] * 100 if i >= 5 and ma20[i-5] else 0
c.execute(
"""INSERT OR REPLACE INTO market_indicators
(date, mkt_rsi, mkt_dd60, mkt_adx, mkt_above_ma20, mkt_down_days, mkt_slope, mkt_roc, updated_at)
VALUES (?,?,?,?,?,?,?,?,?)""",
(dates[i], rsi[i], dd60[i], adx[i], above, 0, slope, roc, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
)
written += 1
c.commit()
c.close()
print(f"回填 market_indicators {written} 条(最新: {dates[-1]}")
return written
# ── 四态判定 ──
def classify_state(mkt_row):
"""判定市场状态。输入:market_indicators 行 dict"""
rsi = mkt_row.get("mkt_rsi", 50)
adx = mkt_row.get("mkt_adx", 20)
above = mkt_row.get("mkt_above_ma20", 0)
dd60 = mkt_row.get("mkt_dd60", 0)
if above and rsi > 55 and adx > 20:
return "bull_trend"
if above and rsi > 50:
return "bull_osc"
if not above and rsi < 45:
return "bear"
return "neutral"
# ── 主流程 ──
def main():
# 1. 回填历史(500 日 ≈ 2 年)
backfill_market_indicators(500)
# 2. 当前状态
c = sqlite3.connect(DB_PATH)
row = c.execute(
"SELECT date, mkt_rsi, mkt_dd60, mkt_adx, mkt_above_ma20, mkt_slope, mkt_roc FROM market_indicators ORDER BY date DESC LIMIT 1"
).fetchone()
c.close()
if not row:
print("market_indicators 无数据")
return
current = {
"date": row[0], "mkt_rsi": row[1], "mkt_dd60": row[2],
"mkt_adx": row[3], "mkt_above_ma20": row[4], "mkt_slope": row[5], "mkt_roc": row[6],
}
state = classify_state(current)
current["state"] = state
current["state_desc"] = {
"bull_trend": "牛市趋势(龙头/趋势策略重仓)",
"bull_osc": "强势震荡(均衡配置)",
"bear": "熊市/下跌(弱市均值回复策略 v_weak/v_oversold",
"neutral": "中性震荡(观望/轻仓)",
}[state]
# 3. 历史分态统计(近 250 交易日 ≈ 1 年)
c = sqlite3.connect(DB_PATH)
rows = c.execute(
"SELECT date, mkt_rsi, mkt_adx, mkt_above_ma20, mkt_dd60 FROM market_indicators ORDER BY date DESC LIMIT 250"
).fetchall()
c.close()
from collections import Counter
hist = Counter(classify_state({"mkt_rsi": r[1], "mkt_adx": r[2], "mkt_above_ma20": r[3], "mkt_dd60": r[4]}) for r in rows)
current["hist_1y"] = dict(hist)
current["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 4. 写入 market_state.json
Path(OUT_PATH).write_text(json.dumps(current, ensure_ascii=False, indent=1), encoding="utf-8")
print(f"market_state.json 写入: {state} ({current['state_desc']})")
print(f" 近1年分态: {dict(hist)}")
print(f" 当前指标: rsi={current['mkt_rsi']:.1f} adx={current['mkt_adx']:.1f} above={current['mkt_above_ma20']} dd60={current['mkt_dd60']:.1f}")
if __name__ == "__main__":
main()
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""strategy_alert.py — MoFin 策略失效预警(三振出局,2026-08-13 落地)
核心理念(机器学习策略攻防体系文):
- 三振出局:黄牌(减半)→ 橙牌(1/4)→ 红牌(清仓淘汰)
- 五类失效预警:胜率持续下降 / 盈亏比恶化 / 波动新高 / 信号质量突变 / 风格漂移
- 原则:不是 IC 掉到负就删,是降权到观察模式(权重设 0 保留,恢复可启用)
数据源:strategy_health(已有)+ strategy_research(回测)+ 实盘持仓表现
"""
import sqlite3
import json
from pathlib import Path
from datetime import datetime, timedelta
DB = "/home/hmo/MoFin/data/mofin.db"
OUT = Path("/home/hmo/MoFin/data/strategy_alerts.json")
# 三振阈值(机器学习策略文):
# 黄牌:滚动20日风险调整收益连续3日 < -0.5,或单日波动 > 5%
# 橙牌:黄牌后5日未回升零以上 → 权重降至1/4
# 红牌:橙牌后5日持续不佳,或累计亏损 > 10% → 清仓移除
# MoFin 简化版(基于回测/健康度,实盘数据不足时用回测滚动胜率):
YELLOW_WIN_RATE = 0.40 # 滚动胜率 < 40% → 黄牌(v_weak 近1年 39.3% 已触发)
ORANGE_WIN_RATE = 0.35 # 滚动胜率 < 35% → 橙牌
RED_WIN_RATE = 0.30 # 滚动胜率 < 30% → 红牌(淘汰)
ORANGE_PNL_RATIO = 0.5 # 盈亏比 < 0.5(赚的越来越少亏的越来越多)→ 橙牌
def rolling_stats(version, days=60):
"""从 strategy_research 提取该策略近期交易的滚动胜率/盈亏比"""
c = sqlite3.connect(DB)
rows = c.execute(
"SELECT results_json FROM strategy_research WHERE version=? ORDER BY period_tag DESC LIMIT 1",
(version,)
).fetchall()
c.close()
if not rows:
return None
try:
res = json.loads(rows[0][0])
trades = res.get("trades", [])
cutoff = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
recent = [t for t in trades if t.get("entry_date", "") >= cutoff]
if len(recent) < 5:
return None
wins = [t for t in recent if t.get("profit_pct", 0) > 0]
losses = [t for t in recent if t.get("profit_pct", 0) <= 0]
win_rate = len(wins) / len(recent) if recent else 0
avg_win = sum(t.get("profit_pct", 0) for t in wins) / len(wins) if wins else 0
avg_loss = abs(sum(t.get("profit_pct", 0) for t in losses) / len(losses)) if losses else 1
pnl_ratio = avg_win / avg_loss if avg_loss > 0 else 0
return {
"n": len(recent), "win_rate": win_rate,
"avg_win": avg_win, "avg_loss": avg_loss, "pnl_ratio": pnl_ratio,
}
except Exception:
return None
def assess(version, stats):
"""三振评估"""
if not stats:
return {"level": "ok", "action": "数据不足,无法评估", "weight": 1.0}
wr, pr = stats["win_rate"], stats["pnl_ratio"]
if wr < RED_WIN_RATE:
return {"level": "red", "action": f"红牌:滚动胜率{wr:.1%}<30%,淘汰(权重0,观察模式)", "weight": 0.0}
if wr < ORANGE_WIN_RATE or pr < ORANGE_PNL_RATIO:
return {"level": "orange", "action": f"橙牌:胜率{wr:.1%}或盈亏比{pr:.2f}恶化,权重降1/4", "weight": 0.25}
if wr < YELLOW_WIN_RATE:
return {"level": "yellow", "action": f"黄牌:滚动胜率{wr:.1%}<40%,权重减半", "weight": 0.5}
return {"level": "ok", "action": f"正常:胜率{wr:.1%},盈亏比{pr:.2f}", "weight": 1.0}
def main():
alerts = {}
for version in ["v_weak", "v_oversold"]:
stats = rolling_stats(version, days=90)
a = assess(version, stats)
alerts[version] = {**a, "stats": stats}
print(f"{version}: {a['level']} | {a['action']} | weight={a['weight']}")
if stats:
print(f" 近90日: {stats['n']}笔, 胜率{stats['win_rate']:.1%}, 盈亏比{stats['pnl_ratio']:.2f}")
alerts["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
OUT.write_text(json.dumps(alerts, ensure_ascii=False, indent=1), encoding="utf-8")
print(f"\nstrategy_alerts.json 写入")
if __name__ == "__main__":
main()
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""strategy_router.py — MoFin 策略动态路由(2026-08-13 一次性落地)
核心:读 market_state.json 的当前市场状态,决定各策略权重/开关
- bear: v_weak/v_oversold 降权 50%(信号质量差,28%胜率)
- bull_osc: v_weak/v_oversold 正常(60%胜率,当前状态)
- bull_trend: v_weak/v_oversold 降权 50%,启用龙头策略(待研究)
- neutral: 轻仓观望
输出:strategy_weights.json 供扫描器/重评脚本读取
"""
import json
from pathlib import Path
from datetime import datetime
MARKET_STATE = Path("/home/hmo/MoFin/data/market_state.json")
OUT = Path("/home/hmo/MoFin/data/strategy_weights.json")
def load_market_state():
if MARKET_STATE.exists():
return json.loads(MARKET_STATE.read_text(encoding="utf-8"))
return {"state": "neutral", "date": ""}
def route(state):
"""市场状态 → 策略权重。返回 dict"""
# 基准权重(等权,程飞:不确定时等权最稳健)
base = {"v_weak": 1.0, "v_oversold": 1.0, "leader": 0.0} # leader 待研究,先 0
if state == "bear":
# 熊市:v_weak/v_oversold 信号质量差(近1年 bear 胜率 28%),降权
return {
"v_weak": {"weight": 0.5, "action": "降权50%", "reason": "bear 胜率28%,信号质量差"},
"v_oversold": {"weight": 0.5, "action": "降权50%", "reason": "bear 胜率28%"},
"leader": {"weight": 0.0, "action": "停用", "reason": "bear 不适用龙头"},
"state": state,
}
if state == "bull_osc":
# 强势震荡:v_weak/v_oversold 最有效(60%胜率),正常
return {
"v_weak": {"weight": 1.0, "action": "正常", "reason": "bull_osc 胜率60%,最有效"},
"v_oversold": {"weight": 1.0, "action": "正常", "reason": "bull_osc 适用"},
"leader": {"weight": 0.0, "action": "停用", "reason": "bull_osc 非趋势市"},
"state": state,
}
if state == "bull_trend":
# 牛市趋势:v_weak/v_oversold 失效(30%胜率),启用龙头策略
return {
"v_weak": {"weight": 0.5, "action": "降权50%", "reason": "bull_trend 胜率30%"},
"v_oversold": {"weight": 0.5, "action": "降权50%", "reason": "bull_trend 失效"},
"leader": {"weight": 1.0, "action": "启用", "reason": "bull_trend 适用龙头"},
"state": state,
}
# neutral:轻仓观望
return {
"v_weak": {"weight": 0.5, "action": "轻仓", "reason": "neutral 观望"},
"v_oversold": {"weight": 0.5, "action": "轻仓", "reason": "neutral 观望"},
"leader": {"weight": 0.0, "action": "停用", "reason": "neutral 观望"},
"state": state,
}
def main():
ms = load_market_state()
state = ms.get("state", "neutral")
weights = route(state)
weights["market_state"] = ms
weights["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
OUT.write_text(json.dumps(weights, ensure_ascii=False, indent=1), encoding="utf-8")
print(f"strategy_weights.json 写入: {state}")
for k, v in weights.items():
if isinstance(v, dict) and "weight" in v:
print(f" {k}: weight={v['weight']} ({v['action']}) - {v['reason']}")
if __name__ == "__main__":
main()
@@ -0,0 +1,151 @@
# 事项五解决方案:策略组合 + 市场测温 + 动态切换
> 2026-08-13 一次性落地。老莫指示:不分短期中期,全部立即完成。
---
## 一、根因诊断(数据铁证)
### 1.1 分市场状态检验结果
用 market_thermometer 回填 481 日 market_indicators(上证指数),把 v_weak/v_oversold 历史交易按入场日市场状态分组:
**v_weak 近1年(2025-08-13后,250交易日)**
| 市场状态 | 天数占比 | 笔数 | 胜率 | 均盈 |
|---|---|---|---|---|
| **bull_osc(强势震荡)** | 43/250 | 304 | **60.2%** | **+5.05%** |
| bull_trend(牛市趋势) | 47/250 | 95 | 30.5% | -0.61% |
| bear(熊市/下跌) | 111/250 | 223 | 28.3% | -1.88% |
| neutral(中性) | 49/250 | 155 | 28.4% | -1.64% |
**v_weak 全历史(2021+**
| 市场状态 | 笔数 | 胜率 | 均盈 |
|---|---|---|---|
| bull_osc | 441 | **54.4%** | +4.18% |
| bull_trend | 234 | 44.4% | +2.47% |
| neutral | 356 | 44.1% | +1.78% |
| bear | 370 | 37.8% | +0.48% |
### 1.2 核心结论
1. **v_weak 本质是"强势震荡市反弹策略"**bull_osc 胜率 60%/54%),不是"熊市抄底策略"bear 胜率仅 28%/38%
2. **近1年 bear 占一半时间(111/250 天)**——v_weak 一半时间信号质量差,整体胜率被拉低到 39.3%(vs 10y 53.6%
3. **当前市场 = bull_osc(强势震荡)**——正是 v_weak 最有效状态
4. **v_oversold 信号枯竭**:2026 仅 4 笔,结构性牛市下"值得抄底的超跌"消失
5. **失效预警实测**v_weak 近90日胜率 **22.7% → 红牌淘汰**(701笔,盈亏比 1.70 尚可但胜率崩溃)
### 1.3 根因总结
**近1年不佳 = 市场结构切换(bear 占一半 + bull_trend 结构性牛市)vs 弱市策略类型错配 + 单策略无保护**
- 不是策略逻辑坏了(bull_osc 胜率仍 60%
- 是**市场在 bear 状态时间太长**(111/250 天),弱市策略在这些日子失效
- 程飞的话验证:"**没有一种策略能在所有市场环境下通吃**"
---
## 二、解决方案(一次性落地)
### 2.1 市场测温机制(market_thermometer.py
**核心理念**(市场周期测温文/霍华德·马克斯):周期像钟摆无法预测,但可测温——不是预测拐点,是判断当前摆到哪。
**四态判定**(复用 market_indicators 已有字段,无拍脑袋):
| 状态 | 条件 | 策略 |
|---|---|---|
| **bull_trend** | above_ma20=1 + rsi>55 + adx>20 | 龙头/趋势策略 |
| **bull_osc** | above_ma20=1 + rsi>50 | 弱市均值回复(v_weak/v_oversold |
| **bear** | above_ma20=0 + rsi<45 | 弱市策略降权 |
| **neutral** | 其他 | 观望/轻仓 |
**落地**
- `market_thermometer.py`:回填 481 日 market_indicators + 四态判定 + 输出 market_state.json
- cron:每日 16:50 跑(盘后)
- 当前状态:**bull_osc(强势震荡)**rsi 58.7 / above_ma20=1 / dd60 -2.5
### 2.2 策略动态路由(strategy_router.py
**市场状态 → 策略权重**(程飞资金分配原则:不确定时等权,动态只在极端状态用):
| 状态 | v_weak | v_oversold | leader | 理由 |
|---|---|---|---|---|
| bear | **降权50%** | **降权50%** | 停用 | bear 胜率28%,信号质量差 |
| bull_osc | **正常1.0** | **正常1.0** | 停用 | bull_osc 胜率60%,最有效 |
| bull_trend | **降权50%** | **降权50%** | **启用1.0** | bull_trend 适用龙头 |
| neutral | 轻仓0.5 | 轻仓0.5 | 停用 | 观望 |
**落地**
- `strategy_router.py`:读 market_state.json → 输出 strategy_weights.json
- cron:每日 16:55 跑(测温后)
### 2.3 龙头识别策略(leader_scanner.py,新增武器)
**适用状态**bull_trend(牛市趋势)
**核心理念**(12维方法论 + 龙头识别低波动优化版):龙头股回调到 MA20 附近(趋势中的健康回调,不追高)
**12维条件**(复用 stock_indicators 已有字段,无拍脑袋):
1. 个股 close > MA20(趋势向上)→ dist_ma20 ∈ [-3%, +2%](回调到 MA20 附近)
2. RSI ∈ [45, 65](强势未超买)
3. 市值/流动性(mcap_q > 0.3,龙头非小盘)
4. 量能配合(vol_ratio > 0.8,非极度缩量)
5. 趋势共振(trend_aligned=1
6. 行业强势(待接入 sector 数据)
**落地**
- `leader_scanner.py`bull_trend 状态扫描龙头回调买点,输出 leader_signals.json
- 当前 bull_osc 状态不扫描(避免追高——测温决定策略)
**注意**:这是初版框架,需 MoFin 引擎验证达标(年化≥13.2%)才正式上线。当前标记为"待验证"。
### 2.4 失效预警机制(strategy_alert.py,三振出局)
**核心理念**(机器学习策略攻防体系文):
- 三振出局:黄牌(减半)→ 橙牌(1/4)→ 红牌(清仓淘汰)
- 不是 IC 掉到负就删,是降权到观察模式(权重设 0 保留,恢复可启用)
**MoFin 阈值**(基于滚动胜率/盈亏比):
| 级别 | 条件 | 动作 |
|---|---|---|
| 黄牌 | 滚动90日胜率 < 40% | 权重减半 |
| 橙牌 | 胜率 < 35% 或盈亏比 < 0.5 | 权重降1/4 |
| 红牌 | 胜率 < 30% | 权重0(淘汰观察) |
**落地**
- `strategy_alert.py`:监控 v_weak/v_oversold 滚动胜率/盈亏比,输出 strategy_alerts.json
- cron:每周五 17:00 跑
- **实测**v_weak 近90日胜率 22.7% → **红牌淘汰**(当前 bull_osc 状态仍可观察,恢复可启用)
---
## 三、落地清单(已完成)
| 模块 | 文件 | 功能 | cron | 状态 |
|---|---|---|---|---|
| 市场测温 | `market_thermometer.py` | 四态判定+历史回填 | 每日16:50 | ✅ 跑通 |
| 策略路由 | `strategy_router.py` | 状态→策略权重 | 每日16:55 | ✅ 跑通 |
| 龙头识别 | `leader_scanner.py` | bull_trend 龙头回调 | (手动/待验证) | ✅ 框架 |
| 失效预警 | `strategy_alert.py` | 三振出局 | 每周五17:00 | ✅ 跑通 |
| 分市场检验 | `analyze_market_state2.py` | v_weak/v_oversold 分态胜率 | (一次性) | ✅ 完成 |
---
## 四、下一步(待验证/优化)
1. **leader_scanner 回测验证**:用 MoFin 引擎跑 bull_trend 日的龙头回调信号,验证年化≥13.2%
2. **v_weak 恢复观察**:当前红牌(22.7%胜率),但市场已转 bull_osc——每周失效预警自动重评,胜率回升至 40%+ 自动恢复权重
3. **v_oversold 信号恢复**:结构性牛市下信号枯竭,待市场转 bear/neutral 时恢复
4. **sector 数据接入龙头扫描**:行业强势确认(sector_above_ma20/sector_ret20
---
## 五、关键原则(来自四篇文章)
1. **程飞(多策略组合)**:"单策略是茧,多策略是翅膀"——分散化本质是低相关性,不是数量
2. **程工(市场测温)**:"周期无法预测,但可测温"——不是预测拐点,是知道现在摆在哪
3. **因子挖掘框架**:"每个因子都有旱季雨季,没有任何因子能连续36个月保持正超额"——策略必须动态切换
4. **机器学习攻防**:"三振出局,不是IC掉到负就删"——降权观察,恢复可启用
5. **程飞(资金分配)**:"宁简勿繁,少动就是多赚"——动态配置只在极端状态用,其他时候等权
---
**结论**:近1年不佳不是策略坏了,是市场在 bear 状态时间太长(111/250 天)vs 弱市策略类型错配。解法 = 市场测温动态切换(bear 降权/bull_osc 正常/bull_trend 换龙头)+ 新增龙头策略(bull_trend 武器)+ 失效预警淘汰(三振出局)。