用 TradingView 开源的 Lightweight Charts 画港股日K,10月14日显示成10月13日——跨市场K线日期差一天,问题不在图表库,在喂数层。我把同一组真实行情数据喂了两遍,挑出跨市场K线最容易画错的10个地方,文末附一段能直接复制的喂数层代码。数据来自 TickDB 的K线接口。
10月9日,香港证监会透露,香港正在研究延长证券交易时段,港交所第四季度会就现货市场延时发讨论文件。再过两周多,11月1日,美股换冬令时,北京时间开盘从21:30推迟到22:30。
交易时间要变了。做行情App的人该先问自己一句:K线的时间轴是写死的还是算出来的?
我先拿腾讯试了一下。用 TradingView 开源的 Lightweight Charts 画日K,接口返回的数据原样喂进去。10月14日的K线,图上写着13日,整条时间轴都早了一天。我一开始以为是时区配置写错了,后来发现错在喂数据那一步。
做单一市场,这类错一两处;做A股、港股、美股一起的跨市场App,它们会一起冒出来。
图表库只负责画,画得对不对取决于你怎么喂数据。
读完你能带走:10个最常见的画错点和修法,一段约50行的喂数代码,一张上线前体检表。
测试版本:Lightweight Charts 5.2.1
数据来源:TickDB K线接口
核验时间:2026-10
一、问题背景:Lightweight Charts 只管画,不懂行情
Lightweight Charts 免费、轻量,几十行代码就能画出专业样式的K线,很多行情App和交易工具都在用。
但它不知道你的时间戳属于哪个市场,不知道国内习惯红涨绿跌,不知道这只股票上个月除过权,也不知道A股成交量的“1”是一手。这些都得在数据进图表之前处理好,我把这一步叫“喂数层”。
喂数层:数据进图表之前的统一处理层,负责时区、复权、成交量单位、小数位、排序、去重。
日K:以交易日为单位的K线,时间戳通常标在交易所当地零点。
UTC:协调世界时,图表库默认显示基准。
下面每一条都是同一组真实数据喂两遍的对比:一遍原样喂,一遍先过喂数层。数据来自 TickDB 的K线接口。
喂数层数据流图
flowchart LR
A[原始K线] --> B[字段映射]
B --> C[过滤无效时间/收盘价]
C --> D[按时间升序排序]
D --> E{日K还是分钟K?}
E -- 日K --> F[按交易所时区转 YYYY-MM-DD]
E -- 分钟K --> G[毫秒转秒]
F --> H[成交量按市场换算]
G --> H
H --> I[重复时间保留后到]
I --> J[标记最后一根未完成]
J --> K[输出 bars]
K --> L[Lightweight Charts]
喂数层不是“可选优化”,而是跨市场K线进图表前的统一入口。时区、单位、排序、去重、未完成标记都在这里处理,页面层不再各修各的。
二、为什么 Lightweight Charts 日K日期会差一天?
① 腾讯10月14日的K线,标成了13日
结论:日K日期早一天,是时间戳按 UTC 显示造成的。
原因:行情接口的日K时间戳,通常标在交易所当地零点。腾讯10月14日这根是北京时间14日0点,换成 UTC 是13日16点。图表库只按 UTC 显示时间,所以标成13日。用户电脑设成北京时间也没用。
修法:日K别喂时间戳,换成当地日期字符串 '2026-10-14' 再喂。
用户截图发群里一句“你们日期都错了”,比什么 bug 报告都伤口碑。
② 三个市场放进一张图,同一天对不上
结论:三个市场同一天对不上,是各市场零点不同。
原因:腾讯标在北京零点,苹果标在纽约零点,比特币标在 UTC 零点。放在同一条时间轴上,“10月14日”变成了三个点,腾讯那根还掉进了13日。分钟K也一样。我取的15分钟K里,寒武纪第一根标09:45,腾讯第一根标09:30:A股标的是结束时刻,港股标的是开始时刻。两边各有一根“09:45”,管的是相邻的两段。
修法:每个市场先换成各自的当地交易日,再对齐。另外得有人拍板:美股10月14日那一场在北京是14日晚上到15日凌晨,你的首页算它哪一天?
做联动分析、相关性、配对交易,最怕的就是这种错位。
三、涨跌颜色、前复权、汇率小数位怎么设?
③ 涨是绿的,跌是红的
结论:涨跌颜色反了,是图表库默认欧美习惯。
原因:图表库默认绿涨红跌。国内用户第一眼就会看反。
修法:按用户地区设涨跌色,K线和成交量柱一起改。
④ 寒武纪除权那天,像是暴跌了36.7%
结论:除权日像暴跌,是不复权导致缺口。
原因:5月8日是寒武纪除权日。前一天收盘1,868元,当天收盘1,182.53元,不复权的图上断开一个大缺口。前复权后,前一天收盘折算成1,252.68元,当天其实只跌了5.6%。不复权的话,均线、指标在这里都会失真,按涨跌幅设的提醒也会误报。
修法:看图用前复权。做回测记下复权方式,因为每次新的除权都会让前复权的历史价格整体变一次。
⑤ 汇率6.69916,图上只剩6.70
结论:汇率只剩两位小数,是默认精度不够。
原因:图表库默认保留2位小数,股票够用,汇率不够。汇率一天的波动常在千分位,两位小数什么都看不出来。
修法:按品种设小数位,外汇5位,股票2位,低价港股3位。
四、A股分时、美股冬令时、未完成K线怎么处理?
⑥ A股分时图从凌晨01:45开始
结论:A股分时从01:45开始,是UTC显示问题。
原因:到了分时图上更扎眼:09:45显示成01:45,15:00显示成07:00。
修法:用图表库的时间格式化选项,按交易所时区名显示:A股 Asia/Shanghai,美股 America/New_York。别写“加8小时”。
⑦ 11月以后,美股时间错一小时
结论:11月后美股时间错一小时,是写死时差。
原因:很多代码里写死了“美东加12小时就是北京时间”。夏令时是对的,11月1日以后要加13小时。写死的话,开盘、收盘、“交易中”的判断全部早一小时。港股如果真延长交易时段,所有写死“16:00收盘”的地方也得改。
修法:时区换算用时区名,夏令时交给系统处理。交易时段从接口读,别写死在代码里,交易所改时间你也不用发版。
⑧ 最后一根K线,看着像缩量六成
结论:最后一根像缩量,是未收盘。
原因:北京时间23:33打开苹果日K,最新一根成交量1,482万股,前一天3,898万股。其实是这根还没收盘,当天才交易了两个小时。我当时打开苹果日K,最新一根成交量看着像缩量六成,后来才反应过来是半成品。“放量缩量”、均线信号、价格提醒,都会被这根半成品带偏。拿它算策略信号,回测里看不出问题,实盘才出事。
修法:最后一根单独标“未完成”,颜色画浅一点;更新它用 series.update();指标和提醒只用已收盘的K线。
五、A股成交量手股转换、58709年、Value is null 白屏怎么修?
⑨ 寒武纪的量柱,只有腾讯的1/270
结论:寒武纪量柱只有腾讯1/270,是单位不同。
原因:10月14日,寒武纪成交量73,781,单位是手;腾讯19,933,548,单位是股。直接画在一起,寒武纪的柱子几乎看不见。乘100换成股,是738万股,约为腾讯的37%。
修法:A股成交量乘100换成股。跨市场比活跃度,用成交额更稳。
⑩ 日期跑到58709年,或者整张图白屏
结论:日期跑到58709年或白屏,是毫秒未转秒和未排序。
原因:这是上线当天最常见的两种“图没了”:
- 毫秒没转秒:接口时间戳一般是毫秒,图表库要秒。毫秒直接喂进去不报错,横轴写着“58709年”,所有K线挤在一角。
- 没按时间排序:图表库要求数据严格升序、不能重复。顺序一乱,整张图白屏,生产环境只报一句
Value is null,很难查。
顺带一个合规提醒:图表左下角那个小 logo 是开源许可要求的署名。嫌碍眼关掉的话,记得在页面别处补上 TradingView 的署名信息。
修法:毫秒转秒、升序排序、去掉重复时间,三件事都放进喂数层。
六、上线前体检表:跨市场K线10个检查点
| # | 用户看到的 | 修法 |
|---|---|---|
| ① | 日K日期早一天 | 日K换成当地日期字符串 |
| ② | 三个市场同一天对不上 | 先换成各自交易日再对齐 |
| ③ | 涨跌颜色反了 | 按用户地区设颜色 |
| ④ | 除权日像暴跌 | 看图用前复权 |
| ⑤ | 汇率只剩两位小数 | 按品种设小数位 |
| ⑥ | 分时从01:45开始 | 按交易所时区名显示 |
| ⑦ | 冬令时后错一小时 | 时区名换算,时段从接口读 |
| ⑧ | 最后一根像缩量 | 标记未完成,不进指标 |
| ⑨ | A股量柱矮100倍 | 乘100换成股,或用成交额 |
| ⑩ | 58709年、白屏 | 毫秒转秒、升序去重、保留署名 |
K线显示异常排查决策图
flowchart TD
A[K线显示异常] --> B{日K日期早一天?}
B -- 是 --> C[日K换成当地日期字符串]
B -- 否 --> D{分时从01:45开始?}
D -- 是 --> E[按交易所时区名显示]
D -- 否 --> F{横轴58709年?}
F -- 是 --> G[毫秒转秒]
F -- 否 --> H{整图白屏 Value is null?}
H -- 是 --> I[升序排序/去重]
H -- 否 --> J{最后一根像缩量?}
J -- 是 --> K[标记未完成,不进指标]
J -- 否 --> L{涨跌颜色反了?}
L -- 是 --> M[按用户地区设颜色]
L -- 否 --> N[检查复权和小数位]
这张图只覆盖最常见的 6 类显示异常。排查顺序按“用户第一眼看到什么”来排,不是按代码模块排。
七、喂数层完整代码:毫秒转秒、升序去重、时区对齐
10条修法都落在同一个地方:数据进图表之前。别在每个页面各修各的,写一个喂数层,所有K线都从这里过。
喂数层代码
这段代码用本文的真实数据在 Lightweight Charts 5.2.1 里跑通过:
// feed.js:K线数据进图表之前,统一过这一层
const TZ = {
CN: 'Asia/Shanghai', HK: 'Asia/Hong_Kong', US: 'America/New_York', CRYPTO: 'UTC', FX: 'UTC' };
const LOT = {
CN: 100, HK: 1, US: 1, CRYPTO: 1, FX: 1 }; // A股成交量按手,乘100换成股
const DIGI = {
CN: 2, HK: 2, US: 2, CRYPTO: 2, FX: 5 }; // 价格小数位,低价港股、个别品种单独覆盖
const dayOf = (ms, tz) => // 交易所当地日期 'YYYY-MM-DD'
new Intl.DateTimeFormat('en-CA', {
timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' })
.format(new Date(ms));
function normalize(klines, {
market, interval, lastIsForming = false }) {
const tz = TZ[market];
const daily = /^(1d|1w|1M)$/.test(interval);
const rows = klines
.map(k => ({
ms: Number(k.time), o: +k.open, h: +k.high, l: +k.low, c: +k.close,
v: +k.volume * LOT[market], amt: k.quote_volume == null ? null : +k.quote_volume }))
.filter(r => Number.isFinite(r.ms) && Number.isFinite(r.c))
.sort((a, b) => a.ms - b.ms); // 不假设接口返回顺序
const out = [];
for (const r of rows) {
const time = daily ? dayOf(r.ms, tz) : Math.floor(r.ms / 1000); // 日K用日期,分钟K用秒
const bar = {
time, open: r.o, high: r.h, low: r.l, close: r.c, volume: r.v, amount: r.amt };
if (out.length && out[out.length - 1].time === time) out[out.length - 1] = bar; // 重复时间保留后到的
else out.push(bar);
}
if (lastIsForming && out.length) out[out.length - 1].forming = true; // 未收盘,指标和提醒不用它
return out;
}
function chartOptions(market) {
const tz = TZ[market];
const hm = new Intl.DateTimeFormat('zh-CN', {
timeZone: tz, hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
const ymd = new Intl.DateTimeFormat('zh-CN', {
timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' });
const fmt = t => typeof t === 'number' ? `${
ymd.format(new Date(t * 1000))} ${
hm.format(new Date(t * 1000))}` : t;
return {
layout: {
attributionLogo: true }, // 保留署名,或在页面别处放 NOTICE 和署名信息
localization: {
timeFormatter: fmt },
timeScale: {
timeVisible: true,
tickMarkFormatter: t => typeof t === 'number' ? hm.format(new Date(t * 1000)) : null },
};
}
function seriesStyle(market, region = 'CN') {
const [up, down] = region === 'CN' ? ['#e5484d', '#16a34a'] : ['#16a34a', '#e5484d'];
const d = DIGI[market];
return {
upColor: up, downColor: down, wickUpColor: up, wickDownColor: down, borderVisible: false,
priceFormat: {
type: 'price', precision: d, minMove: 10 ** -d } };
}
function safeUpdate(series, bar) {
// 推送乱序时,旧K线不回写
const last = series.data().at(-1);
if (last && bar.time < last.time) return; // 日期字符串和秒数都能直接比大小
series.update(bar);
}
用法:
// resp:后端转发的K线接口返回,API Key 留在后端
const bars = normalize(resp.data.klines, {
market: 'CN', interval: '1d', lastIsForming: true });
const chart = LightweightCharts.createChart(el, chartOptions('CN'));
const candles = chart.addSeries(LightweightCharts.CandlestickSeries, seriesStyle('CN'));
candles.setData(bars);
跨市场,最好只接一套接口
喂数层要按市场分支,这躲不掉,A股按手、港股按股是市场规则。但按数据源分支是可以躲掉的。如果A股、港股、美股、加密、外汇各接一家,每一家的时间戳格式、字段名、复权参数都要单独写一套转换,哪家一升级就可能出错。
TickDB 为开发者和 AI 应用提供统一的全球行情数据接口,让团队通过一套接入获取多市场实时与历史数据,更快构建行情、分析和监控产品。
本文的实验数据都来自 TickDB 的K线接口,和喂数层直接相关的有三点:
- A股、港股、美股、外汇、加密资产的K线是同一组字段,喂数层只需要按市场分支;
- 股票K线用
adjust一个参数切换不复权、前复权、后复权; - 交易时段有单独的接口,按市场返回当地时段,开收盘时间不用写死。
八、FAQ:跨市场K线日期与喂数层常见问题
Q1:为什么 Lightweight Charts 日K日期会差一天?
A:日K时间戳标在交易所当地零点,图表库按 UTC 显示,会往前一天。修法是把日K换成当地日期字符串再喂。
Q2:A股、港股、美股放一张图,同一天为什么对不上?
A:各市场零点不同,分钟K还有开始时刻和结束时刻差异。修法是先换成各自当地交易日,再对齐。
Q3:为什么美股冬令时后时间错一小时?
A:写死“美东加12小时”只适合夏令时,11月1日后要加13小时。修法是用时区名换算,交易时段从接口读。
Q4:最后一根K线能不能用于指标和提醒?
A:不能,未收盘K线是半成品。修法是标记未完成,指标和提醒只用已收盘K线。
Q5:A股成交量为什么只有腾讯的1/270?
A:A股按手,港股按股,A股要乘100换成股;跨市场比活跃度,用成交额更稳。
Q6:Lightweight Charts 时间戳要秒还是毫秒?
A:要秒,毫秒要转秒,否则横轴会跑到58709年。
Q7:日K时间戳怎么转当地日期?
A:用 Intl.DateTimeFormat 按交易所时区输出 YYYY-MM-DD。
Q8:A股、港股、美股时区分别用什么?
A:Asia/Shanghai、Asia/Hong_Kong、America/New_York。
Q9:前复权和不复权在图表上怎么选?
A:看图用前复权,回测记录复权方式。
Q10:Lightweight Charts 数据排序要求?
A:严格升序、不能重复,否则白屏或 Value is null。
九、总结:交易时间会变,喂数层别散落在每张图里
打开你们的K线页面,把光标停在最新一根上,看看日期是不是今天;再找一只最近除过权的股票,看看除权那天。两分钟就能知道,第①④条你们中了没有。
港股延时还在讨论,美股冬令时还有两周多。交易时间会变,复权因子会变,这些变化都放在喂数层里处理,别散落在每一张图里。我后来把同一组数据喂了两遍,才把问题拆开;我这次用 TickDB 的K线接口做的两组对照,也说明同一套字段能减少转换分支。
参考资料
- 21世纪经济报道,香港证监会:将就延长交易时段发讨论文件,2026-10-10
- 每日经济新闻,港交所将就延长交易时段发表讨论文件,2026-10-12
- TradingHours 网站,Nasdaq Trading Hours
- TradingView,Lightweight Charts™ version 5