微信小程序K线图实战:HQChart源码拆解与二次开发指南 简介面向微信小程序开发者的股票图表实现资料以HQChart-master为核心覆盖沪深与港股K线图、走势图绘制以及通达信技术指标语法解析适合需要在小程序中快速集成行情模块、进行二次开发的工程师。资源包共72个文件js脚本主导图表绘制与指标公式编译wxml、wxss、json配置小程序页面html与png用于浏览器端效果调试和界面参考整体仅1.14MB结构轻量。目前已有1081人学习使用可作为个人练习或团队选型的参考。压缩包内包含HQChart-master完整源码、小程序行情模块用例、知临行情图API说明文档、分钟线与日K线示例以及通达信公式编译器相关实现。借助这套代码开发者可以系统地掌握行情接口对接、K线渲染、手势缩放、自定义指标接入等关键环节较快完成一个可运行的微信小程序股票图表功能。1. 微信小程序里的K线图HQChart到底解决什么问题先从一个场景说起。接到一个小程序行情页的需求要同时展示沪深A股和港股的分时走势、日K/周K还支持指标公式切换。第一反应是自己用Canvas画K线做过的人都知道画出来容易画得顺手是另一回事。横坐标怎么压缩、十字光标怎么跟手、拖动缩放手感还有“通达信语法”里的MA(CLOSE,5)这类公式怎么解析每一样都能磨掉一整天。HQChart正是把这条路走通了的图表组件它专门给微信小程序场景提供沪深/港股K线图、走势图渲染能力并把通达信公式语法内置成了指标引擎。这份源码包拆开来看适合两类人一类是赶进度做行情小程序的开发者想直接复用一套能用的图表另一类是研究技术指标、想把通达信公式搬进自己业务的程序。下面我按拆包视角从工程结构到核心绘制逻辑再到真机调试的坑一层层过一遍。2. 项目架构与目录结构图表库怎样在小程序里跑起来一个图表库在小程序里落地和传统H5里调一个npm包是两回事。小程序没有DOM只有Canvas而且组件脚本运行在逻辑层渲染在视图层跨层通信有开销。HQChart的定位不是一个小函数而是一套自带数据适配和Canvas渲染层的方案。这一章先从技术选型讲起再从源码包目录拆出模块边界。2.1 技术选型为什么是微信小程序、Canvas 2D、原生JS常见做法是用纯JavaScript实现图表核心不做任何框架依赖小程序端用自定义组件包裹画布。这样最大的好处是一套核心代码可以复用在H5、小程序以及未来的uni-app/Web环境。HQChart走的就是这个路线核心逻辑全部放在JS里小程序的wxml只保留一个canvas节点数据交互通过组件的事件通道完成。具体到渲染方案微信小程序支持两种Canvas旧版wx.createCanvasContext新版canvas type2d配合createSelectorQuery获取节点后新建Canvas对象。HQChart在小程序里通常用后者因为获取到的Canvas对象更接近浏览器API绘图性能更好也方便做像素比适配。选择原生JS还有一个现实原因通达信公式解析需要操作字符串、构造AST、递归计算如果套一层小程序runtime的框架反而容易踩到兼容坑。源码包里的核心模块几乎不引用任何第三方包这在依赖管理上也是一件省心事。2.2 源码包目录结构从根目录认识模块边界解压HQChart-master.zip之后目录大致可以分成这样几个区域核心JS库、小程序组件层、示例页面、文档与构建配置。我以一份典型工程为例把关键文件列出来HQChart-master/ ├── src/ # 核心图表引擎纯JS无DOM依赖 │ ├── index.js # 统一入口导出图表类 │ ├── chart/ │ │ ├── kline.js # K线图绘制与主控逻辑 │ │ ├── minute.js # 分时走势图逻辑 │ │ ├── coordinate.js # 坐标换算与轴刻度 │ │ └── gesture.js # 触摸手势处理 │ ├── indicator/ │ │ ├── parser.js # 通达信公式字符串解析 │ │ ├── ast.js # 指标语法树结构定义 │ │ └── engine.js # 指标计算引擎按周期递归求值 │ └── utility/ │ ├── format.js # 数字/日期格式化 │ └── request.js # 行情数据请求适配 ├── wx/ │ └── components/ │ └── hqchart/ │ ├── hqchart.js # 小程序自定义组件逻辑层 │ ├── hqchart.wxml # Canvas节点与占位 │ ├── hqchart.wxss # 组件样式 │ └── hqchart.json # 组件配置 ├── pages/ │ └── demo/ │ ├── index.js # 调用示例 │ └── index.wxml ├── package.json └── README.md这个结构最值得学习的不是文件多少而是边界。src/chart管“怎么画”src/indicator管“算什么”wx/components管“怎么接”。如果你要二次开发改指标加公式基本不用碰绘制代码要换数据接口也只需要动utility/request.js和组件的数据适配层。我在自己项目里最常改的就是indicator/下的计算逻辑后面第6章会展开讲。2.3 数据流设计行情数据从哪来、如何渲染整个组件的数据流可以概括为一句话行情数据源 - 统一数据格式 - 图表数据源 - 坐标映射 - Canvas绘制前端拿到接口返回的原始数据通常是一堆历史K线数组字段可能叫c、h、l、o、v也可能叫close、high、low、open、volume。HQChart组件内部不会假设字段名而是由调用方在传入前做一次归一化数组里每一项至少包含open、high、low、close、volume、time这六个字段。常见的归一化代码长这样// 数据适配层把接口返回的数组转成图表需要的格式 function normalizeKline(list) { return list.map((item) { return { open: item.open || item.o, // 开盘价兼容简写字段 high: item.high || item.h, // 最高价 low: item.low || item.l, // 最低价 close: item.close || item.c, // 收盘价 volume: item.volume || item.v, // 成交量 time: item.time || item.date, // 时间戳一般为秒或者毫秒 }; }); }说明一下这段代码做了两层兼容行情源字段可能是英文缩写或全称时间字段可能是数字也可能是字符串。为什么强调数据归一化因为HQChart本身不负责请求数据这部分要自己对接。数据格式对了后续坐标、指标、手势才能顺。我一般会把这段适配逻辑单独放到api/dataSource.js里而不是写进页面切换行情源的时候只改一个文件。数据从网络返回后组件收到的是一个数据数组和配置项例如const chart this.selectComponent(#hqchart); chart.updateData({ data: normData, // 归一化后的K线数组 symbol: 600519, // 股票代码 market: SH, // 市场类型 period: day, // 周期日K visibleCount: 80, // 窗口显示多少根 });这里有一个容易翻车的点小程序setData有大小限制如果一次性传入上千根K线数据量可能接近1MB真机上会明显卡顿。常见做法是做一次“抽稀”先保留最近500根并把数组引用直接传过去避免深度拷贝。至于更多渲染细节下一章进核心模块看。3. 核心模块拆解K线绘制、坐标映射与通达信公式引擎看完目录很多人最关心的是核心代码长什么样。HQChart最有特色的三块坐标换算、K线实体绘制、通达信公式解析。这一章把这三个模块拆开用代码逐段讲。3.1 坐标系统价格与像素怎么换算画K线之前必须把数组里的价格映射到Canvas上的像素位置。最直接的想法是线性映射// 坐标换算价格 - 像素Y function priceToY(price, maxPrice, minPrice, plotHeight) { if (maxPrice minPrice) return plotHeight / 2; // 防除零 return (maxPrice - price) / (maxPrice - minPrice) * plotHeight; }这个函数本身不难难在边界处理。当maxPrice minPrice也就是某一段价格完全平行时分母为零结果变为NaN。常见做法是在进入绘制前先做一次极值判断function computeYRange(data, plotHeight) { let max Number.NEGATIVE_INFINITY; let min Number.POSITIVE_INFINITY; for (let i 0; i data.length; i) { const high data[i].high; const low data[i].low; if (high max) max high; if (low min) min low; } if (max min) { // 退化为一条直线给一个固定偏移避免除零 max 1; min - 1; } const padding (max - min) * 0.05; // 上下留白5%防止蜡烛贴边 return { max: max padding, min: min - padding, height: plotHeight }; }这里的padding很关键。没有留白时最高价和最低价会顶到绘图区上沿视觉上很丑十字光标的数值也容易被裁掉。另外要注意除零保护、NaN判断都要写在坐标模块里而不是写死在绘制函数里这样后面做分时图、成交量图能复用。坐标系统的另一半是时间轴。在分时图里时间轴是均匀分布的比如A股一天240分钟每个点固定位置但在日K图里日期是跳跃的周末、节假日没有数据不能直接按下标均匀排列。HQChart里常见做法是“数据下标即横坐标”不按时间戳等距每根K线占据固定宽度barWidth这样拖动和缩放都是在数据索引上平移开发上会简单很多。// 横坐标第index根K线的中心X function indexToX(index, startIndex, visibleCount, plotWidth) { const barWidth plotWidth / visibleCount; return (index - startIndex) * barWidth barWidth / 2; }参数含义startIndex是可视区最左侧的数据下标visibleCount是当前屏幕上显示的K线数量。如果要做缩放只需要修改visibleCount图形整体会以某个锚点为中心放大缩小这也是后面手势处理的基础。3.2 K线与分时绘制从蜡烛到走势图K线绘制在Canvas上是用fillRect和strokeRect组合出来的。一根K线由实体、上影线、下影线构成红色涨、绿色跌A股习惯港股也兼容这一套。核心绘制代码可以浓缩成这样function drawCandle(ctx, x, item, yRange) { const openY priceToY(item.open, yRange.max, yRange.min, yRange.height); const closeY priceToY(item.close, yRange.max, yRange.min, yRange.height); const highY priceToY(item.high, yRange.max, yRange.min, yRange.height); const lowY priceToY(item.low, yRange.max, yRange.min, yRange.height); const barWidth 6; // 半宽 const isUp item.close item.open; ctx.strokeStyle isUp ? #e74c3c : #2ecc71; // 涨红跌绿 ctx.fillStyle isUp ? #e74c3c : #2ecc71; ctx.lineWidth 1; // 影线 ctx.beginPath(); ctx.moveTo(x, highY); ctx.lineTo(x, lowY); ctx.stroke(); // 实体 const rectY Math.min(openY, closeY); const rectH Math.max(1, Math.abs(closeY - openY)); ctx.fillRect(x - barWidth, rectY, barWidth * 2, rectH); }这个函数里有几个容易被忽略的决策rectH最小为1否则开盘价等于收盘价时实体高度为0看起来很诡异影线和实体共用同一个x中心点涨跌颜色是可配置的不要写死在全局样式里分时走势图则是一张折线图外加昨收价基准线和成交量柱。走势图不需要处理实体和影线但多了一个“昨收价”的Y轴定位价格涨跌颜色是相对昨收价的。常见写法是先用ctx.beginPath()从第一点到最后一个点连起来再在下方填充半透明渐变底色// 分时图绘制先画线再画渐变填充面积 function drawMinuteLine(ctx, points, prevClose, plotHeight, plotWidth) { ctx.beginPath(); const step plotWidth / (points.length - 1); for (let i 0; i points.length; i) { const x i * step; const y priceToY(points[i].price, maxPrice(points, prevClose), minPrice(points, prevClose), plotHeight); if (i 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.strokeStyle #2196f3; ctx.lineWidth 1.5; ctx.stroke(); }行情软件的走势图一般还有一条“昨收基准线”用虚线画在昨收价对应的Y坐标上同时把当前价和涨跌幅标在右上角这些属于组件外层UI的活图表引擎只负责把基础图形画对。3.3 通达信公式引擎字符串怎么变成指标曲线接下来是HQChart最有价值的一块通达信语法解析。通达信公式常见写法是MA(CLOSE,5)、MACD(12,26,9)、REF(HIGH,1)。图表库的任务是把这种类似函数调用的算式解析成可执行的指标对象。第一步是词法分析。把字符串拆成标识符、括号、逗号、数字和运算符先把MA(CLOSE,5)变成tokens数组// 简易词法分析器只处理指标公式常用符号 function tokenize(formula) { const regex /([A-Za-z_][A-Za-z0-9_]*|\d(\.\d)?|[,()\-*/])/g; return formula.match(regex) || []; } console.log(tokenize(MA(CLOSE,5))); // 输出: [MA, (, CLOSE, ,, 5, )]词法分析通过正则匹配字母、数字、运算符。这里常见的一个坑是汉字标识符比如某些指标叫买卖点正则里没包含中文解析直接失败。补全办法是在字符类里加上\u4e00-\u9fa5或把中文当作普通连续字符串处理。第二步是语法分析。用递归下降解析tokens生成一棵AST// 定义节点结构函数调用节点 function createCallNode(funcName, args) { return { type: Call, name: funcName, args }; } // 极简递归解析器只处理NAME(ARG1,ARG2)这种形式 function parseFormula(tokens) { let pos 0; const peek () tokens[pos]; function parseExpression() { const token peek(); if (token () { pos; const result parseExpression(); pos; return result; } if (!isNaN(parseFloat(token))) { pos; return { type: Number, value: parseFloat(token) }; } pos; if (peek() () { pos; const args []; while (peek() ! )) { args.push(parseExpression()); if (peek() ,) pos; } pos; return createCallNode(token, args); } return { type: Var, name: token }; } return parseExpression(); }这段代码省略了运算符优先级只保留函数嵌套但已经能覆盖绝大多数技术指标公式。完整的引擎还需要支持 - * /、比较运算符、条件判断IF等本质上是把递归下降解析器写得更完整。第三步是计算。AST建好后每个节点对应一个求值函数。计算指标时我们拿着K线收盘价数组对每个位置递归求值即可// 指标引擎递归计算AST节点 function evaluate(ast, context) { switch (ast.type) { case Number: return ast.value; case Var: return context.get(ast.name); // 返回一个数据序列 case Call: { if (ast.name MA) { const data evaluate(ast.args[0], context); // close序列 const n evaluate(ast.args[1], context); // 周期 return movingAverage(data, n); } // 其他函数类似处理 } } } function movingAverage(values, n) { const len values.length; const out new Array(len).fill(0); let sum 0; for (let i 0; i len; i) { sum values[i]; if (i n) sum - values[i - n]; out[i] sum / Math.min(i 1, n); } return out; }这里有个细节需要留意evaluate的返回值不一定是单个数字也可能是一个数组比如CLOSE返回全部收盘价序列。指标引擎内部要把“标量”和“序列”区分开很多二次开发翻车都翻在把一个数组当数字用。HQChart的做法是通过context.get(name)统一返回序列然后在函数实现里用滑动窗口计算而不是逐K线取值。通达信公式引擎的完整实现还包含周期切换、指标参数绑定、多指标叠加等但核心就是“词法-语法-求值”三步。如果你后续要把这套语法搬到自己的服务端做选股回测这套思路可以直接复用。4. 落地实战把沪深/港股行情页跑起来前面讲的是原理这一章落到实际从零把HQChart组件接进一个微信小程序项目跑到真机上能出K线。我会按步骤写每一步附上代码和配置。4.1 第一步把组件装进小程序工程解压HQChart-master.zip之后把wx/components/hqchart整个目录复制到你项目的components目录下。如果你的项目用原生小程序写目录结构保持这样pages/ stock/ stock.js stock.json stock.wxml stock.wxss components/ hqchart/ hqchart.js hqchart.json hqchart.wxml hqchart.wxss然后打开pages/stock/stock.json把组件注册进去{ usingComponents: { hqchart: /components/hqchart/hqchart }, navigationBarTitleText: 个股行情 }注册完之后在stock.wxml里放一个组件节点给它一个id后面好在js里查询实例view classchart-container hqchart idhqchart canvas-idstockCanvas bind:chartClickonChartClick/hqchart /view在stock.js里我们通过selectComponent拿到组件实例等数据回来之后再初始化避免渲染空白。4.2 第二步对接行情数据源并做字段归一化现在项目还是没有数据的需要自己接一个行情接口。拿沪深A股举例接口返回结构通常是{ code: 600519, name: 某白酒股, kline: [ { date: 2024-01-02, open: 1685, close: 1690, high: 1700, low: 1675, volume: 32000 } ] }而港股接口可能是{ symbol: 00700, rows: [ { time: 2024-01-02 10:00:00, o: 380.0, h: 385.0, l: 378.0, c: 382.0, v: 1200000 } ] }进入组件前统一转成刚才说的目标格式。我在项目里单独建一个utils/dataAdapter.js避免页面里到处写转换逻辑// utils/dataAdapter.js function mapKline(list, fieldMap) { return list.map((item) ({ open: Number(item[fieldMap.open] || item.open || item.o), high: Number(item[fieldMap.high] || item.high || item.h), low: Number(item[fieldMap.low] || item.low || item.l), close: Number(item[fieldMap.close] || item.close || item.c), volume: Number(item[fieldMap.volume] || item.volume || item.v), time: normalizeTimestamp(item[fieldMap.time] || item.time || item.date), })); } function normalizeTimestamp(value) { if (typeof value number) { // 10位秒级时间戳转13位毫秒 return value 10000000000 ? value * 1000 : value; } // 字符串形式 YYYY-MM-DD HH:mm:ss return new Date(value.replace(/-/g, /)).getTime(); } module.exports { mapKline };这里有一个大坑iOS上new Date(2024-01-02 10:00:00)返回Invalid Date必须把连字符换成斜杠或者老老实实拆字符串。这是我第一次接行情接口时候踩到的血泪经验后面避坑章节会再提。拿到归一化数据后调用组件更新const adapter require(../../utils/dataAdapter); const list res.data.kline; const norm adapter.mapKline(list, { open: open, high: high, low: low, close: close, volume: volume, time: date }); const chart this.selectComponent(#hqchart); chart.updateData({ type: kline, // 图表类型 symbol: 600519, // 股票代码 market: SH, // 市场 period: day, // 周期 data: norm, // K线数组 visibleCount: 80, // 屏幕显示80根 isShowMA: true, // 显示均线 });参数说明visibleCount控制K线宽度80根在手机上刚好能看清实体market字段会影响涨价颜色和数值单位。如果你是港股market填HK价格小数位会是两位A股会显示到两位但跳价单位不同这些组件内部都有默认规则。4.3 第三步分时走势图和K线图切换行情页通常要能在“分时”和“日K”之间切换。实现方式不复杂给组件加一个type字段分时用minuteK线用kline切换时重新调用updateDataswitchChartType(type) { if (type minute) { this.selectComponent(#hqchart).updateData({ type: minute, symbol: this.data.symbol, market: this.data.market, data: this.data.minuteData, }); } else { this.selectComponent(#hqchart).updateData({ type: kline, symbol: this.data.symbol, market: this.data.market, period: day, data: this.data.klineData, }); } }这里要注意两点分时数据是一个一个时间点不是OHLC分时图通常需要昨天的收盘价做基准所以数据适配层要多传一个preClose字段。很多第一次接入的人把K线数组塞给分时图结果画出来是一条乱线。另一个常见需求是“盘中分钟K”。分钟K和日K共用type: kline只是把period改成minute、1min或5min数据源的粒度在接口层就差很多了。组件本身不关心粒度只关心数组里每一条的时间戳是不是严格递增。如果接口偶尔返回乱序数据务必在适配层排一下序不然后续十字光标索引会错位。4.4 小程序的Canvas性能适配真机上跑起来之后常见的现象是图形发虚。原因是小程序Canvas默认渲染尺寸和CSS尺寸不一致尤其在高DPI设备上。HQChart组件内部会读取wx.getWindowInfo()或旧版wx.getSystemInfoSync()的pixelRatio然后把Canvas的宽高乘以pixelRatio再通过ctx.scale(pixelRatio, pixelRatio)回到逻辑尺寸绘制。如果你用的是组件的原生实现不需要自己处理但如果你是参考源码自己磨了一套一定要在初始化时做这一步const info wx.getWindowInfo(); const pixelRatio info.pixelRatio || 2; const canvas this.canvas; canvas.width this.width * pixelRatio; canvas.height this.height * pixelRatio; this.ctx.scale(pixelRatio, pixelRatio);不做的后果就是图形边缘出现锯齿文字模糊K线蜡烛看着像一团马赛克。这属于“怎么看都不对”类问题排查优先级很高。5. 常见问题排查从真机模糊到公式解析失败的踩坑记录跑通是一回事跑顺是另一回事。整理几条我拆HQChart源码和实际接入时最常踩的坑每条都是现象、原因、解决三步方便你对号入座。5.1 现象真机上K线图和文字模糊模拟器正常这是Canvas常见病。模拟器上窗口的pixelRatio一般是1真机iPhone是3如果不缩放Canvas位图分辨率只有CSS尺寸的三分之一直接被拉伸放大。原因就是没做ctx.scale(pixelRatio, pixelRatio)。解决方法是第一步先获取pixelRatio并设置Canvas尺寸第二步把所有绘制代码包在一个draw()函数里任何尺寸变化后都重新执行draw()。代码可以这样加this.canvas.width this.width * pixelRatio; this.canvas.height this.height * pixelRatio; this.ctx.scale(pixelRatio, pixelRatio);注意scale调用只需要一次不要放在每帧绘制里不然会叠加放大导致图形飞出屏幕。5.2 现象指标公式里带中文或全角符号解析直接报错接口或用户输入的指标字符串有时会把逗号写成中文逗号例如MA(CLOSE5)正则把当作普通字符语法分析时读到不认识的tokenAST构建失败。原因在于词法分析器通常只支持半角符号。解决方式很简单解析前先做一次文本归一化把全角逗号、括号、空格替换为半角。function normalizeFormula(formula) { return formula .replace(//g, ,) .replace(//g, () .replace(//g, )) .replace(/\s/g, ); }更彻底的办法是扩展词法分析器让中文字符和全角符号都进入token集合。不过作为落地项目先做一层预处理性价比更高。5.3 现象拖动K线图时手势和图形错位缩放时图像从两端猛地放大这是坐标映射没算对锚点。缩放时常见实现是直接改visibleCount但缩放的锚点不是手势中心而是最左边于是视觉上图形像被拉伸车一样。HQChart的做法是用“手势中心对应的数据下标”作为锚点缩放前后保证这个下标在屏幕上的X坐标不变。排查时可以这样验证在touchmove里console.log出手势中心的index和缩放后的startIndex如果锚点计算用的不是同一个坐标系比如一个用逻辑像素、一个用物理像素就会出问题。解决方法是整条链路都使用逻辑像素只在最后ctx.scale时换算物理DPI。5.4 现象内存持续上涨页面反复进出后卡死K线图在页面卸载时释放不彻底。问题源头通常是事件监听和定时器没有销毁。detached() { // 组件生命周期页面卸载时触发 if (this._onCanvasTouch) { this.offTouchEvent(this._onCanvasTouch); } this._timer clearInterval(this._timer); this.canvas null; this.ctx null; }另外如果每次updateData都新建一个大数组并且没有丢弃旧引用也会导致内存膨胀。处理办法是让组件内部保存一个data引用更新时直接替换引用而不是在原数组上追加。5.5 现象港股时间不对K线日期差一天港股和A股都在东八区但如果行情源返回的是UTC时间字符串直接new Date()解析的时候会按本地时区处理结果日期偏移。例如2024-01-02T00:00:00Z在香港时区是当天早上8点没问题但如果服务端返回的是其他时区格式就容易出现解析差异。解决方法是统一在数据适配层把时间转为Number时间戳并且声明时区。通常的做法是服务端直接返回秒级时间戳前端不要再去转字符串。如果硬要处理字符串用replace(/-/g, /)之后再new Date在iOS上能避免非法日期。这五条坑只是入门级别真正做行情软件还要面对网络重连、断线缓存、指标周期切换等。但先把这些处理掉你已经能交付一个可用的行情页了。6. 二次开发指南换主题、注册新指标、改造数据推送如果你已经跑通了默认的K线图下一步大概率是改样式、改指标、改数据接口。这一章讲三个最常用的扩展点也是我实际改HQChart时总结出的几个技巧。6.1 主题定制把红绿配色改成自己的品牌色HQChart的配色入口一般在组件初始化参数里。常见做法是把颜色集中收敛到一个theme对象而不是散落在各个绘制函数里const theme { upColor: #e74c3c, // 上涨颜色 downColor: #2ecc71, // 下跌颜色 crossColor: #666666, // 十字光标颜色 bgColor: #ffffff, // 背景色 gridColor: #f0f0f0, // 网格线颜色 textColor: #333333, }; const chart this.selectComponent(#hqchart); chart.setTheme(theme); chart.refresh(); // 主题生效需要重绘如果你想做动态换肤维护两套theme对象通过一个开关来回切换。但注意换肤之后要重新调用一次refresh()否则只有部分颜色生效。6.2 扩展指标把通达信公式注册成内置指标通达信公式引擎的扩展方式是注册新函数。比如我要加一个“乖离率”指标BIAS(CLOSE,6)在指标定义表里添加一条记录核心就是告诉引擎“函数名、参数个数、怎么算”。// 伪代码示意注册指标函数 IndicatorEngine.register(BIAS, { params: [6], // 默认参数 calc(context) { const close context.get(CLOSE); const n this.params[0]; const ma movingAverage(close, n); const result close.map((price, i) { return (price - ma[i]) / ma[i] * 100; }); return result; }, });参数说明CLOSE从上下文里取出一整根序列movingAverage是前面写过的滑动平均BIAS的输出是百分比序列。注册好之后把指标名加到图表的indicator列表里K线副图区域就会画出来。有一点要注意指标计算返回的序列长度要和K线数据长度一致如果内部用了REF、HHV这类函数开头一段会是null组件要跳过空值绘制否则会把0值画出来误导用户。6.3 改造数据接口从定时刷新到WebSocket推送很多行情页用的是HTTP接口轮询K线图每分钟刷一次。轮询的缺点是真机耗电、数据延迟。二次开发时可以把utils/request.js改成WebSocket接收推送增量更新最后一根K线。// 简化版WebSocket接入 function connectStream(symbol, onTick) { const socket wx.connectSocket({ url: wss://your-host/quote }); socket.onMessage((msg) { const tick JSON.parse(msg.data); // 如果是同一个时间戳就替换最后一根K线否则追加新K线 onTick(tick); }); return socket; }这里的关键是“增量更新”逻辑用time字段和当前数组最后一项比对。相同则更新末尾不同则push新数据然后只重绘最后一次。这需要组件把上一次绘制的Canvas作为缓存底图数据变了再局部更新性能上比全量updateData好很多。我一般会在组件里加一个updateLastBar(tick)方法里面只重算最后三根K线的位置避免重绘整个窗口。最后说点习惯。二次开发做的越多越觉得图表组件最难的不是画巴而是状态管理。坐标、数据、指标、主题各自有状态一个改动了其他都要跟着联动。从那以后我每次给HQChart加功能前都强制自己先画一张状态流转图数据进入是入口坐标变化是驱动重绘指标计算是副作用主题只是画布属性。想清楚了再动手翻车概率会低很多。希望帮到你。本文还有配套的精品资源点击获取