#!/usr/bin/env python3 """step37_final_v3.py — 最终模拟 v3(阶段过滤 + 支撑压力 + 最优参数) 规则(全部数据驱动): - 信号: 预测信号 + 大盘阶段过滤(排除高位/牛市) 保留: mkt_dd60 <= -5(大盘已回撤,非高位) - 入场: 信号日收盘买 - 止损: 支撑下方5%(step34验证) - 止盈: 压力位分批卖 - 兜底: 40日 - 资金: 10槽 + 15%仓位(step35最优) """ import numpy as np import pandas as pd import sqlite3 import sys sys.path.insert(0, "/tmp") from sr_calculator import SRCalculator 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"])} dates = sorted(panel["date"].unique()) # 大盘回撤 conn = sqlite3.connect("file:/home/hmo/MoFin/data/mofin.db?mode=ro", uri=True) idx_df = pd.read_sql("SELECT date, close, high FROM stock_daily WHERE code='sh000001' ORDER BY date", conn) idx_df["date"] = idx_df["date"].astype(str) idx_df["hi60"] = idx_df["high"].rolling(60).max() idx_df["mkt_dd60"] = (idx_df["close"] / idx_df["hi60"] - 1) * 100 dd_map = dict(zip(idx_df["date"], idx_df["mkt_dd60"])) panel["mkt_dd60"] = panel["date"].map(dd_map) # 信号:预测信号 + 阶段过滤(大盘已回撤,非高位) sig_cond = ( (panel["mkt_rsi"] < 50) & (panel["mcap_q"] < 0.2) & (panel["pe_q"] < 0.2) & (panel["news3"] >= 1) & (panel["sec_ret20"] < 0) & (panel["bias60"] < -20) & (panel["mkt_dd60"] <= -5) # 阶段过滤:大盘已回撤(排除高位/牛市) ) 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)] cand["year"] = cand["date"].str[:4] print("信号(含阶段过滤):", len(cand), flush=True) print("月均: {:.1f}".format(len(cand)/max(len(cand["year"].unique()),1)/12), flush=True) print("分年:", {str(y): int(c) for y, c in cand["year"].value_counts().sort_index().items()}, flush=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) 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"])} closes = df["close"].values highs = df["high"].values lows = df["low"].values cand["kidx"] = (cand["code"] + "_" + cand["date"]).map(dpos) cand = cand.dropna(subset=["kidx"]).copy() cand["kidx"] = cand["kidx"].astype(int) print("可定位:", len(cand), flush=True) # 支撑压力 sr = SRCalculator() print("=== 计算支撑压力 ===", flush=True) sig_list = [] for r in cand.itertuples(): bars_code = sr.get_bars(r.code) d_idx = bars_code.index[bars_code["date"] == r.date] if len(d_idx) == 0: continue sr_full = sr.sr_full(r.code, d_idx[0]) pv = sr_full["pivot"] chip = sr_full["chip"] if not pv: continue sig_close = closes[r.kidx] support = pv["s2"] if chip and chip["chip_ss"] < sig_close: support = max(pv["s2"], chip["chip_ss"]) resist = pv["r2"] if chip and chip["chip_sr"] > sig_close: resist = min(pv["r2"], chip["chip_sr"]) if support >= sig_close or resist <= sig_close: continue sig_list.append({"code": r.code, "date": r.date, "kidx": r.kidx, "sig_close": sig_close, "support": support, "resist": resist}) sigdf = pd.DataFrame(sig_list) print("有支撑压力:", len(sigdf), flush=True) # ── 模拟(10槽+40日+15%+支撑保护)── INIT_CAP = 1_000_000 SLOTS = 10 HOLD = 40 POS_FRAC = 0.15 STOP_BUF = 0.05 sig_by_date = {} for r in sigdf.itertuples(): sig_by_date.setdefault(r.date, []).append(r) positions = {} cash = INIT_CAP nav_history = [] trades = [] for di, d in enumerate(dates): for code in list(positions.keys()): pos = positions[code] k = dpos.get(code + "_" + d) if k is None: continue hi, lo, cl = highs[k], lows[k], closes[k] if hi >= pos["tp"]: sell_qty = pos["qty"] // 2 if sell_qty > 0: cash += sell_qty * pos["tp"] pos["qty"] -= sell_qty trades.append({"code": code, "date": d, "ret": (pos["tp"]/pos["avg_cost"]-1)*100, "reason": "tp"}) if pos["qty"] <= 0: del positions[code] continue if lo <= pos["stop"]: cash += pos["qty"] * lo trades.append({"code": code, "date": d, "ret": (lo/pos["avg_cost"]-1)*100, "reason": "stop"}) del positions[code] continue if di - pos["entry_di"] >= HOLD: cash += pos["qty"] * cl trades.append({"code": code, "date": d, "ret": (cl/pos["avg_cost"]-1)*100, "reason": "hold40"}) del positions[code] continue if d in sig_by_date: for r in sig_by_date[d]: if len(positions) >= SLOTS: break if r.code in positions: continue cur_nav = cash for c, p in positions.items(): k = dpos.get(c + "_" + d) px = closes[k] if k is not None else p["avg_cost"] cur_nav += p["qty"] * px pos_val = cur_nav * POS_FRAC price = r.sig_close if price <= 0: continue qty = int(pos_val / price) if qty <= 0 or qty * price > cash: continue cash -= qty * price positions[r.code] = { "entry_di": di, "qty": qty, "avg_cost": price, "stop": r.support * (1 - STOP_BUF), "tp": r.resist, } nav = cash for c, p in positions.items(): k = dpos.get(c + "_" + d) px = closes[k] if k is not None else p["avg_cost"] nav += p["qty"] * px nav_history.append({"date": d, "nav": nav, "n_pos": len(positions), "cash": cash}) nav_df = pd.DataFrame(nav_history) nav_df.to_csv("/tmp/step37_nav.csv", index=False) tr_df = pd.DataFrame(trades) tr_df.to_csv("/tmp/step37_trades.csv", index=False) final_nav = nav_df["nav"].iloc[-1] total_ret = (final_nav / INIT_CAP - 1) * 100 years = (pd.to_datetime(nav_df["date"].iloc[-1]) - pd.to_datetime(nav_df["date"].iloc[0])).days / 365 cagr = ((final_nav / INIT_CAP) ** (1 / years) - 1) * 100 if years > 0 else 0 cummax = nav_df["nav"].cummax() dd = (nav_df["nav"] / cummax - 1) * 100 max_dd = dd.min() print("\n=== 最终v3(阶段过滤)结果 ===", flush=True) print("总收益: {:.1f}%".format(total_ret)) print("年化(CAGR): {:.2f}%".format(cagr)) print("最大回撤: {:.1f}%".format(max_dd)) print("持仓峰值: {}".format(nav_df["n_pos"].max())) print("交易笔数: {}".format(len(tr_df))) if len(tr_df) > 0: print("平均收益: {:.2f}% 胜率: {:.1f}%".format(tr_df["ret"].mean(), (tr_df["ret"]>0).mean()*100)) print("卖出原因:", tr_df["reason"].value_counts().to_dict()) nav_df["year"] = nav_df["date"].str[:4] yr_end = nav_df.groupby("year")["nav"].last() print("\n分年净值:") for y, v in yr_end.items(): print(" {}: {:.0f} ({}%)".format(y, v, (v/INIT_CAP-1)*100)) print("现金占比均值: {:.1f}%".format((nav_df["cash"]/nav_df["nav"]).mean()*100)) print("\n=== 完成 ===", flush=True)