K线

测试页面-分时
测试的时候,可以从我们的web官网,点击对应市场的类别进去,就可以看到web版本的行情图了。
分时请求类似这样:
https://d.10jqka.com.cn/v6/time/hs_300033/last.js
沪深
上证
http://m.10jqka.com.cn/stockpage/hs_600000
深证
http://m.10jqka.com.cn/stockpage/hs_002142
创业板
http://m.10jqka.com.cn/stockpage/hs_300033
科创板
https://m.10jqka.com.cn/stockpage/hs_688022
代码里面市场是写的hckcb,但是会重定向到hc。
这个市场是通过688开头来判断的。
港股
https://m.10jqka.com.cn/stockpage/hk_HK0700
注意:港股代码是4位的
数字的话,第一位是固定0,字母的话把0换成HK
比如:腾讯控股,我们客户端里面显示的代码是00700,而请求web行情数据时,要改为HK0700,比如:
https://d.10jqka.com.cn/v6/line/hk_HK0700/01/last360.js
美股
https://m.10jqka.com.cn/stockpage/usa_MOGU
画图流程
- 配置数据请求的参数
- 拼装数据请求的url
- 发送数据请求
- 解析和格式化数据
- 绘图
程序分析
行情图其实是由几种基本图形叠加构成的,线条部分,普通的line即可,关键在于K线图的柱子,是通过hqbar这种类型来实现的,代码位于d3charts的src/chart/hqbar目录下。
画图比较简单,核心还是在于数据的获取,即datapool相关的程序。
数据获取-DataPool
文件结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| |-- common | |-- OthersHq.js // 成交明细、五档行情数据等 | |-- index.js
|-- fs | |-- FsHistory.js | |-- FsMoneyFlow.js // TODO:资金流向数据 | |-- FsToday.js // 处理当日分时数据,这个需要重点关注 | |-- index.js
|-- kline | |-- KlineBase.js | |-- KlineCommon.js | |-- KlineLast.js | |-- index.js
|-- configs.js |-- DataPool.js |-- DataProvider.js // 请求数据 |-- helper.js |-- stockstatus.js // TODO |-- StockType.js // 对不同市场(通过正则匹配股票代码前缀来区分市场)做了一些差异化的处理(比如x轴指定显示的坐标内容-fsXaxisShow)
|
可以从src/datapool/DataPool.js的register()方法开始阅读代码。
这里有做单例设置,所以多次调用D3Charts.getDataPool()是没关系的,不会重复生成实例。
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| var klinedata = { type: 'klineLast', code: 'hs_300033', ma: [5, 10, 30], lastDays: 360 };
var dp = D3Charts.getDataPool(); dp.setStatus({ enableFilterUrl: true });
var dataProvider = D3Charts.getDataPool().register(klinedata); D3Charts.getDataPool().onAction(dataProvider, 'PROVIDER_UPDATE.myproject', function (d) {
if (d.fetchStatus.code !== '000') return;
var needConvertoShou = d.data.stockType.needConvertoShou; var keepLen = d.data.stockType.keepLen; var minValueSpan = 20;
var kData = d.data.dataArray;
var testN = kData.length var testMarkAreaDateStart = kData[testN - 30].t var testMarkAreaDateEnd = kData[testN - 10].t
var testMarkAreaDateStart1 = kData[testN - 60].t var testMarkAreaDateEnd1 = kData[testN - 50].t
var testMarkAreaDateStart2 = kData[testN - 45].t var testMarkAreaDateEnd2 = kData[testN - 35].t });
|
数据源配置
和我们之前取服务端行情数据很类似:
1 2 3 4 5 6
| { type: 'klineLast', code: code, ma: [5, 10, 30], lastDays: 360 }
|
要特别注意src/datapool/DataProvider.js这个文件,默认的数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| static defaultOption = { code:'hs_300033', version: dataVersion, dType: 'common', protocol: dataProtocol, isKeepingGet: false, intervalTime: 60000 }
|
会将默认配置和用户传入的配置进行合并,形成最终的数据请求配置。
DataProvider有一个缓存机制,会通过私有storage变量,缓存配置信息,详见enableClassManagement()这个函数。
特别注意:
请求行情数据,最终发出去的其实是一个.js的资源请求,而这个.js文件的url,是通过数据项id、请求的数据长度、周期等转换而成的。
具体转换规则详见DataProvider.js中的getDataId()这个方法。
更换url地址
1
| DataPool的getFilterUrl(url)方法
|
正常情况下是不需要应用方更换地址的,只有特殊需求场景才需要,比如港股要求必须展示延时15分钟的数据。
解析结果
因为数据类型有多种,因此在datapool目录下,有多个解析数据结果的文件,比如解析K线数据的,就是src/datapool/kline/KLineCommon.js.
这类解析文件,一般会包含如下功能:
1、定义数据请求的url规则urlFormatter
2、对接口返回的数据进行格式化,比如精度处理等
一些问题
hxc3是什么?
这是兼容IE低版本(IE7、8)的数据池。
hxc3.dataPool = D3charts.dataPool 拥有相同的api
现在没必要用这个了,新版的已经集成了行情图了。
数据中的ioac分别代表什么?
可以查看这个文档。
单个K线数据的结构类似这样(可以在KLineBase.js中查看注释):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| { a: 8.93, c: 7.859999999999999, i: 7.85, ma5: 7.86, ma10: 7.86, ma30: 7.86, n: 9661747, o: 8.32, yc: 8.32, np: 198423455, afn: 23434444, s: "be", t: "20091225", 'h': parseFloat(obj['1968584']), 'yj': parseFloat(obj['66']), 'yc': '', 's': '' }
|
all.js返回的price格式说明
http://d.10jqka.com.cn/v6/line/hs_300033/01/all.js
价格price的格式,是四个数字为一组,表示一天的价格信息;第一个数字是最低价,后面3个数字是相对最低价的涨跌值,注意后面3个数字不是直接的价格数值。
并且数值需要除以接口返回的priceFactor这个字段,才是真正的精度。
所以可以看到KlineCommon.js中的arrangeHistory()方法解析数据时,是这样的:
1 2 3 4 5 6 7 8 9
| for (var k = 0; k < 4; k++) { if (result[i].i || result[i].i == 0) { result[i][priceName[k]]= price[4*i+k]/priceFactor + result[i].i; }else{ result[i][priceName[k]]= price[4*i+k]/priceFactor; } }
|
比如同花顺的历史数据,从2009.12.25开始,前面2个日期是这样的:
172,47,108,1,162,5,49,17
精度字段priceFactor是100,那么也就是2009.12.25的:
- 最低价是172/100=1.72
- 开盘价是1.72 + 47/100 = 2.19
- 最高价是1.72 + 108/100 = 2.80
- 收盘价是1.72 + 1/100 = 1.73
last.js返回的price格式说明
逻辑在KLineLast.js中,以分号分割每日数据,然后以逗号分割单日的数据项。
单日数据项每个下标对应的含义如下:
- 0:日期
- 1:开盘价
- 2:最高价
- 3:最低价
- 4:收盘价
- 5:成交量
- 6:成交额
- 7:换手率
- 8:昨结价
- 9:盘后成交量 目前只有科创板有该值
- 10:盘后成交笔数 目前只有科创板有该值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| let isGetTotalFirstData = false;
if (totalKlineNum == 0 || !d.data) return rr;
let dataArr = d.data.split(';');
for (let i = 0; i < dataArr.length; i++) { let oneDayArr = dataArr[i].split(','); if (forceHistory && needPopDate && needPopDate == oneDayArr[0]) { break; } result.push({ t: oneDayArr[0], o: parseFloat(oneDayArr[1]), a: parseFloat(oneDayArr[2]), i: parseFloat(oneDayArr[3]), c: parseFloat(oneDayArr[4]), n: parseFloat(oneDayArr[5]), np: parseFloat(oneDayArr[6]), h: parseFloat(oneDayArr[7]), s: undefined, yc: undefined, yj: typeof oneDayArr[8] !== 'undefined' && oneDayArr[8] !== '' ? parseFloat(oneDayArr[8]) : undefined, an: parseFloat(oneDayArr[9]), anp: parseFloat(oneDayArr[10]) });
result[i].s = getKLineStatus(result[i], result[i - 1]); if(i === 0){ result[i].yc = result[i].o; isGetTotalFirstData = (firstDate == result[i].t); }else{ result[i].yc = result[i-1].c; } }
if(isGetTotalFirstData){ result[0].yc = parseFloat(issuePrice); }
|
OtherHq的数据字段
位于OtherHq.js中,以注释形式进行了说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
|
涨跌幅是如何计算的?
优先用昨结价,如果没有,采用昨收价:
1 2 3 4 5
| var lastPrice = data.yc; var lastPrice4Rate = data.yj || lastPrice result.rate = decimalRound(((curPrise - lastPrice4Rate) / lastPrice4Rate) * 100, 2) + '%';;
|
作图
K线图有个官方的名字,叫做蜡烛图,因此这种形状我们命名为CandleSticks,作图逻辑位于src/shape/CandleSticks.js文件中。
蜡烛图的数据结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var candle = { shape: { data: pointsByColor[d], bandWidth: bandwidth, yStart: _y1Start, percent: 1, drawTemplate: hqbarType }, style: { stroke: '', fill: '' }, z2: 1, cursor: 'default' }
|
绘制柱子,则是计算好坐标数据,然后通过canvas的moveTo、lineTo、rect等API实现作图。以普通蜡烛图为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| function klinePrice(data, bandWidth, fixed, ctx) { var x, adjust, temp1, temp2; if (bandWidth <= 3) { map(data, (d, i) => { x = add_5(d[1].lineX, fixed); temp1 = add_5(d[1].lineYTop, fixed); temp2 = add_5(d[1].lineYBottom, fixed); adjust = Math.abs(temp1 - temp2) < 2 ? 2 : 0 ctx.moveTo(x, temp1 - adjust); ctx.lineTo(x, temp2); }); } else { map(data, (d, i) => { x = add_5(d[1].lineX, fixed); if (d[1].h < 1) { ctx.moveTo(add_5(d[0], fixed), add_5(d[1].y, fixed)); ctx.lineTo(add_5(d[0] + bandWidth, fixed), add_5(d[1].y, fixed)); } else { ctx.rect(add_5(d[0], fixed), add_5(d[1].y, fixed), bandWidth, d[1].h); }
ctx.moveTo(x, add_5(d[1].lineYTop, fixed)); ctx.lineTo(x, add_5(d[1].y, fixed)); ctx.moveTo(x, add_5(d[1].y + d[1].h, fixed)); ctx.lineTo(x, add_5(d[1].lineYBottom, fixed));
}) } }
|
K线图的柱子数据的计算逻辑
在HqbarModel.js中,可以重点看下getDrawData(info)这个方法,将原始数据计算为绘图所需的几何数据。
其中柱子的高度是开盘价与收盘价之差的绝对值:
1
| h: Math.abs(yScale(info.o) - yScale(info.c)),
|
数据池(datapool)
数据通过d.10jqka.com.cn域名获取,源头是行情服务器。
接口负责人:ZS、LKJ、LZQ
数据池文档(新版):里面有各个字段的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| a: 8.93 // 最高价 max c: 7.859999999999999 // 收盘价 close i: 7.85 // 最低价 min ma5: 7.86 // ma5 均价线 ma10: 7.86 ma30: 7.86 n: 9661747 // 成交量 volume o: 8.32 // 开盘价 open yc: 8.32 // 昨收价 yesterdayClose np: 198423455 // 成交额 volumePrice afn: 23434444 // 盘后成交量,仅 科创板市场有该值 s: "be" // k线颜色 be --> 下跌, up--> 上涨 eq -> 持平 t: "20091225" // 时间 time
av: 69.722 // 均价 , 外汇等市场此内容为空 n: 9200 // 成交量 nowp: 69.7 // 现价 np: 641438 // 成交金额 t: "20180828 1500" // 时间 nowpPct: -0.008264462809917363, // 现价对比昨收价(转换成%的话,需要乘以100) avPct: -0.007489669421487582 // 均价对比昨收价(转换成%的话,需要乘以100)
|
其他相关文档:
WEB行情文档汇总
实时行情数据接口
支持的市场列表
分时接口
K线接口
K线时间段接口
图形超出画图区域怎么办?
可以通过axis下的space进行配置:

一般如果是图上要标东西,东西超出当前绘图区域了,我们会用y轴的space,或者domainScale来调整。而不是通过修改grid的top来调整,因为修改grid的top,这个标记像是掉出去了,没有画面里面配的那么一体,效果会变成类似这样:

阴影区域
通过markArea进行配置,在markArea[x].data中设置阴影区域的起止范围。
注意这个范围是由2个坐标点决定的,是一个方形区块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| markArea: [{ $axisIndex: [0, 1], label: { normal: { show: true, style: { position: 'inside', fontSize: 16 } } }, areaStyle: { normal: { fill: 'rgba(91,212,147,0.2)' } }, data: [{ points: [{ xValue: testMarkAreaDateStart, y: 'top' }, { xValue: testMarkAreaDateEnd, y: 'bottom' } ] }], z: 1 }, { $axisIndex: [0, 2], label: { normal: { show: true, style: { position: 'inside', fontSize: 16 } } }, areaStyle: { normal: { fill: 'rgba(91,212,147,0.2)' } }, data: [{ points: [{ xValue: testMarkAreaDateStart, y: 'top' }, { xValue: testMarkAreaDateEnd, y: 'bottom' } ] }], z: 1 }, { $axisIndex: [0, 3], label: { normal: { show: true, style: { position: 'inside', fontSize: 16 } } }, areaStyle: { normal: { fill: 'rgba(91,212,147,0.2)' } }, data: [{ points: [{ xValue: testMarkAreaDateStart, y: 'top' }, { xValue: testMarkAreaDateEnd, y: 'bottom' } ] }], z: 1 }, ],
|
如何在一个画布上画多个图形?
可以配置多个Y轴axis,给其设置不同的$gridIndex。
注意显示多个图形时,grid也要对应的配置多个。
如何设置坐标轴上显示的tick?
通过axis[x].tickValues进行控制,注意:x轴和y轴的返回值似乎不一样。
针对x轴:
1 2 3 4 5 6 7 8 9 10 11 12 13
| tickValues: function (domain) { var n = domain.length - 1; if (n < 6) { return [domain[0]]; } else if (n < minValueSpan) { return [domain[0], domain[n]]; } else { var split = n / 6; return [domain[Math.round(n / 6)], domain[Math.round(n * 3 / 6)], domain[Math.round(n * 5 / 6)] ]; } }
|
针对y轴,可以让该配置返回一个数组,数组有几个值,就显示几个tick:
1 2 3 4 5 6 7 8
| tickValues: function (domain) { var min = domain[0]; var max = domain[1]; var detar = max - min; return [domain[0], detar / 4 + domain[0], detar * 2 / 4 + domain[0], detar * 3 / 4 + domain[0], domain[1] ]; }
|
如何在K线上画tip?
参考这个示例:http://datav.iwencai.com/platform/chartconfig.html#chartId=258
通过添加markpoint来实现,大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| chart.setOption(option, false, { 'VIEW_DID_RENDER_AFTER_SET_OPTION': function () { let tips = [] tips.push({ symbol: { type: 'rect', offset: [0, 0], size: [50, 50], style: { normal: { fill: "#FF0000", stroke: "#000000" }, } }, xValue: '20190726', y: 10, valueIndex: 1, info: 'what info' })
option.markPoint[0].data = tips console.log('option', option);
chart.setOption(option) } });
|
如何在tip中自定义文字?
tip的位置如何自定义?
y是指图像中心距离画布顶端的距离
如果想控制tip的高度,可以设置yValue
这个值有两种处理方法,一种是在绘图之前,你先根据你拿来绘图的data去找一下这个数据线的对应值,然后用这个值设置yValue
另一种是在第一次绘图之后,再获取markpoint的index值,再去series里找对应的那根线的数据,然后再重置markpoint的位置
第二种需要重绘,性能不如第一种
如果画出来的markpoint位置不对,可以设置offset来调整。
如何控制markpoint的层级?
后画的在上层,遮住先画的。
K线柱子宽度如何自动变化?
这个逻辑在HqbarView.js中。
这是根据像素来判断的,当计算出来的单个柱子宽度的像素值小于等于3,就会自动画成一根线条;这估计是以前定的规范
因此当设置的一屏显示的数据较多时(比如90个交易日),在不同机型上看到的柱子宽度会不一致,可能苹果手机上就是正常的宽度(苹果分辨率高),Android手机上就是一根线。
markpoint的三角形怎么画的?
数据请求相关的问题
请求实现的原理
用的fetch-jsonp这个库:
https://github.com/mudenglong/fetch-jsonp
回调只是传递了一个名称过去,感觉是fetch-jsonp内部做了处理(KlineBase.js中):
1、创建这个回调函数
2、请求成功后,http返回的内容自动调用这个回调函数
3、这个回调函数将数据通过fetchSuccess()传递给数据处理逻辑:
如何设置自动请求数据的频率?
通过intervalTime这个配置项来设置:
1 2 3 4 5 6 7 8 9
| var fsdata = { type: 'fsToday', code: code, isKeepingGet: true, intervalTime: 2000 };
var dataProvider = D3Charts.getDataPool().register(fsdata);
|
如何停止自动请求数据?
数据请求是通过DataProvider来执行的,因此调用其stopGetData()方法即可:
1 2 3 4 5 6
| var dataProvider = D3Charts.getDataPool().register(fsdata);
setTimeout(() => { dataProvider.stopGetData(); }, 10000);
|
注意:目前只能停止,不能重新开启;有必要的话,可以销毁重新创建数据池和图表。
为什么请求历史K线数据时,不同服务器返回的.all接口最后一个日期不一致?
因为历史数据的缓存频率极低(12个小时,估计是历史K线的访问请求很大,所以做了这个策略,因此历史请求中的当日数据即使返回了也是不能用的),因此允许最后一天的数据不实时,靠前端用.today.js接口的数据进行补全。
所以请求历史K线时,可以看到总共会发出2个请求:
http://d.10jqka.com.cn/v6/line/hs_300033/01/all.js
http://d.10jqka.com.cn/v6/line/hs_300033/01/defer/today.js
如何控制显示的K线数据的数量?
在数据请求完成后,对数据进行裁剪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| / 获得数据 var dataProvider = D3Charts.getDataPool().register(klinedata); D3Charts.getDataPool().onAction( dataProvider, "PROVIDER_UPDATE.myproject", function (d) { if (d.fetchStatus.code !== "000") return;
var needConvertoShou = d.data.stockType.needConvertoShou; var keepLen = d.data.stockType.keepLen; var minValueSpan = 20;
var kData = d.data.dataArray; kData.splice(-1); chart.setOption({ data: [ { originData: kData, }, ], }); } );
|
url中指定取数范围
分时
分时图显示的时间范围是如何设置的?
目前分时显示多长时间段是由后端返回的数据控制的,是根据返回的结果中的dotsCount字段来确定X轴范围的,详见hqHelper.js:
1 2 3 4
| var xData = []; for (let i = 0; i < totalTimeNum; i++) { xData.push('___' + i); }
|
比如这个接口:http://d.10jqka.com.cn/v6/time/hs_300033/last.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| { "hs_300033": { "name": "同花顺", "open": 1, "stop": 0, "isTrading": 0, "rt": "0930-1130,1300-1500,1505-1530", "tradeTime": ["0930-1130", "1300-1500", "1505-1530"], "pre": "79.48", "date": "20221019", "data": "0930,79.41,333522,79.410,4200;0931,79.50,2154939,79.504,27100......1529,78.37,23511,78.862,300.00;1530,78.37,23511,78.862,300.00", "dotsCount": 268, "dates": ["20221019"], "afterTradeTime": "1500", "marketType": "HS_stock_gem" } }
|
这是中证300波动指数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "120_000803": { "name": "300波动", "open": 1, "stop": 0, "isTrading": 0, "rt": "0930-1130,1300-1500", "tradeTime": ["0900-1600"], "pre": "6075.0680", "date": "20221019", "data": "0930,6065.9981,203461960,13.0258,15619862;0931,6072.1400,706629050,11.4459,63892886;......1459,6006.3511,0,14.3568,0;1500,6002.7955,718455000,14.3164,59834300", "dotsCount": 421, "dates": ["20221019"], "afterTradeTime": "", "marketType": "" } }
|
这个就是根据dotsCount来绘制的,因此x轴画出来感觉都快到晚上20点了:

成交量
潜规则
不同市场,展示的分时图横坐标不一样
详见StockType.js中的配置。
不同市场对应的正则匹配规则,是根据这个wiki来设置的。
警告:我们把这个规则写死在了前端,感觉是有风险的;如果后面市场有变更,那么所有应用方都需要升级D3Charts的版本才行。
市场限制规则
详见hqHelper.js中的getLimitByMarket()方法,对市场做了一些限制,比如:
部分市场默认设置了不显示成交量
程序标记:名称:市场代码
Others_wh:外汇:97、98、99
Others_zq:债券:19、35
Others_pmetal:贵金属:41
Others_gqh:国外期货:217,、219
具体的标记和市场对应关系,可以查看StockType.js文件。
ZP任务交接
打包可以单独打hq的包,避免js太大。
分时图
五档盘口和成交明细,是我们单独请求了接口,通过回调传给了应用方,他们自己去画表格。
和普通折线图的区别,就是横坐标是固定的,及时现在只到10:30,也会画到15:00.
市场影响的因素包括(hqHelper、StockType):X轴展示的刻度,以及小数精度
可以控制台打印处理好的数据,看下结果。
数据格式:时间,加个,成交额,均价,成交量。
URL拼接规则有一个wiki进行说明,我们程序里面也封装好了。
K线图
形状是我们扩展了一个shape
未来展望
功能优化
1、调试工具:选择后,自动生成配置
2、实时性:我们前端绘图搞不搞得定?数据可以服务端接口优化,也可以我们从客户端获取。
3、异常监控
4、原生体验:RN
产品化:TradingView
我们要是能搞出来,也不错
通过组件化快速搭建一个tradingview!