一个事件驱动量化算法的技术拆解
这篇文章不是比赛说明书,而是一篇从技术实现出发的算法拆解。目标很明确:围绕 pred_compact.py 这份代码,解释这套事件驱动量化框架为什么这样设计、每个模块分别承担什么功能,以及它背后的建模逻辑是什么。
我会尽量避免把讨论写成“代码注释扩写版”,而是把重点放在方法本身。换句话说,这里更关心的不是某一行代码怎么执行,而是这套方法如何把事件、图谱关系与价格信息组织成一个可训练、可验证、可交易的策略系统。
一、这段代码到底在做什么
概括地说,这个算法在做一件事:
把新闻事件、事件和股票的关联关系、股票历史价格这三类信息揉在一起,然后预测“下一个决策周应该买哪几只股票”。
它不是单纯看 K 线,也不是单纯做新闻情绪分析,而是一个混合框架:
- 用事件信息判断“市场最近在炒什么”
- 用事件与公司的映射关系判断“谁最可能受影响”
- 用价格和成交数据判断“这种影响有没有可能兑现成股价表现”
- 最后用机器学习做排序,挑出最值得买的 3 只股票
如果换一种更贴近研究问题的说法,这个算法试图回答的是:
近期发生的这些事,最后会利好哪些股票,而且这种利好在下周还有没有交易价值?
二、为什么“事件驱动”是一个值得研究的方向
传统量化研究里,价格、成交量与财务数据通常是最先被使用的变量。但如果把时间尺度缩短到几天到几周,市场波动往往并不只是统计规律的自然展开,而是与一系列具体事件密切相关,比如:
- 某家公司中标大订单
- 某个行业迎来政策利好
- 某种资源品价格大涨
- 某家公司被处罚、减持、爆雷
这些事件往往先作用于预期,再通过交易行为体现在价格上。
因此,事件驱动策略的核心思想可以概括为:
- 先找到事件
- 再找到被事件影响的股票
- 最后判断这种影响会不会在未来一段时间变成收益
这份代码正是沿着这条路径展开的。它并不假定“事件一出现,股价必然上涨”,而是试图进一步判断:哪些事件更重要、哪些股票更可能受益、这种受益是否仍具有尚未兑现的交易价值。
三、这份代码依赖哪些数据
pred_compact.py 不是直接拿一个表就开始训练,它依赖好几类数据。
1. 事件表
events.csv
这里面记录的是事件本身,比如:
- 事件编号
- 事件名称
- 事件日期
- 影响周期
- 强度
它对应的是“市场上最近发生了什么”。
2. 事件-股票映射表
event_company_map.csv
这个表非常关键。因为事件本身并不能直接交易,真正可交易的是股票。
所以必须知道:
- 这个事件影响哪些公司
- 是直接影响还是间接影响
- 影响关系有多强
换句话说,这张表负责回答:
事件和股票之间怎么连起来?
3. 股票价格数据
daily_kline_batches 或 price_panel.csv
这部分提供:
- 开盘价
- 收盘价
- 涨跌幅
- 成交量
- 成交额
这部分数据负责回答:
就算事件利好,这只股票现在的价格状态适不适合买?
4. 静态辅助数据
包括:
- 行业信息
- 上市日期
- 指数成分股信息
这些变量不是主角,但它们能帮助模型理解股票的背景。
四、这不是泛化意义上的“预测股价”,而是周频交易决策
在量化语境里,“预测”这个词很容易带来误解。很多人看到机器学习模型,首先会想到逐日预测涨跌,甚至预测第二天的收盘价。
但这段代码处理的不是这个问题。
它处理的是一个更接近实务的问题:以周为单位进行选股决策。
它把每周二设为统一的决策点,也就是 decision_tuesday。
然后目标不是预测长期走势,而是预测:
从这个决策周开始,到本周结束,这只股票的收益怎么样?
代码里的标签大致可以理解为:
这个标签的金融含义并不复杂:
- 在周内找一个买入点
- 在周末附近卖出
- 计算这一周的收益率
因此,这套策略既不是高频交易,也不是中长期配置,而是一个典型的短周期事件交易框架。
五、第一步:把事件对齐到可交易的决策时点
事件发生的时间是乱的:
- 有的在周一
- 有的在周三
- 有的在周五
但模型训练需要统一的时间截面,否则不同事件之间就很难放在同一个决策框架下比较。
所以代码做了一件很关键的事情:把不同日期的事件统一映射到决策周二。
核心逻辑是:
- 如果事件发生在周一,就归到本周周二
- 如果事件发生在周二及以后,就归到下周周二
代码中的数学表达是: $$ T(e) = \operatorname{next_decision_tuesday}(t_e) $$
这一步的直观含义是:
每一条事件,最终都要被分配到一个“该拿来做交易决策的周”
这一步之所以重要,是因为事件研究里最容易失控的地方恰恰是时间对齐。一旦时间定义含混,后续标签、特征和验证都会一起失真。
六、第二步:为每条“事件-股票边”构造影响权重
这份代码最有意思的地方之一,在于它没有把“事件影响股票”处理成简单的 0/1 关系,而是进一步为每条关系计算了一个连续权重。
这样做的原因并不复杂。
因为现实里不同事件的交易价值差别很大:
- 有些事件非常强
- 有些只是边角信息
- 有些是刚发生的
- 有些已经发酵很多天
- 有些股票是直接受益
- 有些股票只是蹭概念
这些差别如果不被量化,模型最终看到的就只有“有关联”或“没有关联”,信息会过于粗糙。
代码中的权重公式是:
这个公式可以拆开来看。
1. relation_strength
它表示事件与股票之间的关联强弱。
比如:
- 某公司就是事件主体,那关联强度通常很高
- 某公司只是产业链外围映射,强度就低
2. intensity_score
它表示事件本身的冲击强度。
例如:
- 普通公告和重大政策的强度显然不一样
- 一般订单和超大订单的市场影响也不一样
3. decay
它表示时间衰减。
代码里用了半衰期思想:
它表达的是:
一个事件离决策时点越远,它的影响越弱;大致每过 7 天,影响衰减一半。
这与事件交易的经验事实是吻合的。很多利好如果在一周前已经被充分传播,那么等到正式决策时,其可交易价值往往已经明显下降。
4. direct
它表示该股票是否属于直接受益对象。
如果是直接关系,代码额外乘了 1.5 的加成:
这里的含义也很直观:真正的一线受益标的,通常比外围映射标的更具有交易确定性。
七、第三步:识别事件方向
代码没有引入复杂的文本模型,而是用规则法完成了一个相当务实的方向识别。
它会根据:
event_namerelation_typenote
里的关键词,把事件方向划成:
也就是:
1:利多-1:利空0:中性或不确定
比如:
- “中标”“订单”“增长”“景气”偏利多
- “处罚”“亏损”“违约”“减持”偏利空
这一步的意义在于:
模型不只是知道“这只股票有关联事件”,还知道这个事件大概率是好消息还是坏消息。
从建模角度看,这里体现的是一种很克制的取舍:当样本量有限、文本标签质量不完全稳定时,规则法未必落后于复杂 NLP,反而可能更稳健、更可解释。
八、第四步:把多个事件压缩成一个股票周样本
到这里为止,每一条数据还是“某事件影响某股票”的边级别数据。
但机器学习训练时,需要的是“某周某只股票”的样本。
问题在于,一只股票在同一周往往不只对应一个事件。
这里的答案就是:聚合。
设某个决策周里,股票 对应的事件集合为 。
代码会围绕这个集合计算一批统计特征。
1. 事件数量
它反映的是,这只股票在本周被多少事件共同覆盖。
2. 正向事件和负向事件数量
这组变量告诉模型:
- 当前这只股票是“利多事件扎堆”
- 还是“利空事件堆积”
3. 事件总权重、最大权重、平均权重
这三个量分别对应:
- 总体驱动力有多强
- 最强单一事件有多强
- 平均事件质量怎么样
4. 方向统计
它本质上描述的是:
这一周围绕该股票的事件氛围,整体偏正面还是偏负面?
九、第五步:把事件类别信息压缩进特征体系
事件不只有强弱和方向,还有类型差异。
比如:
- 公司层面的事件
- 行业层面的事件
- 宏观政策事件
- 地缘事件
这些事件即使都利多,交易逻辑也不一样。
代码在这里采取的是一种很节制的做法:它没有直接展开高维 one-hot,而是把类别先压缩成少量桶,再统计每类事件权重之和。
例如宏观类事件:
这样处理的好处是:
- 信息保留了
- 维度没有爆炸
- 对小样本更稳
如果要用一句话概括这里的方法论,那就是:好的特征工程,不是把所有信息都塞给模型,而是把信息整理成模型真正能学习的形式。
十、第六步:拼接价格特征,避免“只看事件不看市场状态”
如果一个模型只看事件,不看价格,常见问题是:
- 有些事件早就被交易过了
- 有些股票虽然有利好,但走势已经走坏
- 有些股票短期波动太大,不适合参与
因此,代码又拼接了一批价格状态特征。
1. 动量
这两个特征分别刻画短期和中短期趋势。
2. 波动率
波动率高,通常意味着不确定性更高。
同样的事件,在高波动股票和低波动股票上的兑现效率往往并不相同。
3. 成交活跃度
它们反映的是:
这只股票最近有没有明显放量、放额
如果事件刚出现、量价就已经明显活跃,那么至少可以说明市场资金正在注意它。
十一、第七步:补充个股背景与市场背景
除了事件和价格,代码还加了两类常见的辅助变量。
1. 个股背景
比如:
- 行业是否和事件逻辑匹配
- 是否是最近上市的新股
- 是否属于重要指数成分股
这些变量帮助模型理解:
- 它是不是事件主线上的核心标的
- 它的风格属性是什么
2. 上周表现
代码还加入了:
以及市场整体的上一周收益背景。
这一步其实是在捕捉一个很现实的现象:
有些股票在事件真正爆发前,资金已经提前埋伏了。
因此,模型不能只看“这周发生了什么”,还要看“市场此前如何反应”。
十二、第八步:为什么这里要用两个模型
这是这份代码里另一个相当值得注意的设计。
它没有只训练一个回归模型,而是同时训练了两个模型:
1. 回归模型
预测未来一周收益率:
2. 分类模型
预测未来一周上涨概率:
原因在于:
因为“涨不涨”和“涨多少”不是一回事。
举个例子:
- A 股票上涨概率高,但每次只涨一点
- B 股票上涨概率一般,但一旦涨就涨很多
如果你只做分类,可能选到很多“小赚但空间不大”的股票。
如果你只做回归,又可能过度偏好高波动标的。
所以代码把两个问题拆开:
- 分类模型负责胜率
- 回归模型负责空间
这是一个非常典型、也非常实用的量化建模思路。
十三、第九步:如何把两个模型的结果合成为最终选股分数
模型训练完之后,还没有直接输出买哪只股票。
它还要再做一层交易决策。
代码定义了一个综合得分:
这几个部分分别代表:
0.58 * prob_up:先保证上涨概率0.22 * pred_ret:再看收益空间0.12 * weight_norm:强化事件主线强度0.08 * prev_week_return:保留一定趋势延续信息
这一步很像一个“二次决策层”。
也就是说,机器学习模型不是直接给出最终答案,而是先给出若干预测值,再由策略规则把这些预测值转成可交易分数。
如果要概括这里的要点,那就是:量化策略往往不是“模型 = 策略”,而是“模型 + 交易规则 = 策略”。
十四、第十步:最终如何选股与分仓
最终流程是这样的:
- 先筛掉上涨概率太低的股票
- 在剩下的股票中按综合分排序
- 取前 3 只
- 给这 3 只股票分配资金比例
分配方法不是主观指定,而是 softmax:
这个公式的好处是:
- 分数越高,仓位越大
- 总权重自动归一化到 1
- 不容易出现特别极端的仓位
最终输出文件里最重要的三列就是:
- 事件名称
- 股票代码
- 资金比例
到这里,一套完整的事件驱动选股框架就闭环了。
十五、从工程角度看,这份代码为什么值得讨论
在我看来,这份代码最值得讨论的并不是 XGBoost 本身,而是其中体现出的几种建模习惯。
1. 先把问题改写成可监督学习的形式
真实问题是“下周买什么”,代码把它转成了:
- 输入:事件、图谱、价格特征
- 输出:下一周收益和上涨概率
只有先把问题结构化,模型才真正有发挥空间。
2. 特征工程比模型堆料更重要
这份代码没有追求特别花哨的模型,但特征工程很扎实:
- 事件权重
- 方向判断
- 时间衰减
- 多事件聚合
- 价格状态
- 上周背景
这再次说明一个现实:在很多中小型量化任务里,特征设计往往比更换模型更重要。
3. 时序验证比随机验证更靠谱
代码没有乱打训练集和测试集,而是按时间滚动验证。
这符合真实交易场景,也能避免未来信息泄露。
4. 模型输出不等于最终交易决策
代码没有直接拿回归结果排序,而是进一步做了综合打分和仓位分配。
这说明它的目标不只是“拟合数据”,而是“落地交易”。
十六、如果要读这份代码,什么顺序更合适
我建议按下面顺序看:
- 先看
generate_decision - 再看
fit_as_of - 再看
build_decision_samples - 然后看
_build_price_snapshot - 最后回头看
load_data和辅助函数
之所以推荐这样看,是因为:
如果一开始就从头顺着读,很容易陷入局部细节。
而从主流程逆推,更容易先看清:
- 最终输出是什么
- 为了得到这个输出,需要哪些中间变量
- 每个特征到底是为了哪一步服务
十七、这套算法的优点与局限
优点
- 思路完整,从事件到选股再到分仓是一条闭环
- 特征设计很有金融含义,解释性强
- 没有盲目堆复杂模型,整体结构较为克制
- 周频框架噪声相对较小,稳定性更容易控制
不足
- 事件方向判断还是偏规则法,语义能力有限
- 没有建模更复杂的事件传播链
- 没有显式风险控制模块
- 对更高频、更复杂的交易场景还不够
但从方法展示与比赛算法实现的角度看,这套方案已经相当具有代表性。
十八、写在最后:这套方法真正值得带走的东西
如果读完这篇文章只保留一个判断,我希望是这个:
一个好的量化算法,不是上来就调模型,而是先把金融问题拆成可以计算、可以验证、可以交易的多个小问题。
这份 pred_compact.py 做得好的地方就在这里:
- 它先定义交易周期
- 再量化事件影响
- 再聚合成特征
- 再训练模型
- 最后再做选股和分仓
这就是一个完整的技术框架。
把这份代码当成一个模板去看,至少可以学到四件事:
- 学会怎样把原始数据整理成样本
- 学会怎样把事件转成特征
- 学会怎样让模型服务于交易目标
- 学会怎样把预测结果变成实际投资决策
如果沿着这个框架继续扩展,比较自然的方向通常有三个:
- 更好的事件文本理解
- 更丰富的行业与市场背景特征
- 更严格的风险控制和仓位管理
附:运行入口
核心代码文件仍然是 pred_compact.py。
运行示例:
python pred_compact.py --root-dir . --target-date 2024-06-18 --output result.xlsx --allow-roll-back
如果准备进一步读这份工程,建议一边看这篇文章,一边对照源代码里的这几个函数:
build_weekly_labelsbuild_decision_samples_walk_forward_validatefit_as_ofgenerate_decision
这样会更容易把方法和实现对应起来。
评论
欢迎友好交流,理性讨论