基础入门
本讲在已有价格、均线和买入信号的基础上,补出一列最小卖出信号,并把买卖条件放回同一张表里并排检查。目标是让入门者真正得到一套可入场、可离场的最小规则表达,为下一讲回测入口做好准备。
第 5 讲要做的事情很直接:在已经有 close、ma_5 和 buy_signal 的工作表上,再补出一列最小卖出信号 sell_signal。如果第 4 讲解决的是“什么时候考虑入场”,那这一讲解决的就是“什么时候考虑离场”。
学完这一讲后,你可以直接完成下面几件事:
对入门者来说,这一步很关键,因为从这里开始,你手里的表已经不只是“价格 + 指标”,而是具备了最基础的入场和出场条件。
很多新手在刚接触回测时,会把注意力全部放在“怎么买”,忽略“怎么卖”。但对任何一套最小策略来说,卖出条件不是附属品,而是策略能否真正闭环的另一半。没有卖出信号,你顶多只有一列会不断提示“这里看起来能进场”的布尔值;一旦补上卖出信号,你才开始具备“什么时候退出”的规则表达。
第 5 讲单独拎出来讲,还有一个现实原因:如果你把买入、卖出、回测入口全挤在一讲里,新手很容易混淆三层东西:
所以第 5 讲的正确节奏是,先把卖出这一层独立写清楚,再往回测入口推进。
signal_df = signal_df.copy()
print(signal_df[['date', 'close', 'ma_5', 'buy_signal']].head(12))
当前这张表里至少应该已经有:
dateclosema_5buy_signal如果 buy_signal 还没稳住,第 5 讲就不该往下赶。因为卖出信号最好的练习方式,就是和买入信号并排看。
这一讲和上一讲是镜像关系:规则仍然尽量简单,重点是把离场条件也变成能检查、能复用的一列布尔值。
本讲采用和第 4 讲镜像的一条规则:当收盘价小于 5 日均线时,认为满足卖出条件。它不意味着这就是最优规则,只是为了让你先得到一条和买入规则完全对称、容易验证的离场条件。
signal_df['sell_signal'] = signal_df['close'] < signal_df['ma_5']
这一行做完以后,你就有了第二列核心信号。此时最重要的变化不是代码长度,而是你的工作表已经同时具备“可入场”和“可离场”两类条件。
print(signal_df[['date', 'close', 'ma_5', 'buy_signal', 'sell_signal']].head(15))
一定不要只看 sell_signal 这一列。第 5 讲的重点是看相互关系,而不是孤立看一个真假值。你应该能从同一张表上同时看到:价格在哪些位置高于均线,哪些位置低于均线。
sell_rows = signal_df[signal_df['sell_signal']]
print(sell_rows[['date', 'close', 'ma_5']].head())
这样做的好处和第 4 讲完全一致:它会把命中的样本点直接拉出来,让你更快判断规则是不是在你预期的位置被触发。
验证卖出信号时,重点要看它能否和买入信号一起把样本分出不同位置,而不是只看数量多不多。
assert 'sell_signal' in signal_df.columns
assert signal_df['sell_signal'].dtype == bool or str(signal_df['sell_signal'].dtype) == 'bool'
print(signal_df[['close', 'ma_5', 'buy_signal', 'sell_signal']].head(12))
你至少应该看见,close > ma_5 和 close < ma_5 会把样本分到不同位置上,而不是所有行都长得一样。
print(signal_df[signal_df['sell_signal']][['date', 'close', 'ma_5']].head())
卖出信号看起来“不好看”很常见,因为它会直接暴露样本强弱和规则边界。
这不一定是错。若样本中有一段持续走弱,close < ma_5 很可能会连续成立很多行。第 5 讲不追求“数量好看”,只追求“规则表达正确”。
也不一定错。如果样本整体偏强,价格大部分时间都压在均线上方,那么卖出信号自然会少。关键不是数量,而是它是否出现在符合你直觉的位置。
这是完全可能的。比如当价格恰好等于均线时,若你使用的是严格的大于和小于,它就不会被归到任一侧。这不是 bug,而是规则边界的一部分。
和买入信号一样,前几行如果 ma_5 还是空值,那么 sell_signal 也没有太强解释意义。这说明第 3 讲的均线窗口逻辑仍然在发挥作用。
<,也可以用 <=,只要自己清楚规则口径。import pandas as pd
def add_sell_signal(df: pd.DataFrame) -> pd.DataFrame:
out = df.copy()
out['sell_signal'] = out['close'] < out['ma_5']
return out
signal_df = add_sell_signal(signal_df)
print(signal_df[['date', 'close', 'ma_5', 'buy_signal', 'sell_signal']].head(15))
这段函数和上一讲的买入信号函数一起,已经足够构成一套最小策略信号层。
你可以把第 4 讲和第 5 讲理解成一件事的两个面。第 4 讲告诉你,价格高于均线时如何定义“考虑进场”;第 5 讲则告诉你,价格低于均线时如何定义“考虑离场”。两讲单独拆开,是为了让你把每一边都看清;合在一起看,你就会发现,这其实已经是一套极小但完整的规则系统。
这也是为什么第 5 讲最推荐的检查方式,是把 close、ma_5、buy_signal、sell_signal 一次性打印出来。你要看的不是哪一列单独是否漂亮,而是整张表是否在表达一套一致的规则。
到第 5 讲结束时,你手里终于具备了跑第一轮回测所需的最关键输入:一列买入条件和一列卖出条件。下一讲之所以能自然过渡到“跑第一轮向量化回测”,正是因为这两列已经存在。也就是说,第 6 讲并不是无中生有地新增一套复杂逻辑,而只是把你现在这张表交给 vectorbt 的回测入口去解释。
如果你现在已经能稳定得到一列 sell_signal,并能把它和 buy_signal 一起放在同一张表里解释,那第 5 讲就已经完成了它的任务。
本讲是《vectorbt回测入门短课》的第 5/8 讲,当前主题是《构造卖出信号》。
上一讲:第 4 讲《构造买入信号》。
下一讲:第 6 讲《跑第一轮向量化回测》。
后续安排:第 7 讲《查看收益回撤和交易次数》;第 8 讲《比较5日和10日窗口》。
风险揭示与免责声明
本页面内容仅用于量化研究与技术交流,旨在展示研究方法与流程,不构成对任何金融产品、证券或衍生品的要约、招揽、推荐或保证。
本文所涉历史数据、回测结果与示例参数不代表未来表现,也不应作为投资决策依据。
市场存在波动、流动性与执行偏差等不确定性,任何策略均可能出现收益波动或阶段性失效。
读者应结合自身风险承受能力进行独立判断,并在必要时咨询持牌专业机构意见。