Files
MoFin/scripts/research/step30b_three_phase_fast.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

129 lines
5.1 KiB
Python

#!/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)