Files
MoFin/scripts/research/step28_predictive_signal.py
T
hmo b9c68a83a7 docs: 预测超跌反弹策略研究成果归档(方法论/策略文档/研究记录/脚本)
- 新增 strategy_research_methodology.md(由果及因/12维/铁律/支撑压力规范)
- 新增 predictive_oversold_strategy.md(v5定稿,年化18.57%)
- 新增 deployment-plan-predictive-oversold.md(整合部署计划)
- 归档 docs/research/(63份研究过程文档)+ scripts/research/(19个研究脚本)
- 更新 docs/README.md 文档中心(策略研究章节)
2026-08-10 14:37:21 +08:00

64 lines
3.0 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
"""step28_predictive_signal.py — 组合预测因子为信号(数据定阈值,全量验证)
step27 扫描出的预测因子(事前可计算):
- 大盘RSI<41(弱市大涨率4.97% vs 基线3.52%
- 小市值<0.2分位(4.60%
- 低PE<0.2分位(4.66%
- 新闻≥1条(4.23-4.52%
组合这些因子,验证叠加后的预测力(全量不抽样)
"""
import numpy as np
import pandas as pd
print("=== 加载 ===", flush=True)
panel = pd.read_pickle("/tmp/panel_12d.pkl")
panel = panel.sort_values(["code", "date"]).reset_index(drop=True)
panel["fwd_ret60"] = panel.groupby("code")["close"].transform(lambda x: x.shift(-60)/x - 1) * 100
panel["is_big"] = (panel["fwd_ret60"] >= 50).astype(int)
print("面板:", len(panel), flush=True)
base = panel.dropna(subset=["fwd_ret60"])
print("基线: 大涨率={:.2f}% avg60={:.2f}%".format(
base["is_big"].mean()*100, base["fwd_ret60"].mean()), flush=True)
def validate(cond, label, min_n=100):
s = base[cond]
if len(s) < min_n:
print("{}: n={} 样本不足".format(label, len(s)), flush=True)
return None
rate = s["is_big"].mean()*100
avg = s["fwd_ret60"].mean()
wr = (s["fwd_ret60"]>0).mean()*100
# 去重统计信号量(同股30日)
cand = s[["code", "date"]].sort_values(["code", "date"])
cand["prev"] = cand.groupby("code")["date"].shift(1)
cand["gap"] = (pd.to_datetime(cand["date"]) - pd.to_datetime(cand["prev"])).dt.days
cand = cand[(cand["prev"].isna()) | (cand["gap"] > 30)]
cand["year"] = cand["date"].str[:4]
n_dedup = len(cand)
monthly = n_dedup / max(len(cand["year"].unique()),1) / 12
print("{}: n={} 大涨率={:.2f}% avg60={:.2f}% wr={:.1f}% | 去重{} 月均{:.1f}".format(
label, len(s), rate, avg, wr, n_dedup, monthly), flush=True)
print(" 分年:", {str(y): int(c) for y, c in cand["year"].value_counts().sort_index().items()}, flush=True)
return {"n": n_dedup, "monthly": monthly, "rate": rate, "avg": avg, "wr": wr}
print("\n=== 单因子 ===", flush=True)
validate(panel["mkt_rsi"] < 41, "大盘RSI<41")
validate(panel["mcap_q"] < 0.2, "小市值<0.2")
validate(panel["pe_q"] < 0.2, "低PE<0.2")
validate(panel["news3"] >= 1, "新闻>=1")
print("\n=== 两因子组合 ===", flush=True)
validate((panel["mkt_rsi"] < 41) & (panel["mcap_q"] < 0.2), "弱市+小市值")
validate((panel["mkt_rsi"] < 41) & (panel["pe_q"] < 0.2), "弱市+低PE")
validate((panel["mcap_q"] < 0.2) & (panel["pe_q"] < 0.2), "小市值+低PE")
print("\n=== 三因子组合 ===", flush=True)
validate((panel["mkt_rsi"] < 41) & (panel["mcap_q"] < 0.2) & (panel["pe_q"] < 0.2), "弱市+小市值+低PE")
validate((panel["mkt_rsi"] < 41) & (panel["mcap_q"] < 0.2) & (panel["news3"] >= 1), "弱市+小市值+新闻")
print("\n=== 四因子组合 ===", flush=True)
validate((panel["mkt_rsi"] < 41) & (panel["mcap_q"] < 0.2) & (panel["pe_q"] < 0.2) & (panel["news3"] >= 1), "弱市+小市值+低PE+新闻")
print("\n=== 完成 ===", flush=True)