# -*- coding: utf-8 -*- """验证:top-N(score择优) vs 随机抽样 的收益差异""" import sys, random sys.path.insert(0, "/home/hmo/MoFin") import pandas as pd import numpy as np def alpha_score(mcap_q, rsi, sec_ret20, news3): sc = 0 if mcap_q is not None and not np.isnan(mcap_q): sc += 40 if mcap_q < 0.2 else 32 if mcap_q < 0.4 else 24 if mcap_q < 0.6 else 16 if mcap_q < 0.8 else 8 if rsi is not None and not np.isnan(rsi): sc += 30 if rsi >= 45 else 22 if rsi >= 35 else 12 if rsi >= 25 else 6 if sec_ret20 is not None and not np.isnan(sec_ret20): sc += 20 if sec_ret20 >= 0 else 16 if sec_ret20 >= -10 else 8 if sec_ret20 >= -20 else 3 if news3 is not None and not np.isnan(news3): sc += 10 if news3 >= 2 else 7 if news3 >= 1 else 2 return sc panel = pd.read_pickle("/tmp/panel_12d.pkl") panel = panel.sort_values(["code", "date"]).reset_index(drop=True) g = panel.groupby("code", group_keys=False) def fwd_max(s, w): return s[::-1].rolling(w, min_periods=1).max()[::-1] def fwd_min(s, w): return s[::-1].rolling(w, min_periods=1).min()[::-1] panel["fwd_max60"] = g["close"].transform(lambda x: fwd_max(x, 60)) panel["fwd_min60"] = g["close"].transform(lambda x: fwd_min(x, 60)) panic = panel[panel["mkt_rsi"] < 25].copy() sig = panic[(panic["mcap_q"] < 0.4) & (panic["rsi"] >= 35) & (panic["sec_ret20"] >= -10)].copy() sig["score"] = sig.apply(lambda r: alpha_score(r["mcap_q"], r["rsi"], r["sec_ret20"], r["news3"]), axis=1) def pnl_of(s): ep = s["close"] fmax, fmin = s["fwd_max60"], s["fwd_min60"] if fmax >= ep * 1.30: return 30.0 if fmin <= ep * 0.88: return -12.0 return (fmax / ep - 1) * 100 if not pd.isna(fmax) else 0 days = {dt: grp for dt, grp in sig.groupby("date")} # top5 择优 top5 = sig.sort_values(["date", "score"], ascending=[True, False]).groupby("date").head(5) tp = top5.apply(pnl_of, axis=1) print(f"top5(score择优): n={len(top5)} 平均pnl={tp.mean():.2f}% 胜率={(tp>0).mean()*100:.1f}%") # 随机抽5 rng = random.Random(42) allr = [] for trial in range(20): chosen = [] for dt, grp in days.items(): n = min(5, len(grp)) chosen.extend(grp.sample(n=n, random_state=1000*trial+7).index.tolist()) rp = sig.loc[chosen].apply(pnl_of, axis=1) allr.append(rp.mean()) print(f"随机抽5×20次: 平均={np.mean(allr):.2f}% 范围{min(allr):.2f}~{max(allr):.2f}%") # top10 top10 = sig.sort_values(["date", "score"], ascending=[True, False]).groupby("date").head(10) tp10 = top10.apply(pnl_of, axis=1) print(f"top10(score择优): n={len(top10)} 平均pnl={tp10.mean():.2f}% 胜率={(tp10>0).mean()*100:.1f}%") allr10 = [] for trial in range(20): chosen = [] for dt, grp in days.items(): n = min(10, len(grp)) chosen.extend(grp.sample(n=n, random_state=2000*trial+13).index.tolist()) rp = sig.loc[chosen].apply(pnl_of, axis=1) allr10.append(rp.mean()) print(f"随机抽10×20次: 平均={np.mean(allr10):.2f}% 范围{min(allr10):.2f}~{max(allr10):.2f}%") # bottom5(最低分对照组) bot5 = sig.sort_values(["date", "score"], ascending=[True, True]).groupby("date").head(5) bp = bot5.apply(pnl_of, axis=1) print(f"bottom5(最低分): n={len(bot5)} 平均pnl={bp.mean():.2f}% 胜率={(bp>0).mean()*100:.1f}%")