如何使用Tushare和Echarts來畫股票K線圖
技術支援
Tushare大資料社群官網
首先介紹一下這次要使用的兩個工具,Tushare是一個基于Python的金融資料接口,擁有豐富的資料内容,如股票、基金、期貨、數字貨币等行情資料,公司财務、基金經理等基本面資料等(詳細介紹進官網)。如果你之前沒有注冊過Tushare,而且恰好對金融量化、金融資料分析感興趣,不妨注冊一個Tushare賬号,可以擷取想要的資料,點選注冊。
ECharts官網
常見的資料可視化庫:
- D3.js 目前 Web 端評價最高的 Javascript 可視化工具庫(入手難)
- ECharts.js 百度出品的一個開源 Javascript 資料可視化庫
- Highcharts.js 國外的前端資料可視化庫,非商用免費,被許多國外大公司所使用
- AntV 螞蟻金服全新一代資料可視化解決方案 等等
- Highcharts 和 Echarts 就像是 Office 和 WPS 的關系
ECharts,一個使用 JavaScript 實作的開源可視化庫,可以流暢的運作在 PC 和移動裝置上,相容目前絕大部分浏覽器(IE8/9/10/11,Chrome,Firefox,Safari等),底層依賴矢量圖形庫 ZRender,提供直覺,互動豐富,可高度個性化定制的資料可視化圖表,詳細介紹可進官網檢視.
大白話:
- 是一個JS插件
- 性能好可流暢運作PC與移動裝置
- 相容主流浏覽器
- 提供很多常用圖表,且可定制(折線圖、柱狀圖、散點圖、餅圖、K線圖)
實作結果
先展示一下最終的結果,下面第一張圖是利用Tushare和Echarts做出而來的貴州茅台的日K圖,第二張圖是同花順網頁版的貴州茅台的日K圖。除了資料的展示不一樣之外,資料的内容是一樣的。
實作步驟
1.搭建運作環境
我這裡的運作環境是Python 3.8.0,Tushare的版本是1.2.62的,因為Tushare是Python的第三方包,是以需要導入,詳細步驟見:說明。
# 導入tushare
import tushare as ts
# 設定token
ts.set_token('your token here')
# 初始化pro接口
pro = ts.pro_api( )
至此資料擷取的環境已經搭建好了,我們來試一下擷取的資料是什麼樣子的。
daily_data=pro.daily(ts_code='600519.SH', start_date='20210101', end_date='20210220')
print(daily_data)
print(type(daily_data))
輸出的結果是下面的圖中所顯示的,你會發現有交易日期,開盤價,收盤價,最高價,最低價,成交量等資料。資料的形式是DataFrame類型的,也就是說
daily_data
具備DataFrame的一般方法和屬性,是以資料的顯示是從最新資料顯示在最前面,但是K線需要的是最近成交的資料顯示在最後面,待會我們在來處理資料的問題,這一步說明我們資料接口已經可以使用了。
現在開始要引入資料可視化的JavaScript檔案,點選進入下載下傳位址,會彈出下面的圖檔,需要下載下傳的内容是
echarts.js
或者
echarts.min.js
都可以,下載下傳完後要記得放到對應的工作環境中,友善後面的引入。
2.先畫圖
在畫圖之前,我們有必要對Echarts有個基本的了解,建議先去Echarts官網浏覽5 分鐘上手 ECharts,可以對Echarts有個基本的了解.
使用步驟:
- 引入 ECharts:通過标簽方式直接引入建構好的 echarts 檔案。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <!-- 引入 ECharts 檔案 --> <script src="echarts.min.js"></script> </head> </html>
- 準備一個具備大小的DOM容器,用來放圖。
- 初始化echarts執行個體對象
- 指定配置項和資料(option)
var option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [{ data: [820, 932, 901, 934, 1290, 1330, 1320], type: 'line' }] };
- 将配置項設定給echarts執行個體對象
需要了解的主要配置:
series
xAxis
yAxis
grid
tooltip
title
legend
color
- series:系列清單。每個系列通過
決定自己的圖表類型;大白話:圖示資料,指定什麼類型的圖示,可以多個圖表重疊。type
- xAxis:直角坐标系 grid 中的 x 軸
- boundaryGap: 坐标軸兩邊留白政策 true,這時候刻度隻是作為分隔線,标簽和資料點都會在兩個刻度之間的帶(band)中間。
- yAxis:直角坐标系 grid 中的 y 軸
- grid:直角坐标系内繪圖網格。
- title:标題元件
- tooltip:提示框元件
- legend:圖例元件
- color:調色盤顔色清單[]
OK,對上面的知識點有了一定的了解之後,我們可以開始來畫我們需要的K線圖了。我先把Echarts的全部代碼先貼出來,後面在分别解釋沒塊代碼的作用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>debug</title>
<style>
#k-box{
height: 100%;
width: 50%;
background: white;
display: inline-block;
}
</style>
<script src="echarts.min.js"></script>
</head>
<body>
<div id="k-box"></div>
<script>
const chartDom = document.getElementById('k-box');
const myChart = echarts.init(chartDom);
var option;
const upColor = '#008000';
const downColor = '#c00c00';
// 對資料進行處理的函數,将交易日期,成交量,價格資訊分别放置在不同的數組
function splitData(rawData) {
var categoryData = [];
var values = [];
var volumes = [];
for (var i = 0; i < rawData.length; i++) {
categoryData.push(rawData[i].splice(0, 1)[0]);
values.push(rawData[i]);
volumes.push([i, rawData[i][4], rawData[i][0] > rawData[i][1] ? 1 : -1]);
}
return {
categoryData: categoryData,
values: values,
volumes: volumes
};
}
// 用于計算均線的函數
function calculateMA(dayCount, data) {
var result = [];
for (var i = 0, len = data.values.length; i < len; i++) {
if (i < dayCount) {
result.push('-');
continue;
}
var sum = 0;
for (var j = 0; j < dayCount; j++) {
sum += data.values[i - j][1];
}
result.push(+(sum / dayCount).toFixed(3));
}
return result;
}
// data的資料格式是[['交易日期',開盤價,收盤價,最高價,最低價,成交量][][]....]
var data = splitData(data);
myChart.setOption(option = {
animation: false,
legend: {
bottom: 10,
left: 'center',
data: ['Dow-Jones index', 'MA5', 'MA10', 'MA20', 'MA30']
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
borderWidth: 1,
borderColor: '#ccc',
padding: 10,
textStyle: {
color: '#000'
},
position: function (pos, params, el, elRect, size) {
var obj = {top: 10};
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 30;
return obj;
}
// extraCssText: 'width: 170px'
},
axisPointer: {
link: {xAxisIndex: 'all'},
label: {
backgroundColor: '#777'
}
},
toolbox: {
feature: {
dataZoom: {
yAxisIndex: false
},
brush: {
type: ['lineX', 'clear']
}
}
},
brush: {
xAxisIndex: 'all',
brushLink: 'all',
outOfBrush: {
colorAlpha: 0.1
}
},
visualMap: {
show: false,
seriesIndex: 5,
dimension: 2,
pieces: [{
value: 1,
color: downColor
}, {
value: -1,
color: upColor
}]
},
grid: [
{
left: '10%',
right: '8%',
height: '50%'
},
{
left: '10%',
right: '8%',
top: '63%',
height: '16%'
}
],
xAxis: [
{
type: 'category',
data: data.categoryData,
scale: true,
boundaryGap: false,
axisLine: {onZero: false},
splitLine: {show: false},
splitNumber: 20,
min: 'dataMin',
max: 'dataMax',
axisPointer: {
z: 100
}
},
{
type: 'category',
gridIndex: 1,
data: data.categoryData,
scale: true,
boundaryGap: false,
axisLine: {onZero: false},
axisTick: {show: false},
splitLine: {show: false},
axisLabel: {show: false},
splitNumber: 20,
min: 'dataMin',
max: 'dataMax'
}
],
yAxis: [
{
scale: true,
splitArea: {
show: true
}
},
{
scale: true,
gridIndex: 1,
splitNumber: 2,
axisLabel: {show: false},
axisLine: {show: false},
axisTick: {show: false},
splitLine: {show: false}
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1],
start: 98,
end: 100
},
{
show: true,
xAxisIndex: [0, 1],
type: 'slider',
top: '85%',
start: 98,
end: 100
}
],
series: [
{
name: 'Dow-Jones index',
type: 'candlestick',
data: data.values,
itemStyle: {
color: upColor,
color0: downColor,
//改動過
borderColor: upColor,
borderColor0: downColor
},
tooltip: {
formatter: function (param) {
param = param[0];
return [
'Date: ' + param.name + '<hr size=1 style="margin: 3px 0">',
'Open: ' + param.data[0] + '<br/>',
'Close: ' + param.data[1] + '<br/>',
'Lowest: ' + param.data[2] + '<br/>',
'Highest: ' + param.data[3] + '<br/>'
].join('');
}
}
},
{
name: 'MA5',
type: 'line',
data: calculateMA(5, data),
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: 'MA10',
type: 'line',
data: calculateMA(10, data),
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: 'MA20',
type: 'line',
data: calculateMA(20, data),
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: 'MA30',
type: 'line',
data: calculateMA(30, data),
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: 'Volume',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
data: data.volumes
}
]
}, true);
option && myChart.setOption(option);
</script>
</body>
</html>
初始化echarts執行個體對象,前面的DOM容器已經放好。
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
// 設定股票上漲和下跌的顔色
var upColor = '#008000';
var downColor = '#c00c00';
開始将圖檔畫在網頁上,我們先設定标題,即圖檔展示後顯示的名稱。
myChart.setOption(option = {
// 圖檔的名稱,字型等樣式.
title: {
text: '股票日K',
left: 0,
// 設定字型樣式
textStyle: {
fontSize:28,
}
},
// 是否開啟動畫效果.
animation: true,
},true);
legend圖例元件展現了不同系列的标記(symbol),顔色和名字。可以通過點選圖例控制哪些系列不顯示。
legend: {
// 距離top 9個像數.的值可以是像 20 這樣的具體像素值,可以是像 '20%' 這樣相對于容器高寬的 百分比,也可以是 'top', 'middle', 'bottom'。
top: 9,
left: '45%',
data: ['日K', 'MA5', 'MA10', 'MA20', 'MA30']
},
tooltip是否顯示提示框元件,包括提示框浮層和 axisPointer。
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255,255,255,0.6)',
// 坐标軸訓示器是訓示坐标軸目前刻度的工具。
axisPointer: {
type: 'cross'
},
borderWidth: 1,
borderColor: '#ccc',
padding: 10,
textStyle: {
color: '#000'
},
position: function (pos, params, el, elRect, size) {
var obj = {top: 10};
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 30;
return obj;
}
},
axisPointer滑鼠移動到一定位置後Y軸和X軸上顯示的文字說明.
axisPointer: {
link: {xAxisIndex: 'all'},
label: {
// {# 滑鼠移到那裡x軸和y軸顯示的數字#}
backgroundColor: '#777'
}
},
toolbox 工具欄。内置有導出圖檔,資料視圖,動态類型切換,資料區域縮放,重置五個工具,具體需要什麼功能看個人需要。
toolbox: {
feature: {
dataZoom: {
yAxisIndex: false
},
brush: {
type: ['lineX', 'clear']
}
}
},
brush
是區域選擇元件,使用者可以選擇圖中一部分資料,進而便于向使用者展示被選中資料,或者他們的一些統計計算結果,隐藏K線資料和5日均線資料。
brush: {
xAxisIndex: 'all',
brushLink: 'all',
outOfBrush: {
colorAlpha: 0.1
}
},
visualMap
是視覺映射元件,用于進行『視覺編碼』,也就是将資料映射到視覺元素(視覺通道)。
visualMap: {
show: false,
seriesIndex: 5,
dimension: 2,
pieces: [{
value: 1,
color: downColor
}, {
value: -1,
color: upColor
}]
},
grid直角坐标系内繪圖網格,單個 grid 内最多可以放置上下兩個 X 軸,左右兩個 Y 軸。可以在網格上繪制折線圖,柱狀圖,散點圖(氣泡圖)。
grid: [
{ // 用于控制圖像與左邊的邊距
left: '5%',
right: '8%',
height: '50%'
},
{
left: '10%',
right: '8%',
top: '63%',
height: '16%'
}
],
xAxis設定X軸等相關的資料,樣式,字型樣式,寬高等。
xAxis: [
{
type: 'category',
// 日期,格式是['2016-03-01','2016-03-02','2016-03-03'.......]
data: data.categoryData,
scale: true,
boundaryGap: false,
axisLine: {onZero: false},
splitLine: {show: false},
splitNumber: 20,
min: 'dataMin',
max: 'dataMax',
axisPointer: {
z: 100
}
},
{
type: 'category',
gridIndex: 1,
data: data.categoryData,
scale: true,
boundaryGap: false,
axisLine: {onZero: false},
axisTick: {show: false},
splitLine: {show: false},
axisLabel: {show: false},
splitNumber: 20,
min: 'dataMin',
max: 'dataMax'
}
],
yAxis設定Y軸等相關的資料,樣式,字型樣式,寬高等。
yAxis: [
{
scale: true,
splitArea: {
show: true
}
},
{
scale: true,
gridIndex: 1,
splitNumber: 2,
axisLabel: {show: false},
axisLine: {show: false},
axisTick: {show: false},
splitLine: {show: false}
}
],
dataZoom
元件 用于區域縮放,進而能自由關注細節的資料資訊,或者概覽資料整體,或者去除離群點的影響。
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1],
start: 98,
end: 100
},
{
show: true,
xAxisIndex: [0, 1],
type: 'slider',
top: '85%',
start: 98,
end: 100
}
],
series-candlestickCandlestick 即我們常說的
K線圖
。
series: [
{
name: '日K',
type: 'candlestick',
// 股票價格,格式是[['開盤價','收盤價','最低價',最高價,成交量],['開盤價','收盤價','最低價',最高價,成交量]....]
data: data.values,
itemStyle: {
color: upColor,
color0: downColor,
borderColor: null,
borderColor0: null
},
tooltip: {
formatter: function (param) {
param = param[0];
return [
'Date: ' + param.name + '<hr size=1 style="margin: 3px 0">',
'Open: ' + param.data[0] + '<br/>',
'Close: ' + param.data[1] + '<br/>',
'Lowest: ' + param.data[2] + '<br/>',
'Highest: ' + param.data[3] + '<br/>'
].join('');
}
},
{
name: 'Volume',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
// [0, 168890000, 1]
data: data.volumes
}
},
MA5:5日均線線的樣式二設定,下面的代碼隻展示了5日均線,其他均線的原理其實是一樣的,這裡的
data: calculateMA(5, data)
借助了函數
calculateMA( )
function calculateMA(dayCount, data) {
var result = [];
for (var i = 0, len = data.values.length; i < len; i++) {
if (i < dayCount) {
result.push('-');
continue;
}
var sum = 0;
for (var j = 0; j < dayCount; j++) {
sum += data.values[i - j][1];
}
result.push(+(sum / dayCount).toFixed(3));
}
return result;
}
{
name: 'MA5',
type: 'line',
data: calculateMA(5, data),
smooth: true,
showSymbol: false,
lineStyle: {
opacity: 0.8
}
最後不要忘記将剛才指定的配置項和資料顯示圖表
到現在,資料展示方面的圖已經出做來了,但是如果沒有資料的提供,K線圖還是畫不出來的,接下來,我們要處理資料了,注意資料的格式是
[[内容1],[内容2],[内容3]...]
,二維數組的形式展現.
3.後填資料
在搭建運作環境中,我們已經知道從Tushare中提取的資料是DataFrame形式的,但是提供給JavaScript的資料是二維數組,一般來說,資料的處理應該在後端處理,前端就負責實作資料的展現,是以我建議在後端就把資料處理好,後面直接傳給前端。
股票的K線圖,從上市那天就會有,是以,一般來說我們要擷取的是股票上市那天到今天的交易資料。
import tushare as ts
import time
pro = ts.pro_api( )
# 實際應用用這裡用的是參數傳遞,不會定死。
ts_code = '600519.SH'
# 我們需要的是公司的上市日期,是否退市,退市日期
data = pro.stock_basic(ts_code=ts_code,fields='list_status,list_date,delist_date')
data=data.iloc[0]
# 輸出結果: 0 L 20010827 None
# L:上市 D:退市 P:暫停上市
# 如果不是退市,那麼結束日期(YYYYMMDD)定為今天.
start_date =data['list_date']
if data['list_status'] !='D':
end_date = time.strftime("%Y %m %d").replace(' ','')
else:
# 如果是退市,,那麼結束日期(YYYYMMDD)定為退市日期
end_date=data['delist_date']
# 擷取股票從上市到現在(退市)的所有資訊,
all_daily_info = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date)
daily_info_list=[]
# ['交易日期','開盤價','收盤價','最低價',最高價,成交量]
sele_daily_info=all_daily_info[['trade_date','open','close','low','high','vol']].sort_index(ascending=False)
for num in range(len(sele_daily_info)):
# 将DataFrame格式的資料轉換成清單,再轉換成二維數組
daily_info_list.append(list(sele_daily_info.iloc[num]))
OK,資料也處理好了,現在就需要将資料傳遞到前端了,如果該方法我就不涉及了,方法也挺多的,我用的是Django前端架構,也可以用Ajax傳遞。好了,至此,資料方面和可視化方面已經全部做好了,最終的效果就是上面第一張圖。如果有什麼疑問,歡迎咨詢。