
getBars 实战:把 iTick K 线映射到 TradingView bars
1. getBars 真正做的不是“拿数据”,而是“交付协议”
很多人写 getBars 时,只盯着有没有成功调用上游接口,觉得只要能拿回一组 K 线数组,任务就算完成。实际上,TradingView 真正在意的是:你交付的 bars 是否符合它的协议预期。也就是说,哪怕你拿到的数据本身没问题,只要顺序错了、时间边界不稳、字段类型不统一,图表表现就会异常。
所以在理解 getBars 时,必须把它看成“协议交付层”而不是“请求转发层”。它负责把 iTick 的原始返回整理成 TradingView 可以安全消费的 bars 数组,并且要在 noData、回补窗口、历史分页这几个关键语义上保持一致。只要这个层次想清楚,代码其实并不复杂。
2. iTick K 线字段与 TradingView bars 的差异
iTick 的 K 线结果与 TradingView bars 已经非常接近,但“接近”不等于“可以直接塞进去”。TradingView 需要的是 time/open/high/low/close/volume 这一套标准字段,而且默认希望它们是数值、顺序稳定、时间单调上升。iTick 返回的数据里,虽然同样包含这些核心信息,但你仍然要主动做映射、清洗和排序。
这一步不要偷懒。很多项目里最隐蔽的 bug,恰恰就来自“字段看起来差不多,于是直接透传”。一开始多写几行清洗代码,后面会省下大量排查时间。
3. 先在服务端把上游请求稳定下来
为了让 getBars 足够干净,最好先把 iTick 历史请求统一收口到服务端。浏览器只向自己的 /api 请求 bars,服务端再根据 symbol 和 resolution 去请求 iTick。这样做的好处有三个:token 不暴露、缓存容易做、上游异常能统一处理。
如果你还没建立这层代理,建议先从 https://itick.org/zh-cn 和 https://docs.itick.org/zh-cn 把历史 K 线的基础参数确认清楚,再来实现映射逻辑。尤其是 market、region、code、kType 与 et 这些字段,必须从一开始就用同一套语义。
4. 一个最小可用的映射函数应该长什么样
最小可用的 bars 映射函数应该做四件事:确保字段存在、转成数字、映射命名、按时间升序排序。不要把这些细节分散在页面各处,最好统一放在一个函数里,这样历史加载、分页补数和后续调试都能复用同样的逻辑。
type ITickKline = {
t: number;
o: number;
h: number;
l: number;
c: number;
v?: number;
};
type Bar = {
time: number;
open: number;
high: number;
low: number;
close: number;
volume?: number;
};
export function mapITickToBars(rows: ITickKline[]): Bar[] {
return rows
.map((row) => ({
time: Number(row.t),
open: Number(row.o),
high: Number(row.h),
low: Number(row.l),
close: Number(row.c),
volume: row.v == null ? undefined : Number(row.v),
}))
.filter((bar) => Number.isFinite(bar.time) && Number.isFinite(bar.open) && Number.isFinite(bar.close))
.sort((a, b) => a.time - b.time);
}
这个函数看似简单,但几乎覆盖了 bars 映射最关键的稳定性要求。真实项目里最需要的,往往正是这种“很朴素但不留隐患”的代码。
5. getBars 与 periodParams 的关系必须看懂
真正写 getBars 时,很多人会忽略 periodParams,转而固定请求某个 limit。这样做在 demo 阶段可能还能用,但一旦用户开始拖动回看历史,你就会发现图表行为非常奇怪。因为 TradingView 发起请求时,其实已经告诉了你当前所需时间窗口,尤其是 to 和 firstDataRequest 两个信息非常关键。
更稳妥的做法是:首屏请求可以适当多拉一点,后续回补时则按窗口分页向前取。这样不仅更贴近图表协议,也能显著改善缓存命中率和滚动体验。getBars 不是“每次都给点数据”,而是“根据图表当前状态交付恰好的数据”。
6. 一个完整的 getBars 示例
下面这段代码把服务端请求、字段映射和历史回调串成了一个最小闭环。它的重点是:先请求自己的服务端,再把 iTick 返回统一映射成 bars,最后根据 bars 是否为空返回 noData。这才是 getBars 最典型的落地方式。
export async function getBars(
symbolInfo: { ticker: string },
resolution: string,
periodParams: { to: number; firstDataRequest: boolean },
onHistoryCallback: (bars: Bar[], meta: { noData: boolean }) => void,
onErrorCallback: (message: string) => void,
) {
try {
const url = new URL("/api/itick/history", window.location.origin);
url.searchParams.set("market", "crypto");
url.searchParams.set("region", "US");
url.searchParams.set("code", symbolInfo.ticker);
url.searchParams.set("resolution", resolution);
url.searchParams.set("limit", periodParams.firstDataRequest ? "500" : "300");
url.searchParams.set("to", String(periodParams.to));
const res = await fetch(url, { headers: { accept: "application/json" } });
const json = await res.json();
const bars = mapITickToBars(json.data ?? []);
onHistoryCallback(bars, { noData: bars.length === 0 });
} catch (error) {
onErrorCallback(String(error));
}
}
如果你的 getBars 基本结构已经像这样清晰,后续再加缓存、日志和更多市场支持都不会难。
7. noData 为什么经常被误用
noData 看起来只是一个简单布尔值,但它对图表行为影响非常大。一旦你过早地返回 noData: true,TradingView 可能就会停止继续向前回补历史数据。很多“拖到前面就没了”的问题,本质上并不是上游没数据,而是你在某一次空数组返回时过早宣布“历史已经结束”。
因此,对 noData 的判断一定要谨慎。最理想的做法是结合具体市场、分页逻辑和返回窗口来判断,而不是简单地“数组为空就永远没有更多”。这一步虽然细,但会直接决定历史体验是否稳定。
8. 排序、重复 bar 与边界时间
bars 映射里最常见的三个问题分别是:顺序错误、重复 bar、时间边界不稳。顺序错误通常来自上游返回未排序或你在合并分页结果时打乱了顺序;重复 bar 则常见于翻页时边界控制不严格;时间边界不稳通常与 et、resolution 映射或时区处理有关。
这些问题看上去不大,但图表会立刻给出反馈:要么是局部空白,要么是最后一根反复跳动,要么是拖动时断断续续。遇到这种现象时,优先回到映射层检查,不要第一时间怀疑渲染层。大多数时候,bars 本身已经告诉你问题在哪。
9. 日志与缓存应该围绕 getBars 建立
getBars 是最适合建立日志和缓存的地方,因为它天然具备稳定的输入参数:symbol、resolution、to、limit。只要你把这些参数打到日志里,再记录返回 bar 数量和耗时,很多历史数据问题都会变得非常容易定位。缓存也是一样,只要 key 设计清楚,命中率通常不会低。
很多项目一开始没做这一步,等到页面复杂起来再补,就会非常痛苦。因为那时候 bars 映射逻辑可能已经散落到多个地方,很难统一回收。相反,若你从第一版就把 getBars 作为历史链路中心,后面几乎所有优化都能围绕它展开。
10. 小结
把 iTick K 线稳定映射成 TradingView bars,真正关键的不是写出一段能运行的 map(),而是把字段、排序、时间边界和 noData 语义一起处理正确。只要这几件事都稳定,getBars 就会从“可能能跑”变成“长期可靠”。
下一步如果你准备继续强化这条链路,建议优先补缓存与日志,再去细化多市场 resolution 映射。因为 bars 一旦稳定,历史体验和实时链路都会受益。实现细节仍建议继续对照 https://itick.org/zh-cn 与 https://docs.itick.org/zh-cn 做逐项核验。


