API 参考

顶层类的公开接口:Chart、ChartWidget、ChartWidgetGrid 和 ChartGrid。

Chart

无界面渲染器。界面由你自己实现;订阅事件;以命令式方式修改状态。

构造

new Chart(host: HTMLElement, options?: ChartOptions)

数据

方法用途
setData(data)替换整个序列。
appendBar(bar)追加一根新K线;如已启用则自动滚动。
appendBars(bars)批量追加;指标只重新计算一次。
updateLastBar(bar)修改当前正在形成的K线。
updateLastBarFromTick(tick)把一笔 tick 合并进最后一根K线。
getData()读取原始 OHLC 序列。

图表类型与主题

方法用途
setChartType(type)18 种类型之一——参见 图表类型。
setTheme(name)在内置主题之间切换。
setTimeframe(tf)切换当前周期;重新连接实时数据流。

渲染器

方法用途
renderer 选项'canvas'(默认)、'webgl' 或 'auto'(仅在硬件 GPU 上用 WebGL)。
setRenderer(mode)运行时切换。返回当前实际使用的渲染器:没有 WebGL 2 时为 'canvas'。
getRenderer()'canvas' 或 'webgl'。

GPU 绘制哪些内容以及测量数字:性能 → WebGL 渲染器。

指标

方法用途
addIndicator(id, params?, position?)添加叠加指标或副图指标。返回实例 id。
updateIndicator(instanceId, params)修改一个运行中的指标。
removeIndicator(instanceId)删除并销毁。

坐标轴与缩放

价格轴(右侧区域)和时间轴(底部区域)可直接用指针操作, 手势与交易员熟悉的一致:

手势效果
上下拖动价格轴压缩 / 拉伸纵向价格范围(会关闭自动缩放)。
左右拖动时间轴放大 / 缩小时间轴。
双击价格轴重新启用自动缩放。
双击时间轴让全部数据适应视口。

时区。时间轴标签和十字光标的时间标签默认跟随浏览器本地时区; 可以在设置面板中切换为固定的 UTC 偏移(或切回本地),也可以直接调用:

chart.setTimezoneOffset(-300)  // EST (UTC-5), in minutes
chart.setTimezoneOffset(330)   // IST (UTC+5:30)
chart.setTimezoneOffset(null)  // back to browser-local

同样的效果也可以通过代码实现:

chart.setAutoScale(false)  // freeze the current price range
chart.setLogScale(true)    // switch to logarithmic price scale
chart.setInvertScale(true) // upside down (Alt+I in the widget)
chart.fitContent()         // zoom out to all data
chart.scrollToEnd()
chart.setVisibleRangePreset('3M')       // 1D 5D 1M 3M 6M YTD 1Y 5Y All
chart.goToTime(Date.UTC(2025, 0, 1))    // centre that bar (Alt+G in the widget)

价格坐标模式。除常规和对数外,坐标轴还可以以第一根可见K线为基准 重新计算标签——percentage 显示百分比涨跌,indexedTo100 把基准设为 100。常规、百分比和以 100 为基准三种模式共用同一套线性几何,只有标签不同。 可在图表设置面板中设置,也可以直接调用:

chart.setScaleMode('percentage')   // axis labels: +12.34% from first visible bar
chart.setScaleMode('indexedTo100') // first visible bar reads as 100
chart.setScaleMode('logarithmic')
chart.getScaleMode()

价格与时间格式

价格可以按你自己的方式显示,也可以像债券及其期货报价那样以一个点的分数显示 (101'16 表示 101 又 16/32)。该格式作用于价格坐标上所有显示价格的地方:坐标轴、十字光标、 最新价标签、图例、提示框、委托、提醒和画线标签;坐标轴刻度落在整分数上,roundPrice 也按整分数取整。指标窗格保留各自的数字格式。时间同样可以按你的方式显示;格式化函数会得知当前标签的类型。

new Chart(host, { priceFormat: { denominator: 32 } })                    // 101'16
chart.setPriceFormat({ denominator: 32, subDenominator: 2 })            // 101'165: 16½ 32nds
chart.setPriceFormat((p) => '$' + p.toFixed(2))
chart.setPriceFormat(null)                                              // decimals again

chart.setTimeFormatter((time, { kind, timeZone }) =>
  // kind: 'date' (a daily bar), 'day' (a new day), 'time' (within a day), 'crosshair'
  new Intl.DateTimeFormat('en-GB', { timeZone: timeZone ?? undefined, hour: '2-digit', minute: '2-digit' }).format(time))

品种对比

可以在价格坐标上显示另一个品种的涨跌幅(addCompareSymbol),也可以把它的价格放在独立坐标或独立窗格中, 或者显示图表收盘价与它的价差或比值——后几种都是指标(compareSymbol、spread), 和其他指标一样有图例、数值标签和提醒,并会保存在布局中。另一个品种的K线按时间与图表的K线对齐。 图表会请求它需要的K线;切换周期后请重新提供。

chart.addCompareSymbol('eth', 'ETHUSDT', ethBars, '#7c4dff')   // percent change, on this scale
chart.addIndicator('compareSymbol', { symbol: 'ETHUSDT' }, 'bottom', { scale: 'left' })  // own scale
chart.addIndicator('spread', { symbol: 'ETHUSDT', mode: 'ratio' })                      // own pane

chart.on('symbolSeriesRequest', async ({ payload }) =>
  chart.setSymbolSeries(payload.symbol, await adapter.fetchHistory(payload.symbol, '1h', 1000)))
chart.getRequiredSymbols()                           // what to fetch again on a new interval
chart.setPaneScale(spreadId, { percent: true })     // a pane in percent of its first value

在 ChartWidget 中,对象树的对比按钮会先询问品种,再询问对比方式:涨跌幅、独立坐标、独立副图、价差或比值。

导出数据

以 CSV 或 JSON 导出K线,每条指标线各占一列,列名与图例中的名称一致。ChartWidget 的图表右键菜单中有 导出数据 (CSV)。

chart.exportAllData('csv', 'btc-1h.csv')                 // every bar loaded
chart.exportVisibleData('json', undefined, { indicators: false })
const text = chart.getExportText('csv', { range: 'visible' })
const { bars, columns } = chart.getExportData()          // columns: { name, values }[]

成交量分布(Volume Profile)

在可见范围内按价格分组统计成交量的水平直方图。默认关闭—— 可通过代码或组件的设置面板开启:

chart.setVolumeProfileVisible(true)
chart.setVolumeProfileConfig({
  buckets: 48,        // resolution of the histogram
  widthRatio: 0.18,   // % of chart width
  opacity: 0.32,
  highlightPoC: true, // mark the highest-volume bucket
})

波段标记(枢轴点)

用小三角形标出分形波段高点 / 低点(▼ 位于已确认的枢轴高点上方,▲ 位于枢轴低点下方)。 强度参数决定两侧各需要有多少根更低的K线。可在设置面板中切换,或者:

chart.setPivotMarkersVisible(true)
chart.setPivotMarkersConfig({ left: 5, right: 5, showLabels: true })

// market-structure labels (HH / HL / LH / LL) instead of price
chart.setPivotMarkersConfig({ structureLabels: true })

// pure detection + classification are exported
import { findPivots, classifyPivots } from '@tradecanvas/core'
const pivots = findPivots(bars, 5, 5)        // [{ index, price, type }]
const structure = classifyPivots(pivots)     // adds label: 'HH'|'LH'|'HL'|'LL'

交易时段着色(常规交易时段)

把常规交易时段以外的K线(盘前 / 盘后或隔夜休市)调暗,让常规交易时段更加醒目。 默认使用美股常规交易时段(纽约时间 09:30–16:00,已考虑夏令时); 可以用“一天中的分钟数”加上市场时区来配置时间窗口。 如果某个品种的数据源提供了交易时段信息,会自动设置。

chart.setSessionShadingVisible(true)
chart.setSessionShadingConfig({
  startMinute: 9 * 60 + 30,     // 09:30
  endMinute: 16 * 60,           // 16:00 (end-exclusive; end < start wraps midnight)
  timeZone: 'America/New_York', // or tzOffsetMinutes: -300 for a fixed offset
})

延长交易时段。关闭后,品种常规交易时段(按其 timezone 计算的 SymbolInfo.sessions)以外的K线会离开图表;它们会被暂存起来,实时K线和历史分页也是如此, 重新开启后即恢复。日线及更长周期的K线保持不变。

chart.setSymbolInfo({ symbol: 'AAPL', timezone: 'America/New_York', sessions: [{ start: '09:30', end: '16:00' }] })
chart.setExtendedHours(false)     // or new Chart(host, { extendedHours: false })
chart.isExtendedHoursVisible()

前一周期高低点(PDH / PDL / PDC)

将前一日(或前一周)的最高价、最低价、收盘价以及当前周期的开盘价绘制为带标签的水平线—— 这正是日内交易者关注的支撑 / 阻力位。可在设置面板中切换,也可以直接调用:

chart.setPeriodLevelsVisible(true)
chart.setPeriodLevelsPeriod('week')   // 'day' (PDH/PDL/PDC) | 'week' (PWH/PWL/PWC)

// pure computation is exported
import { computePeriodLevels } from '@tradecanvas/core'
const levels = computePeriodLevels(bars, 'day')  // [{ id, label, price }]

市场轮廓(TPO)

一种“价格停留时间”直方图:每根K线会为其价格范围触及的每个价格档位贡献一个 TPO, 由此显示控制点(Point of Control,成交最密集的价格)和价值区(约 70% 的 TPO)。 它与成交量分布不同——按时间而非成交量加权——并固定在左侧,因此两者可以同时显示。 默认关闭;可在设置面板中切换,也可以直接调用:

chart.setMarketProfileVisible(true)
chart.setMarketProfileConfig({
  buckets: 48,
  widthRatio: 0.18,
  opacity: 0.32,
  valueAreaPct: 0.7,  // fraction of TPOs in the value area
  highlightPoC: true, // dashed line at the point of control
})

// split into one mini-profile per calendar-day session
chart.setMarketProfileConfig({ splitBySession: true })

// classic TPO letters per session (when zoomed in enough to be legible)
chart.setMarketProfileConfig({ splitBySession: true, letters: true })

// pure computation is exported too
import { computeMarketProfile, computeSessionProfiles } from '@tradecanvas/core'
const profile = computeMarketProfile(bars, priceMin, priceMax, { buckets: 48 })
const sessions = computeSessionProfiles(bars, priceMin, priceMax)  // per-day TPO

触控与移动端

手势操作
单指拖动(图表区域)平移并移动十字光标
双指捏合以中点为中心缩放
长按(约 500 ms)在K线上固定 OHLC 提示(相当于移动端的 Alt+点击)
在价格轴 / 时间轴区域内单指拖动缩放对应的坐标轴

当视口宽度小于 640 px 时,弹窗(设置、快捷键列表、命令面板、代码搜索) 会自动切换为底部抽屉样式,带拖动手柄,并适配安全区域的内边距。

测量工具

按住 Shift 在图表上拖动,即可测量两点之间的K线数 × 价差—— 浮层会显示价格 Δ(绝对值 + 百分比)、K线数量和时间跨度。 松开鼠标后浮层立即消失,不会保存到状态中。

事件

所有事件都通过 ChartEventMap 提供类型:

chart.on('orderPlace', e => /* OrderPlacePayload */)
chart.on('orderModify', e => /* OrderModifyPayload */)
chart.on('signalMarkerAdd', e => /* { marker } */)
chart.on('tradeZoneAdd', e => /* { zone } */)
chart.on('dataUpdate', e => /* { length } */)
chart.on('ordersChange', e => /* { orders } */)
chart.on('positionsChange', e => /* { positions } */)
chart.on('executionFill', e => /* { side, price, quantity, reason, pnl } */)
chart.on('chartContextMenu', e => /* { area, x, y, price, time } */)
chart.on('stateChange', () => /* 画线、指标、提醒、图表类型或主题可能已变化 */)
chart.on('paneChange', e => /* { instanceId, change: 'collapsed' | 'maximized' | 'order' } */)
chart.on('chartTypeChange', e => /* { type, previous } */)
chart.on('symbolChange', e => /* { symbol, previous } */)
chart.on('timeframeChange', e => /* { timeframe, previous } */)
chart.on('historyChange', e => /* { canUndo, canRedo } */)
chart.on('drawingSelect', e => /* { ids, primary } */)
chart.on('rendererChange', e => /* { renderer: 'canvas' | 'webgl', reason?: 'unsupported' | 'contextLost' } */)

为你自己的改动提供撤销

recordUndo 把你的一次改动放进图表的撤销历史,和画线、指标放在一起:Ctrl/Cmd+Z 调用 undo, 重做调用 redo。针对同一 subject、时间上相近的改动算作一步。ChartWidget 就是这样记录它的设置和图表类型的。

const before = panel.color
panel.color = 'red'
chart.recordUndo({ subject: 'panel-color', undo: () => (panel.color = before), redo: () => (panel.color = 'red') })

键盘与屏幕阅读器

获得焦点的图表可以用方向键滚动(按住 Shift 一次十根K线),用 ↑/↓ 或 +/− 缩放,用 Home 和 End 跳到开头和末尾。 对屏幕阅读器而言,它是一个带摘要(品种、类型、周期、最新价)的应用程序;按键移动视图后,它会说出屏幕上显示的内容, 逗号和句号键则逐根朗读K线。a11y.labels 把这些文字换成你的语言;a11y: false 则不加这部分。 scrollBars(n) 和 selectDrawing(id) 用代码完成按键和点击所做的事。

new Chart(host, { a11y: { labels: { role: 'gráfico', summary: '{what}. Último precio {close}.' } } })

来自指针的价格可以用 chart.roundPrice(price) 对齐到市场的价格刻度: 取品种 minTick 的整数倍,未设置时按品种的价格精度取整。ChartWidget 的菜单和下单窗口都会这样做。

ChartWidget

为 Chart 包上一套完整界面。同一个实例可通过 widget.chart 访问。

import { ChartWidget } from '@tradecanvas/chart/widget'

const widget = new ChartWidget(host, {
  symbol: 'BTCUSDT',
  timeframe: '5m',
  theme: 'dark',
  adapter: new BinanceAdapter(),
  historyLimit: 500,
  trading: true,
  features: { drawings: true, indicators: true },
  onReady: (chart) => { /* ... */ },
})

widget.chart.setData(...)
widget.destroy()

组件快捷键

快捷键操作
Ctrl / ⌘ + K命令面板(指标、图表类型、画线……)
Ctrl / ⌘ + P代码搜索——在已配置的品种列表中模糊查找
Ctrl / ⌘ + S保存布局(首次保存时会询问名称)
0–9输入周期(5、15m、1h、1D)后按 Enter(设置 intervalTyping: false 可关闭)
Alt + T / H / J / V / C / F趋势线、水平线、水平射线、垂直线、十字线、斐波那契回撤
?显示快捷键列表
Alt + 点击图表在鼠标所在K线上固定 OHLC 提示(同时显示与实时十字光标的差值)
Esc取消固定提示 / 取消当前画线
点击工具栏中的品种打开代码搜索弹窗
点击工具栏中的播放按钮打开K线回放进度条(播放 / 单步 / 跳转 / 速度)

运行时可用 widget.setSymbols(['BTCUSDT', 'ETHUSDT', …]) 更新可搜索的品种列表。

数据窗口

一个浮动的读数面板,显示鼠标所在K线的精确 O/H/L/C/V、K线涨跌,以及每个已添加指标的数值—— 移动十字光标时实时更新。可从命令面板中切换(Ctrl/⌘ K → “显示/隐藏数据窗口”)。

可分享视图(深层链接)

把整个视图——品种、周期、图表类型、价格坐标、指标(含参数)和画线——编码为一个紧凑的、 可安全放入 URL 的字符串,用于深层链接。设置 shareUrl: true 后, 组件会在加载时恢复 #tcw=… 哈希,命令面板中的“分享视图”操作 会把链接复制到剪贴板。

const widget = new ChartWidget(host, { shareUrl: true })

const token = widget.exportState()      // portable string
await widget.importState(token)         // restore a view
await widget.copyShareLink()            // copy "<url>#tcw=<token>"

命名布局

工具栏上的布局按钮会以一个名称保存图表:品种、周期、价格坐标、图表类型、指标、画线和提醒 (不包括主题,主题由查看者自己决定)。可从该按钮的菜单中打开、重命名和删除布局; 当前打开的布局会在变化时自动保存,按 Ctrl/⌘ S 也可以保存。 除非你提供一个 storage,布局都保存在当前浏览器的 localStorage 中。 storage 由四个方法组成,每个方法都可以返回 promise。

import { ChartWidget, type LayoutStorage } from '@tradecanvas/chart/widget'

const server: LayoutStorage = {
  list: () => api.get('/layouts'),              // [{ id, name, symbol, timeframe, updatedAt }]
  load: (id) => api.get(`/layouts/${id}`),     // { ...summary, content } 或 null
  save: (layout) => api.put(`/layouts/${layout.id}`, layout),
  remove: (id) => api.delete(`/layouts/${id}`),
}

const widget = new ChartWidget(host, {
  layouts: { storage: server, autoSave: true, openLast: true },  // 设为 false 则不启用
})

const layouts = widget.getLayoutSession()!
await layouts.saveAs('Swing BTC')
await layouts.open(id)
layouts.current()          // { id, name, … } 或 null
layouts.setAutoSave(false)

// 只取内容本身,可以存放在任何地方
const json = widget.getLayoutContent()
await widget.applyLayoutContent(json)

内置两种存储:localStorageLayouts(prefix) 和 memoryLayouts()。 读取已保存的内容时会做防御性校验:无法解析的布局会被拒绝,而不会只应用一半。 每个布局都会记录自己的 kind('chart' 或 'grid'), 因此图表组件和网格可以共用一个存储,各自只列出自己的布局。保存、打开和自动保存会逐个依次执行, 因此保存绝不会写入在它之后打开的布局。

按品种保存的布局

另外,还可以自动把每个品种的指标组合、画线、提醒和图表类型 持久化到 localStorage:

new ChartWidget(host, {
  symbol: 'BTCUSDT',
  symbols: ['BTCUSDT', 'ETHUSDT', 'SOLUSDT'],
  adapter: new BinanceAdapter(),
  persistLayouts: true,  // or { keyPrefix: 'myapp:', debounceMs: 2000 }
})

// Reset a single symbol's layout
widget.clearSavedLayout('BTCUSDT')

切换品种和销毁组件时都会立即写入布局,因此用户离开页面时不会丢失任何内容。

拖放导入数据

把 CSV 或 JSON 文件拖到图表上即可立即载入。默认启用—— 设置 dragDropImport: false 可关闭。解析器支持常见的列布局 (time, open, high, low, close, volume)、 ISO 8601 时间戳以及 Unix 秒 / 毫秒。

// Programmatic use
import { parseOHLCV } from '@tradecanvas/chart'

const { data, rowCount, skipped } = parseOHLCV(csvText)
chart.setData(data)

周期重采样

用 widget.setData() 传入你最细粒度的序列,工具栏的周期按钮会在客户端 进行聚合——一份数据驱动所有周期,无需重新请求。只要没有连接实时适配器就会生效; 设置 resampleTimeframes: false 可关闭。周线默认以周一为起点 (weekStartsOn: 0 表示周日)。

const widget = new ChartWidget(host, {
  symbol: 'BTCUSDT',
  timeframe: '1h',
  timeframes: ['5m', '15m', '1h', '4h', '1d', '1w'],
})
widget.setData(oneMinuteBars)   // base series; clicking 4h/1d/1w resamples it

// Or use the pure function directly
import { resampleOHLCV, inferTimeframeMs } from '@tradecanvas/chart'

const hourly = resampleOHLCV(oneMinuteBars, '1h')   // OHLC merged, volume summed
const fourHour = resampleOHLCV(oneMinuteBars, '4h', { weekStartsOn: 1 })

分组遵循日历规则:日内和日线周期对齐 UTC 纪元边界,周线对齐所配置的一周起始日, 月 / 季度 / 年对齐日历边界。输入的K线永远不会被修改。

自选列表

右侧面板中有多个品种列表,每一行显示最新价、涨跌幅 % 和走势图。列表可在面板菜单中切换、新建、重命名和删除; 品种可从代码搜索(+)添加,可移除,也可通过拖动或 Alt+↑/↓ 重新排序。各行的报价来自适配器的 subscribeQuotes、你自己的报价源,或你推送的数据。

const widget = new ChartWidget(host, {
  symbol: 'BTCUSDT',
  adapter: new BinanceAdapter(),               // its quotes fill the rows
  watchlist: {
    lists: [
      { id: 'majors', name: 'Majors', symbols: ['BTCUSDT', 'ETHUSDT'] },
      { id: 'alts', name: 'Alts', symbols: ['SOLUSDT', 'ADAUSDT'] },
    ],
    persist: true,                             // kept in this browser
    onChange: (lists, active) => save(lists),  // or keep them yourself
  },
})

widget.addToWatchlist('BNBUSDT', 'alts')
widget.setActiveWatchlist('alts')
widget.setQuotes([{ symbol: 'AAPL', last: 190.2, prevClose: 188.1 }])   // from your own feed
widget.getQuote('AAPL')

watchlist: true 仍和以前一样显示一个由 symbols 组成的列表;setWatchlistEntry 仍可推送某一行的价格、涨跌和走势图。

品种信息

从工具栏的 ⓘ 按钮(或命令面板)打开的面板:品种的名称、最新价和涨跌、市场状态及距下次开盘或收盘的倒计时、 当日开盘价、波动区间、成交量、昨收、买价和卖价、最小变动价位、货币、时区和交易时段,以及新闻。市场休市时,状态栏会显示出来。

new ChartWidget(host, {
  symbol: 'AAPL',
  news: (symbol, limit) => api.headlines(symbol, limit),   // or the adapter's fetchNews
})
widget.toggleSymbolInfo(true)

导航、撤销与文字方向

图表上的按钮——缩小和放大、向前和向后滚动(按住会持续滚动)、重置——在鼠标位于图表上时显示 (navigation: false 可去掉它们)。设置的改动和图表类型的切换可以用 Ctrl/Cmd+Z 撤销,与画线和指标一样。 在阿拉伯语、希伯来语、波斯语或乌尔都语下,组件会左右镜像(dir: 'auto';或 'rtl' / 'ltr'); 图表中的时间仍从左向右推进。

收藏的画线工具

把常用的画线工具固定到侧边栏顶部的收藏栏。 右键点击任意工具(在分组弹出菜单或收藏栏中)即可固定或取消固定; 该设置会持久化到 localStorage。可用 drawingFavorites 预设初始收藏:

new ChartWidget(host, {
  drawingFavorites: ['trendLine', 'horizontalLine', 'fibRetracement', 'rectangle'],
})

画线样式与模板

画线侧边栏上的调色板按钮会打开样式浮层——为下一条画线(以及当前选中的画线)选择颜色、 线宽和线型,还可以保存带名称的模板到 localStorage,一键复用。 对应的代码调用:

chart.setDrawingStyle({ color: '#e8505b', lineWidth: 2, lineStyle: 'dashed' })
chart.getDrawingStyle()
chart.setSelectedDrawingStyle({ color: '#1fa874' })  // restyle the selected drawing

对象树

工具栏中的图层按钮会打开对象树面板,列出所有已添加的指标和画线。 指标可以删除;每条画线都可以单独显示 / 隐藏、锁定 / 解锁、设置和删除, 分组则连同其中的画线一起列出。 默认启用——设置 objectTree: false 可关闭。画线控制对应以下调用:

chart.getDrawings()                 // DrawingState[] (id, type, visible, locked)
chart.setDrawingVisible(id, false)  // hide a single drawing
chart.setDrawingLocked(id, true)    // lock it from edits
chart.removeDrawing(id)
chart.groupDrawings(ids, 'Weekly levels')  // 一起隐藏、锁定和选中
chart.renameDrawingGroup(groupId, 'Old highs')
chart.getActiveIndicators()         // active indicator instances
chart.updateIndicator(instanceId, { period: 50 })  // re-tune params live
chart.removeIndicator(instanceId)

画线设置与菜单

双击一条画线,或在对象树中点击它的齿轮按钮,即可打开它的设置:样式、工具自身的设置 (斐波那契级别、延伸、标签……)以及按图表时区显示的各个点。右键点击画线可打开它的菜单: 设置、基于其线条的提醒、顺序、分组、锁定、隐藏、创建副本和删除。侧边栏里还有橡皮擦、 缩放工具,以及可关闭、弱、强三档的磁吸。底层 API 参见 画线工具。

每行指标上的齿轮按钮会打开设置对话框,它会读取指标的参数 (数值、开关、颜色),并通过 updateIndicator 实时应用修改—— 修改周期或颜色时无需先删除再重新添加。

对象树中的对比区域可以把其他品种叠加为归一化的折线。 连接实时适配器时,点击 + 按钮会打开品种选择器,通过 adapter.fetchHistory 获取该品种的历史数据,并以百分比模式添加(这样价格量级不同的品种可以共用一个坐标轴)。 切换周期时,对比数据会自动重新获取。对应的代码调用:

widget.addCompareSymbol('ETHUSDT')   // fetches + overlays (needs an adapter)

// or drive the chart directly with your own data
chart.addCompareSymbol('ETHUSDT', 'ETH', ethBars, '#627eea')
chart.setCompareMode('percent')      // 'percent' | 'absolute'
chart.removeCompareSymbol('ETHUSDT')

价格提醒

除了价格水平,提醒还可以把一条线与另一条线比较(价格穿越移动平均线、MACD 穿越其信号线), 在若干根K线内(2 到 500 根)涨跌达到一定百分比时触发,只看已收盘的K线(影线刺破后又收回不会触发), 以及到期失效。组件的提醒面板支持以上全部功能;在代码中,它们是 addAlert 的最后一个参数。 每次有新价格(来自 setCurrentPrice 或已连接的数据源)时,提醒都会与其监控的线一起检查:

const ema = chart.addIndicator('ema', { period: 50 })
const rsi = chart.addIndicator('rsi')
chart.addAlert(NaN, 'crossingUp', 'above the 50 EMA', 'price', undefined, { target: `${ema}:value` })
chart.addAlert(NaN, 'movesUp', 'pump', 'price', undefined, { percent: 5, bars: 12 })
chart.addAlert(70, 'greaterThan', 'RSI closed above 70', `${rsi}:value`, 'RSI', { onBarClose: true })
chart.addAlert(64_000, 'crossing', 'today only', 'price', undefined, { expiresAt: Date.now() + 86_400_000 })

chart.on('alertExpired', (e) => e.payload)   // it reached its time without firing
chart.on('alertTriggered', (e) => e.payload)  // { id, condition, channel, target?, percent?, bars?, … }

工具栏中的铃铛按钮会打开浮动面板,用于添加、查看和删除价格提醒; 提醒触发时会弹出通知。提醒线也可以拖动—— 在图表上抓住它并滑动即可修改价格(移动提醒会重新激活它)。默认启用—— 设置 alerts: false 可关闭。也可以通过 Chart API 和带类型的提醒事件以代码方式控制:

// Add from code (condition: 'crossing' | 'crossingUp' | 'crossingDown'
//                          | 'greaterThan' | 'lessThan')
const id = chart.addAlert(64200, 'crossingUp', 'breakout')
chart.removeAlert(id)
chart.getAlerts()      // PriceAlert[]
chart.saveAlerts('tcw:alerts:BTCUSDT')   // localStorage persistence
chart.loadAlerts('tcw:alerts:BTCUSDT')

// React to triggers
chart.on('alertTriggered', (e) => {
  console.log('hit', e.payload.price, e.payload.message)
})
// also: 'alertAdd' / 'alertRemove' / 'alertUpdate' (fired on drag)

// Indicator alerts: bind to an indicator line via channel '<instanceId>:<key>'.
// In the widget, the alerts panel's source dropdown lists every active line.
const ema = chart.addIndicator('rsi')
chart.addAlert(70, 'crossingUp', 'RSI overbought', `${ema}:rsi`, 'RSI')

提醒触发时可选择播放声音和 / 或发送桌面通知(两者默认都关闭)。 sound: true 播放内置提示音;也可以传入 URL 使用自定义声音。 desktop: true 使用 Notification API,首次使用时会请求权限。

new ChartWidget(host, {
  alertNotifications: { sound: true, desktop: true },
})

自定义按钮和菜单项

向工具栏添加按钮(内置图标或你自己的元素、文字、开关),并向图表的右键菜单添加菜单项, 它们会排在组件自带的项目之后。

const news = widget.addToolbarButton({
  id: 'news',
  label: 'News',
  icon: 'bell',            // 或一个 <svg> 元素;或 text: 'News'
  side: 'right',           // 'left' 会与图表控件放在一起
  toggle: true,
  onClick: () => news?.setActive(togglePanel()),
})
news?.setText('3')
news?.remove()

new ChartWidget(host, {
  chartMenuItems: ({ area, price, time }) => area === 'plot' && price !== undefined
    ? [{ label: `Copy ${price.toFixed(2)}`, icon: 'check', onSelect: () => copy(price) }]
    : [],
})

ChartWidgetGrid

多个图表组件并排显示,每个都有自己的品种、周期、指标和画线。上方的工具条用于选择排列方式、 联动各个图表,并把整个网格保存为一个命名布局。最后点击的图表就是当前图表(带边框)。

import { ChartWidgetGrid } from '@tradecanvas/chart/widget'

const grid = new ChartWidgetGrid(host, {
  layout: '2x2',                                   // '1x1' '1x2' '2x1' '2x2' '1x3' '3x1' '2x3' '3x2'
  widget: { timeframe: '1h' },                    // 作用于每个图表
  adapter: () => new BinanceAdapter(),            // 每个图表一个:一个适配器只维持一条数据流
  cells: [{ symbol: 'BTCUSDT' }, { symbol: 'ETHUSDT' }, { symbol: 'SOLUSDT' }, { symbol: 'BNBUSDT' }],
  sync: { crosshair: true, time: false, symbol: false, interval: false, drawings: false, replay: false },
})

grid.setLayout('1x2')
grid.setSync({ interval: true })   // 让其他图表与当前图表保持一致
grid.getActiveWidget().getChart()
grid.getLayoutSession()?.saveAs('Majors')

// 每个图表创建时(初始时,以及网格变大时)
new ChartWidgetGrid(host, {
  onChartAdd: (widget, index) => widget.getChart().addIndicator('ema', { period: 21 }),
})

十字光标同步会在每个图表上显示指针所在的时间;时间同步会让其他图表随正在操作的图表一起滚动和缩放; 画线会复制到显示同一品种的图表上(开启时会把这些图表的画线合并到一起,不会丢失任何画线); 回放同步会把其他图表回放到与正在回放的图表相同的时间(K线比回放步长更长的图表会显示包含该时间的整根K线)。 网格缩小时被移除的图表会被收起并保留在已保存的布局中,网格再次变大时会原样恢复; 全新的图表在品种和周期处于同步状态时,会使用当前图表的品种和周期打开。

ChartGrid

由无界面图表(不带工具栏)组成的同步联动多图表布局;如需完整界面,请参见 ChartWidgetGrid。

import { ChartGrid } from '@tradecanvas/chart'

const grid = new ChartGrid(host, { layout: '2x2', theme: 'dark' })
// 每个图表一个适配器:一个适配器只维持一条数据流
await grid.connectAll(() => new BinanceAdapter(), ['BTCUSDT','ETHUSDT','SOLUSDT','BNBUSDT'], '5m')
grid.setLayout('1x2')

布局:'1x1'、'1x2'、'2x1'、'2x2'、'1x3'、'3x1'、'2x3'、'3x2'。