港股拟延长交易:TradingView做跨市场行情App,K线为何差一天

TickDB Research ·

用 TradingView 开源的 Lightweight Charts 画腾讯10月14日的日K,图上显示的是10月13日。图表库没错,数据也没错,错在喂数据的那一步。我用真实行情把同一组数据喂了两遍,挑出跨市场K线最容易画错的10个地方,文末附一段能直接复制的喂数代码。

10月9日,香港证监会透露,香港正在研究延长证券交易时段,港交所第四季度会就现货市场延时发讨论文件。再过两周多,11月1日,美股换冬令时,北京时间开盘从21:30推迟到22:30。

交易时间要变了。做行情App的人该先问自己一句:K线的时间轴是写死的还是算出来的?

我先拿腾讯试了一下。用 TradingView 开源的 Lightweight Charts 画日K,接口返回的数据原样喂进去:

image.png

10月14日的K线,图上写着13日,整条时间轴都早了一天。

做单一市场,这类错一两处;做A股、港股、美股一起的跨市场App,它们会一起冒出来。

图表库只负责画,画得对不对取决于你怎么喂数据。

读完你能带走:10个最常见的画错点和修法,一段约50行的喂数代码,一张上线前体检表。


一、图表库只管画,不懂行情

Lightweight Charts 免费、轻量,几十行代码就能画出专业样式的K线,很多行情App和交易工具都在用。

但它不知道你的时间戳属于哪个市场,不知道国内习惯红涨绿跌,不知道这只股票上个月除过权,也不知道A股成交量的“1”是一手。这些都得在数据进图表之前处理好,我把这一步叫“喂数层”。

下面每一条都是同一组真实数据喂两遍的对比:一遍原样喂,一遍先过喂数层。


二、首页对比图

① 腾讯10月14日的K线,标成了13日

行情接口的日K时间戳,通常标在交易所当地零点。腾讯10月14日这根是北京时间14日0点,换成 UTC 是13日16点。图表库只按 UTC 显示时间,所以标成13日。用户电脑设成北京时间也没用。

用户截图发群里一句“你们日期都错了”,比什么 bug 报告都伤口碑。

修法:日K别喂时间戳,换成当地日期字符串 '2026-10-14' 再喂。

② 三个市场放进一张图,同一天对不上

腾讯标在北京零点,苹果标在纽约零点,比特币标在 UTC 零点。放在同一条时间轴上,“10月14日”变成了三个点,腾讯那根还掉进了13日。

image.png

分钟K也一样。我取的15分钟K里,寒武纪第一根标09:45,腾讯第一根标09:30:A股标的是结束时刻,港股标的是开始时刻。两边各有一根“09:45”,管的是相邻的两段。

做联动分析、相关性、配对交易,最怕的就是这种错位。

修法:每个市场先换成各自的当地交易日,再对齐。另外得有人拍板:美股10月14日那一场在北京是14日晚上到15日凌晨,你的首页算它哪一天?


三、个股K线

③ 涨是绿的,跌是红的

图表库默认绿涨红跌,这是欧美习惯。国内用户第一眼就会看反。

修法:按用户地区设涨跌色,K线和成交量柱一起改。

④ 寒武纪除权那天,像是暴跌了36.7%

5月8日是寒武纪除权日。前一天收盘1,868元,当天收盘1,182.53元,不复权的图上断开一个大缺口。

image.png

前复权后,前一天收盘折算成1,252.68元,当天其实只跌了5.6%。不复权的话,均线、指标在这里都会失真,按涨跌幅设的提醒也会误报。

修法:看图用前复权。做回测记下复权方式,因为每次新的除权都会让前复权的历史价格整体变一次。

⑤ 汇率6.69916,图上只剩6.70

图表库默认保留2位小数,股票够用,汇率不够。汇率一天的波动常在千分位,两位小数什么都看不出来。

修法:按品种设小数位,外汇5位,股票2位,低价港股3位。


四、盘中分时

⑥ A股分时图从凌晨01:45开始

还是 UTC 显示的问题,到了分时图上更扎眼:09:45显示成01:45,15:00显示成07:00。

image.png

修法:用图表库的时间格式化选项,按交易所时区名显示:A股 Asia/Shanghai,美股 America/New_York。别写“加8小时”。

⑦ 11月以后,美股时间错一小时

很多代码里写死了“美东加12小时就是北京时间”。夏令时是对的,11月1日以后要加13小时。

image.png

写死的话,开盘、收盘、“交易中”的判断全部早一小时。港股如果真延长交易时段,所有写死“16:00收盘”的地方也得改。

修法:时区换算用时区名,夏令时交给系统处理。交易时段从接口读,别写死在代码里,交易所改时间你也不用发版。

⑧ 最后一根K线,看着像缩量六成

北京时间23:33打开苹果日K,最新一根成交量1,482万股,前一天3,898万股。其实是这根还没收盘,当天才交易了两个小时。

image.png

“放量缩量”、均线信号、价格提醒,都会被这根半成品带偏。拿它算策略信号,回测里看不出问题,实盘才出事。

修法:最后一根单独标“未完成”,颜色画浅一点;更新它用 series.update();指标和提醒只用已收盘的K线。


五、成交量和上线当天

⑨ 寒武纪的量柱,只有腾讯的1/270

10月14日,寒武纪成交量73,781,单位是手;腾讯19,933,548,单位是股。直接画在一起,寒武纪的柱子几乎看不见。乘100换成股,是738万股,约为腾讯的37%。

修法:A股成交量乘100换成股。跨市场比活跃度,用成交额更稳。

⑩ 日期跑到58709年,或者整张图白屏

这是上线当天最常见的两种“图没了”:

  • 毫秒没转秒:接口时间戳一般是毫秒,图表库要秒。毫秒直接喂进去不报错,横轴写着“58709年”,所有K线挤在一角。
  • 没按时间排序:图表库要求数据严格升序、不能重复。顺序一乱,整张图白屏,生产环境只报一句 Value is null,很难查。

顺带一个合规提醒:图表左下角那个小 logo 是开源许可要求的署名。嫌碍眼关掉的话,记得在页面别处补上 TradingView 的署名和链接。

修法:毫秒转秒、升序排序、去掉重复时间,三件事都放进喂数层。


六、上线前体检表

#用户看到的修法
①日K日期早一天日K换成当地日期字符串
②三个市场同一天对不上先换成各自交易日再对齐
③涨跌颜色反了按用户地区设颜色
④除权日像暴跌看图用前复权
⑤汇率只剩两位小数按品种设小数位
⑥分时从01:45开始按交易所时区名显示
⑦冬令时后错一小时时区名换算,时段从接口读
⑧最后一根像缩量标记未完成,不进指标
⑨A股量柱矮100倍乘100换成股,或用成交额
⑩58709年、白屏毫秒转秒、升序去重、保留署名

七、怎么做:一个喂数层

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 一个参数切换不复权、前复权、后复权;
  • 交易时段有单独的接口,按市场返回当地时段,开收盘时间不用写死。

最后

打开你们的K线页面,把光标停在最新一根上,看看日期是不是今天;再找一只最近除过权的股票,看看除权那天。两分钟就能知道,第①④条你们中了没有。

港股延时还在讨论,美股冬令时还有两周多。交易时间会变,复权因子会变,这些变化都放在喂数层里处理,别散落在每一张图里。


参考资料

  1. 21世纪经济报道,[香港证监会:将就延长交易时段发讨论文件],2026-10-10
  2. 每日经济新闻,[港交所将就延长交易时段发表讨论文件],2026-10-12
  3. TradingHours.com,[Nasdaq Trading Hours]
  4. TradingView,[Lightweight Charts™ version 5]

相关文章

谷歌开放AI自建应用:让AI写跨市场行情App要过12关

让 AI 写一个跨市场行情面板只要两分半,模拟数据下一切正常;接上真实接口,第一屏全是“数据获取失败”。我用同一个 AI 写了5份行情代码:不给资料的3份,在适用的检查项里错了27处;先让它查一遍真实接口的2份,只错1处。这是一份让 AI 写行情 App 的全景指南,12道关,附提示词模板和自检脚本。 10月13日,谷歌宣布:美国18岁以上的用户,可以用 Google AI Studio 让…

TickDB Research ·

跨市场行情应用的涨跌幅:同刻黄金−0.69%还是+0.24%

做跨市场行情App,最容易被用户截图投诉的,是价格旁边那个红绿百分比。同一刻、同一个金价,涨跌可以是−0.69%,也可以是+0.24%。本文拆解涨幅榜、价格提醒、持仓页、首页详情页4个最容易出错的场景,附一张跨市场分母清单。 做跨市场行情App,最容易被用户截图投诉的,往往是价格旁边那个红绿百分比。 2026年10月13日周二,北京时间10:56,黄金4,175.15美元。同一个价格,我按5…

TickDB Research ·

A股美股港股行情看板Demo三天、上线三个月:拆解7个返工点

同一张行情表,寒武纪的成交额自己算是5,414万元,接口给的是54.20亿元,差了100倍,原因只是一个单位。行情看板三天就能接通,上线后的返工,几乎都来自界面上说不清“自己是什么”的数字。 本文依据上交所、港交所的一手文件,加上10月12日盘中对7类资产的实测,给你四样东西: 一张“价格身份证”:每个数字上线前要答的5个问题 7个返工点逐条拆:钱在哪里算错、为什么会错 一份可以直接发给数据…

TickDB Research ·

自选股跨了A股港股美股,还在打开三个APP翻?盘中扫异动前,先定这五个字段

自选股四十只,A股港股美股都有,每天早上打开三个 APP 翻一遍。有天腾讯盘中跌了 3%,又弹回来,我在外面吃饭,完全没看到。下午再打开,只看到它跌了 2%。 这个 2% 是结果,不是过程。 问题不在我没有实时数据。问题在我看到的那个数字——涨跌幅——它只告诉我一个方向,没告诉我这个方向是怎么走出来的。 同一只股票,只看涨跌幅和看开高低收加成交额,你做出的判断可以完全相反。股票池越大,这个差…

TickDB Research ·

行情数据源怎么选?对比了 Alpaca、米筐、Wind 、vnpy等6家

同一根K线,不同数据源可能给出不同结果。 你选的不是数据源,是翻译方式。 一个让我亏过钱的问题。 回测里某个信号持续有效,实盘对不上。差异不大但一直在。排查了三周,策略逻辑、滑点、手续费、执行延迟全查过。 最后发现问题出在K线时间标签的约定上——数据源把分钟标签落在“结束”,而团队的策略逻辑假设标签是“开始”。这个处理,数据商的文档里没有写,采购合同里也没有约定。 问题不在策略,在团队从没验…

TickDB Research ·