做基本面策略回测,不想用 backtrader 这样的框架,但 Alphalens 也不适合日线;另外,作为初学者,希望能从零开始实现一个策略回测,有助于了解策略回测的原理。这个想法也没错,关键是,要如何实现?
这一系列文章,将以中金2023年的一期研报为例,介绍如何实现一个较复杂的策略回测。我们将学习到如何获取各种数据,如何进行数据预处理,如何设计回测框架等关键技术。
图1 中金研报
这个策略从股息收益、资本利得和风险规避三大维度入手,综合了事件选股和因子选股的手段,最终回测结果表明,近5年年化收益率达到29%。
图2 基本面策略构建思路
我们将实现一个可用于月度调仓策略的普适性框架,它有这些特点:
- 按月调仓 适用于基本面策略
- 模块职责划分清晰 易于叠加组合
- 简单易上手
核心驱动框架
往简单里说,回测就是在指定的时间段(过去)里:
- 根据当时能得到的数据,发出交易信号
- 根据交易信号进行调仓
- 计算策略的每个交易期的收益
- 策略评估与可视化
一般来说,第一步是策略的核心。策略不同,要使用的数据也不同,构建的因子,以及决策逻辑也不同。但其它部分可以做到重复使用。特别是第4步,策略的评估与可视化,我们将使用 Quantstats 来实现。

我们先介绍如何实现第2步和第3步,即调仓和收益计算。
假设我们已经获取了股票的日线行情数据。由于我们的策略是按月调仓,所以,我们将行情数据重采样成了月线数据。
现在,这份数据看起来如下图所示:
图3 月行情数据
对月度调仓策略,先将数据重采样到月是一个重要的技巧。如果不这样,你就要先确定每个月的调仓日(每个月还不固定),再根据这个调仓日,去查找个股当天的因子数据和收盘价。一旦调仓日有个股停牌,就会出现数据缺失。
另一个好处是,现在我们可以在上月发出调仓信号后,按下月的开盘价买入,下月的收盘价卖出,这样严格地避开了未来数据。一些策略在计算收益时,仅使用收盘价,这样不可避免地要么引入未来数据,或者信号响应不及时。
现在,计算收益就变得很简单。比如,计算基准收益就是:
df.groupby("month").apply(
lambda x: (x.close / x.open - 1).mean()
)
图4 基准收益计算
如果要计算组合的收益率呢?我们需要增加一列,先标记出哪些股票在当月的股票池中:
图5 组合收益计算
在图5中,我们增加了3列。其中 filter_1 是用来筛选股票的基础数据,比如,研报要求按每月筛选出股息率前 500 的股票,这一列就可以是股息率数据。
flag 列是根据 filter_1 列的数据,按照规则生成的『个股是否在股票池』的标记。在图中,假定规则是,如果 filter_1 大于零,则该个股在下个月的股票池中。这样我们就得到了2023年2月的股票池。
现在,我们计算策略收益时,只需要按月执行:
∑(returns×flag)∑flag \frac{\sum(returns \times flag)}{\sum flag} ∑flag∑(returns×flag)
就能得到每月的策略收益。这一步相当于执行代码:
df.groupby("month").apply(
lambda group: group[group["flag"] == 1]["returns"].mean()
)
到这一步为止,我们已经明确了要实现一个极简的月度回测框架,大致上要做的事情如下:
- 获取行情数据,重采样成为月度数据
- 根据策略要求,获取相关数据,构建因子
- 将第2步中构建的因子 (factor) 也重采样成为月度数据,附加到1中生成的 DataFrame 中
- 根据策略逻辑,将 factor 列转换成为 flag 列。
- 按月计算基准收益和策略收益
- 调用 quantstats 生成回测报告。
Moonshot 的实现
我们把这个极简框架命名为 Moonshot,因为它更适合固定按月调仓换股的策略。它的核心是一个名为 Moonshot 的类:
class Moonshot:
def __init__(self, daily_bars:pd.DataFrame):
self.data: pd.DataFrame = resample_to_month(daily_bars, open='first', close='last'

最低0.47元/天 解锁文章
1203

被折叠的 条评论
为什么被折叠?



