株価データはほぼ常に時系列です。pandas には時系列を扱う専用の仕組みがあり、DatetimeIndex をうまく使うと、月次・週次への変換、移動平均、営業日調整までを短いコードで書けます。
本記事では、DatetimeIndex の作成から、resample によるリサンプリング、rolling による移動窓、営業日カレンダーの基本までを扱います。
目次
- インストール
- サンプルデータの準備
- DatetimeIndex の便利機能
- リサンプリング(日次 → 週次・月次)
- 移動窓(rolling)
- 拡張窓(expanding)
- シフトと差分
- 営業日のずらし(BusinessDay)
- 欠損日と再インデックス
インストール
pip install pandas検証バージョン: Python 3.12.5 / pandas 2.2.3
サンプルデータの準備
連続した日付の終値を Series で用意します。
import numpy as npimport 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_range の freq="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 - 1print(drawdown.min()) # 期間中の最大ドローダウンシフトと差分
shift(n) は系列を n 行ずらします。リターン計算や先読み防止に必須です。
prev_close = close.shift(1)ret = close / prev_close - 1print(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)を必ず挟む