深圳融克迪特科技有限公司 Logo,金融科技,量化交易,软件开发

深圳融克迪特科技有限公司

RONG CREDIT TECHNOLOGY CO., LTD.

基础入门

【vectorbt 系列 第4讲】构造买入信号

本讲把价格与均线关系翻译成第一列可检查的买入布尔信号,覆盖规则定义、布尔列生成、命中样本检查和信号解释。目标是让入门者理解“信号”本质上是规则表达,而不是直接等于最终回测结果。

2026-04-23 智铨研究 阅读时长 8 分钟

目录

  1. 本节目标
  2. 为什么先做买入信号,而不是同时做全套策略
  3. 沿用第 3 讲已经有ma_5的工作表
  4. 四步构造最小买入信号
  5. 先定义规则口径
  6. 把规则写成布尔列
  7. 把关键列一起打印
  8. 只筛出True的行做快速检查
  9. 确认买入信号列是有效的
  10. 新列存在
  11. 新列是布尔含义
  12. 至少出现过一次True
  13. 信号只在均线可用后才有意义
  14. 为什么买入信号看着不自然
  15. 前几行都是False
  16. 信号太多
  17. 一条True都没有
  18. 哪些情况仍然算本讲完成
  19. 写一个最小信号函数
  20. 完成标准
  21. 信号列不是“策略结果”,只是规则表达
  22. 看信号时不要只看数量,要看位置
  23. 先避免前视偏差
  24. 如果想把信号看得更清楚,可以加一列解释文本
  25. 信号过多或过少时,应该怎么理解
  26. 这一讲对下一讲卖出信号有什么铺垫
  27. 一个更完整的第 4 讲检查片段
  28. 系列衔接
  29. 风险揭示与免责声明

1. 本节目标

第 4 讲开始第一次把“指标”推进成“信号”。本讲的目标是:基于第 3 讲已经算出的 5 日均线,构造一列最简单、最容易解释的买入信号。这里的买入信号不追求复杂,不涉及完整策略,只要求你第一次明确看到:某个条件成立时,程序可以把它标成 True

学完这一讲后,你可以直接完成下面几件事:

  1. 知道买入信号依赖哪些列。
  2. 会写出一条最小的布尔条件。
  3. 能打印并检查哪些行被标记成了买入时刻。

这是从“看到指标”走向“能用指标做决策”的关键一步。

2. 为什么先做买入信号,而不是同时做全套策略

如果一上来就同时做买入、卖出、仓位和回测结果,新手很容易在一个步骤里混入四五层逻辑,最后连错误出在哪里都看不出来。第 4 讲故意只做买入信号,就是为了让你先学会:

  1. 用一条清楚的规则定义“什么时候认为该入场”。
  2. 把这条规则变成布尔列。
  3. 检查布尔列和价格、均线之间的对应关系。

向量化回测的很多复杂性,最后都建立在这种最基础的布尔列之上。

3. 沿用第 3 讲已经有ma_5的工作表

signal_df = work_df.copy()
print(signal_df[['date', 'close', 'ma_5']].head(10))

当前表里至少应该已经有:

如果这三列都还没稳住,就不应该急着往下做信号。

4. 四步构造最小买入信号

买入信号这一讲的重点不是追求复杂规则,而是把“条件成立时生成一列布尔值”这个动作真正走通。

5. 先定义规则口径

本讲用最简单的口径:当收盘价大于 5 日均线时,认为满足买入条件。这条规则不意味着它就是最好的策略,只是它足够简单,适合入门练习。

6. 把规则写成布尔列

signal_df['buy_signal'] = signal_df['close'] > signal_df['ma_5']

这一步之后,你就会得到一列 True / False。这就是第 4 讲最核心的产物。

7. 把关键列一起打印

print(signal_df[['date', 'close', 'ma_5', 'buy_signal']].head(12))

一定要把这四列一起看,因为你现在关心的不是“有没有生成一列布尔值”,而是“这个布尔值到底是不是按我理解的逻辑出现的”。

8. 只筛出True的行做快速检查

buy_rows = signal_df[signal_df['buy_signal']]
print(buy_rows[['date', 'close', 'ma_5']].head())

这一步特别有用,因为它会把“规则命中的样本点”直接拉出来,让你快速判断是否有明显不合理的地方。

9. 确认买入信号列是有效的

验证时别只盯着有没有新列,更要确认这列真假值和价格、均线之间的关系说得通。

10. 新列存在

assert 'buy_signal' in signal_df.columns

11. 新列是布尔含义

assert signal_df['buy_signal'].dtype == bool or str(signal_df['buy_signal'].dtype) == 'bool'

12. 至少出现过一次True

assert signal_df['buy_signal'].sum() >= 0

如果你当前样本较短,也可能没有太多 True,但你至少要能解释为什么。

13. 信号只在均线可用后才有意义

print(signal_df[['close', 'ma_5', 'buy_signal']].head(10))

你应该看到前几行因为 ma_5 还没稳定,信号解释价值也有限。

14. 为什么买入信号看着不自然

信号列第一次出现时,最常见的疑问都和数量多少、前几行表现,以及触发位置是否自然有关。

15. 前几行都是False

这很可能只是因为 ma_5 前几行还是空值,比较结果自然不会有意义。

16. 信号太多

如果规则写得太宽,比如单纯用 close > ma_5,在趋势向上的小样本里 True 可能很多。这不是错误,只是说明当前规则非常基础。

17. 一条True都没有

先别怀疑 vectorbt。先看样本长度、排序、以及 ma_5 是否真的算出来了。

18. 哪些情况仍然算本讲完成

  1. 买入信号很多,不算失败。
  2. 买入信号很少,也不一定失败。
  3. 规则可以写成 >=>,只要你自己清楚口径即可。
  4. 当前不要求把买卖信号合在一起,本讲只完成买入侧。

19. 写一个最小信号函数

import pandas as pd

def add_buy_signal(df: pd.DataFrame) -> pd.DataFrame:
    out = df.copy()
    out['buy_signal'] = out['close'] > out['ma_5']
    return out

signal_df = add_buy_signal(work_df)
print(signal_df[['date', 'close', 'ma_5', 'buy_signal']].head(12))

20. 完成标准

如果你已经能把一条价格和均线的比较规则,稳定变成一列可检查的 buy_signal,那第 4 讲就已经完成了它的任务。

21. 信号列不是“策略结果”,只是规则表达

新手很容易把“信号”误以为已经接近最终收益结果。其实不是。第 4 讲里的 buy_signal,本质上只是把一句规则翻译成一列布尔值。比如“收盘价大于 5 日均线”,程序就把满足的行标成 True。它还没有回答你最终赚没赚钱,也还没有处理卖出、持仓、手续费等现实问题。

第 4 讲故意把它单独拎出来,就是为了让你先把“规则表达”这层学清楚。因为后面所有回测动作,无论多复杂,最后都绕不开这种布尔列。你越早把这件事看清楚,后面越不容易把所有逻辑混成一团。

22. 看信号时不要只看数量,要看位置

很多人生成 buy_signal 后,第一反应是统计有多少个 True。这个动作不是没用,但对第 4 讲来说,更关键的是看这些 True 出现在什么位置、对应什么价格和均线关系。建议你固定做两步:

print(signal_df[['date', 'close', 'ma_5', 'buy_signal']].head(12))
print(signal_df[signal_df['buy_signal']][['date', 'close', 'ma_5']].head())

第一步让你看整体,第二步让你看命中的样本。只有当这两步都看过,你才真正知道程序是不是按照你的理解在打标记。

23. 先避免前视偏差

入门阶段暂时还不展开完整回测理论,但可以先埋一个很重要的观念:当前信号列最好始终建立在当期可见的数据上。第 4 讲里我们用的是同一行 closema_5 去比较,这在入门层面足够简单,也容易解释。你现在不一定要马上处理所有时点对齐问题,但至少要养成一个意识:不要不知不觉把未来数据带进当前决策。

这也是为什么第 4 讲要先让你肉眼检查每一行的价格、均线和信号对应关系。你一旦看得见它们怎么对齐,后面在真正做回测时,就更容易理解哪些写法会引入偏差,哪些写法更稳。

24. 如果想把信号看得更清楚,可以加一列解释文本

有时候单看 True / False 还是有点抽象,你可以临时加一列解释字段:

signal_df['buy_reason'] = signal_df['buy_signal'].map({True: 'close 高于 ma_5', False: 'close 不高于 ma_5'})
print(signal_df[['date', 'close', 'ma_5', 'buy_signal', 'buy_reason']].head(12))

这列不一定要一直保留,但它对新手非常友好。因为你会明显感觉到,原来所谓“构造信号”,就是把一条自然语言规则变成程序里一列可检查的结果。

25. 信号过多或过少时,应该怎么理解

第 4 讲里如果你的 buy_signal 很多,不代表错;如果很少,也不一定错。你更应该问的是:这和当前样本走势是否一致。比如在一段持续上行的小样本里,close > ma_5 很可能经常成立;在震荡或下行样本里,信号自然会少一些。第 4 讲当前的目标不是追求“好看”的命中数量,而是确认布尔规则确实在按预期运行。

26. 这一讲对下一讲卖出信号有什么铺垫

下一讲要做卖出信号时,你会发现思路几乎是镜像的。买入信号这边已经帮你建立了三件事:

  1. 规则先写成一句自然语言。
  2. 再翻译成布尔表达式。
  3. 最后把命中的行单独打印出来检查。

只要这三步已经跑顺了,第 5 讲其实就是在同一张表上再构造另一列布尔信号。也就是说,第 4 讲真正帮你搭的,不只是一个 buy_signal,而是一套后面能重复用的构造方法。

27. 一个更完整的第 4 讲检查片段

signal_df['buy_signal'] = signal_df['close'] > signal_df['ma_5']
print(signal_df[['date', 'close', 'ma_5', 'buy_signal']].head(12))
print('buy_signal 个数 =', int(signal_df['buy_signal'].sum()))
print('第一次出现 True 的样本:')
print(signal_df[signal_df['buy_signal']][['date', 'close', 'ma_5']].head(1))

这段片段的价值在于,它把“生成信号”和“解释信号”绑在一起了。对入门者来说,这比单独写出那一行比较表达式更完整,也更容易真正形成理解。

28. 系列衔接

本讲是《vectorbt回测入门短课》的第 4/8 讲,当前主题是《构造买入信号》。

上一讲:第 3 讲《计算5日均线》。

下一讲:第 5 讲《构造卖出信号》。

后续安排:第 6 讲《跑第一轮向量化回测》;第 7 讲《查看收益回撤和交易次数》。

29. 风险揭示与免责声明

风险揭示与免责声明

本页面内容仅用于量化研究与技术交流,旨在展示研究方法与流程,不构成对任何金融产品、证券或衍生品的要约、招揽、推荐或保证。

本文所涉历史数据、回测结果与示例参数不代表未来表现,也不应作为投资决策依据。

市场存在波动、流动性与执行偏差等不确定性,任何策略均可能出现收益波动或阶段性失效。

读者应结合自身风险承受能力进行独立判断,并在必要时咨询持牌专业机构意见。