页面日历数据 · 13 / 29
日历模块
日历模块为您的策略提供公司的分红历史、拆股历史、财报日期、IPO 日期和近期 SEC 申报文件 —— 并允许回测自动将拆股和分红应用到您的持仓上。
在 Run backtest 对话框的 Symbol & data 标签页中启用 Include calendar data。它默认开启,您的选择(以及两个子选项)会被记住,供下次运行使用。日历会为运行所交易的每个标的自动加载,包括由选股器解析出的标的全集 —— 没有按标的选择的选择器。智能体的 Test 对话框有相同的选项 —— Include calendar data (dividends, splits)(参见智能体)。
仅限回测。 日历数据会为回测、Debug Test 和智能体测试加载。前向运行 —— chart forward 测试、AutoTrading 中的 bulk 运行以及 QuantCloud —— 永远不会获得日历,因此其中的 calendar 始终为 None。
为什么重要:价格 K 线是原始的
回测的蜡烛图是未复权的。没有日历数据时:
- 4 拆 1 的拆股看起来像一夜之间价格暴跌 75%,您的模拟持仓也随之暴跌。
- 分红永远不会到账,这会悄悄低估任何长期持仓的总收益 —— 对典型的分红股来说大约是每年 2%。
两个子选项无需任何代码改动即可修复这两个问题。
calendar 回调参数
向任意回调添加 calendar 参数,句柄就会被传入:
def on_bar(bar_index, bar, calendar=None, symbol=None):
if calendar is None: # always guard — not every run enables calendar data
return
days = calendar.days_to_earnings(bar["t"], symbol)
if days is not None and days <= 2:
return # sit out the earnings print
divs = calendar.dividends(bar["t"], symbol)
if divs and (divs[-1].amount or 0) > 0.5: # amount can be None
...适用于 on_bar、on_tick 和 on_timer。calendar 参数是按名称匹配的,因此没有 calendar 参数的脚本行为与以前完全相同,而声明了 calendar 的脚本会收到句柄或 None —— 无论您的签名还接受什么,绝不会收到其他对象。如果您还读取基本面数据,推荐的签名是 def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None)。
您也可以不使用参数而获取同一个句柄:脚本全局变量 qc_calendar,或 quantcraft.process.runtime.qc_calendar。
方法
| 方法 | 返回 |
|---|---|
dividends(bar_time, symbol=None) | 该 K 线时已公开的分红 |
splits(bar_time, symbol=None) | 截至该 K 线已公告的拆股 |
filings(bar_time, symbol=None) | 该 K 线时已公开的 SEC 申报文件 |
next_earnings(bar_time, symbol=None) | {date, basis, estimated, …} 或 None |
days_to_earnings(bar_time, symbol=None) | int 或 None |
ipo_date(symbol=None) | date 或 None |
current(bar_time, symbol=None) | 该 K 线时以上所有内容的快照字典,或 None |
dividends_between(start, end, symbol=None) | 除息日落在 (start, end] 内的分红 |
splits_between(start, end, symbol=None) | 生效日落在 (start, end] 内的拆股 |
has(symbol=None) | 该标的是否有任何日历数据 |
symbols / primary_symbol() / get(symbol=None) | 已加载的标的、默认标的,以及每个标的的原始日历(或 None) |
_between 方法使用影响日期(“它何时影响我的持仓?”),这与上面基于 K 线时间的方法不同,后者使用的是“我当时能知道吗?”的日期 —— 参见下一节。
bar_time 接受 bar["t"]、bar.t[0]、datetime、date 或 epoch 秒数。
列表按从旧到新排序 —— dividends(...)[-1] 是最近的一条。这与基本面数据相反,在基本面数据中索引 0 是最新的周期。
没有日历数据的标的返回 [] 或 None,绝不会报错。 大盘股以外的大多数股票代码在服务器上都没有日历,因此请判断是否为空,而不是等待异常。
每个事件两个日期 —— 请使用正确的那个
每笔分红和拆股都带有两个日期,选错会引入前视偏差:
| “我当时能知道吗?” | “它何时影响我的持仓?” | |
|---|---|---|
| 分红 | declaration_date | ex_date |
| 拆股 | filing_date | effective_date |
dividends() / splits() / filings() 依据第一列过滤 —— 它们只返回在您的 K 线时已公开的内容。拆股往往在生效前数周就已可见,因为 8-K 是提前提交的:
from datetime import date
bar_date = date.fromisoformat(str(bar["t"])[:10])
splits = calendar.splits(bar["t"], symbol)
upcoming = [s for s in splits
if s.effective_date and s.effective_date > bar_date] # announced, not yet effective事件字段
DividendEvent: ex_date, amount, estimated, type, declaration_date,
record_date, payment_date, period_end
SplitEvent: effective_date, ratio, type ("forward" | "reverse"), filing_date, form
FilingEvent: form, filing_date, report_date, items, category,
accession_number, primary_document, primary_document_description
IpoInfo: date, first_trade_date, sourceamount(分红)和 effective_date(拆股)可能为 None —— 比较前请先判断。
申报文件的 category 为以下之一:earnings、acquisition、officer-director、charter-amendment、agreement、regulation-fd、auditor-change、shareholder-vote、cybersecurity、other。
estimated 标志
EDGAR 不发布除息日。除非有交易所数据,否则除息日按宣告日 + 10 天估算,精度约为 ±7 天,并且 estimated is True。默认情况下,模拟仍会入账估算的分红(策略 best_available);运行预设 (.qcs) 可以将 dividendDatePolicy 设为 exact_only(跳过估算的)或 off —— 对话框中没有对应的控件。如果您的策略对除息日敏感,请在自己的逻辑中据此过滤:
exact = [d for d in calendar.dividends(bar["t"], symbol) if not d.estimated]模拟中的公司行动
启用日历数据时,两个子选项默认都开启,并且都在每根 K 线的回调之前运行 —— 因此 on_bar 看到的始终是已调整过的持仓。
为拆股调整持仓 —— 在生效日,股数乘以比例,成本基础除以该比例。止损和目标价也会被重新缩放。由于 qty × entry_price 不变,您的净值曲线在拆股前后保持连续。
价格 K 线刻意保持未复权,因此 bar["close"] 是真实的历史价格。基于原始收盘价序列计算的指标仍会看到拆股造成的缺口 —— 如果这很重要,请使用 calendar.splits(...) 调整您自己的序列。
将分红计入现金 —— 在除息日,对多头会将 qty × amount 加入现金,对空头则扣除(卖空者要向出借人支付分红)。这会反映在净值和所有收益指标中,但不会计入 realized_pnl,后者仅包含交易。请从账户指标中读取总额:
def on_finish(bars, symbol=None, bars_by_symbol=None):
from quantcraft.process.runtime import account
m = account.refresh_metrics({})
print(m["total_dividend_income"], m["total_splits_applied"])在哪里查看公司行动
没有任何东西会悄悄改变您的持仓或现金:
- 输出面板 —— 每应用一次公司行动就有一行
[calendar],并在末尾给出汇总:[calendar] 2024-01-10 AAPL — SPLIT 4:1 applied to 1 position(s): 100 -> 400 shares (cost basis unchanged; bars stay unadjusted) [calendar] 2024-02-09 AAPL — DIVIDEND $0.2400/share on 400 share(s): +$96.00 cash … [calendar] corporate actions — 1 split(s) applied; 1 dividend(s) applied (0 exact ex-date, 1 estimated +/-7d) - Test Results —— Split 或 Dividend 行会内联出现在已平仓交易之间(此时卡片标题为 Closed trades & events),显示比例或每股金额及现金影响。估算的除息日会标记为
est.这些行在图表上没有#标记,并且不会出现在 CSV 导出中。
next_earnings 是时点数据
在回测中,next_earnings 绝不会返回服务器以今天为基准计算的“下一个财报日期” —— 那会对历史 K 线造成前视。它是根据您的 K 线时可见的最近一次财报申报文件,加上公司的披露节奏重建出来的,因此 basis == "filing-cadence" 且 estimated is True。
当尚无可见的财报申报文件时 —— 运行早期,或该标的的申报历史没有追溯到那么早 —— 它会返回 None。请务必判断这一点。
当您看不到分红时
未应用任何内容的运行会在输出面板中说明原因:
| 日志内容 | 含义 |
|---|---|
loaded 3/5 symbol(s); 2 without calendar data | 在开始时打印:有多少标的获得了日历 |
none of 5 symbol(s) returned calendar data: … | 运行中没有任何标的有日历数据 —— 无法应用任何内容 |
no calendar data on the server | 该标的的日历任务尚未运行 |
calendar has no dividend or split history | 不分红,没有记录的拆股 |
… ex-dates 2019-05-10..2020-05-08 | 它分红,但不在您测试的日期范围内 |
N with no ex-date, which can never be applied | 有历史记录,但无法放到时间轴上 |
no positions were held … | 某项行动到期时没有任何持仓 |
如果完全没有出现 [calendar] 行,说明日历数据未附加到该次运行 —— 请检查 Symbol & data 标签页上的 Include calendar data 开关。

