株価データはほぼ常に時系列です。pandas には時系列を扱う専用の仕組みがあり、DatetimeIndex をうまく使うと、月次・週次への変換、移動平均、営業日調整までを短いコードで書けます。

本記事では、DatetimeIndex の作成から、resample によるリサンプリング、rolling による移動窓、営業日カレンダーの基本までを扱います。

目次

  1. インストール
  2. サンプルデータの準備
  3. DatetimeIndex の便利機能
  4. リサンプリング(日次 → 週次・月次)
  5. 移動窓(rolling)
  6. 拡張窓(expanding)
  7. シフトと差分
  8. 営業日のずらし(BusinessDay)
  9. 欠損日と再インデックス

インストール

Terminal window
pip install pandas

検証バージョン: Python 3.12.5 / pandas 2.2.3

サンプルデータの準備

連続した日付の終値を Series で用意します。

import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
dates = pd.date_range("2026-01-05", periods=60, freq="B") # 60 営業日
prices = 2900 + rng.normal(0, 30, size=len(dates)).cumsum()
close = pd.Series(prices.round(1), index=dates, name="C")
print(close.head())
print(close.index)

pd.date_rangefreq="B"営業日(平日) の意味です。出力されるインデックスは DatetimeIndex で、時系列専用の機能が使えるようになります。

DatetimeIndex の便利機能

DatetimeIndex は、文字列での部分一致スライスができます。

print(close.loc["2026-01"]) # 1 月だけ
print(close.loc["2026-01":"2026-02"]) # 1 月から 2 月末まで

属性経由で年月日や曜日も取り出せます。

print(close.index.year[:5])
print(close.index.month[:5])
print(close.index.day_name()[:5])

DataFrame の列(Date)に対しても、pd.to_datetime で変換した後に .dt 経由で同じ機能が使えます。

df = close.reset_index().rename(columns={"index": "Date"})
df["weekday"] = df["Date"].dt.day_name()
print(df.head())

リサンプリング(日次 → 週次・月次)

resample は時間軸での再集計を行います。SQL の GROUP BY 月 に近い操作ですが、頻度の指定が文字列で簡潔です。

weekly = close.resample("W-FRI").last() # 金曜終値ベースで週次に
monthly = close.resample("ME").last() # 月末終値ベースで月次に
print(weekly.head())
print(monthly.head())

代表的な頻度コードを表にまとめます。

コード意味
D日次(暦日)
B営業日(月〜金)
W-FRI週次(金曜起点)
ME月末(Month End)
MS月初(Month Start)
QE四半期末
YE年末

集計関数は last のほか、first / mean / max / min / sum / agg が使えます。OHLC を作るには .agg が便利です。

ohlc_monthly = close.resample("ME").agg(["first", "max", "min", "last"])
ohlc_monthly.columns = ["O", "H", "L", "C"]
print(ohlc_monthly.head())

移動窓(rolling)

rolling は固定幅のウィンドウで集計を行います。移動平均線(SMA)はこの代表例です。

sma_5 = close.rolling(window=5).mean()
sma_25 = close.rolling(window=25).mean()
print(sma_5.head(10))

ウィンドウ幅に満たない先頭部分は NaN になります。これは仕様で、未来データを含めずに計算を始めるための保護です。

min_periods で「最低何件あれば計算する」を指定できます。先頭の NaN を減らしたいときに使います。

sma_5_relaxed = close.rolling(window=5, min_periods=1).mean()

集計関数は mean / sum / max / min / std / apply が使えます。

volatility_20 = close.pct_change().rolling(20).std(ddof=1)
print(volatility_20.tail())

拡張窓(expanding)

「先頭から現在までの累積」を計算するときは expanding を使います。

running_max = close.expanding().max()
print(running_max.tail())

最大ドローダウン(過去最高値からの下落率)の計算によく使います。

drawdown = close / running_max - 1
print(drawdown.min()) # 期間中の最大ドローダウン

シフトと差分

shift(n) は系列を n 行ずらします。リターン計算や先読み防止に必須です。

prev_close = close.shift(1)
ret = close / prev_close - 1
print(ret.head())

diff() は前期との差分(x - x.shift(1) と同等)を返します。

print(close.diff().head())

バックテストでは「当日のシグナルで翌日約定」を表現するため signal.shift(1) を使います(#11-3「移動平均クロス戦略を 5 銘柄でバックテスト」 参照)。

営業日のずらし(BusinessDay)

「3 営業日後の日付」を扱うときは BusinessDay オフセットを使います。

from pandas.tseries.offsets import BusinessDay
today = pd.Timestamp("2026-04-30")
print(today + BusinessDay(3)) # 3 営業日後

日本の祝日を考慮するには pandas_market_calendars などの追加ライブラリが必要です。J-Quants 由来のデータは元々営業日のみが含まれるため、多くの場合は気にせずインデックスをそのまま使えます。

欠損日と再インデックス

複数銘柄を比較するとき、銘柄ごとに上場日が違って欠損日が生じることがあります。reindex で揃えてから ffill で前日値を埋めると、行数を合わせられます。

full_index = pd.date_range(close.index.min(), close.index.max(), freq="B")
aligned = close.reindex(full_index).ffill()
print(aligned.head())

ffill を使うかは分析目的次第です。リターン計算では「欠損日に値が無いまま」が正しいケースもあるため、機械的に埋めない判断も重要です。

生成AI へのプロンプト例

時系列処理は条件が増えがちです。前提を箇条書きで明示すると、生成AI のコードが安定します。

pandas で次の時系列処理を行うコードを書いてください。
入力:
- df(列: Date, Code, C)
- 期間: 約 4 年分、銘柄数 5
要件:
- Date を DatetimeIndex に設定
- 銘柄ごとに月末終値ベースの月次系列を作る
- 月次の単純リターンを計算
- 12 ヶ月の移動平均と標準偏差を追加
- 出力は Date(月末)を index、銘柄を columns に持つ DataFrame を 3 つ
(price, return, volatility)
- pandas 2.2 系

入力の構造・出力の形・ライブラリ版を具体化すると、再現可能なコードが返ります。

まとめ

  • DatetimeIndex を持たせると、文字列スライスや年月日抽出など時系列特有の操作が可能になる
  • resample は時間軸の再集計、rolling は固定幅の移動窓
  • expanding は累積、shift / diff はずらしと差分
  • 先読み防止のために、シグナル計算では shift(1) を必ず挟む