文档1.0 (latest)
页面日历数据 · 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_dateex_date
拆股filing_dateeffective_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, source

amount(分红)和 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 开关。


另请参阅

  • 回测 — Run backtest 对话框和 Include calendar data 选项
  • 策略生命周期 — calendar 回调参数、qc_calendar 运行时全局变量
  • 账户模型 — 分红现金和拆股调整的落点
  • 选股器 — 选股器解析股票代码列表时日历如何加载
  • 测试结果 — 已平仓交易表中的 Split / Dividend 行
  • 输出面板 — [calendar] 日志行
阅读约 4 分钟建议修改 ↗
日历数据 — 文档 (1.0 (latest)) | QuantCraft