【第8回】株価チャート配信サーバー
February 11, 2021
株価サーバー python無一物中Project記事一覧
- 【第1回】株価サーバーの構築の概要
- 【第2回】無尽蔵から株価データの一括取得
- 【第3回】無尽蔵株価データから銘柄毎の時系列データを生成
- 【第4回】無尽蔵から自動で株価データを取得し株価データベースを更新
- 【第5回】出来ず株価の補填と株式分割を自動調整してチャート表示
- 【第6回】mplfinanceを使用してテクニカルチャート表示
- 【第7回】テクニカル指標による銘柄スクリーニング
- 【第8回】株価チャート配信サーバー(本記事)
- 【第9回】チャート形状の認識
はじめに
本連載の【第5回】と【第6回】で、mplfinanceを使用して株価チャートを表示させる方法を紹介しました。これはいずれもpythonが動作するデスクトップ環境でチャートを表示させるものであり、あまり実用性はありませんでした。
そこで今回は、株価チャート配信サーバを構築し、webブラウザでどこからでも株価チャートを表示させることを目指します。
実装には、pythonでwebサイトサーバ側の振る舞いを記述できるFlaskというフレームワークを使用します。python+Flask+mplfinanceの実行環境を、docker-composeで動かし、システムとしてまとめます。
百聞は一見に如かず、まずはデモサイトで動作を確認してみてください。レスポンシブデザインではないので、PC専用ですが。。
尚、今回のソースコードは、GitHubに置いています。
実行環境
ディレクトリ構成
chart_demo/
Dockerfile
docker-compose.yml
opt/
flask_chart.py # 株価配信サーバのソース
muzinzo/ # チャートを表示するための私設パッケージ
share/ # 株価データを置く場所
data/
kdic.csv # 銘柄辞書
1000/
1001.csv
1002.csv
...
2000/
...
9000/
9001.csv
...
static/ # Flaskで使用するcssファイル等
templates/ # Flaskで使用するhtmlファイル
data/ ディレクトリの中に、1000/ ~ 9000/ のサブディレクトリを切ってその中に、「証券コード.csv」という名前の株価日足時系列データを置きます。
kdic.csvは、以下の形式の銘柄名の辞書csvファイル
1001,日経225,東証1部
1002,TOPIX,東証1部
1301,極洋,東証1部
...
9995,グローセル,東証1部
9996,サトー商会,JAQ
9997,ベルーナ,東証1部
株価日足時系列データのcsvファイルの例は以下。
2015/01/05,275,277,274,275,239000000
2015/01/06,274,275,270,272,480000000
2015/01/07,270,273,270,271,217000000
...
2021/02/05,454,494,452,493,7411100000
2021/02/08,498,528,498,512,6054100000
2021/02/09,521,530,505,510,4063500000
このデータ形式による株価データの取得方法は、は本連載の【第2回】~【第4回】に載せていますので、参考にしてください。
Flaskによる株価チャート配信サーバ
以下はFlaskによる株価チャート配信サーバのソースです。たったこれだけ。。。チャート表示に私設のMuzinzoクラスを使っていて中身が隠ぺいされていますが、全貌はGitHubのソースを見てください。 私設Muzinzoクラスを使用しないバージョンは、Qiitaで以前公開しました。
import os
import glob
import io
from flask import Flask, send_file, request, render_template
from flask_cors import CORS
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from muzinzo.Muzinzo import Muzinzo
app = Flask(__name__)
CORS(app)
DATA_PATH = "/root/opt/share/data/"
mz = Muzinzo(DATA_PATH)
# http://サーバ:5000/
@app.route('/')
def hello():
return "Chart Server"
# http://サーバ:5000/candle2?code=1001&term=200&volume=True
@app.route('/candle2')
def candle2():
code = request.args.get('code', default=None, type=str)
term = request.args.get('term', default=200, type=int)
volume = request.args.get('volume', default="True", type=str)
return render_template("candle2.html", title="chart", code=code, term=term, volume=volume)
# http://サーバ:5000/candle?code=1001&term=200&volume=True
@app.route("/candle")
def candle():
code = request.args.get('code', default=None, type=str)
term = request.args.get('term', default=200, type=int)
volume = str2bool(request.args.get('volume', default="True", type=str))
stochastic = str2bool(request.args.get('stochastic', default="True", type=str))
macd = str2bool(request.args.get('macd', default="True", type=str))
mav = str2bool(request.args.get('mav', default="True", type=str))
image = io.BytesIO()
mz.plot_chart(code, term, volume=volume, stochastic=stochastic, macd=macd, mav=mav)
plt.savefig(image, format='png')
image.seek(0)
return send_file(image, attachment_filename="image.png")
class StockList:
def __init__(self, code, name):
self.code = code
self.name = name[:8]
def walk_around(dic, sl):
for i in range(1, 10):
input_path = mz.get_database_path() + str(i*1000) + "/*.csv"
csv_files = sorted(glob.glob(input_path))
for csv_file in csv_files:
filename = os.path.basename(csv_file)
code = filename[0:4]
s = StockList(code, dic[code].name)
sl.append(s)
# http://サーバ:5000/list/
@app.route("/list")
def list():
dic = mz.get_dict()
sl = []
walk_around(dic, sl)
return render_template("list.html", title='Stock List', members=sl)
def str2bool(s):
return s.lower() in ["true", "t", "yes", "y", "1"]
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
http://サーバ名:5000/
によりroot()が走ります。ここでは、クライアントに”Chart Server”という文字列を表示させるだけです。
http://サーバ名:5000/candle?code=6897&term=200&volume=True
によりcandle()が走ります。
candle()では、urlパラメータを抽出し、それに従いmpl.plot()でチャートを表示し、それを(メモリ上の)imageにpngでセーブし、クライアントに返します。
同じパラメータでcandle2()もありますが、candle()では画像を直接返すのに対し、candle2()では、画像をimgタグで貼り付けたhtmlページを返すようにしています。ページをデコるような場合は、こちらのほうがよろしいかと。
http://サーバ名:5000/list
によりlist()が走ります。
list()では、左側に銘柄のselector、右側にチャートの2分割画面のhtmlを生成してクライアントに返します。Flaskでhtmlを返すには、render_template()により、あらかじめテンプレートとして用意したhtmlファイル(ここではlist.html)に、必要な値を挿入したページを作成します。
以下、銘柄リストを選択するselectとチャートを表示するiframeを並べただけのhtmlですが、銘柄毎にoptionが置換されます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>{{ title }}</title>
<link rel="stylesheet" type= "text/css" href= "{{ url_for('static', filename='css/list.css') }}">
</head>
<body onload="document.forms.frm.sel.size = 30;">
<script>
window.onresize = function change_height() {
document.forms.frm.select_code.size = window.innerHeight/24;
}
function disp_chart() {
var value = document.getElementById("select_code").value;
var url = '/candle2?code='+value+'&term=100';
document.getElementById('inline-frame').contentWindow.location.replace(url);
}
</script>
<form id="frm" width=20%>
<select id="select_code" size="30" onchange="disp_chart()">
{% for member in members %}
<option value="{{ member.code}}">{{ member.code}} {{ member.name }}</option>
{% endfor %}
</select>
</form>
<iframe id="inline-frame"
align="top"
width=80%
height=600
src="/candle?code=1001">
</iframe>
</body>
</html>
docker-composeによる実装
pythonの実行イメージを作って、そこでFlaskサーバを稼働させています。
FROM python:3.9-slim
USER root
RUN apt-get update
RUN apt-get -y install locales && \
localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm
ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NOWARNINGS yes
RUN apt-get install -y vim
RUN pip install --upgrade pip && \
pip install --upgrade setuptools && \
pip install --upgrade Flask flask_cors mplfinance japanize-matplotlib
RUN cp -p /usr/local/lib/python3.9/site-packages/japanize_matplotlib/fonts/ipaexg.ttf /usr/local/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/
RUN echo "font.family : IPAexGothic">>/usr/local/lib/python3.9/site-packages/matplotlib/mpl-data/matplotlibrc
CMD ["python", "/root/opt/flask_chart.py"]
mplfinanceで株価チャートのタイトルや軸名などを日本語で表示させる場合、日本語フォントを別途インストールする必要があります。日本語フォントとしてIPAフォントをインストールする場合が多いのですが、IPAフォント単独でpipでインストールすることができません。matplotlibの日本語対応版のjapanize-matplotlibをインストールすると、IPAフォントがいっしょにインストールされるので、pipでjapanize-matplotlibをインストールし、その後、IPAフォントをmplfinanceのフォントフォルダーにコピーするようにしました。
docker-compose.yml
version: '3'
services:
chart:
restart: always
build: .
container_name: 'chart'
working_dir: '/root/opt'
tty: true
ports:
- 5000:5000
volumes:
- ./opt:/root/opt
株価サーバの起動
$ docker-compose up -d --build
必要に応じてサーバの5000番ポートを開けておいてください。
株価チャートを画像として単独表示させる場合は、webブラウザで、
http://サーバ名:5000/candle?code=1301&term=100&volume=True
を叩けば表示されます。
http://サーバ名:5000/candle2?code=1301&term=100&volume=True
を叩けば、株価チャートをimgタグで埋め込んだhtmlが表示されます。ブラウザのウィンドウサイズに応じて画像が拡大縮小するようにしています。
を叩けば、銘柄選択selector付チャートページが表示されます。デモサイトはこちらです。
まとめ
無一物中Project第8回では、サーバで株価チャートを配信する方法を紹介しました。