基础入门
本讲在最小回测数据上生成第一列 5 日均线,围绕滚动窗口、前几行空值、手工核验和结果解读展开。目标是让入门者先把最基础的指标计算看懂,为后续买卖信号和向量化回测打下中间层。
本讲要完成的动作非常清楚:在第 2 讲已经整理好的最小回测数据上,计算第一列 5 日均线,并理解滚动窗口在向量化回测里意味着什么。如果第 2 讲解决的是“输入表长什么样”,那第 3 讲解决的就是“如何在这张表上得到第一列可解释指标”。
学完这一讲后,你可以直接完成下面几件事:
这是 vectorbt 入门短课里第一次真正从“数据准备”走向“指标计算”。
5 日均线是最适合入门向量化思路的第一步指标,因为它:
换句话说,它不是最复杂的指标,但它正好处在“足够简单,又能带动后面几讲”的位置上。
import pandas as pd
work_df = single_df.copy()
work_df['close'] = pd.to_numeric(work_df['close'], errors='coerce')
print(work_df.head())
这一步的核心,是再次确认 close 已经是一列真正可计算的数值列。因为如果输入都没整理好,均线结果再漂亮也不可信。
这一讲只做一件事:让第一列均线稳定落到表上。四步顺下来,输入、计算和解释关系都会清楚很多。
print(work_df[['date', 'close']].head())
print(work_df[['date', 'close']].tail())
入门阶段一定要先确认顺序。滚动窗口计算对顺序非常敏感,如果表不是按时间从早到晚排好,后面得到的均线虽然有数字,但业务意义是错的。
work_df['ma_5'] = work_df['close'].rolling(window=5).mean()
这一行就是本讲最核心的动作。它把当前行及前 4 行的收盘价做平均,并把结果放进一列新的指标列里。
print(work_df[['date', 'close', 'ma_5']].head(10))
后面你会拿 close 和 ma_5 做信号判断,所以现在就应该把两列放在一起看,而不是只孤立地盯着均线列。
print(work_df['ma_5'].head(6))
窗口长度为 5,前 4 行没有足够历史样本,出现空值是正确表现。这一步必须看清楚,因为后面构造信号时,前几行自然也会受到影响。
均线列是否可用,核心看窗口规律有没有正常体现出来,而不是只看有没有生成一列数字。
assert 'ma_5' in work_df.columns
assert work_df['ma_5'].head(4).isna().all()
assert pd.notna(work_df.loc[4, 'ma_5'])
assert work_df['ma_5'].notna().sum() > 0
下面这些现象第一次看到时很容易误判成报错,其实大多都属于滚动窗口的正常表现。
这是正常的滚动窗口行为,不是错误。
这通常意味着 close 列没有被正确转成数值,或者表里的有效样本行数小于 5。
先检查排序,再检查取的是否真是 close 列。多数情况下,问题不是 rolling 本身,而是输入顺序或字段取错了。
ma5、ma_5 或别的清晰名字。import pandas as pd
def add_ma5(df: pd.DataFrame) -> pd.DataFrame:
out = df.copy()
out['close'] = pd.to_numeric(out['close'], errors='coerce')
out['ma_5'] = out['close'].rolling(window=5).mean()
return out
work_df = add_ma5(single_df)
print(work_df[['date', 'close', 'ma_5']].head(10))
如果你现在已经能稳定地把 close 列变成一列 ma_5,并能解释前几行空值为什么出现,那第 3 讲就过关了。
和 AkShare 那边一样,vectorbt 入门阶段最有效的验证方式之一,就是手工核对第一条完整均线值。因为当 window=5 时,第 5 行会第一次得到有效结果,非常适合作为检查点。
first_window = work_df.loc[0:4, 'close']
print(first_window.tolist())
print('手工均值 =', first_window.mean())
print('程序 ma_5 =', work_df.loc[4, 'ma_5'])
只要这两者一致,你对滚动计算的信任就会明显提升。对新手来说,这种“亲手核一行”的体验特别重要,因为它能打破一种常见误解:以为回测和指标计算都是黑盒,自己只能照着抄代码。其实不是。至少在第 3 讲这个层面,均线完全是你能逐行解释清楚的。
vectorbt 是为了回测服务的,但第 3 讲仍然故意停在“先把指标列算稳”。原因很务实。如果你还没看清 close 和 ma_5 的关系,就直接把两列送进后面的入场出场逻辑,一旦结果奇怪,你几乎无法判断是信号错了还是均线本身就没准备好。
所以第 3 讲的真正作用,是在回测前建立一个中间层:
后面一旦继续做买入、卖出或回测,你就不再是“直接把 API 堆起来”,而是在一个自己看得懂的中间层之上继续推进。
第 3 讲不要求你做复杂统计,但建议你至少观察下面这些现象:
你可以直接把最近几行拉出来看:
print(work_df[['date', 'close', 'ma_5']].tail(8))
这一步对后面构造信号尤其重要。因为信号说到底,就是在观察价格和均线之间发生了什么关系。如果你现在连这两列并排时在表达什么都还没看清,后面信号列就会变成一串难以解释的真假值。
虽然我们当前是 vectorbt 入门短课,但第 3 讲依然用 pandas 的 rolling() 来做第一列均线。这并不矛盾,反而很合理。原因在于:vectorbt 的优势在于后续把指标、信号和回测链起来;而入门阶段先用 pandas 把最基础的窗口计算看清楚,理解成本最低。
等你后面走到更复杂的批量参数比较、指标工厂或组合评估时,再更深入用 vectorbt 的封装能力,会更顺手。第 3 讲当前的定位不是“展示框架最强能力”,而是“先让你把一个基础指标彻底弄明白”。
下一讲要做的事,其实已经被第 3 讲准备好了。因为买入信号最常见的一种写法,就是拿 close 和 ma_5 直接比较。也就是说,第 4 讲不是凭空新增一块逻辑,而是在第 3 讲已经得到的这两列上做布尔判断。所以第 3 讲只要做得扎实,后面的信号课会轻松很多;反过来,如果第 3 讲只是“勉强跑通”,第 4 讲开始你就会被真假值和空值一起困住。
print(work_df[['date', 'close', 'ma_5']].head(10))
print('前4行是否为空:', work_df['ma_5'].head(4).isna().all())
print('第5行是否有值:', pd.notna(work_df.loc[4, 'ma_5']))
print('最后3行:')
print(work_df[['date', 'close', 'ma_5']].tail(3))
把这一段固定下来,你后面每次换窗口、换样本时都可以拿来快速验证。
本讲是《vectorbt回测入门短课》的第 3/8 讲,当前主题是《计算5日均线》。
上一讲:第 2 讲《准备最小回测数据》。
下一讲:第 4 讲《构造买入信号》。
后续安排:第 5 讲《构造卖出信号》;第 6 讲《跑第一轮向量化回测》。
风险揭示与免责声明
本页面内容仅用于量化研究与技术交流,旨在展示研究方法与流程,不构成对任何金融产品、证券或衍生品的要约、招揽、推荐或保证。
本文所涉历史数据、回测结果与示例参数不代表未来表现,也不应作为投资决策依据。
市场存在波动、流动性与执行偏差等不确定性,任何策略均可能出现收益波动或阶段性失效。
读者应结合自身风险承受能力进行独立判断,并在必要时咨询持牌专业机构意见。