#!/usr/bin/env python3 """step30b_three_phase_fast.py — 三段分析(向量化,快速版) 用 DataFrame 向量化替代逐信号循环 """ import numpy as np import pandas as pd import sqlite3 print("=== 加载 ===", flush=True) panel = pd.read_pickle("/tmp/panel_12d.pkl") panel = panel.sort_values(["code", "date"]).reset_index(drop=True) panel["_key"] = panel["code"] + "_" + panel["date"] pos_map = {k: i for i, k in enumerate(panel["_key"])} print("面板:", len(panel), flush=True) sig_cond = ( (panel["mkt_rsi"] < 41) & (panel["mcap_q"] < 0.2) & (panel["pe_q"] < 0.2) & (panel["news3"] >= 1) & (panel["sec_ret20"] < 0) & (panel["bias60"] < -20) ) cand = panel[sig_cond][["code", "date"]].copy() cand = cand.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)] print("最终信号:", len(cand), flush=True) # 加载K线,一次读入所有需要的股票 conn = sqlite3.connect("file:/home/hmo/MoFin/data/mofin.db?mode=ro", uri=True) codes = cand["code"].unique().tolist() ph = ",".join("?" * len(codes)) df = pd.read_sql("SELECT code, date, close, high, low FROM stock_daily WHERE code IN ({}) ORDER BY code, date".format(ph), conn, params=codes) df["date"] = df["date"].astype(str) df["code"] = df["code"].astype(str).str.zfill(6) print("K线:", len(df), flush=True) # 建位置索引 df = df.sort_values(["code", "date"]).reset_index(drop=True) df["_key"] = df["code"] + "_" + df["date"] dpos = {k: i for i, k in enumerate(df["_key"])} print("K线索引:", len(dpos), flush=True) # 为每个信号定位 K线位置 cand["kloc"] = cand["code"] + "_" + cand["date"] cand["kidx"] = cand["kloc"].map(dpos) cand = cand.dropna(subset=["kidx"]).copy() cand["kidx"] = cand["kidx"].astype(int) print("可定位信号:", len(cand), flush=True) # ── A段:入场时机(向量化,用 numpy 切片)── print("\n=== A段:入场时机(持有30日)===", flush=True) results = {m: [] for m in ["信号日收盘", "次日开盘", "回升1%", "回升3%", "3日企稳"]} closes = df["close"].values opens = df["close"].values # 无open列,用close近似 highs = df["high"].values lows = df["low"].values for r in cand.itertuples(): idx = r.kidx if idx + 80 >= len(closes): continue sig_close = closes[idx] if sig_close <= 0: continue # 信号日收盘买,持有30日 results["信号日收盘"].append((closes[idx+min(30, 79)] / sig_close - 1) * 100) # 次日开盘 if opens[idx+1] > 0: results["次日开盘"].append((closes[idx+min(30, 79)] / opens[idx+1] - 1) * 100) # 回升1%/3% for pct, mn in [(0.01, "回升1%"), (0.03, "回升3%")]: for j in range(1, min(10, 80)): if closes[idx+j] / sig_close - 1 >= pct: buy = closes[idx+j] results[mn].append((closes[idx+j+min(30, 79-j)] / buy - 1) * 100 if idx+j+30 < len(closes) else (closes[min(idx+79, len(closes)-1)] / buy - 1) * 100) break # 3日企稳 sig_low = lows[idx] for j in range(1, min(4, 80)): if lows[idx+j] >= sig_low * 0.98: buy5 = closes[idx+j] results["3日企稳"].append((closes[idx+j+min(30, 79-j)] / buy5 - 1) * 100 if idx+j+30 < len(closes) else (closes[min(idx+79, len(closes)-1)] / buy5 - 1) * 100) break print("| 入场方式 | n | 30日收益均值 | 胜率 |", flush=True) print("|---|---:|---:|---:|", flush=True) for m, arr in results.items(): if len(arr) < 50: continue a = np.array(arr) print("| {} | {} | {:.2f}% | {:.1f}% |".format(m, len(a), a.mean(), (a>0).mean()*100), flush=True) # ── B段:离场时机(信号日买)── print("\n=== B段:离场时机 ===", flush=True) def sim_exit_fast(idx, buy, tp_pct, sl_pct, max_hold): if buy <= 0: return None for j in range(1, min(max_hold+1, 80)): hi, lo = highs[idx+j], lows[idx+j] if tp_pct and hi >= buy * (1 + tp_pct): return tp_pct * 100 if sl_pct and lo <= buy * (1 - sl_pct): return (lo / buy - 1) * 100 return (closes[min(idx+max_hold, len(closes)-1)] / buy - 1) * 100 print("| 出场规则 | n | 均值 | 胜率 |", flush=True) print("|---|---:|---:|---:|", flush=True) for tp, sl, hold, label in [ (None, None, 20, "持有20日"), (None, None, 40, "持有40日"), (0.15, 0.10, 60, "tp15%/sl10%/60日"), (0.20, 0.10, 60, "tp20%/sl10%/60日"), (0.25, 0.10, 60, "tp25%/sl10%/60日"), (0.30, 0.10, 60, "tp30%/sl10%/60日"), (0.20, 0.15, 60, "tp20%/sl15%/60日"), (0.30, 0.15, 60, "tp30%/sl15%/60日"), ]: rets = [] for r in cand.itertuples(): idx = r.kidx if idx + 80 >= len(closes): continue sig_close = closes[idx] ret = sim_exit_fast(idx, sig_close, tp, sl, hold) if ret is not None: rets.append(ret) if len(rets) > 50: a = np.array(rets) print("| {} | {} | {:.2f}% | {:.1f}% |".format(label, len(a), a.mean(), (a>0).mean()*100), flush=True) print("\n=== 完成 ===", flush=True)