页面OHLCV 与 K 线数据 · 10 / 29
OHLCV 与 K 线数据
QuantCraft 回测需要你所交易的每个标的的 OHLCV(开盘价、最高价、最低价、收盘价、成交量)K 线。引擎会在策略运行之前加载这些数据,然后在每一步用当前 K 线视图(bar)驱动你的策略生命周期回调。运行结束时,你可以选择性地使用传入 on_finish(bars, …) 的完整序列。加载设置在 Test(运行代码)中配置。
本页涵盖数据来自哪里(加载器)、日期范围与预热如何工作,以及如何在 on_finish 中使用完整序列的 bars 参数。当你混用价格与申报文件数据时,请搭配基本面数据;有关交易与价格如何关联,请参见账户模型。
加载器(数据源)
在 Run backtest 的 Symbol & data 标签页中,你选择一个价格数据源。引擎在后台使用两种加载器之一:
已连接券商的数据源
- 使用你的 API 密钥和密钥密文,或带有有效链接 id 的 OAuth 关联连接,从你的已连接券商账户加载 K 线。
- 支持美国股票和加密货币(你在对话框中选择资产类型)。
- 支持许多时间周期——日线、周线、小时线、4 小时线,以及若干日内分钟线(与应用图表的时间周期标签一致)。
- 历史数据分块获取,直到满足请求的范围(或在使用旧版“不指定明确日期”模式时,直到达到最小 K 线数量)。
当你需要券商关联的数据或完整的日内时间周期集(1 分钟到 4 小时,外加日线和周线)时,请使用已连接券商的数据源。
QuantCraft 存储(托管)
- 当你已登录时,从 QuantCraft 托管存储加载 K 线(应用会发送你的会话令牌)。
- 文件按标的和时间周期组织。在免费计划中,回测对话框提供日线、周线和月线;Premium 解锁日内和扩展时间周期。
- 先获取完整文件,再可选地按你选定的日历日期范围过滤。
- 日线 K 线的
t只含日期,例如"2024-01-02",按 UTC 读取。
QuantCraft B2 时间周期代码:1s、1m、2m、m5、15m、30m、h1、2h、4h、d、w、m、q、y。请注意:m 表示月线,而不是分钟——1 分钟是 1m,5 分钟是 m5。
如果某个日期范围内没有 K 线,加载会失败并显示类似 No AAPL bars (d) between 2010-01-01 and 2010-02-01. Available range is … to … — adjust the backtest dates. 的消息。
当你已登录但没有券商凭据,或者想要 QuantCraft 标的目录中的托管 OHLCV 数据包(5 分钟、小时、日、周或月)时,请使用 QuantCraft 存储。
在两者之间选择
| 需求 | 常见选择 |
|---|---|
| 来自已连接券商的完整日内集合(1m、5m、15m、30m、1H、4H 等) | 已连接券商的数据源 |
| QuantCraft 目录中的 5 分钟或小时线 | QuantCraft 存储 |
| 托管文件中的日线/周线/月线 | QuantCraft 存储 |
| 已连接的券商账户 + 密钥 | 已连接券商的数据源 |
| 无券商凭据的已登录访问 | QuantCraft 存储 |
在策略回调中,请从引擎传给你的
bar对象读取价格——而不是从全局 OHLCV 单例读取。在 IDE 中,模块级的qc_ohlcv在你的文件加载时通常未设置;依赖它可能会出错。在以筛选器为目标的运行中它也是None,而在已设置的地方,它涵盖包含预热的完整加载范围——而不是图表所绘制的已执行窗口——因此对图表序列来说它是错误的数据来源。bar视图对当前模拟步骤始终是正确的。
时间范围
你通过开始日期、结束日期以及可选的预热来控制哪些 K 线参与回测。
日历范围(start_date + end_date)
当你在 Simulation 标签页中同时设置两个日期时(格式 YYYY-MM-DD,按 UTC 日历日解释):
- 引擎只保留落在该窗口内的 K 线。
- 该范围按半开区间处理:按产品通常的约定,从开始日的开始到结束日的结束(结束日历日的 K 线按引擎的配置纳入)。
**已连接券商的数据源:**针对该日历跨度请求 K 线。
QuantCraft 存储:下载文件的完整序列,然后过滤到相同的日历日窗口。
如果你省略两个日期:
- 已连接券商的数据源使用旧版向后回溯:引擎沿时间向后回溯,直到加载足够的历史(受时间周期限制),而不是你输入的固定日历窗口。
- QuantCraft 存储使用文件中的所有 K 线(无额外过滤)。
预热 K 线(warmup_bars)
可选的非负整数:在你的开始日期之前额外加载的 K 线,以便指标可以回看而不会耗尽历史。
- 预热 K 线在第一根执行 K 线上可通过
bar.close[1]、bar.close[2]、… 和bar.bars_back看到。 on_bar/on_tick/on_timer仍然只在你选定的执行窗口内运行——预热仅用于历史,不会在结果中增加模拟的“交易日”。
bar_index 与日历
bar_index仅统计执行 K 线(你所选范围内的第一根 K 线是0,然后是1,……)。它不统计仅用于预热的 K 线。- K 线在执行窗口内按从旧到新的顺序模拟。
回调中的 K 线数据(bar)
在每个 on_bar 和 on_tick(以及配置后前向运行中的 on_timer)中,引擎会传入 bar:针对该回调所属标的的 OHLC K 线视图(OhlcvBarView)。它的行为类似只读映射,但 isinstance(bar, dict) 为假——请使用 bar["close"] 或属性式访问,而不要使用仅限 dict 的 API。
在回测中,on_tick 接收来自每根 K 线 OHLC 的合成价格(每根 K 线四次调用,没有真实的 tick 历史)。在前向运行中,on_tick 在你已连接券商的每次实时报价时触发,同时 price 参数跟踪最新的标记价——参见策略生命周期。
你可以把它当作只读映射来使用:
o = float(bar["open"])
h = float(bar["high"])
l = float(bar["low"])
c = float(bar["close"])
v = float(bar["volume"])
t = bar["t"] # ISO timestamp string (QuantCraft B2 daily bars: a date like "2024-01-02", UTC)你还可以通过偏移索引读取相对于当前 K 线的历史(同一根 K 线、之前的收盘价等):
current_close = bar.close[0]
prev_close = bar.close[1]bar.close[0]是当前 K 线上下文的收盘价(与回测on_bar中的float(bar["close"])含义相同)——有关它在各个回调中分别指哪根 K 线,参见on_bar与on_tick中的 K 线索引。- 较大的索引会沿时间向后移动;
bar.bars_back告诉你可以回看多远。
on_bar 与 on_tick 中的 K 线索引
bar.close[0]、bar.close[1]、… 之类的偏移始终从回调能看到的最新 K 线开始向后计数——但哪一根是“最新”取决于回调:

on_bar在 K 线收盘时触发。索引[0]是刚刚收盘的那根 K 线,[1]是它之前的一根,[2]是再之前的一根,依此类推。每个索引都指向一根已完成的 K 线。on_tick在 K 线仍在形成时触发。索引[0]是正在形成的 K 线(其值随每个 tick 变化),[1]是最近一根已收盘的 K 线,[2]是再之前的一根,依此类推。
因此同一根 K 线在 on_tick 中的索引比在 on_bar 中高 1:
| K 线 | 在 on_bar 中 | 在 on_tick 中 |
|---|---|---|
| 正在形成(尚未收盘) | 不可见 | [0] |
| 刚收盘 | [0] | [1] |
| 之前一根 | [1] | [2] |
| 之前两根 | [2] | [3] |
def on_bar(bar_index, bar, fundamentals=None, symbol=None):
last_closed = bar.close[0] # the bar that just closed
one_before = bar.close[1]
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, symbol=None):
forming = bar.close[0] # still forming — latest price so far
last_closed = bar.close[1] # same candle on_bar saw as [0]同样的位移适用于视图上的每个序列(open、high、low、close、volume)。如果你把逻辑从 on_bar 移到 on_tick,请给每个偏移加 1,以便继续读取同样的 K 线。
回测:
on_tick把当前 K 线回放为四个合成 tick,所以那里的[0]是正在模拟的 K 线,从其完整的行读取(参见下面的[0]处的成交量)——它不像前向运行那样是一根部分构建的 K 线。
实时数据的准确性(前向运行)
来自券商的实时价格流只是近似值,并不是市场的完整记录:
- 免费实时数据计划通常只能看到单一交易所的成交——只占美国全部交易的一小部分——因此实时构建的 K 线显示的成交量要低得多,并且可能错过真实的最高价和最低价。
- 付费的合并行情计划能看到每个交易所,但成交仍可能延迟、成批到达,尤其是在非常活跃的股票开盘前后。
应用会在每根实时构建的 K 线收盘后不久,用券商最终确定的历史 K 线替换它。在 1 分钟这样的小时间周期上,这意味着:
| 索引 | 含义 | 用途 |
|---|---|---|
bar.close[0] | 由实时 tick 构建的正在形成的 K 线 | 当前价格估计 |
bar.close[1] | 刚收盘的 K 线——在最初约 10–30 秒内可能仍是由 tick 构建的 | 新 K 线刚开始后请谨慎使用 |
bar.close[2] | 完全确定的 K 线 | 需要确认收盘价的信号 |
对于依赖 1 分钟 K 线上已确认收盘价的信号,请优先使用 bar.close[2]。
[0] 处的成交量:
- **前向
on_tick:**正在形成的 K 线上的volume[0]是临时值(通常是单个 tick 的大小)。请使用volume[1]获取最近一根已完成 K 线的成交量。 - 回测:四个合成 tick 都使用完整的 K 线行——
volume[0]是整根 K 线的成交量,而不是临时的 tick 大小。
这里是每一步交易逻辑的合适位置:入场、K 线收盘时出场、简单移动平均线,以及任何只需要当前 K 线加回看数据的逻辑。
主时间周期与附加时间周期(get_bar)
运行的主时间周期始终可通过 rt.get_bar 使用(无需在 Additional timeframes 下列出)。当你的策略还需要不同分辨率的 K 线时(例如在 5 分钟回测中使用日线趋势过滤器),请在 Run backtest 对话框 Symbol & data 的 Additional timeframes 下拉框中选择这些附加周期,然后在任何回调中读取:
from quantcraft import Timeframe
import quantcraft.runtime as rt
def on_bar(bar_index, bar, fundamentals=None, symbol=None):
# Primary TF works without listing it under Additional timeframes
# (use the Timeframe member that matches the modal primary, e.g. M5):
prev_primary = rt.get_bar(Timeframe.M5, shift=1)
daily = rt.get_bar(Timeframe.DAILY, shift=1)
if daily and bar.close[0] > daily.close:
... # primary bar closed above yesterday's daily close在回测中配置 Additional timeframes;将选择保存在运行预设(.qcs)的 secondaryTimeframes 下。
BarSnapshot 字段
get_bar 返回一个 BarSnapshot(或 None),包含:open、high、low、close、volume、t。
shift 的语义
shift | .close | .high / .low / .open |
|---|---|---|
0 | 当前执行价格——在同一回调中所有时间周期都相同(回测;参见下面的前向说明) | 来自该时间周期存储的 K 线——与主时间周期不统一 |
>= 1 | 该时间周期上已完成 K 线的收盘价 | 已完成 K 线的完整 OHLC——适合用于上一根 K 线的影线 |
回测:get_bar 在主时间周期和每个附加时间周期上都只能看到截至当前模拟步骤的 K 线——绝不会看到更晚的。在 shift=0 时,副时间周期的 high/low 会与每个时间周期的模拟时钟按时间对齐。get_bar 读取时钟标的的 K 线;在多标的运行中,请使用每个代码自己的 bar.close[i] 获取其历史。
前向运行:主 bar 跟随图表工具栏的时间周期。副缓冲区在该时间周期的数据流上有 K 线完成时更新——shift=0 时的 high/low 可能滞后,直到该时间段完成;要可靠地获得上一根 K 线的 OHLC,请优先使用 shift=1。前向的 shift=0 .close 是每个缓冲区自己的最新收盘价,在各时间周期之间不统一。在 on_tick 中,主 K 线上的 volume[0] 可能是临时的(仍在形成)。
当该时间周期对本次运行没有缓冲区(既不是主周期,也未在 Additional timeframes 下选择)、在 Screener 回测中(没有时钟标的——请使用每个代码自己的 bar.close[i] 偏移获取历史),或数据不可用时,返回 None。回测中有一个例外:对你未选择的时间周期使用 shift=0 会返回主时间周期的当前快照,而不是 None。
tf 可以是 Timeframe 成员,也可以是 "1D" / "5Min" 之类的字符串。
Timeframe 枚举
使用 from quantcraft import Timeframe 导入:
| 成员 | 字符串值 | 说明 |
|---|---|---|
M1 | "1Min" | 1 分钟 K 线 |
M5 | "5Min" | 5 分钟 K 线 |
M15 | "15Min" | 15 分钟 K 线 |
M30 | "30Min" | 30 分钟 K 线 |
H1 | "1Hour" | 小时 K 线 |
H4 | "4Hour" | 4 小时 K 线 |
DAILY | "1D" | 日 K 线 |
WEEKLY | "1Week" | 周 K 线 |
某些 QuantCraft B2 时间周期(2 分钟、2 小时、1 秒、月线 m、季线 q、年线 y)没有 Timeframe 成员——请以字符串形式传给 get_bar。
可选的完整序列:on_finish(bars, …)
模拟结束时,引擎会调用一次 on_finish。如果你的策略定义了扩展签名,你会收到完整序列的 OHLC 行,用于后处理——覆盖整个运行的指标、导出序列、为 refresh_metrics 提供最终标记价等。
def on_finish(bars, symbol=None, bars_by_symbol=None):
...bars — 时钟标的,从旧到新
bars是时钟标的的字典list(若该代码已加载则为脚本的SYMBOL,否则为第一个已加载的标的——对话框没有主标的控件)。在 Screener 运行中没有时钟标的:bars为[],symbol为"",因此请使用bars_by_symbol。- 顺序是时间顺序:最旧的 K 线在前,最新的在后——与笔记本风格 API 中
QcOhlcv的索引[0] = 最新的约定相反。 - 每一行至少包含
open、high、low、close、volume,并在存在时间戳时包含t。启用基本面数据后,行中还可能包含该 K 线的fundamentals快照。
典型用法:
def on_finish(bars, symbol=None, bars_by_symbol=None):
if not bars:
return
closes = [float(b["close"]) for b in bars]
last = float(bars[-1]["close"])
sym = (symbol or "AAPL").strip().upper()
metrics = account.refresh_metrics({sym: last})
chart_indicators.add(...)调用 account.refresh_metrics({symbol: last_price}) 时,请使用 bars[-1]["close"](或最后一行的字段)作为最终标记价,这样未平仓头寸会在运行结束时被估值。
对于 chart_indicators,请优先使用你在运行逐根遍历 K 线时按标的累积的值,仅用 on_finish 来交接;只有对你没有逐根跟踪的指标才从 bars 重新计算——参见指标序列。
bars_by_symbol — 多标的运行
当你回测不止一个代码时,bars 仍然只指时钟标的。要获取每个标的的完整对齐序列,请使用 bars_by_symbol:
- 一个从大写代码到与
bars形状相同的列表(每个标的按时间顺序的 dict)的映射。 - 当你在结束时需要横截面或逐标的的完整历史(相关性、相对强弱、逐标的指标)时使用它。
何时不需要 on_finish
如果你的策略只需要当前 K 线和回看偏移,你可以只实现 on_bar / on_tick,跳过繁重的完整序列工作。对于逻辑而言,on_finish 是可选的——但它仍是交接 chart_indicators 序列的地方,也是 refresh_metrics 获得最后价格的地方。请注意,on_finish 中的每个 return 都是静默的:运行依然成功,只是跳过了其后的内容。
这与 Test Results 的关系
结构化的回测结果包含一个 ohlcv.bars(或等效)列表:每个模拟的执行步骤一行,带有 OHLC 字段和可选的 fundamentals 快照——与你在回调中看到的一致。on_finish(bars, …) 与引擎用于时钟标的“整个运行”计算的是同一条时间序列,因此你的结束时代码与结果和图表中显示的内容相匹配。
快速参考
| 主题 | 面向用户的摘要 |
|---|---|
| 加载器 | 已连接券商的数据源(密钥 + 丰富的日内数据)与 QuantCraft 存储(已登录;日线/周线/月线,Premium 提供更多)。 |
| 日期 | YYYY-MM-DD 开始 + 结束 → 按日历过滤的窗口;两者都省略 → 券商数据源的旧版回溯或 QuantCraft 存储的完整文件。 |
| 预热 | 开始之前为 bar 偏移提供的额外历史;回调仅在执行窗口内运行。 |
| 每一步 | 在 on_bar / on_tick 中使用 bar / bar["close"] / bar.close[k]。[0] = on_bar 中最后一根已收盘 K 线,on_tick 中正在形成的 K 线。 |
| 主/附加时间周期 | 主周期始终通过 rt.get_bar;附加周期通过 Additional timeframes + rt.get_bar(Timeframe.X, shift)。 |
| 完整序列 | 使用 on_finish(bars, symbol=..., bars_by_symbol=...) —— bars 为时钟标的的从旧到新序列;bars_by_symbol 为多标的运行中的所有标的。 |
| 全球因子收益 | 不在 QcOhlcv 或 bar 上——请使用 Fama-French 因子(fama_french.qc)。月度 rows 是 YYYYMM 字典;请使用 latest_row,不要用 rows[0]。 |

