第一章:季节性因子实战
做跨期套利这么多年,我最大的体会就是——市场是有节奏的。这个节奏,说白了就是季节性。今天咱们就来聊聊,怎么把这种节奏变成实实在在的交易信号。
1.1 构建季节性因子
先说说什么是季节性因子。我习惯把它理解成「历史同期规律」。比如豆粕,每年七八月总是容易走强,这就是季节性。
构建方法其实不复杂。我个人常用的是「滚动窗口法」:
# 伪代码示例
def build_seasonal_factor(price_series, lookback_years=5):
# 提取每年同期的价格变化
seasonal_returns = []
for year in range(lookback_years):
# 取当年第t周的收益率
weekly_return = price_series[year*52 + t] / price_series[year*52 + t-1] - 1
seasonal_returns.append(weekly_return)
# 计算均值作为季节性因子
factor = np.mean(seasonal_returns)
return factor
嗯,这里要注意:样本量不能太少。我曾经用3年数据做回测,结果过拟合得一塌糊涂。后来改成5年起步,效果才稳定下来。
核心要点:
- 至少5年历史数据
- 剔除极端行情(比如2020年3月)
- 按品种单独计算,别混用
1.2 季节性因子的回测框架
回测框架这东西,说白了就是「模拟历史交易」。但很多人容易犯一个错——用未来数据。你想想看,如果回测时用了未来信息,那结果再漂亮也是假的。
我建议这样搭框架:
class SeasonalBacktest:
def __init__(self, data, factor):
self.data = data
self.factor = factor
def run(self):
# 滚动回测,每次只用历史数据
for i in range(len(self.data)):
# 只使用i之前的数据计算因子
historical_data = self.data[:i]
current_factor = self.calc_factor(historical_data)
# 生成信号
signal = self.generate_signal(current_factor)
# 记录结果
self.record_trade(signal, self.data[i])
避坑指南:我曾经在回测里不小心用了全样本均值,结果夏普比率高达3.0。后来发现是数据泄露了。记住:回测必须严格按时间顺序,不能用未来数据。
回测框架里,我习惯加三个关键指标:
| 指标 | 含义 | 我的经验阈值 |
|---|---|---|
| 夏普比率 | 风险调整后收益 | > 1.5 才算合格 |
| 最大回撤 | 最惨的时候亏多少 | < 15% 才敢实盘 |
| 胜率 | 赚钱的交易占比 | > 55% 比较靠谱 |
1.3 因子择时与信号生成
因子有了,回测框架搭好了,接下来就是怎么用。因子择时,说白了就是「什么时候该出手」。
我常用的方法有两种:
- 阈值法:因子超过某个值就开仓。比如季节性因子 > 0.8 时做多。
- 分位数法:把因子排序,只取前20%的极端值。
举个例子:
def generate_signal(factor, threshold=0.8):
if factor > threshold:
return 'long' # 做多
elif factor < -threshold:
return 'short' # 做空
else:
return 'neutral' # 观望
我的小技巧:别死守一个阈值。我习惯用滚动分位数,比如过去60天的80%分位。这样阈值会自适应市场变化,比固定值灵活得多。
信号生成后,还要考虑仓位管理。我个人习惯:信号越强,仓位越重。但最大仓位不超过总资金的30%。
知识体系总览
下面这张图,是我做这个章节时画的框架。它把季节性因子的整个流程串起来了:
这张图把整个流程串起来了。从数据开始,到因子构建,再到回测验证,最后生成交易信号。每一步都环环相扣,缺一不可。
本章核心总结:
- 季节性因子要基于5年以上数据
- 回测框架必须防止未来数据泄露
- 因子择时用阈值法或分位数法
- 信号越强仓位越重,但别超过30%
好了,这一章就到这里。记住:季节性因子不是万能药,但它能帮你抓住市场里那些「大概率会重复」的规律。用好了,它就是你的秘密武器。