Vibe-Trading 事件驱动策略实战指南:从情感打分到信号聚合的完整实现
【免费下载链接】Vibe-Trading"Vibe-Trading: Your Personal Trading Agent"项目地址: https://gitcode.com/GitHub_Trending/vi/Vibe-Trading
事件驱动(Event-Driven)是 Vibe-Trading 中一类以新闻、公告与宏观事件为数据源、由 LLM 充当 NLP 引擎的策略形态。本文将完整拆解仓库内agent/src/skills/event-driven/SKILL.md所定义的工作流、事件 CSV 数据规范、时间衰减与信号加权算法,并结合 示例信号引擎 与 RSSHub 事件提供器 的源码实现,为你呈现一条「数据采集 → LLM 打分 → CSV 落盘 → 信号聚合 → 回测接入」的端到端落地路径,读完即可在自己的策略中复现。
一、技能定位与整体架构
agent/src/skills/event-driven/SKILL.md的元信息将该技能归类为category: strategy,其核心思想是:把 LLM 当作 NLP 引擎来打分新闻事件,事件数据统一管理为 CSV,再将技术信号与事件信号做加权聚合,得到最终交易信号。
该文档强调一条贯穿全流程的关键原则:
事件 CSV 是数据层,
signal_engine.py是逻辑层,两者必须保持解耦。
这一分层设计在仓库源码中得到了印证:事件侧的数据层由 rsshub_events.py 承担(负责抓取、归一化、打分、落列),逻辑层则由策略开发者自行实现的signal_engine类承担(文档同目录提供了完整范本 example_signal_engine.py)。数据层不关心策略逻辑,逻辑层不关心数据来源,二者通过统一的event_score列衔接。
二、四步工作流
文档定义的标准工作流如下:
- 数据采集:使用
read_url工具抓取新闻与公告全文; - LLM 分析:LLM 阅读新闻,用标准化提示词在
-1.0到1.0区间打分(极度看空到极度看多); - 生成事件 CSV:按
date,event_type,score,source,summary五列 Schema 写入数据; - 信号聚合:
signal_engine.py读取事件 CSV,施加时间衰减,并与技术信号合并。
其中read_url是 Agent 内置工具,其实现位于 web_reader_tool.py:它通过 Jina Reader(r.jina.ai)把目标 URL 转为 Markdown 文本,内置 30 秒超时、8000 字符截断,并对目标地址做了严格的安全过滤(拒绝localhost、私网 IP、带凭据的 URL 等),返回结果包含title、content、url字段。也就是说,Agent 抓取到的是已经清洗过的正文,可直接交给 LLM 打分,无需额外做网页解析。
三、事件 CSV Schema 与字段语义
事件数据统一落盘为 CSV,文档给出了可直接复制的示例:
date,event_type,score,source,summary 2024-01-15,earnings,0.8,read_url,Q4 revenue beat expectations by 30% 2024-01-20,macro,-0.5,read_url,Central bank raised rates by 25bp 2024-02-01,policy,0.3,read_url,New-energy subsidies extended 2024-02-10,sentiment,-0.7,read_url,Bearish sentiment surged on social media 2024-03-05,insider,0.4,read_url,CEO bought 5 million shares五个字段的语义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| date | str(YYYY-MM-DD) | 事件变得可知的日期(发布时间,而非发生时间;若收盘后发布则顺延到下一交易日) |
| event_type | str | earnings / macro / policy / sentiment / insider / technical_break |
| score | float | -1.0 ~ 1.0(标准化 LLM 打分) |
| source | str | 数据源标签(如read_url) |
| summary | str | 事件摘要(一句话,不要包含逗号) |
关于date的「可知日期」约定,仓库级实现给出了严格的落地方案:在 rsshub_events.py 中定义了收盘截断小时DEFAULT_CLOSE_CUTOFF_HOUR = 16,_knowable_date()函数(见第 264-278 行)会把当日 16 点及以后发布的条目滚动到下一个自然日,与技能文档「收盘后发布 → 使用下一交易日」的规则完全对齐。这正是回测避免未来函数(look-ahead bias)的第一道闸门。
四、事件类型体系与影响周期
文档按事件类型规定了各自的影响周期,这决定了时间衰减参数的调优方向:
| 类型 | 含义 | 典型影响 | 持续时间 |
|---|---|---|---|
| earnings | 财报发布 | 短期冲击 | 1-5 天 |
| macro | 宏观数据 / 央行政策 | 中期影响 | 5-20 天 |
| policy | 行业政策 / 监管变化 | 长期影响 | 20-60 天 |
| sentiment | 市场情绪 / 舆情 | 短期冲击 | 1-3 天 |
| insider | 内部交易 / 大宗交易 | 中期信号 | 5-10 天 |
| technical_break | 关键技术位突破 | 短期催化剂 | 1-5 天 |
从实现细节看,这一事件分类法也被 FeedSpec 数据结构继承——每个 RSSHub 订阅源在注册时必须声明自己产出的事件类型(event_type),从而在数据源头就完成事件分类,而非在策略层事后打标。
五、信号聚合:时间衰减与加权组合
5.1 事件信号的时间衰减
事件对价格的影响随天数呈指数衰减。文档给出的核心算法如下:
import numpy as np import pandas as pd def compute_event_signal(event_df: pd.DataFrame, dates: pd.DatetimeIndex, decay_lambda: float = 0.1, min_score_threshold: float = 0.2, event_lookback: int = 30) -> pd.Series: """Compute an event-driven signal with time decay.""" event_df = event_df[event_df["score"].abs() >= min_score_threshold].copy() event_df["date"] = pd.to_datetime(event_df["date"]) signal = pd.Series(0.0, index=dates) for trade_date in dates: # Only consider events published on or before trade_date (avoid look-ahead) mask = (event_df["date"] <= trade_date) & \ (event_df["date"] >= trade_date - pd.Timedelta(days=event_lookback)) relevant = event_df[mask] if relevant.empty: continue days_since = (trade_date - relevant["date"]).dt.days.values scores = relevant["score"].values # Exponential decay: score * exp(-lambda * days) decayed = scores * np.exp(-decay_lambda * days_since) # Sum multiple events and clip to [-1, 1] signal[trade_date] = np.clip(decayed.sum(), -1.0, 1.0) return signal算法的三个要点:
- 防未来函数:只取
date <= trade_date的事件,保证任一交易日只能看到当日及之前已知的事件; - 回看窗口:
event_lookback = 30天之外的事件直接排除,避免陈旧事件长期滞留; - 阈值过滤:
|score| < min_score_threshold的事件先被剔除,只保留有实质信息量的条目; - 多事件叠加:窗口内多个事件做衰减后求和,再
clip到[-1.0, 1.0]。
值得注意的是,这套「衰减求和 + 窗口 + 截断」公式在仓库生产级实现 enrich_price_frames_with_events 中得到了向量化复刻:它为每根 bar 计算event_score(衰减求和、clip 到[-1,1])与event_count(窗口内事件数)两个附加列,直接用 NumPy 的 datetime64 与np.exp完成逐 bar 计算,逻辑与技能文档完全一致。
5.2 技术信号与事件信号的加权融合
最终交易信号由技术信号与事件信号按权重合成:
def combine_signals(tech_signal: pd.Series, event_signal: pd.Series, alpha: float = 0.6) -> pd.Series: """Combine technical and event signals with weights.""" combined = alpha * tech_signal + (1 - alpha) * event_signal return combined.clip(-1.0, 1.0)默认alpha = 0.6:技术信号占 60%,事件信号占 40%。当没有事件时event_signal = 0,组合信号退化为纯技术信号,这是设计上的刻意行为——事件驱动是增强层,而非主策略。
六、可调参数速查
文档给出的参数表及默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
| alpha | 0.6 | 技术信号权重(1-alpha为事件权重) |
| decay_lambda | 0.1 | 衰减系数(越大衰减越快;0.1≈ 10 天衰减到 37%) |
| event_lookback | 30 | 事件回看窗口(天),更早的事件被排除 |
| min_score_threshold | 0.2 | 最低分数阈值,|score|低于此值的事件被忽略 |
这些默认值同样体现在生产实现中:enrich_price_frames_with_events 的decay_lambda=0.1、lookback=30、min_abs_score=0.0与技能默认保持对齐(min_abs_score在技能侧为 0.2,属于文档中「策略层过滤」与「数据层过滤」的粒度差异——数据层默认不丢事件,把阈值判断留给策略)。
七、LLM 打分提示词模板
为保证打分跨时间、跨模型版本的一致性,文档规定抓取新闻后必须使用以下标准化提示词:
You are a financial event analyst. Read the following news / announcement and score its impact on the stock price. Scoring scale: - 1.0: extremely bullish (for example, earnings far above expectations, major favorable policy) - 0.5: moderately bullish (for example, earnings slightly above expectations, favorable industry news) - 0.2: mildly bullish - 0.0: neutral (no obvious impact) - -0.2: mildly bearish - -0.5: moderately bearish (for example, earnings below expectations, tighter industry regulation) - -1.0: extremely bearish (for example, accounting fraud, major violations, black-swan event) Score strictly on the scale above. Output one number only. Do not explain. News content: {news_content} Score:模板的关键约束是「只输出一个数字、不做解释」,从而让打分结果可以机械化地解析进 CSV 的score列。值得注意的是,仓库在数据层还内置了一套确定性的词典打分器作为无需 LLM 的兜底基线——default_lexicon_scorer 通过统计标题与摘要中正负面词条(如 beat/beat、surge、record、growth 等正面词,与 miss、plunge、fraud、bankruptcy 等负面词)的净占比,产出[-1.0, 1.0]的分数。query_events接受可插拔的scorer参数,既可以用词典打分器做离线快速验证,也可以如技能文档描述的那样注入 LLM 裁判,两种打分路径共享同一套数据流。
八、回测中的事件数据:从 CSV 到手写引擎
技能文档明确指出:历史回测要求提前备好完整的历史事件 CSV,不能在回测中实时抓取,且建议按标的维护独立的事件文件。文档同目录的 example_signal_engine.py 给出了一种可直接套用的手写引擎范式:
class SignalEngine: """Long-only sentiment pre-filter over the enriched ``event_score`` column.""" def __init__(self, score_threshold: float = 0.2, top_n: int | None = None) -> None: self.score_threshold = score_threshold self.top_n = top_n def generate(self, data_map: Dict[str, pd.DataFrame]) -> Dict[str, pd.Series]: ... for dt in date_index: scored: List[tuple[str, float]] = [] for code, df in data_map.items(): if dt not in df.index: continue score = df.loc[dt].get("event_score", np.nan) if pd.notna(score) and score >= self.score_threshold: scored.append((code, float(score))) ... weight = 1.0 / len(scored) for code, _ in scored: signals[code].at[dt] = weight ...这个引擎实现了一个典型的情绪预筛(sentiment pre-filter)多头策略:每个 bar 上选出event_score >= score_threshold的标的,按分数降序(可top_n截断),对入选标的等权做多。event_score列由回测配置中的event_feeds触发数据层富化(enrichment)自动生成,并且天然防未来——它只反映t日及之前可知的事件,引擎本身从不主动前移数据。
九、常见陷阱与规避清单
文档共列出 7 条实战中反复踩坑的经验,这些在源码中都有对应的工程化对策:
- 未来函数:CSV 的
date必须是「可知日期」,收盘后发布的事件要用下一交易日;回测中严格执行event_date <= trade_date。对应实现:_knowable_date的收盘截断 +query_events的knowable_date <= as_of过滤([rsshub_events.py](https://link.gitcode.com/i/4fd762b20e7059ba31bdb1f3d3d58e6a#L264-L278, L397))。 - 重复打分:同一事件可能被多个新闻源重复报道而产生多行。按
(date, event_type)去重或取均值。对应实现:query_events按(ts_code, knowable_date, event_type, summary)去重(第 398 行)。 - 情绪漂移:提示词或模型版本变化会导致 LLM 打分尺度漂移。对策是固定提示词模板、定期重新校准。数据层的词典打分器可作为稳定的比对基线。
- 事件稀疏性:大多数交易日没有事件,事件信号为 0、最终信号由技术面主导是正常现象,严禁为「填空」而捏造数据。
- 回测事件数据:必须提前备好历史事件 CSV(建议按标的分文件),不能回测时实时抓取。
summary字段中的逗号:这是 CSV 解析错误的常见原因——摘要中避免逗号,或用pd.read_csv(quoting=csv.QUOTE_ALL)读取。对应实现:_clean_summary在数据层直接做逗号剥离(第 281-285 行)。- 衰减参数敏感性:
decay_lambda过大事件影响消失过快、过小则陈旧事件长期存活;理想上不同事件类型(财报 vs 政策)应有不同衰减曲线,但默认统一为0.1,是一种可接受的简化。
十、信号约定与依赖
信号约定:
- 纯事件信号:
[-1.0, 1.0](由事件分数 + 时间衰减计算得出); - 组合信号:
alpha * tech_signal + (1 - alpha) * event_signal,clip 到[-1.0, 1.0]; - 无事件时
event_signal = 0,组合信号退化为纯技术信号。
依赖:仅需pip install pandas numpy。LLM 分析由 Agent 自身完成,read_url工具内置,无额外依赖。这意味着该技能可以零成本地在任意回测框架中接入——只要数据层产出了合规的event_score列,手写引擎即可直接消费。
总结
事件驱动策略的本质是把「信息冲击」变成可回测、可调参、可叠加在技术信号之上的量化信号。Vibe-Trading 的这套技能设计通过三层保障让这件事变得工程上可靠:标准化提示词保证 LLM 打分可复现,CSV + 可知日期约定保证数据层与逻辑层解耦且天然防未来,指数衰减 + 加权合成保证信号可解释、可调参。若要进一步深入,推荐阅读 示例信号引擎完整实现 与 RSSHub 事件提供器,前者是技能文档的最小可运行范本,后者则是生产级的 point-in-time 事件数据管线,两者对照阅读即可完整掌握该技能的落地全貌。
【免费下载链接】Vibe-Trading"Vibe-Trading: Your Personal Trading Agent"项目地址: https://gitcode.com/GitHub_Trending/vi/Vibe-Trading
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考