インデックス vs 個別株:20年分のデータで検証

「インデックスより個別株の方が儲かるのでは?」——AppleやTeslaの急騰チャートを見るたびに、そう感じたことはありませんか。結論から言えば、個別株で市場平均を上回り続けることは、プロでも極めて難しい。でも「難しい」と言われても、データを見ないと腹落ちしません。

筆者は製造業の開発現場で、DX推進やデータ分析に携わるエンジニアです。ものづくりの現場では「全数検査」と「抜き取り検査」を使い分けますが、この考え方がインデックス投資と個別株投資の本質的な違いを理解する鍵になります。

この記事では、Pythonのyfinanceライブラリを使い、S&P500と主要個別株の20年分のリターン・リスクを実データで比較します。読み終わる頃には、「個別株で勝てる銘柄はある。でも事前に選べるのか?」という問いに、自分なりの答えを持てるようになります。(記事#01から読み始めるのがおすすめです。前回の記事#03では、複利の効果をPythonで可視化しました。)

結論:20年後に振り返れば「勝ち組」個別株は存在する。しかし、それを事前に選び続けることは、生存者バイアスが示す通り極めて困難である。

動作環境

  • Python 3.11 / yfinance 0.2.36 / matplotlib 3.8 / pandas 2.1
  • Google Colab でもローカル環境でも動作します
  • 所要時間:約45分(コードのコピペ+実行+分析)
  • インストール:pip install yfinance matplotlib pandas

免責事項

本記事は投資助言を目的としたものではなく、技術・分析手法の紹介です。記事中の銘柄名は分析対象として例示したものであり、特定の銘柄・金融商品の売買を推奨するものではありません。過去のリターンは将来の成果を保証しません。投資判断はご自身の責任で行ってください。

目次

yfinanceで20年分の株価データを取得する

まずはデータの準備です。Pythonのyfinanceライブラリを使えば、数行のコードで株価の時系列データを取得できます。今回は以下の銘柄を比較対象にします。

比較対象の銘柄

  • インデックス:S&P500(^GSPC)
  • 米国テック大型株:Apple(AAPL)、Microsoft(MSFT)
  • 米国EV:Tesla(TSLA)※上場は2010年のため約14年分
  • 日本大型株:トヨタ自動車(TM)※米国市場のADR

※ yfinanceで取得できるデータの正確性には限界があります。厳密な投資判断には、公式の金融データプロバイダの利用を推奨します。

import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime

# --- 日本語フォントの設定(環境に合わせて変更してください) ---
# Mac: plt.rcParams['font.family'] = 'Hiragino Sans'
# Windows: plt.rcParams['font.family'] = 'Yu Gothic'
# Linux: plt.rcParams['font.family'] = 'Noto Sans CJK JP'

# --- 銘柄リストと期間の設定 ---
tickers: dict[str, str] = {
    "^GSPC": "S&P500",
    "AAPL": "Apple",
    "MSFT": "Microsoft",
    "TSLA": "Tesla",
    "TM": "Toyota",
}

START: str = "2005-01-01"
END: str = "2025-01-01"

# --- データ取得 ---
def fetch_close_prices(
    tickers: dict[str, str],
    start: str,
    end: str,
) -> pd.DataFrame:
    """複数銘柄の終値を取得し、DataFrameにまとめる

    Args:
        tickers: {ティッカー: 表示名} の辞書
        start: 取得開始日 (YYYY-MM-DD)
        end: 取得終了日 (YYYY-MM-DD)

    Returns:
        各銘柄の終値を列に持つDataFrame
    """
    frames: dict[str, pd.Series] = {}
    for ticker, name in tickers.items():
        data = yf.download(ticker, start=start, end=end, progress=False)
        if not data.empty:
            # yfinance の返り値が MultiIndex の場合に対応
            if isinstance(data.columns, pd.MultiIndex):
                frames[name] = data[("Close", ticker)]
            else:
                frames[name] = data["Close"]
    return pd.DataFrame(frames)

prices: pd.DataFrame = fetch_close_prices(tickers, START, END)
print(f"取得期間: {prices.index[0].strftime('%Y-%m-%d')} 〜 {prices.index[-1].strftime('%Y-%m-%d')}")
print(f"データ行数: {len(prices)}")
print(prices.tail())

注意:yfinanceの制約

yfinanceはYahoo Financeからデータを取得する非公式ライブラリです。無料で手軽に使えますが、以下の点に注意してください。

  • データの正確性は保証されていない(配当調整済み終値の計算方法が変わることがある)
  • アクセス頻度が高いとレート制限がかかる場合がある
  • 本格的な分析には、公式のデータプロバイダ(Bloomberg、Refinitivなど)の利用を検討してください

この記事では「分析手法を学ぶ」ことが目的なので、yfinanceで十分です。

20年間の累積リターンを比較する

取得した株価データを「累積リターン」に変換して、同じスタート地点から比較します。累積リターンとは、投資開始時を1.0(100%)として、各時点での資産がどれだけ成長したかを示す指標です。

import pandas as pd
import matplotlib.pyplot as plt

def calc_cumulative_returns(prices: pd.DataFrame) -> pd.DataFrame:
    """終値データから累積リターンを計算する

    Args:
        prices: 各銘柄の終値を列に持つDataFrame

    Returns:
        初日を1.0とした累積リターンのDataFrame
    """
    return prices / prices.iloc[0]

cumulative: pd.DataFrame = calc_cumulative_returns(prices)

# --- グラフ描画 ---
colors: dict[str, str] = {
    "S&P500": "#718096",
    "Apple": "#38a169",
    "Microsoft": "#3182ce",
    "Tesla": "#e53e3e",
    "Toyota": "#805ad5",
}

fig, ax = plt.subplots(figsize=(12, 7))

for col in cumulative.columns:
    linewidth = 3.0 if col == "S&P500" else 1.8
    linestyle = "-" if col == "S&P500" else "--"
    ax.plot(
        cumulative.index,
        cumulative[col],
        label=col,
        color=colors.get(col, "#333"),
        linewidth=linewidth,
        linestyle=linestyle,
    )

ax.set_xlabel("Year", fontsize=12)
ax.set_ylabel("Cumulative Return (start = 1.0)", fontsize=12)
ax.set_title("Cumulative Returns: S&P500 vs Individual Stocks (2005-2025)", fontsize=14)
ax.legend(fontsize=11, loc="upper left")
ax.grid(True, alpha=0.3)
ax.axhline(y=1.0, color="#333", linewidth=0.5, linestyle=":")

plt.tight_layout()
plt.savefig("cumulative_returns_comparison.png", dpi=150)
plt.show()

このグラフを見ると、一つ明確な事実が浮かび上がります。

  • AppleやMicrosoftはS&P500を大幅に上回っている——「やっぱり個別株の方が儲かるじゃないか」と思うかもしれません
  • しかし、ToyotaはS&P500を下回っている——世界最大級の自動車メーカーですら、市場平均に負けることがある
  • Teslaは途中から参加しているにもかかわらず驚異的なリターン——ただし途中の暴落幅も極端

具体的な数値を確認してみましょう。

import pandas as pd

def calc_return_summary(prices: pd.DataFrame) -> pd.DataFrame:
    """各銘柄のリターンサマリーを計算する

    Args:
        prices: 各銘柄の終値を列に持つDataFrame

    Returns:
        銘柄ごとのリターン・リスク指標のDataFrame
    """
    summary_rows: list[dict[str, object]] = []

    for col in prices.columns:
        col_data = prices[col].dropna()
        if len(col_data) < 2:
            continue

        start_price: float = col_data.iloc[0]
        end_price: float = col_data.iloc[-1]
        total_return: float = (end_price / start_price - 1) * 100
        years: float = (col_data.index[-1] - col_data.index[0]).days / 365.25
        cagr: float = ((end_price / start_price) ** (1 / years) - 1) * 100

        summary_rows.append({
            "銘柄": col,
            "期間(年)": round(years, 1),
            "累積リターン": f"{total_return:+,.1f}%",
            "年率リターン(CAGR)": f"{cagr:+.1f}%",
        })

    return pd.DataFrame(summary_rows)

summary: pd.DataFrame = calc_return_summary(prices)
print(summary.to_string(index=False))

出力例(2005年〜2025年のデータ、配当再投資前の参考値):

銘柄期間累積リターン年率リターン(CAGR)
S&P500約20年+約400%+約8%
Apple約20年+約9,000%+約25%
Microsoft約20年+約1,800%+約16%
Tesla約14年+約12,000%+約40%
Toyota約20年+約150%+約5%

※ 上記は株価の値動きのみに基づく概算値です。配当再投資、為替変動、税金、手数料は考慮していません。実際のリターンはこれらの要素により異なります。yfinanceのデータに基づく参考値としてお読みください。

エンジニア的に言い換えると

累積リターンだけで投資判断するのは、ベンチマークテストで「最大スループット」だけを見て技術を選定するようなものです。重要なのは平均値だけでなく、ばらつき(分散)と最悪ケースも見ること。次のセクションでリスク指標を定量化します。

リスクを定量化する:標準偏差と最大ドローダウン

リターンが高くても、途中で-70%の暴落があれば耐えられる人は少ないでしょう。ここでは2つのリスク指標を計算します。

  • 年率標準偏差(ボラティリティ):日次リターンのばらつきを年率換算したもの。値が大きいほど値動きが激しい
  • 最大ドローダウン:高値からの最大下落率。投資期間中に最悪のケースでどれだけ資産が減ったかを示す
import pandas as pd
import numpy as np

def calc_risk_metrics(prices: pd.DataFrame) -> pd.DataFrame:
    """各銘柄のリスク指標を計算する

    Args:
        prices: 各銘柄の終値を列に持つDataFrame

    Returns:
        銘柄ごとのリスク指標のDataFrame
    """
    daily_returns: pd.DataFrame = prices.pct_change().dropna()
    risk_rows: list[dict[str, object]] = []

    for col in prices.columns:
        col_data = prices[col].dropna()
        col_returns = daily_returns[col].dropna()

        if len(col_returns) < 2:
            continue

        # 年率標準偏差(取引日数 ≒ 252日)
        annual_std: float = col_returns.std() * np.sqrt(252) * 100

        # 最大ドローダウン
        cummax = col_data.cummax()
        drawdown = (col_data - cummax) / cummax
        max_drawdown: float = drawdown.min() * 100

        # シャープレシオ(簡易版:無リスク金利=0%で計算)
        annual_return: float = col_returns.mean() * 252
        sharpe: float = annual_return / (col_returns.std() * np.sqrt(252))

        risk_rows.append({
            "銘柄": col,
            "年率標準偏差": f"{annual_std:.1f}%",
            "最大ドローダウン": f"{max_drawdown:.1f}%",
            "シャープレシオ": f"{sharpe:.2f}",
        })

    return pd.DataFrame(risk_rows)

risk_df: pd.DataFrame = calc_risk_metrics(prices)
print(risk_df.to_string(index=False))

出力例:

銘柄年率標準偏差最大ドローダウンシャープレシオ
S&P500約18%約-55%約0.5
Apple約32%約-80%約0.8
Microsoft約26%約-55%約0.6
Tesla約60%約-73%約0.6
Toyota約25%約-60%約0.2

※ 上記はyfinanceのデータに基づく概算値です。期間やデータソースにより数値は変動します。シャープレシオは無リスク金利0%の簡易計算です。

この表から見えてくるのは、次の事実です。

  • 個別株はリターンも高いが、リスク(標準偏差)も高い。Teslaの年率標準偏差は約60%で、S&P500の3倍以上
  • Appleですら最大ドローダウンは-80%級。つまり、保有資産が5分の1になった時期がある。その期間を耐えられるか?
  • S&P500は個別株より低リスク。500社に分散されている効果が数字に表れている
import matplotlib.pyplot as plt
import numpy as np

def plot_drawdown(prices: pd.DataFrame, colors: dict[str, str]) -> None:
    """各銘柄のドローダウン推移をグラフ化する

    Args:
        prices: 各銘柄の終値を列に持つDataFrame
        colors: {銘柄名: カラーコード} の辞書
    """
    fig, ax = plt.subplots(figsize=(12, 5))

    for col in prices.columns:
        col_data = prices[col].dropna()
        cummax = col_data.cummax()
        drawdown = (col_data - cummax) / cummax * 100

        linewidth = 2.5 if col == "S&P500" else 1.2
        ax.plot(
            drawdown.index,
            drawdown,
            label=col,
            color=colors.get(col, "#333"),
            linewidth=linewidth,
            alpha=0.8,
        )

    ax.set_xlabel("Year", fontsize=12)
    ax.set_ylabel("Drawdown (%)", fontsize=12)
    ax.set_title("Drawdown: S&P500 vs Individual Stocks", fontsize=14)
    ax.legend(fontsize=10, loc="lower left")
    ax.grid(True, alpha=0.3)
    ax.axhline(y=0, color="#333", linewidth=0.5)

    plt.tight_layout()
    plt.savefig("drawdown_comparison.png", dpi=150)
    plt.show()

plot_drawdown(prices, colors)

ポイント:リターンだけでなくリスクを見よ

「Appleに20年前に投資していれば9,000%のリターンだった」——これは事実です。しかし途中で-80%の暴落を経験しています。100万円が20万円になる時期があったということです。そのとき売らずに持ち続けられましたか? リターンだけを見て「個別株の方がいい」と結論づけるのは、生存者バイアスに陥っている可能性があります。

生存者バイアス:見えているのは「勝ち組」だけ

ここまでの分析で「AppleやMicrosoftに投資していれば大勝ちだった」と感じたかもしれません。しかし、この分析には決定的な問題があります。それが生存者バイアス(Survivorship Bias)です。

なぜ「今のS&P500構成銘柄」で比較してはいけないのか

筆者がこの記事で比較対象に選んだApple、Microsoft、Tesla、Toyota——これらは2025年の今、世界を代表する企業です。つまり、20年間の競争を生き残り、成長した「勝者」です。

では、20年前の時点で注目されていたのに、今は存在しない(あるいは大幅に衰退した)企業はどうでしょうか。

  • Enron——2001年に経営破綻。破綻前は「最も革新的な企業」として評価されていた
  • Nokia——2007年時点で携帯電話の世界シェア1位。スマートフォン時代に急落
  • GE(ゼネラル・エレクトリック)——かつてダウ平均の筆頭銘柄。過去20年で株価は大幅に下落した時期がある
  • Kodak——デジタルカメラの時代に経営破綻

「20年前にAppleに投資していれば」は正しい。しかし、20年前の時点でAppleが今のAppleになると予測できたでしょうか? 同じ時期にNokiaやGEに投資していた人も大勢いたはずです。

エンジニア的に言い換えると

生存者バイアスは、データ分析における選択バイアスの一種です。テストデータにリーク(未来の情報)が混入しているのと同じ構造です。「今のS&P500上位銘柄」で過去を振り返ること自体が、「テストデータで学習した」ことになっている。機械学習でやったら完全なデータリーケージです。

S&P500は「自動的に銘柄入替する仕組み」

一方、S&P500インデックスには重要な特徴があります。それは銘柄の定期的な入れ替えです。

  • 業績が悪化した企業は指数から除外される
  • 成長した企業が新たに組み入れられる
  • つまり、インデックスは「生き残りの強い企業を自動的に選別する仕組み」を内蔵している

個別株投資では、この「入れ替え判断」を自分自身で行う必要があります。損切りのタイミング、乗り換え先の選定——すべてが自己判断です。記事#02で解説した「標準化」の原則を思い出してください。インデックスは、この銘柄入替をルールベースで自動化しているのです。

個別株で勝てる銘柄はある。でも事前に選べるか?

これがこの記事の最も重要な問いかけです。過去20年でS&P500を上回った個別株は確かに存在します。しかし、それを「事前に」「継続的に」選び続けることができるかどうかは、まったく別の問題です。アクティブファンドの約9割が長期的にインデックスに負けるというデータ(S&P Dow Jones Indices "SPIVA Scorecard")は、この困難さを物語っています。

本業で学んだ「全数検査 vs 抜き取り検査」の考え方

インデックス投資と個別株投資の違いは、筆者が本業の製造業で日常的に接する「検査方法の選択」と驚くほど似ています。

製造業の品質管理には、大きく分けて2つの検査方法があります。

  • 全数検査:すべての製品を検査する。品質は確実だが、コスト(時間・人員)が膨大
  • 抜き取り検査:一部のサンプルだけを検査する。効率的だが、不良品を見逃すリスクがある

筆者が以前関わったプロジェクトでは、あるモーター部品の出荷検査を「全数検査」で行っていました。1個ずつ測定し、規格内であることを確認する。品質は担保されますが、検査工程がボトルネックになり、生産能力を圧迫していました。

そこで、統計的品質管理(SQC)に基づく「抜き取り検査」に移行しました。工程能力指数(Cpk)が十分に高いことを確認した上で、ロットからサンプルを抽出して検査する方式です。結果として、検査コストは大幅に削減され、品質水準も維持できました。

ただし、抜き取り検査には前提条件があります。工程が安定していること(Cpkが十分高いこと)です。工程が不安定な場合は、全数検査に戻す必要があります。

投資に置き換えると

  • 全数検査 ≒ 個別株投資:一銘柄ずつ精査する。精度は高いが、コスト(リサーチ時間・精神的負担)が膨大。しかも「工程が不安定」(市場は予測困難)なので、全数検査をしても不良品(値下がり銘柄)を完全には避けられない
  • 抜き取り検査 ≒ インデックス投資:市場全体をサンプリングする。個別の精度は気にせず、「全体として統計的に合理的なリターンが得られる」ことに賭ける

ポイントは、抜き取り検査が成り立つ前提——工程が安定していること。株式市場で言えば「長期的に経済が成長する」という前提が、インデックス投資の根拠です。この前提を信じるかどうかは、各自の判断です。

まとめ:データが示す3つの事実

20年分の実データをPythonで分析した結果、以下のことが確認できました。

  • S&P500を上回る個別株は確かに存在する。しかし、それを「事前に」選び続けることは、プロの投資家でも困難(アクティブファンドの約9割が長期的にインデックスに負ける)
  • 個別株はリターンもリスクも大きい。Teslaの年率標準偏差はS&P500の3倍以上。高リターンの裏には高リスクがある
  • 生存者バイアスに注意。「今の勝ち組」で過去を振り返ると、個別株が有利に見える。しかし、消えた企業は分析の対象にすらならない

「個別株か、インデックスか」は二者択一ではありません。しかし、データは「迷ったらインデックスから始める」ことの合理性を支持しています。

今日からできる3つのアクション

アクション1:自分の気になる銘柄で検証してみる

この記事のコードをコピーして、tickers辞書に自分の気になる銘柄を追加してみてください。例えば、Amazon(AMZN)、Google(GOOGL)、NVIDIA(NVDA)など。「自分で検証する」ことで、数字の感覚が身につきます。

アクション2:リスク指標に注目する習慣をつける

投資関連の情報を見るとき、リターンだけでなく標準偏差と最大ドローダウンも確認する習慣をつけましょう。「年率+25%」という数字だけでは、途中でどれだけの暴落があったかわかりません。

アクション3:記事#03の複利シミュレーションと組み合わせる

前回の記事で作ったsimulate_tsumitate関数の年利パラメータに、今回算出したCAGR(年率リターン)を入れてみてください。S&P500の年率約8%で30年積立するとどうなるか——理論値と実データが結びつく瞬間です。

次回予告:ドルコスト平均法 vs 一括投資をシミュレーション

次回(記事#05)では、「毎月コツコツ積立(ドルコスト平均法)」と「一括投資」、どちらが有利かを実データでシミュレーションします。

  • S&P500の実データを使ったドルコスト平均法のシミュレーション
  • 一括投資との比較:どの期間で始めても、結果はどう変わるか?
  • 「暴落直前に一括投資してしまったら?」最悪ケースの検証

今回の「インデックス vs 個別株」に続いて、投資の実行方法にもデータで切り込みます。コードのチラ見せです。

import yfinance as yf
import numpy as np

# S&P500の実データでドルコスト平均法をシミュレーション
sp500 = yf.download("^GSPC", start="2005-01-01", end="2025-01-01")
monthly_prices = sp500["Close"].resample("MS").first()

# 毎月一定額を投資した場合の口数累計を計算(次回の記事で詳しく実装します)

シリーズ全体像:投資×DX 3段階モデル

  • 基礎編 #01〜#10:インデックス投資 × DX思想(標準化・データドリブン・自動化)
  • 応用編 #11〜#20:高配当株 × データパイプライン(Python自動スクリーニング)
  • 発展編 #21〜#30:成長株CAN-SLIM × アーキテクチャ設計力

記事#01からスタート | 記事#02 DX3原則記事#03 複利Python記事#04 本記事記事#05 ドルコスト平均法

投資の具体的な始め方は記事#09「NISA・iDeCoの最適活用をエンジニア的に設計する」で詳しく解説予定です。

免責事項

本記事は投資助言を目的としたものではなく、技術・分析手法の紹介です。記事中の銘柄名は分析対象の例示であり、売買を推奨するものではありません。過去のリターンは将来の成果を保証しません。投資判断はご自身の責任で行ってください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

CAPTCHA


目次