はじめに
これまでテクニカルスクリーニングのバックテストを行う中で、
処理速度の遅さに課題を感じていました。
特に、複数銘柄に対してループ処理を行う際、
毎回 yfinance
を通じて株価データを取得していたため、
同じ銘柄でも都度ダウンロードが発生し、非効率でした。
例えば以下のようなコードが問題でした。
# -----------------------------
# スクリーニング条件別ループ
# -----------------------------
summary = []
for cond in screening_conditions:
result = []
chart_dir = お好きなディレクトリ
os.makedirs(chart_dir, exist_ok=True)
for _, row in df_list.iterrows():
ticker = row["Ticker"]
df = yf.download(ticker, start="2020-12-01", end="2025-04-06")
time.sleep(0.7)
print(ticker)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
if df.empty or len(df) 75:
continue
df["MA_5"] = df["Close"].rolling(5).mean()
df["MA_25"] = df["Close"].rolling(25).mean()
df["MA_75"] = df["Close"].rolling(75).mean()
delta = df["Close"].diff().values.flatten()
gain = np.where(delta > 0, delta, 0)
loss = np.where(delta 0, -delta, 0)
avg_gain = pd.Series(gain, index=df.index).rolling(14).mean()
avg_loss = pd.Series(loss, index=df.index).rolling(14).mean()
rs = avg_gain / (avg_loss + 1e-10)
df["RSI"] = 100 - (100 / (1 + rs))
df["MA_5_slope"] = df["MA_5"].diff(cond["slope_period"]) / cond["slope_period"]
##--続く--##
問題点は次の通りです。
カテゴリ | 本質的な問題 |
---|---|
データ取得 | データを何度もダウンロードしている |
時間待ち処理 | アクセス制限回避を sleep で行っている |
冗長ループ | 取得・計算・スクリーニングをすべて1つのループで済ませている |
再計算の無駄 | 指標計算を毎ループで毎銘柄に再実行している |
解決の方針
この課題に対応するために、
必要最低限の株価情報のみをまとめた軽量なデータセットを構築しました。
- 対象:東証プライム全銘柄(約1600銘柄)
- 期間:2010年~2025年
- 項目:OHLCV(Open, High, Low, Close, Volume)
- 形式:Parquet形式で銘柄ごとに保存
このようなデータセットを構築することで、次のようなメリットがあります。
ローカルに株価データを保存するメリット
観点 | メリット |
---|---|
速度 | 毎回ダウンロードせず、瞬時に読み込める |
安定性 |
yfinance の障害や制限に左右されない |
拡張性 | 後から特徴量や外部データを追加できる |
また、今後導入する予定の機械学習では、学習や検証を何度も繰り返すため、株価データを毎回オンラインで取得するのは非効率かつ不安定です。再現性や実行速度を確保するためにも、ローカルに保存したデータを使うのが良いと思います。
なぜOHLCVなのか
証券アプリなどで使用されている多くのテクニカル指標は、
実は「OHLCV(始値・高値・安値・終値・出来高)」の5項目だけで再現可能です。
このように、分析に必要なデータを最低限に絞って保存しておくことで、
- 保存容量の削減
- 読み込み速度の向上
- 特徴量生成の柔軟性維持
といったメリットがあると考えられます。
なお、後から必要に応じて以下のような情報も簡単に拡張できると思われます:
- 日付に基づく曜日・月・週番号などのカレンダー特徴量
- 指数データ(TOPIX、日経平均、VIXなど)とのマージ
- 財務・業種・需給・マクロデータの追加
東証プライム全銘柄の日足データ(2010年〜2025年)を .parquet
形式で保存し、
最終的に1つの統合ファイルとして使えるようにすることを目的とします。
この統合データは、後のスクリーニングや機械学習、PCAなどの分析に有用です。
全体の処理フロー
本稿のコードは下記の項目で構成されています:
-
データ取得:銘柄ごとに15年分の日足データを
.parquet
で保存 - バッチ処理:保存された銘柄ファイルを 200 件ずつまとめて分割保存
-
統合処理:バッチファイルを結合して、1つの巨大な
.parquet
を作成 - 中身の確認:データの件数、カラム数、データ型、欠損の数を確認
- 有用性の確認:統合データから個別銘柄を抽出・可視化する
① yfinanceで全銘柄の株価を .parquet
に保存
# Driveマウントとライブラリ
from google.colab import drive
drive.mount('/content/drive')
!pip install curl_cffi --quiet
import yfinance as yf
import pandas as pd
import numpy as np
import os
from tqdm import tqdm
from curl_cffi import requests
session = requests.Session(impersonate="safari15_5")
# 保存先(必要に応じて変更)
output_dir = お好きなディレクトリ
os.makedirs(output_dir, exist_ok=True)
# 銘柄リスト読み込み(前回記事参照)
excel_path = "/content/drive/MyDrive/stock_prediction/data_j.xlsx"
df_all = pd.read_excel(excel_path)
df_all = df_all[df_all["市場・商品区分"] == "プライム(内国株式)"]
df_all["Ticker"] = df_all["コード"].astype(str).str.zfill(4) + ".T"
df_list = df_all[["Ticker", "33業種コード"]].copy()
# 各銘柄のデータを取得して parquet 形式で保存
for _, row in tqdm(df_list.iterrows(), total=len(df_list)):
ticker = row["Ticker"]
industry_code = row["33業種コード"]
try:
df = yf.download(ticker, start="2010-01-01", end="2025-05-25", auto_adjust=False, session=session)
if df.empty or len(df) 100:
continue
df.reset_index(inplace=True)
df = df.astype({
"Open": "float64", "High": "float64", "Low": "float64",
"Close": "float64", "Adj Close": "float64", "Volume": "int64"
})
df["Date"] = pd.to_datetime(df["Date"])
df["Ticker"] = ticker
df["IndustryCode"] = int(industry_code)
df.columns = [col[0] if isinstance(col, tuple) else col for col in df.columns]
df.to_parquet(os.path.join(output_dir, f"{ticker}_OHLCV.parquet"), index=False)
print(f"{ticker} ✅")
except Exception as e:
print(f"{ticker} ❌ failed: {e}")
銘柄リストのエクセルファイルに関しては、2回目の財務スクリーニングの記事に紹介済。
今回はtimesleepなしで行けたが、時と場合により、アクセスエラーが吐き出されるので、頃合いを見て追加した方が良い。
② バッチ処理
株価データは銘柄数が多く、期間も長いため、すべてを一気に読み込むとメモリを圧迫しやすくなります。1600銘柄を一度に統合させても、進む場合もありますが、何回かクラッシュしたことがあります。
そこで、ファイルをあらかじめ200件ずつにまとめておく(バッチ化)ことで、
- メモリ負荷を軽減
- 並列処理や検証を柔軟に行える
- 後の統合処理が安定する
ことが期待できます。このコードの流れは下記の通りです。
- 保存ディレクトリ内の
.parquet
を一覧取得 - 空データやエラーを除外しながら読み込み
- 200件ごとに
batch_0.parquet
,batch_1.parquet
, … として保存
# 入力・出力ディレクトリ(必要に応じて変更)
input_dir = お好きなディレクトリ(↑のファイルが格納されてるところ)
batch_dir = os.path.join(input_dir, "batch_size")
os.makedirs(batch_dir, exist_ok=True)
# 既存のバッチファイルを削除
for f in os.listdir(batch_dir):
if f.startswith("batch_") and f.endswith(".parquet"):
os.remove(os.path.join(batch_dir, f))
print(" 古いバッチファイルを削除しました")
# .parquetファイル一覧(batch_除外)
parquet_files = sorted([
f for f in os.listdir(input_dir)
if f.endswith(".parquet") and not f.startswith("batch_")
])
# バッチ処理(200件ごとに保存)
batch_size = 200
for i in tqdm(range(0, len(parquet_files), batch_size), desc=" バッチ保存中"):
batch_files = parquet_files[i:i + batch_size]
dfs = []
for file in batch_files:
try:
df = pd.read_parquet(os.path.join(input_dir, file))
if not df.empty:
dfs.append(df)
else:
print(f"⚠️ 空データスキップ: {file}")
except Exception as e:
print(f"❌ 読み込み失敗: {file}, 理由: {e}")
if dfs:
df_batch = pd.concat(dfs, ignore_index=True)
batch_path = os.path.join(batch_dir, f"batch_{i // batch_size}.parquet")
df_batch.to_parquet(batch_path, index=False)
print(f"✅ batch_{i // batch_size} saved")
else:
print(f"🚫 batch_{i // batch_size} はスキップ(全データ空)")
③:バッチファイルを1つの統合 .parquet
にまとめる
前のステップで保存した batch_0.parquet
, batch_1.parquet
, … といったバッチファイルを
すべて読み込み、1つの巨大な .parquet
ファイルに統合します。
これが最終的な「全銘柄・全期間データ」の完成版になります。
処理の流れ
-
batch_size/
フォルダ内のファイルをすべて読み込む - 中身を1つの DataFrame にまとめる
-
ticker_combined_OHLCV.parquet
として保存
# ディレクトリ設定(前のステップと同じ場所)
batch_dir = "お好きなディレクトリ/batch_size"
output_parquet = "お好きなディレクトリ/ticker_combined_OHLCV.parquet"
# バッチファイル一覧を取得
batch_files = sorted([f for f in os.listdir(batch_dir) if f.endswith(".parquet")])
# バッチを順に読み込み&統合
df_list = []
for file in tqdm(batch_files, desc=" 統合中"):
path = os.path.join(batch_dir, file)
df = pd.read_parquet(path)
df_list.append(df)
# 結合して1つのファイルに保存
df_combined = pd.concat(df_list, ignore_index=True)
df_combined.to_parquet(output_parquet, index=False)
print(f"\n 統合完了: {output_parquet}")
④:統合済みデータの読み込みと確認
前ステップで作成した ticker_combined_OHLCV.parquet
を読み込み、
中身を確認して問題がないかチェックします。
- 統合後のデータが正しく読み込めるか確認
- カラム構成、欠損、データ型、件数などを検証
# 日付・ディレクトリ設定(適宜変更してください)
input_dir = バッチファイルがあるところ
file_path = os.path.join(input_dir, f"ticker_combined_OHLCV.parquet")
# データ読み込み
df = pd.read_parquet(file_path, engine="pyarrow")
# 中身の確認
print("ファイル:", file_path)
print("shape:", df.shape)
print("columns:", df.columns.tolist())
print("dtypes:\n", df.dtypes)
print("null counts:\n", df.isnull().sum())
print("head:\n", df.head())
コードが正常に動作すれば、次のような出力結果が表示されます。
ファイル: .../ticker_combined_OHLCV.parquet
shape: (5683368, 9)
columns: ['Date', 'Adj Close', 'Close', 'High', 'Low', 'Open', 'Volume', 'Ticker', 'IndustryCode']
dtypes:
Date datetime64[ns]
Adj Close float64
Close float64
High float64
Low float64
Open float64
Volume int64
Ticker object
IndustryCode int64
null counts:
全カラムで欠損値は0件(完全なデータ)
head:
Date Adj Close Close High Low Open Volume Ticker IndustryCode
0 2010-01-04 80.097107 120.00 121.33 116.00 120.00 39000 9743.T 9050
1 2010-01-05 80.987068 121.33 121.33 121.33 121.33 1500 9743.T 9050
2 2010-01-06 78.317177 117.33 118.67 116.67 118.00 16500 9743.T 9050
- 件数:568万行超 → おおよそ2000銘柄×約2800営業日(想定どおり)
- カラム数:9列 → OHLCV+識別情報(ティッカー、業種コード)
- データ型:すべて適切に指定されており、後の処理もスムーズ
- 欠損:なし(理想の状態)
⑤:統合データから個別銘柄を抽出・可視化する
統合済みファイルから、特定の銘柄(例:7203.T)のデータを抽出し、株価の推移を可視化ができるか確認します。
処理の流れ
- 統合ファイルを読み込む
-
"Date"
を時系列インデックスに変換(yfinance風) - 任意のティッカー(ここでは
"7203.T"
)でフィルタ - 株価の推移を可視化+リターンを計算して列追加
import pandas as pd
import os
import matplotlib.pyplot as plt
# 設定
input_dir = お好きなディレクトリ
file_path = os.path.join(input_dir, f"ticker_combined_OHLCV.parquet")
# 統合ファイルの読み込み
df = pd.read_parquet(file_path, engine="pyarrow")
df["Date"] = pd.to_datetime(df["Date"]) # 念のため型変換
df.set_index("Date", inplace=True) # yfinance形式っぽくする
# 特定銘柄(例:7203.T)だけを抽出して昇順に並べる
df_7203 = df[df["Ticker"] == "7203.T"].sort_index()
print(df_7203.head())
# 1日リターンを追加(Closeベース)
df_7203["Return_1d"] = df_7203["Close"].pct_change()
# 株価推移を可視化
df_7203["Close"].plot(title="7203.T - Close Price", figsize=(10, 4))
plt.grid(True)
plt.show()
出力例
Adj Close Close High Low Open Volume Ticker IndustryCode
Date
2010-01-04 512.543579 778.0 783.0 778.0 780.0 30732500 7203.T 3700
2010-01-05 501.343903 761.0 784.0 761.0 784.0 52827500 7203.T 3700
2010-01-06 513.202271 779.0 779.0 779.0 779.0 41519000 7203.T 3700
2010-01-07 507.273041 770.0 782.0 766.0 781.0 31003500 7203.T 3700
2010-01-08 521.766663 792.0 796.0 780.0 781.0 77448500 7203.T 3700
まとめ
本記事では、東証プライム全銘柄の株価データを yfinance
から取得し、.parquet
形式で保存・整理する一連の処理を構築しました。
- 銘柄ごとのデータを
.parquet
で個別保存 - メモリ効率を考慮して200件ずつバッチに分割
- 全バッチを統合して1つのマスターファイルを作成
- 特定銘柄の抽出・時系列処理・可視化まで確認
これにより、以後は Web アクセスなしで高速かつ安定的に株価データを活用できると考えられます。
また、テクニカル指標の追加、スクリーニング、機械学習への応用といった後続の分析において、
再現性と柔軟性を両立したデータ基盤として活用できるはずです。
……しかし、本当にこの統合データセットを使えば、yfinance
を都度呼び出すよりも
高速に、ストレスなく、バックテストができるようになるのでしょうか?
その検証を行わなければ、今回の工程の有用性を証明したことにはなりません。
そこで次回は、
「yfinance vs 自前データセット」徹底・速度対決を行います。
果たしてこのローカルデータセットは、
あの幻の記事『Python×株式投資:月利3〜5%を狙う自動スクリーニング戦略』で行った、
地獄のバックテスト工程─丸3日かかった実験─を短縮できるのでしょうか?
結果が気になる方は、ぜひ次回もご一読ください。
Views: 0