【コピペOK】Pythonで取得・分析した株価データをExcel形式で保存する

Python実装・コード

※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。

Pythonで株価データを取得し、pandasで分析するところまではできるようになった方は多いはずです。

しかし、出力形式がCSVのままだと、普段Excelで株価管理をしている環境との連携に手間がかかります。

「CSVだと文字化けする」「列幅が毎回崩れる」「シートを分けて保存したい」という不満は定番です。

原因は、CSVが単なるテキストファイルであり、書式情報を一切持てないことにあります。

本記事では、pandasのto_excelメソッドとopenpyxlライブラリを使い、株価データをExcel(.xlsx)形式で保存する方法を解説します。

列幅の自動調整や複数シートへの分割出力まで、コピペで動くコードを提供するので、そのまま自分のワークフローに組み込んでください。

CSVとExcel出力の違いと使い分け

CSV出力の限界

CSV(Comma-Separated Values)はテキスト形式のため、軽量で汎用性が高い保存形式です。

しかし以下の制約があり、Excelユーザーとの連携には不向きです。

* 書式情報なし:列幅、フォント、背景色などの情報を保持できません

* 文字コード問題:UTF-8で保存するとExcelで開いた際に日本語が文字化けする場合があります

* 単一シート:1ファイルに1つのデータしか格納できず、銘柄別のシート分割ができません

Excel出力が有効な場面

Excel形式(.xlsx)は書式情報を保持できるため、人間が目視で確認する用途に適しています。

以下のような場面ではCSVよりExcel出力を選んでください。

* 上司や共同投資家にレポートを共有する場合

* 銘柄別・指標別にシートを分けて管理したい場合

* 条件付き書式(Conditional Formatting)で値の大小を色分けしたい場合

プログラム間のデータ受け渡しにはCSV、人間向けの最終出力にはExcelという使い分けが基本です。

【コピペOK】基本のExcel出力コード

まずは最もシンプルな、株価データのExcel保存コードです。


pip install yfinance pandas openpyxl

import datetime
from typing import Final

import pandas as pd
import yfinance as yf

# ==============================
# 設定エリア
# ==============================
TICKERS: Final[list[str]] = ["7203.T", "9984.T", "6758.T"]
PERIOD: Final[str] = "3mo"
OUTPUT_FILE: Final[str] = "stock_report.xlsx"
SHEET_NAME: Final[str] = "株価データ"


# ==============================
# 株価データ取得
# ==============================
def fetch_stock_data(tickers: list[str], period: str) -> pd.DataFrame:
    '複数銘柄の株価データを取得する'
    df: pd.DataFrame = yf.download(tickers, period=period, progress=False)
    return df


# ==============================
# DataFrameの整形
# ==============================
def format_dataframe(df: pd.DataFrame) -> pd.DataFrame:
    'マルチインデックスをフラット化し、日付列を追加する'
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = [f"{col[0]}_{col[1]}" for col in df.columns]
    df = df.reset_index()
    df["Date"] = pd.to_datetime(df["Date"]).dt.strftime("%Y-%m-%d")
    return df


# ==============================
# 列幅の自動調整
# ==============================
def auto_adjust_column_width(writer: pd.ExcelWriter, df: pd.DataFrame, sheet_name: str) -> None:
    '各列の最大文字数に基づいて列幅を設定する'
    worksheet = writer.sheets[sheet_name]
    for i, col in enumerate(df.columns):
        max_length = max(
            df[col].astype(str).map(len).max(),
            len(str(col)),
        )
        adjusted_width = min(max_length + 4, 30)
        worksheet.column_dimensions[chr(65 + i) if i < 26 else f"A{chr(65 + i - 26)}"].width = adjusted_width


# ==============================
# Excel保存
# ==============================
def save_to_excel(df: pd.DataFrame, file_path: str, sheet_name: str) -> None:
    'DataFrameをExcelファイルとして保存する'
    with pd.ExcelWriter(file_path, engine="openpyxl") as writer:
        df.to_excel(writer, sheet_name=sheet_name, index=False)
        auto_adjust_column_width(writer, df, sheet_name)
    print(f"保存完了: {file_path}")


# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
    print(f"データ取得開始: {datetime.datetime.now():%Y-%m-%d %H:%M}")
    raw_df = fetch_stock_data(TICKERS, PERIOD)
    formatted_df = format_dataframe(raw_df)
    print(f"取得行数: {len(formatted_df)}")
    save_to_excel(formatted_df, OUTPUT_FILE, SHEET_NAME)

コードの処理フロー解説

上記のコードは、以下の4ステップで構成されています。

* ステップ1 データ取得:yfinanceで複数銘柄の3か月分OHLCVデータを一括ダウンロードします

* ステップ2 整形:マルチインデックス(MultiIndex)のカラムをClose_7203.Tのようなフラットな文字列に変換し、日付を見やすい書式にします

* ステップ3 列幅調整:各列の最大文字数を計算し、openpyxlのワークシートオブジェクトに列幅を設定します

* ステップ4 保存:pd.ExcelWriterのコンテキストマネージャ内で書き込みと列幅調整をまとめて実行します

TICKERSPERIODを書き換えるだけで、任意の銘柄・期間に対応できます。

【コピペOK】複数シート分割・書式付き出力コード

銘柄ごとにシートを分け、ヘッダーに色を付けた実用的なバージョンです。


import datetime
from typing import Final

import pandas as pd
import yfinance as yf
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter

# ==============================
# 設定エリア
# ==============================
TICKERS: Final[list[str]] = ["7203.T", "9984.T", "6758.T"]
PERIOD: Final[str] = "3mo"
OUTPUT_FILE: Final[str] = "stock_report_multi.xlsx"
HEADER_COLOR: Final[str] = "1F4E79"
HEADER_FONT_COLOR: Final[str] = "FFFFFF"
NUMBER_FORMAT_PRICE: Final[str] = "#,##0.0"
NUMBER_FORMAT_VOLUME: Final[str] = "#,##0"


# ==============================
# 銘柄別データ取得
# ==============================
def fetch_single_ticker(ticker: str, period: str) -> pd.DataFrame:
    '単一銘柄の株価データを取得する'
    df: pd.DataFrame = yf.download(ticker, period=period, progress=False)
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.droplevel("Ticker")
    df = df.reset_index()
    df["Date"] = pd.to_datetime(df["Date"]).dt.strftime("%Y-%m-%d")
    return df


# ==============================
# ヘッダー書式の適用
# ==============================
def style_header(worksheet, col_count: int) -> None:
    'ヘッダー行に背景色・フォント色・中央揃えを適用する'
    fill = PatternFill(start_color=HEADER_COLOR, end_color=HEADER_COLOR, fill_type="solid")
    font = Font(color=HEADER_FONT_COLOR, bold=True, size=11)
    alignment = Alignment(horizontal="center")
    for col_idx in range(1, col_count + 1):
        cell = worksheet.cell(row=1, column=col_idx)
        cell.fill = fill
        cell.font = font
        cell.alignment = alignment


# ==============================
# 数値書式の適用
# ==============================
def style_numbers(worksheet, df: pd.DataFrame) -> None:
    '株価列と出来高列にそれぞれ数値書式を適用する'
    for col_idx, col_name in enumerate(df.columns, start=1):
        fmt = NUMBER_FORMAT_VOLUME if col_name == "Volume" else NUMBER_FORMAT_PRICE
        if col_name == "Date":
            continue
        for row_idx in range(2, len(df) + 2):
            worksheet.cell(row=row_idx, column=col_idx).number_format = fmt


# ==============================
# 列幅の自動調整
# ==============================
def auto_adjust_width(worksheet, df: pd.DataFrame) -> None:
    '列幅をデータの最大文字数に合わせる'
    for col_idx, col_name in enumerate(df.columns, start=1):
        max_len = max(
            df[col_name].astype(str).map(len).max(),
            len(str(col_name)),
        )
        worksheet.column_dimensions[get_column_letter(col_idx)].width = min(max_len + 4, 30)


# ==============================
# 複数シートでExcel保存
# ==============================
def save_multi_sheet(tickers: list[str], period: str, file_path: str) -> None:
    '銘柄ごとにシートを分けてExcelファイルを保存する'
    with pd.ExcelWriter(file_path, engine="openpyxl") as writer:
        for ticker in tickers:
            print(f"  処理中: {ticker}")
            df = fetch_single_ticker(ticker, period)
            sheet_name = ticker.replace(".T", ')
            df.to_excel(writer, sheet_name=sheet_name, index=False)
            ws = writer.sheets[sheet_name]
            style_header(ws, len(df.columns))
            style_numbers(ws, df)
            auto_adjust_width(ws, df)
    print(f"保存完了: {file_path}")


# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
    print(f"レポート作成開始: {datetime.datetime.now():%Y-%m-%d %H:%M}")
    save_multi_sheet(TICKERS, PERIOD, OUTPUT_FILE)

コードの処理フロー解説

上記のコードは、以下の5ステップで構成されています。

* ステップ1 銘柄別取得:各銘柄を個別にyf.downloadで取得し、マルチインデックスを解除します

* ステップ2 シート書き込み:銘柄コード(.Tを除去)をシート名として、ExcelWriterに順次書き込みます

* ステップ3 ヘッダー装飾:openpyxlPatternFillFontでヘッダー行に背景色と白文字を適用します

* ステップ4 数値書式:株価列にはカンマ区切り小数1桁、出来高列にはカンマ区切り整数の表示形式を設定します

* ステップ5 列幅調整:get_column_letterでカラム番号をアルファベットに変換し、列幅を自動設定します

HEADER_COLORを変更すればヘッダーの配色を自由にカスタマイズできます。

Excelファイルの活用テクニック

出力したExcelファイルをさらに活用するためのテクニックを紹介します。

既存のExcelファイルにシートを追記したい場合は、pd.ExcelWritermode引数を"a"(append)に設定してください。

if_sheet_exists引数を"replace"にすると、同名シートがあれば上書きされます。

"overlay"にすると既存データの末尾に追記されます。

日次で自動実行するスクリプトと組み合わせる場合は、日付をシート名にする設計が便利です。

sheet_name=datetime.date.today().strftime("%Y%m%d")とすれば、毎日新しいシートが追加されるExcelファイルが完成します。

ファイルサイズが肥大化するため、30日を超えた古いシートは定期的に削除してください。

よくあるエラーと対処法

ModuleNotFoundError: No module named ‘openpyxl’

pandasto_excelメソッドは内部でopenpyxlを使用しますが、pandasをインストールしてもopenpyxlは自動では入りません。

明示的なインストールが必要です。

以下を試してください。

* pip install openpyxlを実行してください

* 仮想環境を使っている場合は、仮想環境を有効化した状態でインストールしてください

* それでも解決しない場合はpip listopenpyxlが一覧に表示されるか確認してください

PermissionError: [Errno 13] Permission denied

書き込み先のExcelファイルが別のプログラム(Excel等)で開かれている場合に発生します。

ファイルがロックされた状態では上書き保存できません。

以下を試してください。

* Excelアプリケーションで対象ファイルを閉じてからスクリプトを再実行してください

* 自動実行スクリプトの場合は、出力ファイル名に日時を含めて毎回新しいファイルに保存する設計にしてください

* try-exceptPermissionErrorをキャッチし、ファイル名末尾に連番を付与するフォールバック処理を入れるのも有効です

列幅調整で27列以上のデータがずれる

基本コードのchr(65 + i)方式では、27列目以降(AA列〜)の列幅指定が正しく動作しません。

発展版コードで使用しているget_column_letter関数を使えば、何列でも正確に列幅を設定できます。

以下を試してください。

* from openpyxl.utils import get_column_letterをインポートしてください

* chr(65 + i)の部分をget_column_letter(i + 1)に置き換えてください。引数は1始まりである点に注意してください

* 発展版コードのauto_adjust_width関数をそのまま基本コードに移植するのが最も確実です

まとめ

この記事では、pandasとopenpyxlを使ってPythonの株価分析結果をExcel形式で保存する方法を解説しました。

要点を整理します。

* pd.ExcelWriterto_excelメソッドで、DataFrameを.xlsx形式で保存できます

* openpyxlは別途pip installが必要で、pandasと一緒に自動インストールはされません

* 列幅の自動調整にはget_column_letter関数を使い、27列以上のデータにも対応してください

* 銘柄ごとにシートを分割し、ヘッダー書式と数値書式を適用すれば、そのまま共有できるレポートになります

* 出力先ファイルがExcelで開かれているとPermissionErrorになるため、日時付きファイル名で保存する設計を推奨します

次のステップとして、発展版コードにテクニカル指標(RSI・移動平均等)の列を追加し、条件付き書式で売買シグナルをセル色分けする機能を実装してみてください。openpyxlconditional_formattingモジュールを使えば、Pythonだけで完結する自動レポートシステムが完成します。

タイトルとURLをコピーしました