🐍
PythonとGCP Cloud Functionsで指定した企業の株価データとチャート図を定期的にSlack通知する
(※6095の銘柄でSlack通知させたイメージ)
正月休みでノリで作ったスクリプトについて書いていきます。
やりたいこと
- 指定した企業の株価を定期的にSlackに通知したい
- 推移を比較したいので、一定期間の株価の推移のグラフが欲しい
- できれば無料でやりたい
背景
- 自社の株価の推移や時価総額をなんとなく知っておきたかった
- 投資目的ではなかったので、だいたいデイリーくらいで追えればよい
- これまでは気が向いたら株価をググっていたが、めんどくさいのでSlackに通知してほしいと思った
使用技術
- Python(3.9)
- pandas-datareader
- mplfinance(matplotlib)
- GCP
- Cloud Functions
- Cloud Schedulder
- Cloud Pub/Sub
- Docker(開発環境)
- Slack bot API
リポジトリ
- 本リポジトリをCloneしてCloud Functionsにデプロイ→環境変数を設定すれば、Slackに通知されるようになります
実装
概要
- 大まかな構成は上記の通りで、Cloud FunctionsでPythonスクリプトを動かしてデータ取得→Slack通知という流れ
- Cloud Schedulerでcronを定期実行し、Cloud Pub/Subにメッセージを送信
- Cloud Pub/Subのキューイングでメッセージを受信をトリガーにCloud Functions実行
- Cloud Functionsに登録したPythonスクリプトを実行する
API選定
- 色々と調べた結果、無料でAPIを叩けてかつデイリーで株価を取得できるStooqというサイトのAPIを使用した
- 無料の株価APIについて調べたところ、Quandlが有名だが日本株の無償データは2017年末で終了していた
- 株価のAPIは有料のものが多く(例: JPX)、かつ個人で使うには高価なものが多かった
- スクレイピングは禁止されているサイトが多い(例: Yahooファイナンス)ので、大人しくやめておいた
- スプレッドシートの関数経由で株価を取得できるGoogle Financeは、日本株のデータは取得できないようだった
- Stooqはpandas関連のpipライブラリがあり、API利用が比較的容易にできそうだった
- これにより、使用する言語はPythonに自動的に決定した
スクリプト
ということで、作ったスクリプトのメインコードがこちら。
以下でポイントを解説していきます。
株価データ取得
- 以下のメソッドでStooqのAPIを用いて株価を取得しています
- 後ほどチャート図を生成するために、取得したデータを一度CSVファイルにしています
- Cloud FunctionsではCSVファイルの書き込みは
tmp
ディレクトリでないと不可なので注意
- Cloud FunctionsではCSVファイルの書き込みは
import pandas_datareader.stooq as stooq
def generate_csv_with_datareader():
"""
Generate a csv file of OHLCV with date with stooq API
"""
# 株価推移の開始日を指定(3ヶ月を指定)
start_date = today - relativedelta(months=3)
# Stooqのライブラリ経由でAPIを叩く(stock_codeは環境変数で株コードを指定)
stooq_reader = stooq.StooqDailyReader(stock_code, start=start_date, end=today)
# APIで取得したデータを一旦CSVファイルにする
stooq_reader.read().to_csv(f"/tmp/{str(today)}.csv")
チャート図の生成
- 生成したCSVファイルを元に、pandasとmplfinanceでチャート図を生成
- mplfinanceでローソク足チャートを生成する際の
style
は好みのスタイルを指定可能: https://vucavucalife.com/python-mplfinance-candle-stick-style/
- mplfinanceでローソク足チャートを生成する際の
import pandas as pd
import mplfinance as mpf
def generate_stock_chart_image():
"""
Generate a six-month stock chart image with mplfinance
"""
dataframe = pd.read_csv(f"/tmp/{str(today)}.csv", index_col=0, parse_dates=True)
# 推移チャートを作るために日付でデータをソートする
dataframe = dataframe.sort_values('Date')
mpf.plot(dataframe, type='candle', figratio=(12,4),
volume=True, mav=(5, 25), style='yahoo',
savefig=f"/tmp/{str(today)}.png")
Slack通知
- 取得したCSVの(ヘッダーを覗いた)一番上の行が最新の株価情報なので、その内容をSlack通知用の
Slack
クラスに渡す- Cloud Functionsでは
main
関数を呼び出しているが、引数にevent
とcontext
を指定しないと動かないので指定しています
- Cloud Functionsでは
import csv
def main(event, context):
"""
The main function that will be executed when this Python file is executed
"""
generate_csv_with_datareader()
generate_stock_chart_image()
with open(f"/tmp/{str(today)}.csv", 'r', encoding="utf-8") as file:
# Skip header row
reader = csv.reader(file)
header = next(reader)
for i, row in enumerate(csv.DictReader(file, header)):
# Send only the most recent data to Slack notification
if i == 0:
slack.Slack(today, row).post()
また、Slack通知クラスは別ファイルに切り出している(後々LINEなどの他の通知先を追加しやすいようにするため)。Slackクラスのpost
メソッドを実行時にrequestsでSlackのAPIを叩く。
class Slack():
# 略
def post(self):
"""
POST request to Slack file upload API
API docs: https://api.slack.com/methods/files.upload
"""
file = {'file': open(f"/tmp/{str(self.date)}.png", 'rb')}
requests.post(url="https://slack.com/api/files.upload",params=self.params, files=file)
使用したSlackのAPIはファイルアップロードAPI。画像ファイルをSlackにPOSTする必要があるため。requests
のpostメソッドのfiles
に添付したいファイルを指定すればOK。
株価のデータはOHLC(+Volume)というフォーマットで共通化されているので、これをゴニョゴニョして送信時のテキストを作っている。
def __format_text(self, ohlcv):
"""
Create params data for sending Slack notification with API.
:param dict[str, str, str, str, str, str] ohlcv:
:type ohlcv: {
'Date': '2020-12-29',
'Open': '7620',
'High': '8070',
'Low': '7610',
'Close': '8060',
'Volume': '823700'
}
:return: String
"""
text = f"本日は{self.date.strftime('%Y年%m月%d日')}です。\n" \
f"取得可能な最新日付の株価情報をお知らせします。 \n\n"\
f"*始値* {int(ohlcv['Open']):,d}\n" \
f"*高値* {int(ohlcv['High']):,d}\n" \
f"*安値* {int(ohlcv['Low']):,d}\n" \
f"*終値* {int(ohlcv['Close']):,d}\n" \
f"*出来高* {int(ohlcv['Volume']):,d}"
return text
インフラ
gcloud
コマンドで関数をCloud Functionsにデプロイ
- Cloud Functionsのプロジェクトを作成し、その環境にPythonの関数スクリプトをデプロイする
- 先んじてCloud Pub/Subのトピックを作成し、デプロイ時に指定できるようにしておく
-
gcloud
コマンドで以下のように指定すればCLIからデプロイ可能
$ gcloud functions deploy #{FUNCTION_NAME} --entry-point main --project #{PROJECT_ID} --region #{REGION} --runtime python39 --trigger-topic #{PUBUSB_TOPIC_NAME}
環境変数の設定
- Cloud Functions上でファイルを編集し、
.sample.env
ファイルの名前を.env
に変更して環境変数を設定(.env
はgitignoreしているため) -
STOCK_CODE
には取得したい株価の銘柄コードを指定 -
SLACK_API_TOKEN
にはSlack botを作成して取得できるトークンを指定 -
SLACK_CHANNEL_ID
はSlackのチャンネルのIDを指定- ブラウザのSlackから確認可能: https://qiita.com/unsoluble_sugar/items/603e51106d9632f3ea4f
# .sample.env => .env
STOCK_CODE=
SLACK_API_TOKEN=
SLACK_CHANNEL_ID=
Cloud Schedulerでジョブの設定
- Cloud Schedulerのジョブを作成する(画像は平日の正午にジョブを実行する設定例)
- 作成したPub/Subに対してメッセージを送信することで、Cloud Functionsの実行をトリガーする
備考
- Python 3.9のランタイムをCloud Functionsがbetaで使用できるようにしてくれたのでいち早く使ってみた
- 結局3.9特有の機能は使いませんでした
- 2021/05/01現在、AWS Lambdaでは残念ながらPython 3.9をサポートしていないので意図せずCloud Functions専用スクリプトになってしまった(
AWS Lambda Layers
でECRにプッシュしたイメージを起動するようにすれば動かせるはず) - ローカル環境によってはPythonのバージョンを汚したくない等あると思い、Dockerを使った
- Cloud Functionsがサポートしている関係上、Pythonの依存関係をPipenvではなく
requirements.txt
で管理している- 参考: https://cloud.google.com/appengine/docs/standard/python3/runtime?hl=ja#dependencies
- Cloud Functions上でPipenvを使えている記事もあるので、実際のところどうなのかはよくわかっていない
- Python 3.9のランタイムbeta版を先んじて使ったせいかもしれない
参考記事
- 作る途中でお世話になった記事を置いてきます
Discussion