スライドする窓
今日の価格だけでは意味が乏しく、トレーダーは直近の過去と比べます。ローリングウィンドウは直近 n 件の観測を保持して1歩ずつ進むため、各日付はその直近の履歴から計算された統計量(移動平均、直近のボラティリティ、直近の高値など)を持ちます。pandas では series.rolling(n) が窓を表し、.mean() などの統計量がすべての位置で一度に計算します。
import pandas as pd
prices = pd.Series(
[100.0, 102, 101, 105, 107, 106, 110],
index=pd.bdate_range("2026-03-02", periods=7),
name="close",
)
moving_average = prices.rolling(3).mean()
print(moving_average.round(2).tolist())
# [nan, nan, 101.0, 102.67, 104.33, 106.0, 107.67]
print(prices.rolling(3).max().tolist()) # [nan, nan, 102.0, 105.0, 107.0, 107.0, 110.0] the 3-day high系列の最初
最初の n - 1 個の位置にはまだ完全な窓がないため、結果は NaN です。これは正直な扱いで、2つの価格の平均は3日平均ではありません。それでも早く値が欲しいなら、min_periods=1 で使えるデータだけで計算でき、expanding() は固定窓の代わりに開始からのすべてを使います。標準偏差のように複数の点が必要な統計量は、観測が十分に届くまで NaN です。単純リターンのようにすでに NaN で始まる系列では、最初の有効な窓が1つ後ろにずれます。
# min_periods=1: use whatever is available at the start
print(prices.rolling(3, min_periods=1).mean().round(2).tolist()) # [100.0, 101.0, 101.0, 102.67, 104.33, 106.0, 107.67]
# expanding(): everything since the first day, not a fixed window
print(prices.expanding().mean().round(2).tolist()) # [100.0, 101.0, 101.0, 102.0, 103.0, 103.5, 104.43]
# Rolling volatility of the returns: the first return is NaN, so three returns exist only from the 4th day
returns = prices.pct_change()
print(returns.rolling(3).std().round(4).tolist()) # [nan, nan, nan, 0.0249, 0.0248, 0.0246, 0.0237]窓からシグナルへ
ローリング統計量は比較することで売買シグナルになります。Zスコアは今日の価格が直近の平均から直近の標準偏差の何個分離れているかを表します:(price - rolling mean) / rolling std。ローリングオブジェクトは1回作って(r = prices.rolling(3))2回使いましょう。移動平均のクロスは短期平均と長期平均を比べ、短期が上にある間は直近のトレンドが上向きです。比較は真偽値を返し、astype("int64") で0/1のシグナルにします。NaN との比較は False なので、両方の平均が揃うまでシグナルは0です。
# z-score of each price against its own 3-day window
rolling = prices.rolling(3)
zscore = (prices - rolling.mean()) / rolling.std()
print(zscore.round(2).tolist()) # [nan, nan, 0.0, 1.12, 0.87, 0.0, 1.12]# Moving-average crossover: 1 while the 2-day average is above the 4-day average
fast = prices.rolling(2).mean()
slow = prices.rolling(4).mean()
signal = (fast > slow).astype("int64")
print(signal.tolist()) # [0, 0, 0, 1, 1, 1, 1]
# Trade on the next day: the signal of today's close cannot be traded before tomorrow
print(signal.shift(1).fillna(0).astype("int64").tolist()) # [0, 0, 0, 0, 1, 1, 1]未来を覗かないローリングウィンドウは現在の行で終わるので、過去しか使いません。
center=True は現在の行を窓の中央に置くため未来の価格を使い、チャートでは見事でも取引はできません。今日の終値から計算したシグナルは明日適用すべきなので、shift(1) を使います。series.rolling(n).mean()(std()・max()なども)は、各位置で直近n件の統計量を計算する- 最初の
n - 1個はNaN。min_periodsとexpanding()は意図的にそれを変える - Zスコアと移動平均のクロスは窓をシグナルに変える。取引前にシグナルをずらし、
center=Trueは避ける
次は欠損データの扱いです。実際の価格表に必ず含まれる NaN をどうするかを学びます。
ラボ:採点される内容
moving_averageで価格をならす25点rolling_volatilityで直近のリスクを測る25点rolling_zscoreで各価格を窓内で標準化する25点ma_crossoverで2本の平均線をシグナルにする25点