简介:面向微信小程序端股票行情开发的实战源码包,聚焦沪深/港股K线图、分时走势图与技术指标展示,适合已掌握小程序基础、希望快速搭建行情页面的开发者参考。包内围绕行情模块提供分时、历史K线等示例,并涉及通达信语法公式解析、指标计算、图表缩放平移、用户交互与数据接口适配等环节,可用于理解行情图从数据组织到渲染呈现的完整链路。压缩包共72个文件、约1.14MB,以JavaScript源码为核心,配合PNG截图、HTML演示页、CSS/WXSS样式、JSON配置与WXML模板,另有字体文件和API说明文档,目录按模块划分,便于检索和复用。已有1081人学习,适合对照实现K线主图、指标副图、手势操作及微信端性能调试,快速形成可扩展的股票图表方案。
1. 从一张“拖一下就卡住”的行情页说起
在微信小程序里做行情页,最容易低估的是最后那 20%:K 线图能画出来,但一拖就卡、一放大十字光标就对不上价格、切到港股时间轴整体错位。这套 HQChart 源码包要解决的正是这些收尾问题——沪深与港股共用一套 K 线图与分时走势图渲染,指标部分直接吃通达信语法,写MA5:MA(CLOSE,5);这样的公式就能挂上去。它适合三类人:毕设需要一个能当场演示的行情模块、课设要交一份完整图表方案、业务里要接行情页面的前端。下面按需求、架构、数据、绘图、避坑、验收的顺序走一遍,每一步都给到能照着改的代码和参数。
2. 总体架构:三层结构与各自不该碰的边界
图表类项目最容易写成一锅粥——页面里既请求数据又算指标还管 canvas 尺寸,改一行动全身。这套方案的第一件事就是把边界切干净。
2.1 为什么不在小程序里从零手写 canvas 图表
手写并非不可能,但成本分布在很偏的地方。画一根 K 线是几何题,真正吃时间的是这几件:可视区裁剪(几百根 K 线只画屏幕上那几十根)、坐标轴的刻度自适应(价格区间从 3.2 到 380 都要好看)、十字光标吸附与信息浮层、双指缩放与单指拖动的冲突判定、指标窗口高度分配。这些每一条都能单独耗掉两天,而且做出来通常还不如成熟库稳。
HQChart 的价值就在于它把这些当成基础设施做好了:K 线主图、成交量副图、任意多个指标窗口共用一套坐标系和事件分发,你只需要提供数据、告诉它画什么。选型对比大致是这样:
| 方案 | 上手成本 | 交互完备度 | 通达信语法 | 小程序适配 |
|---|---|---|---|---|
| 纯 canvas 自绘 | 极高 | 需自己实现 | 不支持 | 需自行处理 |
| 通用图表库(折线/柱状) | 低 | 只有缩放平移 | 不支持 | 好 |
| 本方案(HQChart 内核) | 中 | 内置十字光标/拖拽/多窗口 | 支持 | 已适配 canvas 2d |
代价是引入一个体量不小的库,需要处理分包和初始化顺序,这一点在第 5 章会专门讲。
2.2 三层结构与职责划分
整个工程切成三层,任何一层都不许越界调用另一层的内部实现:
- 数据适配层:负责把行情接口返回的沪深、港股数据,转成内核认识的定长数组;负责复权标记、分页游标、增量追加。它不认识 canvas,也不认识页面。
- 绘图内核层:HQChart 本体。只认
Symbol、Period、Windows这些配置项,只通过NetworkFilter往外要数据。业务代码不许直接改它的内部变量。 - 业务控制层:页面。管周期切换按钮、指标下拉、加载态、空态、错误提示,以及触摸事件转发。
这样切的好处是:换数据源只动第一层,换图表库只动第二层,加个“横屏看盘”只动第三层。毕设答辩时评委问“如果行情接口换了怎么办”,你能指着分层说清楚,这比背功能列表有说服力得多。
2.3 工程目录与核心库放置位置
把压缩包解压后,核心绘图库整个搬进小程序工程,不要试图只挑几个文件出来,内部有大量循环依赖。推荐的落位方式:
# 小程序工程根目录 miniprogram/ pages/kline/ # K线页 pages/minute/ # 分时页 libs/hqchart/ # 核心库整体放入(解压目录的原样拷贝) utils/quote.js # 数据适配层 utils/symbol.js # 市场代码转换页面里的引用写成相对路径,示例工程里的引用路径以你解压后的实际目录为准,别照抄网上的路径,很容易少一级。app.json里记得给图表页单独分包,subpackages配置的root指向pages/之外的目录更干净:
{ "subpackages": [ { "root": "packageChart", "pages": ["pages/kline/kline", "pages/minute/minute"] } ], "preloadRule": { "pages/index/index": { "network": "all", "packages": ["packageChart"] } } }preloadRule的作用是在首页就预下载分包,用户点进图表页时不用等。这一步在小程序里很关键,否则首屏会有一段空白,体验上很难看。
3. 数据适配层:沪深与港股怎么对齐成一套 K 线结构
内核不认识“沪市”“港股”,它只认识一个Symbol字符串和一串数字。适配层的核心任务就是把两套市场规则压平。
3.1 统一 Bar 数据结构与字段顺序
HQChart 的 K 线数据是定长数组,不是对象数组,顺序固定,写错一位整张图就废了。这是最容易踩的坑之一:
// 把任意来源的行情数据转成内核要的定长数组 // 顺序:[日期, 昨收, 开, 高, 低, 收, 成交量, 成交额] function toBarList(list) { return list.map(function (item) { return [ item.date, // '2024-05-13' 日线;分钟线为 '2024-05-13 10:31' item.yclose, // 昨收,缺了会导致涨跌幅全错 item.open, item.high, item.low, item.close, item.vol, // 成交量,统一成「手」 item.amount // 成交额,统一成「元」 ] }) }第三个位置是昨收,不是开盘。很多人按[日期,开,高,低,收]的自然顺序去拼,图能出来但颜色全反、涨跌幅一塌糊涂,而且不报错——这种静默错误最难查。
港股这边要额外做两件事:一是成交量单位,港股有的接口给“股”,有的给“手”,必须统一,否则量柱高度差两个数量级;二是价格精度,港股低价股有三位小数(比如 0.062),四舍五入到两位会丢掉有效信息,导致 K 线粘连。我一般会在适配层留一个precision字段,跟着Symbol走。
市场代码也在这里归一化:
// 市场后缀决定复权规则和交易时段 function toSymbol(code, market) { var map = { sh: '.sh', sz: '.sz', hk: '.hk' } return code + (map[market] || '.sh') }沪市600000.sh、深市000001.sz、港股00700.hk,后缀错了内核不会报错,只会一直拉不到数据,表现为“图是空的”,排查时容易被误导到 canvas 上去。
3.2 请求封装、分页与增量拉取
图表页的数据加载分三种场景,一定要分开写,别指望一个接口通吃:
- 首屏历史数据:进入页面拉最近 N 根,日线一般 300 到 500 根够看。
- 向左拖拽翻页:用户拖到最左边时,按游标往前再拉一段,追加到数组头部。
- 实时更新:盘中每隔几秒更新最后一根。
把wx.request包一层,统一超时、重试和错误态:
function requestQuote(path, params, retry) { retry = retry || 0 return new Promise(function (resolve, reject) { wx.request({ url: 'https://your-domain.example.com/' + path, data: params, timeout: 8000, success: function (res) { if (res.statusCode === 200 && res.data && res.data.code === 0) { resolve(res.data.data) } else { reject(new Error('bad response: ' + res.statusCode)) } }, fail: function (err) { // 单次重试,避免弱网下直接白屏 if (retry < 1) { setTimeout(function () { requestQuote(path, params, retry + 1).then(resolve, reject) }, 600) } else { reject(err) } } }) }) }参数上,count别一次给到 2000。小程序端一次性解析几千根 K 线会有明显的主线程停顿,尤其低端安卓机。分页大小建议 120 到 200,翻页时用beforeTime或offset游标,不要用“页码”,因为盘中新数据会插到末尾导致页码错位。
翻页的实现要点在合并而不是请求:新拿到的数据要concat到旧数组前面,同时更新内核的起始位置,否则拖动会“跳回去”。追加完调一次刷新接口,让内核重算可视区。
3.3 复权、停牌与港股特有字段
复权是行情里最容易被忽略、又最容易在答辩现场被抓的点。日线必须默认前复权,否则除权那天会出现一个巨大的跳空缺口,看上去像数据错误。配置项里通常是一个整型:
KLine: { Right: 1, // 0 不复权,1 前复权,2 后复权 Period: 0, // 0 日线,1 周线,2 月线,4 一分钟 MaxReqeustDataCount: 800, PageSize: 150 }港股还有两个 A 股没有的情况要处理。第一是半日市和台风停牌,当天可能只有半天数据,K 线的日期序列不能简单按“每个交易日一根”去补,否则会和真实日期错开;正确做法是以接口返回的日期为准,缺失就是缺失。第二是碎股和暗盘,这些数据一般不进日线,如果你的数据源混进来了,记得在适配层过滤掉,不然成交量会突然冒出一根异常柱。
另外提醒一句,别在客户端做复权计算。复权因子涉及分红送配,数据量大且容易算错,交给服务端一次性算好、前端只负责展示,这是血泪经验。
4. 绘图内核接入:canvas 初始化、手势与通达信指标挂载
这一章是整个方案里最容易出问题的地方,因为它同时涉及小程序的渲染机制、事件系统和第三方库的初始化时序。
4.1 canvas 2d 节点初始化与尺寸自适应
小程序现在有两套 canvas:旧版canvas和新版type="2d"。图表类一定要用 2d 版本,它是同层渲染,不会被原生组件盖住,也不会出现拖动时残影。WXML 里这样写:
<canvas type="2d" id="klineCanvas" class="kline-canvas" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" ></canvas>初始化必须等节点真正挂载后再查,onReady里做,别写在onLoad:
onReady: function () { var that = this wx.createSelectorQuery() .select('#klineCanvas') .fields({ node: true, size: true }) .exec(function (res) { if (!res[0] || !res[0].node) return var canvas = res[0].node var dpr = wx.getSystemInfoSync().pixelRatio // 物理像素和逻辑像素要分开设,否则安卓上字会糊 canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr that.chart = HQChart.Chart.JSChart.Init(canvas) that.chart.SetOption(that.buildOption()) }) }两个参数必须成对出现:canvas.width用物理像素(乘了dpr),样式宽高用逻辑像素(rpx 或 px)。只设一个,安卓高分屏上要么模糊要么只画左下角四分之一。这是最高频的翻车点,没有之一。
buildOption()里至少要给全Symbol、Type、Windows三样,缺一个可能就不渲染:
buildOption: function () { return { Type: '历史K线图', Symbol: this.data.symbol, // 如 '00700.hk' IsAutoUpdate: false, // 自己控制刷新节奏 IsShowRightMenu: false, // 小程序里没地方放右键菜单 KLine: { Right: 1, Period: 0, MaxReqeustDataCount: 800, PageSize: 150 }, Windows: [ { Index: 'MA', Modify: false, Change: false }, { Index: 'VOL', Modify: false, Change: false } ] } }Windows数组的顺序就是从上到下的窗口顺序,第一个是主图,后面是副图。Modify和Change关掉,用户就没法在小程序里改指标参数——如果你想让用户能改,第 5 章会讲怎么开。
4.2 触摸事件转发与十字光标
内核不自己绑事件,它要你把小程序的touch事件原样喂进去。这一步看着简单,但坐标系必须换算:
onTouchStart: function (e) { if (!this.chart) return var t = e.touches[0] this.chart.OnTouchStart({ x: t.x, // 相对 canvas 左上角,不是 clientX y: t.y, identifier: t.identifier }) }, onTouchMove: function (e) { if (!this.chart) return var t = e.touches[0] this.chart.OnTouchMove({ x: t.x, y: t.y, identifier: t.identifier }) }, onTouchEnd: function () { if (!this.chart) return this.chart.OnTouchEnd() }t.x/t.y在小程序 canvas 上是相对组件的坐标,直接用;如果你拿到的是clientX,得先减去 canvas 的boundingClientRect.left,否则十字光标会整体偏移。很多人卡在这里以为是库的 bug。
还有一个细节:双指缩放要传两个点的identifier。单点拖动和双指缩放内核靠identifier数量区分,只传touches[0]会让缩放手势失效,用户两根手指搓半天没反应。
主图默认开启十字光标,长按出现、拖动移动、松手消失。如果你想让浮层跟着手指显示“开高低收、涨跌幅、成交量”,只需要在内核回调里拿当前选中项,不用自己算坐标。这块建议直接用它自带的,自己实现一套对齐逻辑很容易在缩放后错位。
4.3 通达信语法指标的组织与编译
这是这套方案相对其他图表库的核心差异点。你在通达信里写惯了MA5:MA(CLOSE,5);、DIF:EMA(CLOSE,12)-EMA(CLOSE,26);,在这里基本可以原样搬过来,不用翻译成 JS。
脚本的组织方式建议按“一指标一文件”,放在libs/hqchart/indicator/下,用一个注册表串起来,方便增删:
// 指标注册表:新增指标只改这里,页面不动 var INDEX_LIST = [ { Name: 'MY_MA', Description: '自定义均线组', IsMainIndex: true, OutCount: 3, Script: [ 'MA5:MA(CLOSE,5);', 'MA10:MA(CLOSE,10);', 'MA20:MA(CLOSE,20);' ].join('\n') }, { Name: 'MY_MACD', Description: 'MACD 副图', IsMainIndex: false, OutCount: 3, Script: [ 'DIF:EMA(CLOSE,12)-EMA(CLOSE,26);', 'DEA:EMA(DIF,9);', 'MACD:(DIF-DEA)*2;' ].join('\n') } ]OutCount必须等于脚本里输出的变量个数,写少了后面的线直接不画,写多了会多出空线,两种情况都不报错。IsMainIndex决定它挂主图还是副图:主图指标和 K 线共用 Y 轴,副图指标自己一套轴,放错了线会挤成一团。
注册进内核时,函数名各版本略有差异(可能叫AddIndex、SetIndexScript之类),接入前先看一眼解压目录里相关模块的导出,别照抄。注册完之后,Windows里把Index换成你的指标名就能挂上:
Windows: [ { Index: 'MY_MA', Modify: false, Change: false }, { Index: 'VOL', Modify: false, Change: false }, { Index: 'MY_MACD', Modify: false, Change: false } ]支撑的通达信函数是这套东西的实用边界。常用的这些基本都有:MA、EMA、SMA、REF、HHV、LLV、CROSS、SUM、ABS、MAX、MIN。写指标时两个习惯能省很多事:一是所有除法都先判断分母,IF(V>0, A/V, 0)这种保护写上去,否则停牌股成交量为 0 会算出NaN,整条线断掉;二是REF的周期参数不要超过你加载的 K 线根数,越界取到的是空值,同样会让线断。
至于“用户能不能自己改参数”,如果要做,就在页面上放几个输入框,改完重新拼Script字符串再注册一次同名指标,然后刷新窗口。别直接改已注册的脚本对象,缓存可能不刷新。
5. 避坑与排查:真机上最容易翻车的五个点
模拟器上一切正常、真机上一塌糊涂,是这类项目的常态。下面五条按出现频率排序。
5.1 真机上图表区域全白,控制台没有任何报错
现象:开发者工具里图表正常,扫码到手机上进入页面,canvas 区域一片白,日志干净。
原因:绝大多数是初始化时序问题。onReady里查节点时,如果页面有wx:if包裹 canvas,或者分包还没下载完,res[0].node是undefined,而代码里没做判空就直接往下走,后面所有调用都在一个空对象上静默失败。
解决:exec回调第一行加if (!res[0] || !res[0].node) { setTimeout(retry, 100); return },做有限次重试;同时确认 canvas 不要放在wx:if里,改用hidden控制显隐。
5.2 拖动 K 线时掉帧,越拖越卡
现象:刚进页面很流畅,拖动十几秒后开始卡顿,低端安卓机尤其明显。
原因:把内核的每次重绘结果通过setData同步到了页面(比如把当前选中价格写到 data 里做浮层),拖动时每秒触发几十次setData,逻辑层和渲染层来回通信直接把帧率打下来。
解决:所有跟随手指变化的内容都画在 canvas 里,不要走setData。只有周期切换、股票切换这种低频操作才允许setData。这一条改完之后通常是数量级的提升。
5.3 港股 K 线整体错位,日期对不上
现象:同一只股票,小程序上看到的价格和别的软件差一截,或者日期整体偏移一天。
原因:一是没做时区归一,港股数据源返回的时间戳可能是 UTC 或带时区偏移,直接当本地时间解析会差 8 小时;二是复权开关没对上,A 股默认前复权、港股数据源默认不复权,两边一对就错。
解决:适配层统一把时间转成YYYY-MM-DD字符串再进内核,不用时间戳;复权参数由Symbol后缀决定,.hk也走前复权,并在切换股票时重建 option 而不是复用旧对象。
5.4 自定义指标挂上了但不显示
现象:Windows里加了指标名,副图窗口出来了,但里面是空的。
原因:三种可能——OutCount和脚本输出变量数不一致;脚本里用了内核不支持的函数,编译直接返回空;IsMainIndex设成了true却挂到了副图位置,Y 轴范围被 K 线价格撑爆,线被压成一条贴底的直线。
解决:先单独用一个最简单的MA5:MA(CLOSE,5);验证通路是否打通,通了再往上加复杂度;把脚本编译的返回值打出来看一眼,绝大多数错误在那里就能看到。
5.5 真机请求全部失败,开发者工具却正常
现象:工具里数据正常加载,真机上一个请求都发不出去。
原因:小程序的服务器域名白名单是跟着小程序配置走的,开发者工具默认不校验,真机强制校验。另外内核初始化的资源加载如果走网络,也要一起加进白名单。
解决:把行情接口域名、静态资源域名都配进 request 合法域名;调试阶段可以临时勾选“不校验合法域名”,但交付前必须关掉再验一遍,这一步别省。
6. 验收与答辩:一套能当场跑通的检查清单
方案写得再好,答辩现场崩一次就白搭。我一般会准备一份固定的检查清单,按顺序走一遍,全部通过才算交付。这套流程分三块:功能、性能、异常。
功能这一块,按“市场 × 周期 × 图表类型”做正交覆盖,别只演示一只股票:
| 检查项 | 通过标准 |
|---|---|
| 沪深日线 | K 线、成交量、均线三层都出,十字光标数值与顶部一致 |
| 港股日线 | 日期序列连续,无跨日错位,低价股小数位正确 |
| 分时图 | 均价线连续,开盘前无数据段不画线 |
| 周期切换 | 日/周/月/分钟来回切 3 次,不残留旧数据 |
| 指标切换 | 主图副图各切一次,窗口高度分配正常 |
| 通达信指标 | 自定义公式能显示,参数改动后立即生效 |
性能这块只看两个数:一是首屏从点击到出图的时间,控制在 2 秒内;二是连续拖拽 30 秒,观察是否出现肉眼可见的卡顿。工具里的性能面板只是参考,真机手感才是准的,高低端机各拿一台跑一遍。
异常这块最容易被跳过,但答辩老师往往就爱问这些:“断网了怎么办”“接口返回空怎么办”“切后台再回来呢”。对应做法分别是:断网显示重试按钮而不是空白;空数据显示“暂无数据”占位,不要让 canvas 保持上一只票的残影;切后台时停掉定时刷新,onShow时重新拉一次最新数据,否则回到前台会看到一段过期的分时线。
演示顺序也有讲究。先讲架构分层,让评委知道你有设计;再打开日线演示十字光标和缩放,这是最直观的;然后切港股,顺带讲复权和时区怎么处理的;最后加一个自定义通达信指标,把Script字符串在屏幕上展示一下——这一步是整套方案里最有辨识度的地方,别放在最后草草带过。整个流程控制在五分钟内,留时间回答问题。
最后说个习惯。从我第一次做行情页开始,每次提测前都会强制自己在一台旧安卓机上从头走一遍:清缓存、断网、进页面、拖动、切股票、切后台再回来。这套流程走下来会翻出七八个模拟器上完全看不到的问题。图表这东西的玄学之处就在于,它不报错,只是画得不对,而“不对”只有眼睛能发现。希望帮到你。
本文还有配套的精品资源,点击获取