# -*- coding: utf-8 -*- """ 159915 日内均值回归 最终交付版(v7) 主推参数(在成本 0.02%~0.03% 口径下 12/12 季度跑赢): 正T(低吸): 15分钟均线, 低于均线0.3%买入, 回归-0.1%或止盈0.3%卖出, 止损1.0%, 仅价>昨收时做 反T(高抛): 15分钟均线, 高于均线0.4%卖出, 回归-0.1%或止盈0.5%买回, 止损0.5%, 仅价<昨收时做 同时输出 0.02/0.03/0.05/0.08% 成本敏感性,诚实暴露策略成立的前提条件 """ import os import csv import json from collections import OrderedDict import importlib.util import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib import font_manager BASE = os.path.dirname(os.path.abspath(__file__)) spec = importlib.util.spec_from_file_location("v2", os.path.join(BASE, "均值回归_双向.py")) v2 = importlib.util.module_from_spec(spec); spec.loader.exec_module(v2) BASE_COST = v2.COST F = v2.F L = {"N": 15, "K": -0.3, "TP": 0.3, "SL": 1.0, "ED": -0.1} S = {"N": 15, "K": 0.4, "TP": 0.5, "SL": 0.5, "ED": -0.1} COSTS = [0.02, 0.03, 0.05, 0.08] MAIN = 0.02 # 主推口径:万0.5佣金 + 限价挂单成交 days = v2.load_days() print(f"载入 {len(days)} 个交易日") q_base, pc = OrderedDict(), None for d, v in days.items(): k = v2.quarter_key(d) s = pc if pc is not None else v["p"][0] / (1 + v["r"][0] / 100) q_base.setdefault(k, {"s": s, "e": v["p"][-1]}) q_base[k]["e"] = v["p"][-1] pc = v["p"][-1] q_base = {k: (x["e"] / x["s"] - 1) * 100 for k, x in q_base.items()} cache = {N: v2.make_dev_cache(days, N) for N in (15,)} tl = v2.run_side(cache[15], "long", K=L["K"], TP=L["TP"], SL=L["SL"], ED=L["ED"], trend_filter=True, skip_open=5, max_per_day=2) ts = v2.run_side(cache[15], "short", K=S["K"], TP=S["TP"], SL=S["SL"], ED=S["ED"], trend_filter=True, skip_open=5, max_per_day=2) trades = tl + ts gross = sum(t["ret"] for t in trades) + len(trades) * BASE_COST print(f"做T笔数 {len(trades)}(正T {len(tl)} / 反T {len(ts)}) 单笔毛收益 {gross/len(trades):.3f}%") def qtable(cost): q = OrderedDict() for t in trades: q.setdefault(v2.quarter_key(t["date"]), []).append((t["ret"] + BASE_COST - cost) * F) q = {k: sum(v) for k, v in sorted(q.items())} return q qts = {c: qtable(c) for c in COSTS} print(f"\n{'季度':<8}{'基准%':>9}" + "".join(f"{'增厚@'+str(c)+'%':>12}" for c in COSTS) + f"{'策略@主推%':>12}") wins = {c: 0 for c in COSTS} for q in q_base: line = f"{q:<8}{q_base[q]:>9.2f}" for c in COSTS: a = qts[c].get(q, 0.0) wins[c] += a > 0 line += f"{a:>12.2f}" line += f"{q_base[q] + qts[MAIN].get(q, 0):>12.2f}" print(line) print() for c in COSTS: tot = sum(qts[c].values()) print(f"成本{c}%: 跑赢季度 {wins[c]}/{len(q_base)} 总增厚 {tot:+.2f}% " f"年化增厚(约2.9年) {((1+tot/100)**(1/2.9)-1)*100:+.2f}%") # ==================== 图表 ==================== for f in ["Microsoft YaHei", "SimHei"]: if any(f.lower() in ff.name.lower() for ff in font_manager.fontManager.ttflist): plt.rcParams["font.family"] = f break plt.rcParams["axes.unicode_minus"] = False by_date = {c: OrderedDict() for c in COSTS} for t in trades: for c in COSTS: by_date[c].setdefault(t["date"], 0.0) by_date[c][t["date"]] += (t["ret"] + BASE_COST - c) * F xs, base_eq = [], [] eq = {c: [] for c in COSTS} ov = {c: 1.0 for c in COSTS} first, base0 = True, None for d, v in days.items(): if first: base0 = v["p"][0] / (1 + v["r"][0] / 100); first = False for c in COSTS: if d in by_date[c]: ov[c] *= (1 + by_date[c][d] / 100) be = v["p"][-1] / base0 xs.append(d); base_eq.append(be) for c in COSTS: eq[c].append(be * ov[c]) fig, axes = plt.subplots(2, 1, figsize=(12.5, 9.5), gridspec_kw={"height_ratios": [2, 1.25]}) ax = axes[0] xi = range(len(xs)) ax.plot(xi, base_eq, color="#888888", lw=1.3, ls="--", label="基准:满仓持有159915") styles = {0.02: ("#e8a33d", 1.2, "-"), 0.03: ("#d43f3f", 1.9, "-"), 0.05: ("#7a5bb5", 1.2, "-."), 0.08: ("#4a7ec8", 1.2, ":")} for c in COSTS: col, lw, ls = styles[c] lab = f"策略(成本{c}%)" + (" ←主推" if c == MAIN else "") ax.plot(xi, eq[c], color=col, lw=lw, ls=ls, label=lab) tk = list(range(0, len(xs), max(1, len(xs) // 12))) ax.set_xticks(tk) ax.set_xticklabels([xs[k][:4] + "-" + xs[k][4:6] for k in tk], rotation=30, fontsize=8) ax.set_ylabel("净值") ax.set_title(f"159915 日内均值回归做T(2023-01~2025-11)|成本0.02%: 策略{(eq[MAIN][-1]-1)*100:.2f}% vs 基准{(base_eq[-1]-1)*100:.2f}%", fontsize=12) ax.legend(loc="upper left", fontsize=8.5) ax.grid(alpha=0.25) axb = axes[1] qs = list(q_base.keys()) xq = list(range(len(qs))) w = 0.4 axb.bar([x - w / 2 for x in xq], [q_base[q] for q in qs], width=w, color="#9aa5b1", label="基准") axb.bar([x + w / 2 for x in xq], [q_base[q] + qts[MAIN].get(q, 0) for q in qs], width=w, color="#d43f3f", label="策略(主推成本0.02%)") axb.set_xticks(xq) axb.set_xticklabels(qs, rotation=30, fontsize=8) axb.axhline(0, color="#999999", lw=0.8) axb.set_ylabel("季度收益(%)") axb.set_title("分季度对比(策略 = 基准 + 做T增厚)", fontsize=11) axb.legend(fontsize=9) axb.grid(alpha=0.25, axis="y") plt.tight_layout() png = os.path.join(BASE, "均值回归_资金曲线.png") plt.savefig(png, dpi=130) csv_path = os.path.join(BASE, "均值回归_交易明细.csv") with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: wr = csv.writer(f) wr.writerow(["日期", "方向", "买入/卖回时间", "平仓时间", "入场价", "出场价", "净收益%@0.02", "入场偏离%", "平仓原因"]) for t in sorted(trades, key=lambda x: (x["date"], x["t_in"])): wr.writerow([t["date"], "正T低吸" if t["side"] == "long" else "反T高抛", "%02d:%02d" % t["t_in"], "%02d:%02d" % t["t_out"], round(t["px_in"], 4), round(t["px_out"], 4), round(t["ret"] + BASE_COST - MAIN, 3), round(t["dev_in"], 2), t["reason"]]) rows = "" for q in q_base: cells = "".join(f"
区间 2023-01-03 ~ 2025-11-28|703个交易日|168,022根1分钟K线|数据:本地购买的一分钟K线
基准:满仓持有 159915,不做择时。
策略:底仓满仓不动(完整保留基准收益)+ 用等额资金做日内双向T,做T增厚即超额来源,不加隔夜风险。
正T(低吸):价格低于15分钟均线0.3%买入;回到均线(-0.1%)或上涨 0.3% 卖出;止损 1.0%;只在价格高于昨收的日子做(顺大势、逆小势)。
反T(高抛):价格高于 15 分钟均线 0.4% 卖出;回到均线(-0.1%)或回落 0.5% 买回;止损 0.5%;只在价格低于昨收的日子做。
执行约束:每方向每日最多 2 笔,14:55 前必须平掉。
| 季度 | 基准 | {''.join(f'增厚@{c}% | ' for c in COSTS)}策略@0.02% | 跑赢 |
|---|
各成本口径下的整体结果:
| 往返成本 | 对应情形 | 跑赢季度 | 总增厚 |
|---|---|---|---|
| 0.02% | 万0.5佣金 + 全部限价挂单成交(主推口径) | {wins[0.02]}/{len(q_base)} | {sum(qts[0.02].values()):+.2f}% |
| 0.03% | 万1佣金 + 挂单成交 | {wins[0.03]}/{len(q_base)} | {sum(qts[0.03].values()):+.2f}% |
| 0.05% | 万2.5佣金 + 少量滑点 | {wins[0.05]}/{len(q_base)} | {sum(qts[0.05].values()):+.2f}% |
| 0.08% | 每笔主动吃1跳(约0.04%) | {wins[0.08]}/{len(q_base)} | {sum(qts[0.08].values()):+.2f}% |
2. "每季度都赢"处在能力边界上:对 413 组邻近参数做稳健性检验,只有 5% 的组合能做到"最多1个季度为负",
绝大多数组合最差季度在 -1.5% 附近;且当成本放宽到 0.05% 时,最好的参数组合也只能做到 10/12 个季度跑赢。不要把它当成必然规律。
3. 最难的环境:"阴跌 + 反弹反复"的季度(如 2024Q2、2025Q2),正T被套、反T被反弹打脸。建议识别到该环境(日线趋势向下 + 波动收窄)时,把做T仓位降到半仓。
4. 成交假设:回测以分钟收盘价成交,实盘中封板、急速跳水时可能无法成交或滑点扩大。交易明细已导出为 CSV,可逐笔核对。
5. 定位:这是"增强"不是"替代"——底仓必须留着,做T只是赚日内波动的钱,长期看增厚主要来自波动率而非趋势。