103
118

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python×株式投資:都度DLはやめた─yfinanceで爆速テクニカル分析を回したい

Last updated at Posted at 2025-06-06

yfinaceデータを用いた軽量データセット(個人用)の構築

はじめに

これまでテクニカルスクリーニングのバックテストを行う中で、
処理速度の遅さに課題を感じていました。

特に、複数銘柄に対してループ処理を行う際、
毎回 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などの分析に有用です。


全体の処理フロー

本稿のコードは下記の項目で構成されています:

  1. データ取得:銘柄ごとに15年分の日足データを .parquet で保存
  2. バッチ処理:保存された銘柄ファイルを 200 件ずつまとめて分割保存
  3. 統合処理:バッチファイルを結合して、1つの巨大な .parquet を作成
  4. 中身の確認:データの件数、カラム数、データ型、欠損の数を確認
  5. 有用性の確認:統合データから個別銘柄を抽出・可視化する

① 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 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なしで進んだが、
時と場合により、時よりかなり頻繁に
アクセスエラーが吐き出されるので、頃合いを見て追加した方が良い。

2025年6月8日
numpy使ってないのに、ライブラリインポートしてたので削除修正


② バッチ処理

株価データは銘柄数が多く、期間も長いため、すべてを一気に読み込むとメモリを圧迫しやすくなります。1600銘柄を一度に統合させても、進む場合もありますが、何回かクラッシュしたことがあります。
そこで、ファイルをあらかじめ200件ずつにまとめておく(バッチ化)ことで、

  • メモリ負荷を軽減
  • 並列処理や検証を柔軟に行える
  • 後の統合処理が安定する

ことが期待できます。このコードの流れは下記の通りです。

  1. 保存ディレクトリ内の .parquet を一覧取得
  2. 空データやエラーを除外しながら読み込み
  3. 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 ファイルに統合します。

これが最終的な「全銘柄・全期間データ」の完成版になります。


処理の流れ

  1. batch_size/ フォルダ内のファイルをすべて読み込む
  2. 中身を1つの DataFrame にまとめる
  3. 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)のデータを抽出し、株価の推移を可視化ができるか確認します。

処理の流れ

  1. 統合ファイルを読み込む
  2. "Date" を時系列インデックスに変換(yfinance風)
  3. 任意のティッカー(ここでは "7203.T")でフィルタ
  4. 株価の推移を可視化+リターンを計算して列追加

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

出力されるグラフ
Screenshot 2025-06-04 at 21.23.54.png

まとめ

本記事では、東証プライム全銘柄の株価データを yfinance から取得し、
.parquet 形式で保存・整理する一連の処理を構築しました。

  • 銘柄ごとのデータを .parquet で個別保存
  • メモリ効率を考慮して200件ずつバッチに分割
  • 全バッチを統合して1つのマスターファイルを作成
  • 特定銘柄の抽出・時系列処理・可視化まで確認

これにより、以後は Web アクセスなしで高速かつ安定的に株価データを活用できると考えられます。
また、テクニカル指標の追加、スクリーニング、機械学習への応用といった後続の分析において、
再現性と柔軟性を両立したデータ基盤として活用できるはずです。

……しかし、本当にこの統合データセットを使えば、yfinance を都度呼び出すよりも
高速に、ストレスなく、バックテストができるようになるのでしょうか?
その検証を行わなければ、今回の工程の有用性を証明したことにはなりません。
そこで次回は、

「yfinance vs 自前データセット」徹底・速度対決を行います。
果たしてこのローカルデータセットは、

あの幻の記事『Python×株式投資:月利3〜5%を狙う自動スクリーニング戦略』で行った、

地獄のバックテスト工程─丸3日かかった実験─を短縮できるのでしょうか?
結果が気になる方は、ぜひ次回もご一読ください。


過去記事もぜひ参考に。
地獄のバックテストの結果についての記事と、
地獄のバックテストをした後に、ネタが思いつかなくて書いた記事です。
いつも読んでいただいて、ありがとうございます。
お陰様で...
あぁ言いたいこと忘れてしまった。

103
118
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
103
118

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?