← 返回目录
p5.1动手⏱ 约 16 分钟

策略规格书:一句话策略有 16 种合法读法

同一句"5日上穿20日买入",最好的读法年化 3.74%,最差 -0.06%——而 16 种全部跑输买入持有

🔎 最后验证 2026-07📚 来源:16 种读法 × 8 只股票的回测于 2026-07-31 在 ECS 实测,输出见 code/outputs/🧰 qlab.engine(本课自研)、pandas 3.0.5、Python 3.12.13
为什么学这个

"5 日均线上穿 20 日均线买入,下穿卖出。"

这句话听起来精确得像一行代码。但它不是一个策略,是一族策略。

因为它没说:均线是 SMA 还是 EMA?上穿要不要确认?买多少仓位?对什么价取平均?

四个问题各有两种都合法的答案 → 16 种实现。同一份数据、同一个引擎跑完:

最好读法  SMA|确认1日|仓位100%|hlc3    年化  3.74%   期末 1,783,804
最差读法  EMA|确认2日|仓位100%|hlc3    年化 -0.06%   期末   990,332
                          读法差异 = 3.80 个百分点年化

而买入持有是 4.40%——16 种读法一种都没跑赢。

这一节讲的就是这件事:在你写下任何参数、做任何优化之前,这 3.80 个百分点已经存在了

💡 打个比方

菜谱写"盐少许"。

十个人做出来是十道菜,而且他们都没做错——"少许"本来就没有唯一解

区别在于:做菜错了你当场尝得出来,策略错了要几个月甚至几年才知道,而且你会以为那是市场的问题

一、四个维度,16 种读法

每个维度都是原句没说清楚的,而两种读法都合法:

维度读法 A读法 B原句哪里没说
均线类型SMAEMA"均线"没说是哪种
信号确认当日站稳即可连续 2 日站稳"上穿"要不要确认
仓位100%50%"买入"没说买多少
价格基准收盘价(H+L+C)/3"均线"没说对什么价取平均

实测(sh.600000,2010-01-04 ~ 2026-07-29,3976 个交易日,初始资金 100 万):

读法年化%最大回撤%夏普期末权益
SMA|确认1日|仓位100%|hlc33.74-34.80.2911,783,804
SMA|确认1日|仓位100%|close2.98-37.90.2521,590,083
SMA|确认1日|仓位50%|hlc32.29-18.80.2921,428,893
EMA|确认1日|仓位100%|close2.08-39.70.2031,383,631
…(中间 11 种略)
EMA|确认2日|仓位100%|hlc3-0.06-42.60.085990,332

参照:同期买入持有年化 4.40%。

16 种读法的最好那一种(3.74%)仍然低于买入持有

二、哪个维度最要命?

把 16 条结果按维度分组求均值:

维度读法 A 均值读法 B 均值均值差
均线类型SMA 2.34%EMA 0.86%1.49 pp
信号确认确认1日 2.09%确认2日 1.11%0.98 pp
仓位100% 1.86%50% 1.34%0.51 pp
价格基准close 1.55%hlc3 1.64%0.09 pp

贡献最大的是"均线类型"——而这恰恰是最少被写进策略描述里的一项。

大部分人会认真讨论"用 5/20 还是 10/30",却默认"均线"就是 SMA。在这四个维度里,这个从没人提的默认值,影响是最大的。

注意口径:这四个维度都是原句没说清楚的东西。本实验没有改动 5/20 这两个参数——参数敏感性是 p5.4 的题目,这里比的是"同一套参数下,读法能差多少"。

三、不是个例:8 只股票

标的最好年化%最差年化%极差 pp买入持有%
sh.6000003.74-0.063.804.40
sh.6000090.69-1.812.513.31
sh.6000108.360.977.382.07
sh.6000112.30-6.018.302.72
sh.6000151.77-1.473.244.54
sh.6000161.13-2.393.522.67
sh.6000181.30-1.062.362.57
sh.6000190.51-3.443.951.93
中位数3.66
⚠️ 避坑

"我挑最好的那种读法"——这句话本身就是 p3.4 讲过的多重检验。

你有 16 种读法,挑出年化最高的那一条,然后说"这个策略年化 3.74%"。

这和从 16 个随机策略里挑最好的一个,在统计上是同一件事:你报告的不是这条策略的期望表现,而是 16 次抽样的最大值

p3.4 的结论在这里直接适用:报告"我试了 16 种,最好的是 X"比报告"X 的年化是 3.74%"诚实得多——前者读者能自己打折,后者不能。

而本例里更尴尬的是:打完折之后,16 种全部跑输买入持有(4.40%)。

🔧 动手做:量一量"没写清楚"值多少钱(5 分钟)

python spec_ambiguity.py——同一句话策略,脚本枚举了 4 个维度的 16 种合法读法,全跑一遍。

你会看到(实测):8 只股票上,同一句话的读法年化极差中位数 3.66 个百分点

想明白:在你写下任何参数、做任何优化之前,光是"没说清楚"就已经制造了 3.66pp 的分歧。你以为在调策略,其实一半的收益差来自那些你没写明的默认选择(下单时点、确认规则、价格基准)。所以:先写规格书,再写代码。

四、解法:规格书是一个可执行对象

规格书不是一段散文,是一个能哈希、能复现、能自检的对象:

@dataclass(frozen=True)
class StrategySpec:
    # ① 数据口径
    symbol: str; start: str; end: str
    adjust: str = "后复权";  source: str = "baostock"
    # ② 信号定义
    ma_type: str = "SMA";   fast: int = 5;  slow: int = 20
    price_base: str = "close";  confirm_days: int = 1
    # ③ 进场
    entry: str = "fast>slow"
    entry_timing: str = "信号次日开盘"        # 引擎结构保证(p4.3)
    order_type: str = "市价单"
    # ④ 出场
    exit: str = "fast<=slow"
    stop_loss: str = "无"                    # 没有也必须显式写「无」
    take_profit: str = "无"
    # ⑤ 仓位
    target_weight: float = 1.0;  max_weight: float = 1.0
    rebalance: str = "信号变化时"
    # ⑥ 成本
    commission_bp: float = 2.5;  stamp_tax_bp: float = 5.0;  slippage_bp: float = 5.0
    # ⑦ 异常处理
    on_suspended: str = "不成交,顺延至下一可交易日"
    on_limit: str = 
    on_insufficient_cash:  = 
    on_missing_data:  = 

     () -> :            
        blob = json.dumps(asdict(), ensure_ascii=, sort_keys=)
         hashlib.sha256(blob.encode()).hexdigest()[:]

七类必答问题

类别少写会怎样
数据口径前复权还是后复权,结果完全不同(p1.3)
信号定义就是本节那 16 种读法
进场"买入"在收盘价还是次日开盘?p4.1 实测差异可达数十个百分点
出场没有止损也必须写"无",否则读者会自己补一个
仓位满仓和半仓的回撤差一倍(-34.8% vs -18.8%)
成本p4.2 实测:成本假设值 74 个百分点
异常停牌/涨跌停/没钱怎么办,不写就是"静默跳过"(p4.1 那个 bug)

规格 → 结果,中间没有自由裁量

规格指纹 fingerprint = fc64bd6362ab
同一份规格跑两次:1,783,804.11 / 1,783,804.11   净值序列逐位相同 True

改一个字段,指纹就变,结果必须重跑:

改动新指纹期末权益相对基准
ma_type → EMA8388597a4bad1,214,129-569,675
confirm_days → 20746520602ec1,536,114-247,690
target_weight → 0.518577270a5d61,428,893-354,911

指纹的作用不是防伪,是防止你自己记混。三个月后你看到一条净值曲线,唯一能确认"它是用哪份规格跑的"的东西,就是这十二个字符。

五、先写规格,再写代码

顺序不能反。理由不是"工程规范",是本节这张表:

如果你先写代码,那 16 种读法里你选了哪一种,是被你当时的手感决定的——而你不会记得。

❓ 测验
同事给你一个策略:「MACD 金叉买入,死叉卖出,年化 18%」。你要求补规格书,最该先问哪一项?
✏️ 填空
规格书里没有止损时,必须显式写「___」,否则读者会自己补一个,那就是第 17 种读法。

📌 免责:本课为技术教学,标的为沪深300 成分股按代码序取样(非精选),所有回测为历史模拟,16 种读法均为演示口径,不构成投资建议

✅ 小结

三件事:

  1. 一句话不是策略,是一族策略。四个维度各两种合法读法 → 16 种实现,年化从 3.74% 到 -0.06%,极差 3.80 个百分点;8 只股票上的极差中位数 3.66 pp而 16 种全部跑输买入持有(4.40%)
  2. 贡献最大的维度是"均线类型"(1.49 pp)——恰恰是最少被写进策略描述、最容易被默认掉的那一项
  3. 规格书是一个可执行对象:七类必答问题(数据/信号/进场/出场/仓位/成本/异常)、规格指纹、改一个字段指纹就变。没有止损也必须显式写"无"

外加一条统计纪律:"我挑最好的那种读法"就是 p3.4 的多重检验。报告"我试了 16 种,最好的是 X",比报告"X 年化 3.74%"诚实得多。

下一节把镜头拉远:五个策略族(趋势跟随/均值回归/时序动量/波动率目标/买入持有)在同一批标的上跑,再按市场状态拆开——你会看到,"熊市表现好"这句话,用事后标签和用交易时能拿到的标签,得出的是两个不同的结论

下一节 → 策略族谱:五个族的适用边界
🔎 来源与核验· 4 条,点开核对
本节每个关键论断都对应一个可追溯的来源 —— 这是本课程"靠谱、不过时"的底线。
「16 种合法读法实测(sh.600000,3976 个交易日,初始 100 万):最好 SMA|确认1日|仓位100%|hlc3 年化 3.74%、期末 1,783,804;最差 EMA|确认2日|仓位100%|hlc3 年化 -0.06%、期末 990,332;极差 3.80 pp;同期买入持有年化 4.40%」
📚 本节 code/spec_ambiguity.py,2026-07-31 于 ECS 实测,输出见 code/outputs/stdout.txt✓ 已核验 2026-07
「维度贡献(16 种分组均值):均线类型 SMA 2.34% vs EMA 0.86%(差 1.49 pp)、信号确认 0.98 pp、仓位 0.51 pp、价格基准 0.09 pp」
📚 同上实测✓ 已核验 2026-07
「8 只股票各自 16 种读法的年化极差:3.80/2.51/7.38/8.30/3.24/3.52/2.36/3.95,中位数 3.66 pp」
📚 同上实测✓ 已核验 2026-07
「规格对象可复现性:同一份规格(指纹 fc64bd6362ab)跑两次净值序列逐位相同;改 ma_type 为 EMA 期末权益 -569,675」
📚 本节 code/spec.py,2026-07-31 于 ECS 实测,输出见 code/outputs/spec_stdout.txt✓ 已核验 2026-07
本实验固定 5/20 参数不变,只改动原句未说明的四个维度;参数敏感性见 p5.4。
智图软件的赞赏码
都看到这了,打个赏呗!
接下来 · p5.2
策略族谱:每个族在什么条件下失效
继续读下一节 →