☰
微信小程序沪深/港股K线组件:通达信指标与Canvas渲染
2026/10/27 6:17:37 网站建设 项目流程

简介:这份资源面向希望用微信小程序展示沪深与港股行情的开发者,尤其适合已掌握小程序基础、想进一步实现专业股票图表的进阶学习者。项目核心解决 K 线图与走势图的绘制、指标公式解析以及多端图表复用问题,可参考其中的行情模块结构来搭建自有看盘工具。资源共 72 个文件,以 js 脚本和 json 配置实现图表逻辑与小程序页面注册,配合 wxml、wxss 搭建界面,另有 html、css 演示页、png、svg 图片素材及字体文件,zip 包约 1.14MB。已有 1081 人浏览学习。读者可获得一套可运行的行情图表代码骨架和通达信语法的编译与指标计算实现思路,涵盖分钟图、历史日线、盘口与公告等展示场景,并通过目录中的接口说明文档理解调用方式。整体适合作为小程序股票图表方向的参考样例,便于对照研究数据处理、渲染与公式解析的完整链路。

1. 微信小程序里画沪深/港股K线:这套股票图表组件包能省掉哪些重复活

先泼一盆冷水:微信小程序里做沪深/港股 K 线图,难点通常不在把蜡烛画出来,而在走势图的数据对齐和通达信语法指标的稳定复算。手头这份股票图表组件包把小程序 canvas 渲染、手势缩放、指标计算壳子搭好了,适合已经拿到行情数据、想把 K 线页快速塞进现有小程序的开发者,也适合同时要跑内地市场和港股、还想复用通达信公式的前端或全栈。它省掉的是重复造轮子的时间,不是行情清洗和边界处理的活;数据源、复权口径、交易时段这些事仍要自己兜住。

2. 拆包先看边界:小程序渲染层、数据层和通达信语法层怎么分工

拿到压缩包后别急着全局搜索组件名,我一般先按职责把它切成三块看:渲染层负责 canvas 节点、触摸手势和视图缩放;数据层负责把行情接口返回的原始行转成统一的 OHLCV 结构;语法层负责把通达信公式解析成可计算的指标序列。三块之间的耦合点越少,后面换数据源、换周期、加港股市场时越不容易翻车。常见误区是把行情请求直接写进图表组件内部,第一版看着快,等你要加港股半日市、前复权、分页补历史时,组件里会变成一团黑匣子。

2.1 渲染层不是普通 Web 图表:canvas 节点与触摸事件要先隔离

小程序里的图表组件和 Web 端图表不是一回事。Web 端习惯直接拿 DOM 和全局事件,小程序里更多是 canvas 节点、组件生命周期、createSelectorQuery和手势命中区域。拆包时先确认组件入口是否要求外部传入 canvas id、宽高、像素比,是否自己处理touchstart/touchmove/touchend。我一般会额外包一层适配器,把“市场类型、周期、K 线数组、公式文本”这四个输入收敛掉,不让页面直接碰组件内部字段。这样即使压缩包里的组件导出名或参数名和示例不同,也只需要改适配器一处。

先在页面配置里注册组件,路径换成压缩包里真实组件目录。下面这个配置只做一件事:把图表组件挂到页面,并关掉页面级滚动,避免手势和页面滚动打架。

{ "usingComponents": { "stock-chart": "/components/stock-chart/index" }, "disableScroll": true }

这段配置的关键参数是usingComponents的路径和disableScroll。路径不要照抄,按你解压后的组件目录改;disableScroll不是万能药,如果页面还有其他内容需要滚动,应该改成局部滚动容器,而不是整页锁死。图表区域最好给固定高度,宽高在createSelectorQuery里拿到后再初始化,避免首帧按 0 宽高绘制。

适配层不要直接假设组件 API,先把差异收口到三个方法:

// adapters/market-chart.js // 作用:把页面层和压缩包内组件的具体 API 隔开 function createChartBridge(component) { if (!component || typeof component.setData !== 'function') { throw new Error('图表组件未正确挂载,先检查 usingComponents 路径'); } function normalizeMarket(market) { // 统一市场标识,页面只传 sh / sz / hk 这类短码 const map = { sh: 'SH', sz: 'SZ', hk: 'HK' }; const key = String(market || '').toLowerCase(); if (!map[key]) throw new Error('未知市场: ' + market); return map[key]; } return { setMarket(market) { component.setData({ marketCode: normalizeMarket(market) }); }, setKline(rows) { // rows 必须是已清洗的 OHLCV 数组,适配层不负责补行情 if (!Array.isArray(rows) || rows.length === 0) { component.setData({ klineData: [], emptyText: '暂无K线数据' }); return; } component.setData({ klineData: rows, emptyText: '' }); }, setFormula(expr) { // 公式文本先透传,解析和计算放到语法层 component.setData({ formulaText: String(expr || '').trim() }); } }; } module.exports = { createChartBridge };

这段代码的重点不是setData本身,而是把市场码、K 线数组、公式文本拆成三个入口。参数说明:market只接受短码,避免页面层传“沪市”“港股”这类中文;rows必须是清洗后的数组,不能把接口原始对象直接塞进去;expr只传文本,不在适配层做eval。真实组件如果要求扁平字段,比如open/close/high/low分开传,就在setKline里做一次映射,不要把映射散落到页面各处。触摸事件也一样,页面只监听组件抛出的“周期切换”“十字光标移动”这类业务事件,不直接绑 canvas 原始坐标。

2.2 数据层要预留沪深/港股字段差异

沪深和港股在 K 线字段上看起来都是开高低收,但落到接口里常见差异不少:有的返回vol,有的返回volume;有的成交量单位是股,有的是手;港股还可能出现停牌、半日市、收盘价精度不同。数据层要做的不是“把字段名改一下”,而是定一个内部标准行,所有市场先转成同一结构再进图表。我一般用{ date, open, high, low, close, volume, amount }作为最小行,date用YYYY-MM-DD或YYYY-MM-DD HH:mm,不直接用时间戳给图表,因为小程序设备时区一变,日期可能整体错一天。

拆包时先找组件示例里的数据入参,确认它需要数组还是对象、需要字符串价格还是数字价格。不要因为示例能跑就把接口原始字段直接喂进去,后面加港股时会出现“内地市场正常、港股成交量柱全空”的问题。字段映射放在数据层,图表层只认标准行,这样换行情源时不用改渲染代码。

2.3 通达信语法层是公式解释器不是绘图库

通达信语法在小程序里最常见的误用,是把它当成“画线配置”直接塞给图表组件。实际上公式要先解析成表达式树,再按 K 线序列逐根计算,最后把结果作为指标线交给渲染层。渲染层只负责把指标数组画成线、柱或面积,不负责理解REF、MA、HHV这些函数。拆包时要确认组件是否自带公式解析;如果自带,先看它支持哪些函数、是否用eval、是否处理了空值和除零;如果不带,就把公式层独立出来,别和行情请求混在一个文件里。这样做的好处是港股和沪深可以共用同一套指标计算,只在数据层处理复权和交易时段差异。

3. 沪深/港股K线数据怎么喂:OHLC、复权、时区和分页的落地处理

图表组件能不能用,一半看渲染,一半看喂进去的数据。K 线图最怕“看起来能画,一放大就错位”:日期重复、停牌日被补成一根横线、港股半日市被合并到次日、分页补历史时数组顺序反了。我的做法是先定标准行,再做市场规则清洗,最后才分页渲染。别把这三步揉成一个fetch回调,否则排查时只能靠打印大数组,效率很低。

3.1 统一成 OHLCV 行数据,字段缺失先补默认值

标准行至少包含日期、开、高、低、收、量。价格统一用数字,不用字符串;成交量统一成股或手,但要在配置里写清楚单位,避免图表成交量柱和指标量能公式对不上。缺失值不要用 0 直接补,0 会在 K 线图上砸出一根假低点。常见做法是跳过缺失行,或者在指标计算时用前值填充,但 K 线绘制必须保留真实交易日。下面这段清洗函数把字段名差异、日期格式、数值类型先收口,后面再按市场补规则。

// data/normalize-kline.js // 把不同行情源的原始行转成图表可用的标准 OHLCV 行 function pick(obj, keys, fallback) { for (const key of keys) { if (obj[key] !== undefined && obj[key] !== null && obj[key] !== '') { return obj[key]; } } return fallback; } function toNumber(value, field, date) { const n = Number(value); if (!Number.isFinite(n)) { throw new Error(`${date} 的 ${field} 不是有效数字: ${value}`); } return n; } function normalizeKline(rawList, options = {}) { const { dateKeys = ['date', 'day', 'time'], volumeKeys = ['volume', 'vol'], priceScale = 1 } = options; const rows = []; const seen = new Set(); for (const raw of rawList || []) { const date = String(pick(raw, dateKeys, '')).slice(0, 19); if (!date) continue; if (seen.has(date)) continue; // 同一天重复行只保留第一条,先排查源数据 seen.add(date); const open = toNumber(pick(raw, ['open', 'o']), 'open', date) * priceScale; const high = toNumber(pick(raw, ['high', 'h']), 'high', date) * priceScale; const low = toNumber(pick(raw, ['low', 'l']), 'low', date) * priceScale; const close = toNumber(pick(raw, ['close', 'c']), 'close', date) * priceScale; const volume = Number(pick(raw, volumeKeys, 0)) || 0; // 基本价格关系校验,避免脏数据把 K 线画反 if (high < Math.max(open, close) || low > Math.min(open, close)) { throw new Error(`${date} 高低价关系异常`); } rows.push({ date, open, high, low, close, volume }); } // 图表通常按时间升序吃数据,接口倒序返回时在这里翻一次 rows.sort((a, b) => a.date.localeCompare(b.date)); return rows; } module.exports = { normalizeKline };

这段清洗逻辑里有几个参数要按实际接口改:dateKeys和volumeKeys是字段别名,接口用tradeDate、tradeVolume就加进去;priceScale用于接口返回“分”而图表要“元”的情况,默认 1 表示不缩放;seen去重只处理完全相同的日期字符串,如果接口同时返回上午和下午两根小时线,不能直接用这个去重,要改成按周期键去重。价格关系校验是后悔药,宁可早报错,也不要把高低价颠倒的数据画到用户面前。成交量缺省补 0 可以接受,但价格缺省不能补 0,必须抛错或跳过。

3.2 港股半日市、停牌和沪深集合竞价别混在一根K里

港股有半日市,收盘时间和平日不同;停牌期间没有成交,但行情接口可能返回收盘价不变、成交量为 0 的行;沪深集合竞价的成交要不要并入第一根分钟 K,也要看你的周期定义。这些问题不处理,指标会在停牌段出现横线,均线被“假平静”拉平,用户一看就觉得图不对。我的处理原则是:日 K 保留真实交易日,停牌日可以不画或画成空心标记;分钟 K 按交易所交易时段切分,不用设备本地时间直接分组;港股半日市用交易日历判断收盘时间,不能写死 15:00。

常见做法是维护一份交易时段配置,按市场区分上午、下午和半日市收盘时间,再在数据层把时间字符串切成交易分钟。下面这张表是我接入时会先落的配置字段,具体时间按你拿到的交易日历校准。

配置项内地市场示例港股示例用途
marketCodeSH/SZHK区分市场和交易规则
amStart09:3009:30上午连续竞价起点
amEnd11:3012:00上午收盘
pmStart13:0013:00下午连续竞价起点
pmEnd15:0016:00平日收盘
halfDayEnd不适用12:00半日市收盘
volumeUnit手或股股成交量柱和量能公式的单位

表里的时间不是让你硬编码到组件里,而是放在数据层配置中。每次拉取分钟线后,先按marketCode取时段,再判断当前交易日是否半日市,最后才分组。停牌行不要直接删掉,日 K 可以保留但标记suspended: true,指标计算时用前收填充或跳过,具体看你的指标语义。集合竞价是否并入第一根分钟 K,要在产品侧定死,不要今天并、明天不并,否则回测和图上指标对不上。

3.3 分页拉取与首屏渲染:先画最近N根再补历史

小程序首屏等全量历史不现实,常见做法是先拉最近 200 到 500 根画出来,用户左滑到边界再补更早的数据。这里有两个坑:补历史后数组顺序要重新排序,不能把旧数据直接concat到末尾;补历史时如果组件内部维护了缩放比例,新数据插入后视图会跳。我的做法是数据层只负责返回“按时间升序的标准行”,页面层维护一个loadedRange,补历史时把新数组和旧数组合并、去重、排序,再整体替换给图表,同时记录当前可视区最左端的日期,替换后让组件恢复到那个日期附近。

分页参数也要定清楚:limit是根数不是天数,endDate是拉取截止日期,direction表示向前补还是向后拉。不要用“页码”做 K 线分页,行情数据会因停牌、半日市导致页大小不稳定。首屏渲染时先给图表一个空数组和 loading 态,等清洗完成再一次性setKline,避免每拉一页就setData一次,小程序渲染层会被频繁更新拖慢。补历史失败时保留旧数据并提示,不要把已有 K 线清空。

4. 通达信语法转小程序:公式解析、指标计算和边界输入

通达信语法是这套资源里最有价值也最容易翻车的部分。很多人一上来就想全量兼容,结果公式里一个未来函数或者DIV0就把整张图算崩。我的建议是先限定子集:均线、引用、求和、最高最低、条件判断这些覆盖大部分常用指标,先跑通再扩。解析时不要用eval,小程序环境对动态执行限制多,安全上也不合适;正确路径是词法分析、语法树、序列计算三步走。港股和内地市场共用指标时,只让数据层处理复权和缺失值,公式层不要写市场分支。

4.1 先限定语法子集:REF/MA/EMA/SUM/HHV/LLV/IF

第一版支持的函数不需要多,下面这些够跑一批常见指标。把不支持函数明确报错,比默默算错强。

函数参数返回说明
REF(X,N)序列、整数序列取 N 根前的值,N 为 0 返回当前值
MA(X,N)序列、整数序列简单移动平均
EMA(X,N)序列、整数序列指数移动平均
SUM(X,N)序列、整数序列N 根求和,N 为 0 时从首根累加
HHV(X,N)序列、整数序列N 根最高值
LLV(X,N)序列、整数序列N 根最低值
IF(C,A,B)条件、序列、序列序列条件为真取 A,否则取 B

不支持DYNAINFO、跨周期引用、未来函数这类能力,第一版直接抛“暂不支持”。指标公式里常见的C、O、H、L、V分别映射到标准行的收盘、开盘、最高、最低、成交量。参数 N 要校验:小于 1 的 N 对MA无意义,REF的 N 为负也要拦掉。除零在IF和除法里都要处理,结果为NaN时该点不画线,不能用 0 顶替。

4.2 解析成AST再算序列,不要用eval

下面是一个缩小版解析器,能处理数字、标识符、函数调用、四则运算和比较。它不追求完整通达信语法,但结构是对的:先分词,再递归下降生成 AST,最后按 K 线长度逐根求值。你可以在这个骨架上加函数,而不是在字符串上做正则替换。

// formula/parser.js // 简化版:支持 MA/REF/EMA/SUM/HHV/LLV/IF、四则运算和比较 function tokenize(input) { const tokens = []; const re = /\s*([A-Za-z_][A-Za-z0-9_]*|\d+(?:\.\d+)?|>=|<=|<>|>|<|=|\+|\-|\*|\/|\(|\)|,|;)/g; let match; let lastIndex = 0; while ((match = re.exec(input)) !== null) { if (match.index !== lastIndex) { throw new Error('公式包含无法识别的字符: ' + input.slice(lastIndex, match.index)); } tokens.push(match[1]); lastIndex = re.lastIndex; } if (lastIndex < input.length) { throw new Error('公式尾部无法识别: ' + input.slice(lastIndex)); } return tokens; } function parse(tokens) { let pos = 0; function peek() { return tokens[pos]; } function next() { return tokens[pos++]; } function expect(value) { if (peek() !== value) throw new Error('期望 ' + value + ',实际 ' + peek()); return next(); } function parsePrimary() { const token = peek(); if (token === '(') { next(); const node = parseCompare(); expect(')'); return node; } if (/^\d/.test(token)) { next(); return { type: 'number', value: Number(token) }; } if (/^[A-Za-z_]/.test(token)) { const name = next(); if (peek() === '(') { next(); const args = []; if (peek() !== ')') { args.push(parseCompare()); while (peek() === ',') { next(); args.push(parseCompare()); } } expect(')'); return { type: 'call', name: name.toUpperCase(), args }; } return { type: 'id', name: name.toUpperCase() }; } throw new Error('无法解析 token: ' + token); } function parseMul() { let node = parsePrimary(); while (peek() === '*' || peek() === '/') { const op = next(); node = { type: 'binary', op, left: node, right: parsePrimary() }; } return node; } function parseAdd() { let node = parseMul(); while (peek() === '+' || peek() === '-') { const op = next(); node = { type: 'binary', op, left: node, right: parseMul() }; } return node; } function parseCompare() { let node = parseAdd(); while (['>', '<', '>=', '<=', '=', '<>'].includes(peek())) { const op = next(); node = { type: 'binary', op, left: node, right: parseAdd() }; } return node; } const ast = parseCompare(); if (pos !== tokens.length) throw new Error('公式存在多余内容: ' + tokens.slice(pos).join(' ')); return ast; } function compile(formula) { const ast = parse(tokenize(formula)); const allowed = new Set(['MA', 'REF', 'EMA', 'SUM', 'HHV', 'LLV', 'IF']); function walk(node) { if (node.type === 'call' && !allowed.has(node.name)) { throw new Error('暂不支持函数: ' + node.name); } if (node.left) walk(node.left); if (node.right) walk(node.right); (node.args || []).forEach(walk); } walk(ast); return ast; } module.exports = { compile };

这段代码的关键点在allowed白名单和compile的整树校验:函数不在白名单直接报错,不要让不支持的公式跑到计算阶段才崩。tokenize只认常见符号,遇到:=这类赋值符号会报无法识别,这是有意的,指标公式先只支持表达式,不支持变量赋值。参数说明:输入是单行公式文本,输出是 AST;如果页面要保存多条指标,应该逐条compile并缓存结果,不要每次渲染都重新解析。比较运算符里=和<>的语义要在求值器里处理成相等和不等,不要直接当赋值。

计算阶段要按 K 线长度生成等长序列。REF在不足 N 根时返回null,MA前 N-1 根返回null,EMA第一根用当前值初始化。IF的条件如果为null,结果也给null,不要默认走假分支。所有序列最后交给图表前,把null转成断点,而不是 0。这样指标线在停牌或数据不足的位置会断开,用户能看出哪里数据不完整。

4.3 港股/沪深指标共用时注意除权和缺失值

同一套指标公式跨市场用时,最容易出问题的是除权和缺失值。内地市场常见前复权、后复权、不复权三种口径,港股也有类似的调整因子;如果 K 线用前复权,指标却用不复权收盘算,均线会和价格对不上。我的做法是在数据层就把复权口径定死,标准行里带上adjustType,公式层只吃已经处理好的收盘序列,不再做二次复权。缺失值方面,停牌日如果保留行,收盘价等于前收,成交量 0,MA会把停牌日算进去;如果产品要求停牌不参与均线,就在数据层标记suspended,计算前过滤,但保留日期轴,图表用空值断开。

参数上要特别注意N的边界:HHV(C,0)表示从首根到当前的最高值,SUM(V,0)表示累计成交量,这类写法在解析器里要单独处理,不能按固定窗口算。除零和空序列也要拦:IF(C>0, A/C, 0)里C为 0 时不能先算A/C再判断,求值器要做短路。港股半日市的分钟线长度和平日不同,指标窗口按根数算时会出现“同样的 N 根,覆盖时间不同”的情况,这是正常的,但要在图例里标清周期,避免用户误读。

5. 避坑与排查:小程序K线从能画到能用的五条血泪记录

这类组件包最容易出现“示例能跑、真实数据就崩”的情况。下面五条是我在类似小程序 K 线项目里踩过的坑,按现象、原因、解决写,方便你对照排查。别等上线后才发现,尤其是手势、时区和公式这三类问题,用户感知非常直接。

5.1 画布层级与滚动冲突:手指一滑页面跟着动

现象:K 线区域左右滑动时,页面整体上下滚,十字光标和缩放一起失灵。原因通常有两个:页面没有隔离滚动容器,或者图表组件只监听了touchmove没阻止默认行为。小程序里手势冲突不是玄学,先看页面是否开了disableScroll,再看图表容器外层有没有可滚动父级。解决:把图表放进固定高度容器,容器设overflow: hidden,页面级滚动和图表手势二选一;如果必须共存,用catchtouchmove拦住图表区域内的移动事件,并在组件卸载时解绑。不要给整个页面加catchtouchmove,否则其他列表也滑不动。

5.2 数据量、时区与复权:日期错一天、成交量柱全空

现象一:日 K 最后一根日期比行情软件晚一天。原因是把时间戳直接交给new Date()再用本地时区格式化。解决:日期字符串按接口原始交易日截取,不要经过设备时区转换;分钟线统一存YYYY-MM-DD HH:mm,展示时只做字符串裁剪。现象二:港股成交量柱全是 0。原因是字段名或单位不匹配,接口返回vol而清洗函数只认volume。解决:把字段别名表放在数据层,成交量单位在配置里写明,指标里的V和图表柱状图共用同一单位。现象三:均线和 K 线对不上。原因是 K 线前复权、指标用不复权。解决:标准行带adjustType,指标计算只吃同口径序列,换口径时整批重算,不要只换图表数据。

5.3 公式与交互:一个未来函数把整张图算崩

现象:输入某个指标后图表空白或指标线飞到天上。原因可能是公式用了未来函数、跨周期引用,或者除零产生了Infinity。解决:解析阶段用白名单拦函数,求值阶段对NaN/Infinity做断点处理,IF做短路求值;指标计算结果先抽样检查最大值和最小值,异常时回退到上一个可用指标并提示。交互上还有一个常见问题:补历史后缩放比例跳回默认。解决:补数据前记录可视区最左日期,替换数组后按日期恢复视图,不要按索引恢复,因为新股或停牌会导致索引偏移。

5.4 首屏 setData 太频繁:越补越卡

现象:左滑补历史几次后,小程序明显卡顿,手势延迟。原因是每补一页就setData一次大数组,渲染层反复 diff。解决:分页请求可以多次,但setKline合并后只调一次;一次补的根数控制在 200 到 500 根,别一次塞几千根;图表内部如果支持增量更新,优先用增量接口,不支持就整批替换但降低频率。排查时在setKline前后打时间戳,看是网络慢还是渲染慢,不要凭感觉优化。

5.5 空数据与异常行没有兜底:白屏比画错更难查

现象:某只港股停牌或新上市,图表白屏,控制台没有明显报错。原因是空数组没走空态,或者清洗函数遇到缺失价格直接抛错但页面没捕获。解决:清洗函数对空列表返回空数组,图表适配层设置emptyText;单行异常要带日期和字段名抛出,页面捕获后展示“该区间数据不完整”,同时保留已有 K 线。下面这段检查函数可以在渲染前跑一遍,先把重复日期、价格关系、空值挑出来。

// utils/check-kline.js // 渲染前抽样检查,别让脏数据直接进图表 function checkKline(rows) { const issues = []; const seen = new Set(); rows.forEach((row, index) => { if (!row.date) issues.push(`第 ${index} 行缺少日期`); if (seen.has(row.date)) issues.push(`${row.date} 日期重复`); seen.add(row.date); ['open', 'high', 'low', 'close'].forEach((key) => { if (!Number.isFinite(row[key])) issues.push(`${row.date} 的 ${key} 非数字`); }); if (row.high < row.low) issues.push(`${row.date} 最高价低于最低价`); if (row.volume < 0) issues.push(`${row.date} 成交量为负`); }); return issues; } module.exports = { checkKline };

这个检查函数不替代清洗,只做渲染前最后一道拦截。issues返回空数组才继续画图;有异常时先打印前十条,定位是源数据问题还是清洗逻辑问题。注意不要在每次手势移动时跑全量检查,放在数据加载和补历史合并后各跑一次即可。参数上,rows必须是标准行,价格字段是数字;如果接口价格是字符串,先在清洗层转数字,别让检查函数做类型转换。

6. 上线前做一轮自检:用三组样本验证K线、指标和交互

最后一章不讲大道理,讲我会强制走一遍的验证流程。找三组样本:一组正常交易日的内地市场日 K,一组含停牌和半日市的港股日 K,一组跨月且带缺口的分钟 K。每组都准备“原始接口返回”和“人工核对过的标准行”,用清洗函数跑一遍,逐字段对比日期、开高低收、成交量。然后拿两条公式验证指标:一条MA(C,5)这种无边界公式,一条带REF和IF的条件公式。指标结果不必和行情软件逐位相等,但趋势、断点位置、空值区间要对得上。

验证项样本通过标准常见失败点
日期对齐跨月日K日期升序、无重复、停牌日保留标记设备时区导致错一天
价格关系港股半日市最高≥开收、最低≤开收复权口径混用
成交量分钟K单位与图例一致vol/volume字段漏映射
指标断点含停牌港股数据不足处断线而非归零null被转成 0
手势恢复补历史后可视区日期不跳按索引恢复视图

再跑一轮交互自检:首屏加载、左滑补历史、切换周期、输入公式、清空公式、快速来回滑动。每个动作后看三件事:有没有报错、K 线日期有没有跳、指标线有没有残留。性能上,首屏渲染控制在可感知的等待内,补一页数据后手势不能明显变慢;如果变慢,先查setData次数和数据量,再查公式是否每次渲染都重新解析。公式 AST 可以按文本缓存,同一公式不要重复编译。

我自己的习惯是每次换行情源或改复权口径,都把这三组样本重新跑一遍,不靠“看起来差不多”过关。血泪经验是:K 线图最贵的不是画出来,而是画错之后用户来问。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询