Skip to content

周期与回放 ​

只需给图表推送一路数据:逐笔成交,或某个基础周期(如 1 分钟)的 K 线。图表会按你所在市场的交易日历,由它合成所有更大的周期。行情回放也基于同一份源数据。

声明源数据 ​

调用 set() 时说明这些 K 线的周期:

ts
chart.data.set(minuteBars, { interval: '1m' });

不传 interval 时,K 线按原样显示,下文的功能都不生效。传入之后,chart.data 的写入操作针对源数据,读取操作(at()、last()、toArray()、length)返回的是显示的 K 线。

周期写成「数量 + 单位」:15s、1m、5m、1h、4h、1D、2D、1W、1M、3M(m 是分钟,M 是月),也可以写成对象,如 { unit: 'hour', count: 4 }。

推送逐笔成交 ​

ts
socket.onmessage = (message) => {
  const { time, price, size } = JSON.parse(message.data);
  chart.data.addTrades({ time, price, size });
};

落在最后一根源 K 线周期内的成交会更新它的最高价、最低价、收盘价和成交量;更晚的成交会开启一根新 K 线。属于更早周期的成交会抛出错误;被交易日历丢弃的成交(交易时段之外)会被跳过,addTrades() 返回实际合入的笔数。也可以从空数据开始:chart.data.set([], { interval: '1s' });逐笔成交和整根 K 线的 update() 可以混用。

切换周期 ​

ts
chart.data.setTimeframe('15m');
chart.data.setTimeframe(null); // 回到源周期

切换周期时原地重新聚合源数据:不需要重新请求,指标、预警和对比序列都会跟着变化。显示周期必须由整数根源 K 线组成:

源周期可合成的周期
1m2m、5m、15m、1h、4h、1D、1W、1M…
1h2h、4h、1D、1W、1M…
1D2D、1W、1M、3M…
1M3M、6M、12M

由 1m 合成 90s、由 7m 合成 1D(一天不能被 7 分钟整除)、由 1W 合成 1M 都会抛出错误。没有声明源周期时调用 setTimeframe() 也会抛出错误。

合成后的 K 线取第一根源 K 线的开盘价、最高的最高价、最低的最低价、最后一根的收盘价和成交量之和。它的时间是所在周期的起点:日内 K 线是所在时间段的起点,日线、周线、月线是交易日当地的零点。

用户往回滚动时,loadMore 给出的是第一根源 K 线的时间,所以可以继续按基础周期请求数据:

ts
chart.on('loadMore', async ({ before }) => {
  chart.data.prepend(await api.minuteBars({ end: before, limit: 2000 }));
});

交易日历 ​

calendar 配置项决定每个时间属于哪个周期:

ts
createChart(element, {
  timeZone: 'Asia/Shanghai', // 时间的显示时区
  calendar: {
    timeZone: 'America/New_York', // 市场所在时区,默认与 timeZone 相同
    sessions: ['09:30-16:00'],
    weekdays: [1, 2, 3, 4, 5]
  }
});
选项默认值含义
timeZonenull日历所在时区;null 时使用图表的 timeZone
sessions[]'HH:MM-HH:MM' 格式的交易时段,时段外的数据会被丢弃;为空表示全天交易
dayStart'00:00'交易日的开始时间;从这个时间起属于下一个日期
weekdays全部交易日的星期(0 = 周日);其他日期顺延到下一个交易日
weekStart1周线从星期几开始
bucketnull自定义周期归属的钩子,见下文

日内 K 线不会跨越交易时段:它从时段的起点开始计算,所以在 ['09:30-11:30', '13:00-15:00'] 上,60 分钟 K 线的起点是 09:30、10:30、13:00 和 14:00。结束时间早于开始时间表示跨越午夜(如 '21:00-02:30')。夏令时切换会被正确处理。

示例 ​

ts
// 加密货币:全天交易,4 小时线按 UTC 对齐。
const crypto = { timeZone: 'UTC' };

// A 股:有午休。
const aShares = {
  timeZone: 'Asia/Shanghai',
  sessions: ['09:30-11:30', '13:00-15:00'],
  weekdays: [1, 2, 3, 4, 5]
};

// 国内期货:夜盘归属下一个交易日,周五夜盘归属下周一。
const chinaFutures = {
  timeZone: 'Asia/Shanghai',
  sessions: ['21:00-02:30', '09:00-10:15', '10:30-11:30', '13:30-15:00'],
  dayStart: '21:00',
  weekdays: [1, 2, 3, 4, 5]
};

// 美股:只保留常规交易时段,盘前盘后的成交会被丢弃。
const usEquities = {
  timeZone: 'America/New_York',
  sessions: ['09:30-16:00'],
  weekdays: [1, 2, 3, 4, 5]
};

// 外汇:以纽约时间 17:00 换日。
const fx = { timeZone: 'America/New_York', dayStart: '17:00', weekdays: [1, 2, 3, 4, 5] };

createChart(element, { calendar: chinaFutures });

节假日与特殊规则 ​

bucket 接收时间、周期和 fallback(同一时间的内置计算结果)。返回周期的起点,或者返回 null 丢弃这个时间。时间往后走时,周期不能往回退。

ts
const holidays = new Set(['2024-10-01', '2024-10-02', '2024-10-03']);

chart.applyOptions({
  calendar: {
    bucket: (time, interval, fallback) => {
      const date = new Date(time + 8 * 3_600_000).toISOString().slice(0, 10);
      return holidays.has(date) ? null : fallback(time);
    }
  }
});

修改交易日历后,数据会重新聚合。

对齐你自己的数据 ​

createCalendar() 返回的就是图表内部使用的日历,可以用来处理服务端返回的数据、导出数据或自己做聚合:

ts
import { createCalendar } from '@kumochart-sdk/core';

const calendar = createCalendar({
  timeZone: 'Asia/Shanghai',
  sessions: ['09:30-11:30', '13:00-15:00']
});
calendar.bucket('2024-05-06T10:15:00+08:00', '1h'); // 上海时间 09:30,以毫秒时间戳表示
calendar.bucket('2024-05-06T12:00:00+08:00', '1h'); // null:午休

K 线类型 ​

K 线变换会用周期本身的 K 线生成显示的 K 线:平均 K 线、砖形图或等幅 K 线。它位于周期之后,所以 5 分钟周期上的砖形图是用 5 分钟收盘价生成的;切换数据或周期时变换保持不变。这三种都是扩展功能,需要先注册一次,可以全部注册,也可以逐个注册。

ts
import { registerExtras } from '@kumochart-sdk/core';

registerExtras(chart);
chart.data.setTransform({ type: 'heikin-ashi' });
chart.data.setTransform({ type: 'renko', params: { box: 50 } }); // 砖块大小 50
chart.data.setTransform({ type: 'range', params: { range: 120 } }); // 每根高低差 120
chart.data.setTransform({ type: 'renko' }); // 砖块大小取平均真实波幅
chart.data.setTransform(null); // 恢复为周期本身的 K 线
类型参数显示的 K 线
heikin-ashi每根 K 线对应一根平滑后的蜡烛:收盘价取 OHLC 的平均值,开盘价取上一根实体的中点
renkobox基于收盘价、对齐到砖块整数倍网格的砖块;反转需要两个砖块
rangerange高低差恰好为 range 的 K 线;上涨的 K 线按开、低、高、收的顺序走完价格路径
  • 不指定大小时,renko 和 range 使用最近 14 根 K 线的平均真实波幅,保留两位有效数字。推送新数据时大小保持不变,K 线重建时(新数据、切换周期或开始回放)重新计算。
  • 指标、预警、标记和回放都基于变换后的 K 线。每个 tick 只重跑周期中的最后一根 K 线,所以实时推送仍是增量的。
  • 砖块的时间取它开始形成的时间;在同一根 K 线中开始的砖块依次相隔 1 毫秒。砖形图和等幅 K 线不代表时间段,所以 K 线倒计时会隐藏。
  • K 线如何绘制仍由主序列类型决定:蜡烛图适合所有变换,用折线画砖形图的收盘价也可以。

自定义变换 ​

变换是一个流:按顺序接收周期的 K 线,返回每根 K 线完成的输出,并报告仍在形成中的那一根。clone() 让图表在每个 tick 只重跑最新的一根。

ts
import type { Bar, BarTransformStream } from '@kumochart-sdk/core';

/** 每成交 `volume` 生成一根 K 线。 */
class VolumeBars implements BarTransformStream {
  constructor(
    private readonly volume: number,
    private forming: Bar | null = null
  ) {}

  next(bar: Bar): Bar[] {
    const merged = this.forming
      ? {
          ...this.forming,
          high: Math.max(this.forming.high, bar.high),
          low: Math.min(this.forming.low, bar.low),
          close: bar.close,
          volume: this.forming.volume + bar.volume
        }
      : bar;
    this.forming = merged.volume >= this.volume ? null : merged;
    return this.forming ? [] : [merged];
  }

  current(): Bar | null {
    return this.forming;
  }

  clone(): BarTransformStream {
    return new VolumeBars(this.volume, this.forming);
  }
}

chart.data.registerTransform<{ volume: number }>('volume', {
  name: 'Volume bars',
  keepsTime: false,
  create: ({ volume }) => new VolumeBars(volume)
});
chart.data.setTransform({ type: 'volume', params: { volume: 5000 } });

create(params, input) 负责校验参数,也可以读取即将收到的 K 线(input),用来计算自动大小之类的统计量。如果每根输入都恰好变成一根时间相同的输出(例如平均 K 线),请设置 keepsTime,图表会保留 K 线倒计时。输出的时间不能递减,所有数值都必须是有限数。

行情回放 ​

回放会隐藏时间游标之后的源数据。所有功能都只能看到已回放的部分:显示周期、指标、预警、对比序列和标记。在更大的周期上,正在形成的那根 K 线由已回放的源 K 线拼出,所以在小时图上回放分钟数据,可以看到每根小时 K 线逐步成形。

ts
chart.replay.start('2024-05-06T10:00:00+08:00', { speed: 2, step: 'bar' });
chart.replay.play();

chart.replay.pause();
chart.replay.step(); // 前进一根 K 线
chart.replay.configure({ step: 'source' });
chart.replay.step(5); // 前进五根源 K 线
chart.replay.seek('2024-05-07T09:30:00+08:00'); // 可前进也可后退

chart.replay.stop(); // 重新显示全部数据
  • step: 'bar' 会补完正在形成的显示 K 线,或显示下一根;step: 'source' 每次放出一根源 K 线。
  • speed 是播放时每秒的步数。
  • state() 返回 { time, playing, ended, speed, step };显示全部数据时返回 null。
  • 回放期间实时数据照常接收(update()、addTrades()),直到游标走到才显示。切换周期不会改变游标。
  • 新放出的 K 线会照常触发预警,alertTrigger 事件中带有 replay: true。
  • clear() 会结束回放。

播放过程会报告进度;和其他 API 调用一样,上面这些方法本身不会触发事件:

ts
chart.on('replay', ({ state, reason }) => {
  progress.value = state.time;

  if (reason === 'end') {
    showSummary();
  }
});

常见的回放界面会让用户点击选择起始 K 线:

ts
chart.on('click', ({ time }) => {
  if (pickingStart && time !== null) {
    chart.replay.start(time);
    chart.replay.step(); // 补完被点击的那根 K 线
    pickingStart = false;
  }
});

Vue 与 React ​

两个组件都通过属性接收源周期、显示周期和 K 线变换:

vue
<TradingChart
  :data="minuteBars"
  interval="1m"
  :timeframe="timeframe"
  :configure="registerExtras"
  :transform="{ type: 'renko' }"
/>
tsx
<TradingChart
  data={minuteBars}
  interval="1m"
  timeframe={timeframe}
  configure={registerExtras}
  transform={{ type: 'renko' }}
/>

再次传入相同的变换(例如直接写在模板里的对象)不会重建 K 线。configure 会在应用变换之前注册扩展功能。

推送逐笔成交和控制回放请通过图表实例(@ready / onReady 或 ref);replay 事件和其他事件一样会被转发。

基于 MIT 许可证发布。