こちらの動画の書き起こし記事になります。
どうもこんにちは、AIジョーです!
前回は、「株予測・理論編」と題して、
日経400銘柄を対象にした機械学習によるロング・ショート戦略についてご紹介しました。
(……と言いつつ、実はまだ理論編の台本を書き上げていません。すみません!)
さて、今回はその続きとして、yfinance を使った株価データの取得方法に取り組んでいきます。
実際に手を動かしながら、データを集めるところからスタートしていきましょう!
始める前に・・・
マイドライブ
└── ai-joe
└── monex400
├── data
│ └── 400銘柄.csv
└── src
└── 000_monex400_kline.ipynb
Googleドライブ上にプロジェクト用のフォルダ構成を作っていきます。
まず、マイドライブ直下に、新しいフォルダ ai-joe を作成します。
次に、ai-joe フォルダの中に、monex400 という名前のフォルダを作ります。
続いて、monex400 の中に、data と src という二つのフォルダを作成します。
data フォルダの中には、400銘柄.csv を配置します。
これは、日経400に採用されている銘柄について、
インターネットで調べた証券コードをまとめたCSVファイルです。
src フォルダの中には、000_monex400_kline.ipynb を配置します。ここに今回のソースコードを記述します。
これで、フォルダとファイルの構成は完成です。
データを扱う準備
import pandas as pd
from datetime import datetime
import pytz
japan = pytz.timezone("Asia/Tokyo")
print(f"開始時刻: {datetime.now(japan)}")
D = f"/content/drive/MyDrive/ai-joe/monex400/data"
まず最初のブロックでは、
データを扱う準備と、スクリプトの開始時刻の表示を行います。
import pandas as pd
from datetime import datetime
import pytz
はじめに、必要なライブラリをインポートします。
ここでは、データを扱うために pandas、日時の操作に使う datetime、タイムゾーンを扱うための pytz を読み込んでいます。
japan = pytz.timezone("Asia/Tokyo")
print(f"開始時刻: {datetime.now(japan)}")
次に、日本のタイムゾーン(Asia/Tokyo)を設定し、スクリプトの開始時刻をコンソールに表示します。
これは、プログラムの実行時間を記録するのに便利な工夫です。
D = f"/content/drive/MyDrive/ai-joe/monex400/data"
最後に、D という変数にデータの保存先パスを設定しています。
ここでは、Google Drive上の ai-joe/monex400/data フォルダを指定しており、
後続のデータ読み込みや保存処理でこのパスを使う予定です。
このブロックで、データ分析を始めるための環境が整いました。
日経400銘柄一覧データを読み込む
monex = pd.read_csv(f"{D}/400銘柄.csv")
monex.rename(columns={"証券コード": "symbol"}, inplace=True)
monex["symbol"] = monex["symbol"].astype(str)
monex
続いて、扱う銘柄リストを読み込みます。
monex = pd.read_csv(f"{D}/400銘柄.csv")
まず、pandasのread_csv関数を使って、
先ほど設定したパス D 以下にある「400銘柄.csv」というファイルを読み込みます。
ちなみに、これはネットで「日経400一覧」というキーワードで検索して自ら収集したものです。
(ちょっと面倒だった・・・。)
monex.rename(columns={"証券コード": "symbol"}, inplace=True)
次に、読み込んだデータの中で、
「証券コード」という列の名前を、英語表記の「symbol」に変更しています。
これにより、今後の分析やプログラム処理で、コードがシンプルで扱いやすくなります。
それから、ちょっと補足です。
実は、個人的に日経400以外に米国株など他のアセットも同じように解析していて。
そのときに、銘柄を識別するための名前を「symbol」に統一して扱うようにしました。
だから、今回ここで「必ずsymbolにしなきゃいけない」というわけではありません。
monex["symbol"] = monex["symbol"].astype(str)
さらに、「symbol」列のデータ型を文字列(str型)に変換しています。
これは、あとでラベルエンコーディングするための準備(だったような?)です。
monex
最後に、整形したmonexデータフレーム全体を表示し、
データの中身を確認しています。
このブロックで、銘柄データの読み込みと整形が完了しました。
(そういえば、普段マネックス使ってるからmonexにしちゃったけど、monexじゃなくても良かった・・・。まあ、この際、細かいことは気にしない。)
データを保存するためのフォルダを作成する
import os
os.makedirs("kline", exist_ok=True)
次に、プログラム内でデータを保存するためのフォルダを作成します。
まず、osモジュールをインポートしています。
これは、ファイルやフォルダの操作を行うための標準ライブラリです。
続いて、os.makedirs関数を使って、
「kline」という名前のフォルダを作成しています。
ここで指定しているexist_ok=Trueというオプションは、
もしすでに同じ名前のフォルダが存在していても、エラーを出さずにスルーする、という意味です。
これにより、毎回プログラムを実行しても、
安心してフォルダ作成処理を走らせることができます。
このブロックで、データの保存先となるフォルダが準備できました。
いよいよ、株価データの取得!
import yfinance as yf
from tqdm import tqdm
import time
from datetime import timedelta
err_list = []
for i in tqdm(range(len(monex))):
time.sleep(0.5)
row = monex.iloc[i]
symbol = row["symbol"]
# --------------------------------------
# 特定シンボル変換
# --------------------------------------
# 特定のシンボルを変換する辞書
symbol_map = {
}
# シンボルを変換(該当する場合のみ変更)
symbol = symbol_map.get(symbol, symbol)
# --------------------------------------
# 該当データなしの場合、スキップ
# --------------------------------------
if row["symbol"] in []:
continue
# --------------------------------------
# データ取得・保存
# --------------------------------------
days_ago = datetime.today() - timedelta(days=3000)
start_date = days_ago.strftime("%Y-%m-%d")
df = yf.download(symbol+".T", start=start_date, interval="1d", progress=False)
is_error = True
if len(df) == 0:
if is_error:
print()
print("ERROR!", i, row["symbol"], symbol)
err_list.append({"index": i, "symbol": row["symbol"], "symbol": symbol})
# break
else:
is_error = False
if not is_error:
file_name = row['symbol'].replace(" ", "_")
df.columns = ["Close","High","Low","Open","Volume"]
df.to_csv(f"kline/{file_name}.csv")
さあ、いよいよ株価データの取得です!
データ取得と進行管理に使うライブラリのインポート
import yfinance as yf
from tqdm import tqdm
import time
from datetime import timedelta
まず、データ取得と進行管理に使うライブラリをインポートします。
import yfinance as yf
まず、yfinanceライブラリをインポートします。
これは、Yahoo Financeから株価データを取得できる非常に便利なライブラリです。
これを使うことで、過去の株価情報を簡単にダウンロードできます。
from tqdm import tqdm
次に、tqdmをインポートします。tqdmは、ループ処理の進捗状況をプログレスバーで表示するためのライブラリです。
データを大量に取得する場合でも、処理の進み具合が一目でわかるようになります。
import time
さらに、Python標準ライブラリのtimeモジュールもインポートします。
これは、一時停止(スリープ)や、実行時間の測定に使います。
from datetime import timedelta
最後に、timedeltaをインポートします。timedeltaは日付に対して一定期間の加算・減算をするために使います。
実際の銘柄ごとの処理(for文)
err_list = []
for i in tqdm(range(len(monex))):
time.sleep(0.5)
row = monex.iloc[i]
symbol = row["symbol"]
ここからは、実際に銘柄ごとの処理を行っていきます。
err_list = []
まず最初に、err_listという空のリストを作成しています。
これは、後でエラーが発生した銘柄を記録するためのリストです。
for i in tqdm(range(len(monex))):
次に、forループを使って、
銘柄リストmonexの全行に対して1件ずつ処理を行う準備をしています。(monexって名前が気になる・・・)
range(len(monex))で、銘柄の件数分だけループを回します。
さらに、ループの進行状況がひと目でわかるように、tqdmを使ってプログレスバーを表示しています。
time.sleep(0.5)
ループの中では、まずtime.sleep(0.5)を実行して、
処理を0.5秒だけ一時停止しています。
これは、アクセス過多を防ぐための工夫で、
特に外部サイト(今回はYahoo Finance)に連続でリクエストを送る場合に重要なマナーです。
row = monex.iloc[i]
symbol = row["symbol"]
次に、現在対象となっている行データ(銘柄情報)をrowという変数に格納し、
そこからsymbol(証券コード)を取り出しています。
この準備によって、
このあと各銘柄に対して個別のデータ取得や処理ができるようになります。
1銘柄ぶんの株価データを取得
days_ago = datetime.today() - timedelta(days=3000)
start_date = days_ago.strftime("%Y-%m-%d")
df = yf.download(symbol+".T", start=start_date, interval="1d", progress=False)
次に、株価データを取得するための準備をしていきます。
まずは、過去3,000日前、つまりおよそ8年ちょっと前の日付を計算しています。
days_ago = datetime.today() - timedelta(days=3000)
start_date = days_ago.strftime("%Y-%m-%d")
ここで求めた start_date を使って、yfinanceライブラリから株価データをダウンロードします。
df = yf.download(symbol+".T", start=start_date, interval="1d", progress=False)
このとき、銘柄コードには「.T」を付けて、東京証券取引所のティッカー形式に合わせています。
また、取得する間隔は「1日ごと」、つまり日足データに設定しています。progress=False にしているのは、ダウンロード中に進捗バーを表示しないようにするためです。
処理をすっきり見せるための小さな工夫ですね。
エラーチェック
is_error = True
if len(df) == 0:
if is_error:
print()
print("ERROR!", i, row["symbol"], symbol)
err_list.append({"index": i, "symbol": row["symbol"], "symbol": symbol})
# break
else:
is_error = False
続いて、
先ほど取得した株価データdfが正常かどうかをチェックします。
is_error = True
まず、is_errorをTrueに設定して、
初期状態ではエラー前提で考えます。
if len(df) == 0:
次に、もしdfの行数、つまり取得したデータ件数が0件だった場合は、
データの取得に失敗したと判断します。
print()
print("ERROR!", i, row["symbol"], symbol)
err_list.append({"index": i, "symbol": row["symbol"], "symbol": symbol})
このとき、エラーの詳細をコンソールに表示します。先頭のprint()はtqdmのプログレスバーの右に表示されると見づらいので、行の先頭文字にエラーが表示されるよう改行をいれてます。
エラーの表示内容は、
i(銘柄リスト上のインデックス番号)row["symbol"](元データ上の証券コード)symbol(”.T“付きのリクエスト用コード)
といった情報です。
さらに、失敗した銘柄の情報をerr_listに記録します。
これにより、あとでどの銘柄でエラーが発生したか一覧で確認できるようになります。
else:
is_error = False
一方、データが1件以上取得できていた場合は、is_errorをFalseにして、正常終了として扱います。
株価データをCSVファイル保存
if not is_error:
file_name = row['symbol'].replace(" ", "_")
df.columns = ["Close","High","Low","Open","Volume"]
df.to_csv(f"kline/{file_name}.csv")
ここでは、
先ほどのエラーチェックで問題がなかった銘柄だけ、
株価データをCSVファイルとして保存します。
if not is_error:
まず、if not is_error:という条件で、
エラーがなかった銘柄だけ処理を続行します。
file_name = row['symbol'].replace(" ", "_")
続いて、ファイル名を作成します。
銘柄コードの中に空白が含まれている場合に備えて、replace(" ", "_")を使い、空白をアンダースコアに置き換えます。
これで、ファイル名として安全な形式になります。(Numeraiか何かの名残・・・。)
df.columns = ["Close","High","Low","Open","Volume"]
次に、データフレームdfの列名を整理します。
もともとYahoo Financeから取得したデータは、
列名がシステム的なものだったりバラつきがあるので、
ここであらためて、
- Close(終値)
- High(高値)
- Low(安値)
- Open(始値)
- Volume(出来高)
の5つに統一しています。(OHLCVの順じゃないのが違和感・・・)
df.to_csv(f"kline/{file_name}.csv")
最後に、整形したデータを、klineフォルダの中にCSVファイルとして保存します。
ファイル名は、作成したfile_nameを使い、
拡張子は.csvです。
このブロックによって、
銘柄ごとの株価データをきれいに保存することができました。
エラー情報をCSVに保存
print("len(err_list): ", len(err_list))
print()
print(err_list)
pd.DataFrame(err_list).to_csv("err_list.csv")
最後に、これまでの処理でエラーが発生した銘柄の情報をまとめます。
print("len(err_list): ", len(err_list))
まず、print("len(err_list): ", len(err_list))で、
エラーが発生した銘柄数、つまりerr_listに記録された件数を表示します。
これで、全体のうちどれくらい失敗したかが一目でわかります。
print()
print(err_list)
続いて、print(err_list)で、
エラーになった銘柄それぞれの情報を一覧表示します。
インデックス番号と銘柄コードの対応も、ここで確認できます。
pd.DataFrame(err_list).to_csv("err_list.csv")
さらに、pandasを使って、err_listをデータフレームに変換し、
その内容をerr_list.csvという名前でCSVファイルに保存しています。
これにより、後から
- どの銘柄で問題が起きたか
- どのリクエストコードに失敗したか
をまとめてチェックできるようになります。
このブロックで、エラー管理が完了し、後続の分析や再取得もスムーズに進められる準備ができました。
データを圧縮してGoogle Driveに保存
!zip -qr kline400.zip kline
!cp err_list.csv {D}
!cp kline400.zip {D}
print(f"終了時刻: {datetime.now(japan)}")
最後の処理として、
取得したデータをまとめて保存し、作業の終了を記録します。
!zip -qr kline400.zip kline
まず、!zip -qr kline400.zip klineで、
これまで保存してきた各銘柄のCSVファイルをひとつにまとめ、kline400.zipという名前のZIPファイルに圧縮しています。
ここで使っているオプションは、
-q(quiet):ログ出力を控えめにする-r(recursive):フォルダ内のすべてのファイルを対象にする
という設定です。
これで、たくさんの銘柄ファイルをコンパクトにまとめることができます。
!cp err_list.csv {D}
次に、!cp err_list.csv {D}で、
エラー情報をまとめたerr_list.csvファイルを、
作業ディレクトリDにコピーしています。
これにより、エラーリストもまとめて管理できます。
!cp kline400.zip {D}
同様に、!cp kline400.zip {D}で、
圧縮したデータファイルもDにコピーします。
これで、Google Drive上にすべての成果物が安全に保存されました。
print(f"終了時刻: {datetime.now(japan)}")
最後に、print(f"終了時刻: {datetime.now(japan)}")を実行して、
スクリプトの終了時刻を日本時間で記録します。
このブロックで、データ収集から保存までの一連の作業が完了しました。
まとめ
これで、yfinanceを使って株価データを取得し、Google Driveに保存するところまで完了しました!
次回は、今回保存したデータをもとに、
特徴量の作成に取り組んでいきます。
コメント