以前、Python で為替レートの折れ線グラフを表示してみましたの記事を書いたときは、ローソク足を表示できなかったのですが、 Jupyter Notebook から JavaScript のライブラリーの Highcharts を読み込んで、ローソク足を表示してみました。
けっこう無理やり実装しました。 ので、あまり参考にならないかもしれません。
環境
- Python 3.5.2
- numpy-1.14.3
- pandas-0.22.0
- requests 2.18.4
- Highcharts v6.1.0
- Highstock v6.1.0
Jupyter Notebook はこの記事の通りインストールしておきました。 それから pandas もこの記事の通りインストールしておきました。
%%html マジックコマンド
%html [--isolated]
Render the cell as a block of HTML
optional arguments:
–isolated
Annotate the cell as ‘isolated’. Isolated cells are rendered inside their own <iframe> tag
Jupyter Notebook のセルの中で HTML を記述するには %%html
マジックコマンドを使うようです。
--isolated
オプションをつけると <iframe>
の中に結果を表示してくれるようです。
このオプションはどういうときに効果を発揮するのかいまいちよくわかりませんでした。
%%javascript マジックコマンド
Run the cell block of Javascript code
Jupyter Notebook のセルの中で JavaScript を記述するには %%javascript
マジックコマンドを使うようです。
%%js
と記述しても同じ意味のようで、 %%javascript
のエイリアスになっているようです。
%%javascript
のマジックコマンドを使わなくても、 %%html
マジックコマンドの中に <script>
を記述しても JavaScript は動かすことができるみたいです。
Highcharts
最初に Highcharts を使ってみます。 また、 Your First Chart | Highcharts を参考にしてみます。
この記事の 1 つコードブロックを Jupyter Notebook の 1 つのセルに入力しながら進めました。
Highcharts の読み込み
Highcharts を読み込みました。
%%html
<script src="https://code.highcharts.com/highcharts.js"></script>
チャートの表示領域の定義
チャートを表示する領域を <div>
で記述しました。
%%html
<div id="container" style="width:100%; height:400px;"></div>
チャートの表示
チャートを表示する処理を JavaScript で記述しました。
%%javascript
$(function () {
var myChart = Highcharts.chart('container', {
chart: {
type: 'bar'
},
title: {
text: 'Fruit Consumption'
},
xAxis: {
categories: ['Apples', 'Bananas', 'Oranges']
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: [{
name: 'Jane',
data: [1, 0, 4]
}, {
name: 'John',
data: [5, 7, 3]
}]
});
});
このセルを実行すると、 1 つ前のセルに入力した <div>
の箇所に棒グラフが表示されました。
$(function () { ... });
って jQuery ですけど、 Jupyter Notebook は jQuery が標準で使えるようになっているみたいでした。
初めて知りました。
Jupyter Notebook はセルを追加したり、 DOM の操作をしたりするのに jQuery を使っているようでした。
Highcharts is not defined
ただ、このままだと問題があって、 .ipynb ファイルを閉じて、開きなおすと、次のエラーが表示されました。
Javascript error adding output!
ReferenceError: Highcharts is not defined
See your browser Javascript console for more details.
Highcharts の読み込みが終わる前にチャートを表示しようとしているように見えました。 ちょっとこれじゃ都合が悪いです。
Most browsers provide similar functionality in the form of a
DOMContentLoaded
event.
$(function () { ... });
って、 DOMContentLoaded のイベントと同じ意味のようです。
DOMContentLoaded イベントは、最初のHTMLドキュメントの読み込みと解析が完了した時に発火し、 スタイルシートや画像、サブフレームの読み込みが終わるのを待ちません。 ページが完全に読み込み終わったことを検知するためにのみ、全く異なるイベント ─ load ─ を使用するべきです。 DOMContentLoaded がより適切である場合に load を誤って使用することが、 信じられないほど頻繁に行われています。
ページが完全に読み込み終わった後に処理したかったので、 load のイベントを実装しました。 が、それでも同じエラーになってしまいました。
.ipynb ファイルの内容
読み込みはどのようになっているのかな、と思って、ソースとかを見てみました。
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<script src=\"https://code.highcharts.com/highcharts.js\"></script>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%html\n",
"<script src=\"https://code.highcharts.com/highcharts.js\"></script>"
]
},
保存した .ipynb ファイルの Highcharts のモジュールの読み込みの箇所を抜粋してみました。 .ipynb ファイルは JSON の形式になっているようで、セルに入力した内容や、出力の結果が含まれているようです。
Jupyter Notebook がドキュメントの読み込み後 ($(function () { ... });
) に、この .ipynb ファイルの内容を DOM に反映しているように見えました。
Jupyter Notebook のページのソースをブラウザーから確認したらすごくシンプルで、どこにも .ipynb の内容がなかったので、動的に DOM を書き換えることによって、 .ipynb ファイルの内容を反映しているように見えたからです。
ドキュメントの読み込み後に <script src=\"https://code.highcharts.com/highcharts.js\"></script>
という内容が DOM に追加されて、そこで初めて Highcharts の読み込みが始まることになります。
だから、ページが完全に読み込み終わったことを検知する load
イベントでは、 jQuery によって動的に書き換えられた <script>
の読み込みの終わりまでは検知できないのだと思います。
だぶん、 load
イベントで検知できるのは、静的なページが完全に読み込み終わったことまでだと思います。
Highcharts が読み込み終わったことを検知するために
無理やりですけど、ここでは Highcharts の Highcharts.chart
関数を使っているので、 Highcharts.chart
が関数として定義されていたら Highcharts の読み込みが終わったものとして、チャートを表示する処理に進めるようにしてみました。
読み込みが終わっていない場合は 1 秒後 (1000 ミリ秒後)にまた同じ処理をするようにしました。
%%javascript
const plot = () => {
if (typeof Highcharts !== 'object' ||
typeof Highcharts.chart !== 'function') {
const timeoutID = setTimeout(plot, 1000);
return {};
}
var myChart = Highcharts.chart('container', {
chart: {
type: 'bar'
},
title: {
text: 'Fruit Consumption'
},
xAxis: {
categories: ['Apples', 'Bananas', 'Oranges']
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: [{
name: 'Jane',
data: [1, 0, 4]
}, {
name: 'John',
data: [5, 7, 3]
}]
});
return myChart;
};
plot();
結果は、 Gist にアップしました。
が、アクセスしても棒グラフは表示されませんでした。
%%html
や %%javascript
を使ったセルは Gist じゃ表示できないみたいでした。
初めて知りました。
Jupyter Notebook Viewer で見ると棒グラフが表示されました。
小細工 (%%html
や %%javascript
) をする場合は Jupyter Notebook Viewer を使った方がいいみたいでした。
Highcharts error #16
まだ、エラーがあります。
Error: Highcharts error #16: www.highcharts.com/errors/16
Highcharts already defined in the page
Highcharts を読み込むセルを何度も実行するとこのエラーがコンソールに出力されるのですが、まあ、放置しました。 たぶん、 Gist や Jupyter Notebook Viewer のビューアーで表示する分には、セルを何度も実行することはできないので、エラーになることはないかな、と思いましたので。
Highstock
Highstock のサンプルも試してみました。 Two panes, candlestick and volume | Highcharts を参考にしました。 無駄にテーマも適用してみました。
Highstock の読み込み
Highstock と関連するモジュールを読み込みました。
%%html
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/drag-panes.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js"></script>
チャートの表示領域の定義
チャートを表示する領域を <div>
で記述しました。
%%html
<div id="container" style="height: 400px; min-width: 310px"></div>
チャートの表示
Highstock のモジュールが読み込めているかどうかの判断は Highcharts のときと同じように実装しました。
%%javascript
const plot = () => {
if (typeof Highcharts !== 'object' ||
typeof Highcharts.stockChart !== 'function') {
const timeoutID = setTimeout(plot, 1000);
return;
}
// [CORS](https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control)
//$.getJSON('https://www.highcharts.com/samples/data/aapl-ohlcv.json', function (data) {
$.getJSON('https://gist.githubusercontent.com/va2577/5a9ec41b4875deb25894147c25dc859e/raw/2b3de82e00a73a874a4a4cfe1b12dcc713cd6b39/aapl-ohlcv.json', function (data) {
// split the data set into ohlc and volume
var ohlc = [],
volume = [],
dataLength = data.length,
// set the allowed units for data grouping
groupingUnits = [[
'week', // unit name
[1] // allowed multiples
], [
'month',
[1, 2, 3, 4, 6]
]],
i = 0;
for (i; i < dataLength; i += 1) {
ohlc.push([
data[i][0], // the date
data[i][1], // open
data[i][2], // high
data[i][3], // low
data[i][4] // close
]);
volume.push([
data[i][0], // the date
data[i][5] // the volume
]);
}
// create the chart
Highcharts.stockChart('container', {
rangeSelector: {
selected: 1
},
title: {
text: 'AAPL Historical'
},
yAxis: [{
labels: {
align: 'right',
x: -3
},
title: {
text: 'OHLC'
},
height: '60%',
lineWidth: 2,
resize: {
enabled: true
}
}, {
labels: {
align: 'right',
x: -3
},
title: {
text: 'Volume'
},
top: '65%',
height: '35%',
offset: 0,
lineWidth: 2
}],
tooltip: {
split: true
},
series: [{
type: 'candlestick',
name: 'AAPL',
data: ohlc,
dataGrouping: {
units: groupingUnits
}
}, {
type: 'column',
name: 'Volume',
data: volume,
yAxis: 1,
dataGrouping: {
units: groupingUnits
}
}]
});
});
};
plot();
Highstock のデモでは、 $.getJSON
でサーバーからデータをとってきているようでした。
CORS の問題があって、この www.highcharts.com からデータを取ってくることができないので、 Gist にアップして、それをとってくるようにしてしまいました。
大丈夫かな。
テーマ
Dark Unica のテーマを highcharts/dark-unica.js at master · highcharts/highcharts からコピペしました。
%%javascript
const theme = () => {
if (typeof Highcharts !== 'object' ||
typeof Highcharts.createElement !== 'function' ||
typeof Highcharts.setOptions !== 'function') {
const timeoutID = setTimeout(theme, 1000);
return;
}
/**
* (c) 2010-2017 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Dark theme for Highcharts JS
* @author Torstein Honsi
*/
'use strict';
/* global document */
// Load the fonts
/*
comment out
import Highcharts from '../parts/Globals.js';
*/
Highcharts.createElement('link', {
href: 'https://fonts.googleapis.com/css?family=Unica+One',
rel: 'stylesheet',
type: 'text/css'
}, null, document.getElementsByTagName('head')[0]);
Highcharts.theme = {
colors: ['#2b908f', '#90ee7e', '#f45b5b', '#7798BF', '#aaeeee', '#ff0066',
'#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
stops: [
[0, '#2a2a2b'],
[1, '#3e3e40']
]
},
style: {
fontFamily: '\'Unica One\', sans-serif'
},
plotBorderColor: '#606063'
},
title: {
style: {
color: '#E0E0E3',
textTransform: 'uppercase',
fontSize: '20px'
}
},
subtitle: {
style: {
color: '#E0E0E3',
textTransform: 'uppercase'
}
},
xAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
title: {
style: {
color: '#A0A0A3'
}
}
},
yAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
tickWidth: 1,
title: {
style: {
color: '#A0A0A3'
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.85)',
style: {
color: '#F0F0F0'
}
},
plotOptions: {
series: {
dataLabels: {
color: '#B0B0B3'
},
marker: {
lineColor: '#333'
}
},
boxplot: {
fillColor: '#505053'
},
candlestick: {
lineColor: 'white'
},
errorbar: {
color: 'white'
}
},
legend: {
itemStyle: {
color: '#E0E0E3'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#606063'
}
},
credits: {
style: {
color: '#666'
}
},
labels: {
style: {
color: '#707073'
}
},
drilldown: {
activeAxisLabelStyle: {
color: '#F0F0F3'
},
activeDataLabelStyle: {
color: '#F0F0F3'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: '#505053'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: '#505053',
stroke: '#000000',
style: {
color: '#CCC'
},
states: {
hover: {
fill: '#707073',
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: '#000003',
stroke: '#000000',
style: {
color: 'white'
}
}
}
},
inputBoxBorderColor: '#505053',
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(255,255,255,0.1)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
},
xAxis: {
gridLineColor: '#505053'
}
},
scrollbar: {
barBackgroundColor: '#808083',
barBorderColor: '#808083',
buttonArrowColor: '#CCC',
buttonBackgroundColor: '#606063',
buttonBorderColor: '#606063',
rifleColor: '#FFF',
trackBackgroundColor: '#404043',
trackBorderColor: '#404043'
},
// special colors for some of the
legendBackgroundColor: 'rgba(0, 0, 0, 0.5)',
background2: '#505053',
dataLabelsColor: '#B0B0B3',
textColor: '#C0C0C0',
contrastTextColor: '#F0F0F3',
maskColor: 'rgba(255,255,255,0.3)'
};
// Apply the theme
Highcharts.setOptions(Highcharts.theme);
};
theme();
import Highcharts from '../parts/Globals.js';
の箇所はコメントアウトしました。
代わりに Highcharts
のオブジェクトが使えるように Highcharts の読み込みを待つように修正しました。
Gist にアップしました。 Jupyter Notebook Viewer から見ることができます。
ローソク足を表示することができました。
pandas の DataFrame からローソク足を表示
今回、最終的にやりたかったことはこういうことじゃなくて、 pandas で加工してきたデータを Highstock のローソク足で表示したいのです。
調べてみたのですが、 Jupyter Notebook で、 Python のコードのセルから、 %%html
や %%javascript
のセルにデータを渡す方法が見つけられませんでした。
代わりに次のクラスと関数を見つけました。
これを使うと、 Python のコードから Jupyter Notebook へ HTML を出力することができるようです。
%%html
もやっていることは同じみたいでした。
たぶん。
なので、 Highstock を操作する JavaScript のコード自体を Python の文字列として定義しておいて、それをもとに IPython.display.HTML
のオブジェクトを生成して、 IPython.display.display
に渡してあげればやりたいことが実現できるようです。
モジュールを読み込む
pandas と HTML と display を読み込みました。
それから、データをみんかぶ FX の米ドル/円のチャートから取ってこようと思うので、 requests も読み込みました。
requests は事前にインストールしておく必要があります。
pip install requests
でインストールしておきました。
import pandas as pd
import requests
from IPython.display import HTML
from IPython.display import display
データを取ってくる
以前、みんかぶ FX のチャートのレートを取得してみましたの記事を書いたのと同じところからデータを取ってきました。
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134' }
r = requests.get('https://fx.minkabu.jp/api/v2/bar/USDJPY/daily.json?count=240', headers=headers)
r.raise_for_status()
r.json()[:10]
結果です。
r.json()
はリスト・配列の形式になっているようで、先頭の 10 行を表示してみました。
[[1497474000000, '109.554', '110.979', '109.258', '110.911'],
[1497560400000, '110.925', '111.415', '110.637', '110.89'],
[1497819600000, '110.841', '111.601', '110.704', '111.526'],
[1497906000000, '111.514', '111.782', '111.306', '111.454'],
[1497992400000, '111.434', '111.738', '111.035', '111.375'],
[1498078800000, '111.337', '111.446', '110.93', '111.319'],
[1498165200000, '111.32', '111.427', '111.142', '111.226'],
[1498424400000, '111.157', '111.939', '111.101', '111.86'],
[1498510800000, '111.82', '112.464', '111.459', '112.337'],
[1498597200000, '112.337', '112.416', '111.829', '112.28']]
pandas の DataFrame
データは、日時と OHLC の 2 次元配列の形式になっているので、あまり引数を指定することなく pandas の DataFrame にできました。
df = pd.DataFrame(data=r.json(), columns=['time', 'open', 'high', 'low', 'close'])
df.head()
結果です。 Highstock のデモのデータの形式を見ると、こちらも 2 次元の配列で日時と OHLC を渡していたので、日時はインデックスにしませんでした。
time open high low close
0 1497474000000 109.554 110.979 109.258 110.911
1 1497560400000 110.925 111.415 110.637 110.89
2 1497819600000 110.841 111.601 110.704 111.526
3 1497906000000 111.514 111.782 111.306 111.454
4 1497992400000 111.434 111.738 111.035 111.375
DataFrame を編集する
日時が UTC (協定世界)なので、 EEST(Eastern European Summer Time)(UTC+3) にしました。 こうすると、週の始まりが月曜日の 0:00 からになり(通常時間は 1:00 からですけれども)、日足が 5 本になるからです。
# [From Timestamps to Epoch](https://pandas.pydata.org/pandas-docs/stable/timeseries.html#from-timestamps-to-epoch)
utc = pd.to_datetime(df['time'], unit='ms')
utc3 = utc + pd.DateOffset(hours=3)
epoch = (utc3 - pd.Timestamp("1970-01-01")) / pd.Timedelta('1ms')
data1 = { 'time': epoch.astype('int64').values, 'open': df['open'].astype('float64').values, 'high': df['high'].astype('float64').values, 'low': df['low'].astype('float64').values, 'close': df['close'].astype('float64').values}
columns1 = ['time', 'open', 'high', 'low', 'close']
df2 = pd.DataFrame(data=data1, columns=columns1)
df2.head()
結果です。
time open high low close
0 1497484800000 109.554 110.979 109.258 110.911
1 1497571200000 110.925 111.415 110.637 110.890
2 1497830400000 110.841 111.601 110.704 111.526
3 1497916800000 111.514 111.782 111.306 111.454
4 1498003200000 111.434 111.738 111.035 111.375
Highstock のローソク足のチャートを表示する
Highstock のローソク足を表示するコードを次のように記述しました。
template = """
<div id="container" style="height: 400px; min-width: 310px"></div>
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js"></script>
<script>
const plot = () => {{
if (typeof Highcharts !== 'object' ||
typeof Highcharts.stockChart !== 'function') {{
const timeoutID = setTimeout(plot, 1000);
return;
}}
// create the chart
Highcharts.stockChart('container', {{
rangeSelector: {{
selected: 1
}},
title: {{
text: '米ドル/円 daily'
}},
series: [{{
type: 'candlestick',
name: 'AAPL',
data: {data}
}}]
}});
}};
plot();
</script>
"""
display(HTML(template.format(data=df2.values.tolist())))
ほとんど文字列の定義になってしまっています。 その文字列は JavaScript のコードになっています。 Highstock のライブラリーを操作するコードです。
フォーマットできる形式になっていて、 JavaScript の {...}
は Python の書式に影響してしまうので、 {{...}}
のようにエスケープしました。
エスケープしていない data: {data}
の箇所に pandas の DataFrame のデータを埋め込みます。
最後の行の display(HTML(template.format(data=df2.values.tolist())))
が Python のコードです。
template.format(data=df2.values.tolist())
で、文字列で定義していた JavaScript のコードの data: {data}
の箇所に pandas で編集したデータを埋め込んでいます。
Highstock のローソク足のチャートのデータの形式が 2 次元配列なので、 df.values.tolist()
のようにして、 DataFrame から 2 次元配列の形式(時刻と OHLC) に変換しています。
HTML(...)
のところで Python の文字列を Jupyter Notebook の IPython.display.HTML クラスにしています。
display(...)
のところで HTML を Jupyter Notebook に表示しています。
JavaScript のコードを全部 Python の文字列で記述してしまっていて、あまり綺麗じゃないですけど、 Python と pandas と Jupyter Notebook を使って、 Highstock のローソク足のチャートを表示することができました。
Gist のソース
Gist にアップしました。
Jupyter Notebook Viewer から結果を見ることができます。
終わり
次の記事を書いたときに Jupyter Notebook にローソク足を表示しようと思っていたのですが、ようやく表示することができました。