Files
MoFin/research/s2_panic_v2_score_verify.py
T

76 lines
3.2 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.
# -*- 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}%")