機械学習 で 株価分析 にチャレンジ Part2

前回の記事はこちら

今回はデータの前処理と株価分析に使用できる特徴量についてそれぞれ解説していきます。
データの前処理や各種特徴量を用いなければ、PCが人と同じように考えることはできません。

プログラミングする上で大事な要素になってきますね。

2.1 株価データ:AIにとってはただの「時系列の数字列」

人間にとっての株価チャートは「流れ」や「勢い」を直感的に感じることができます。
しかし、AI(機械学習モデル)はグラフをそのまま理解できません。
必要なのは、特徴量(feature)と呼ばれる「説明変数」の集合です。

たとえば以下のような生データ:

DateOpenHighLowCloseVolume
2025/09/29180119111796189850,230,600

人間なら「大きく上昇したな」と見てわかりますが、
AIにとってはただの6つの数値です。
このままでは傾向を学べません。

そこで本章のテーマ、「前処理と特徴量生成」が必要になります。

2.2 文字列→数値変換と日付の整理

実際のコードを見ながらデータの取得方法について理解していきましょう。
株価のデータをスクレイピングするのはAPIの規定に抵触する危険性がありますので、
株価データをWEBサイトからコピペしてやってみます。

https://96ut.comというサイトで過去1年分くらいの株価情報を拾えます。

入力データは、「日付」「始値」「高値」「低値」「終値」「出来高」の6データを持ってきます。pandasを使用してデータを読み込ませたら、下記コードによりデータの前処理をします。

df0 = pd.read_csv(StringIO(data_str), sep='\t', header=None,
    names=['Date','Open','High','Low','Close','Volume'])
df0['Date'] = pd.to_datetime(df0['Date'])
for col in ['Open','High','Low','Close','Volume']:
    df0[col] = df0[col].astype(str).str.replace(',', '', regex=False).astype(float)
df0 = df0.sort_values('Date').reset_index(drop=True)

ここでやっていること

  1. 文字列として読み込まれたデータ(”1,234″など)をfloat型に変換
  2. カンマを除去
  3. 日付列をDatetime型に変換して、時系列順にソート

この3つの処理をすることで、「時間軸を持つ数列」として扱えるようになります。

具体例
「新聞の株価欄」をスプレッドシートに打ち込んだ状態から、
AIが読めるように“整理して並べ替えた”段階。


データをPCが理解できる形になったところで、各種特徴量の説明や数式の理解を深めていきましょう。pythonのPandasを使用すれば式の理解せずに使用できますが、どの特徴量を使用すべきか自分で判断できるといいと思いますので説明も載せておきます。

2.3 ステップ2:日次リターン(変化率)の計算

日付別のデータを作れたので、次は前日に比べて株価の終値がどう変化したかを計算するプログラムを作成します。pythonを使用していれば、pandasのpct_change関数を使用することで簡単にデータ処理できます。

df['ret_1'] = df['Close'].pct_change(1)
p = 0.98
lo, hi = df['ret_1'].quantile(1-p), df['ret_1'].quantile(p)
df['ret_1'] = df['ret_1'].clip(lower=lo, upper=hi)
  • pct_change(1) は、前日比の変化率
    例:前日1000円 → 今日1020円 → +0.02(2%上昇)
  • 「どれくらい上がった/下がったか」という情報を得る
  • 外れ値を除外してノイズを減らす
    大量のデータを扱う際に、データの欠損などが原因で変化率が以上となることがあります。
    → quantile(0.02, 0.98) 関数を使用して上位・下位2%の極端な変動をカットできます。

具体例
株式市場ではストップ高/安があるため、変化率は歪みが強い
この外れ値クリップを入れることで、安定した学習が可能になります。

2.4 移動平均(SMA・EMA)

株式投資をやっているほとんどの人が移動平均線はご存知かと思います。機械学習においても使用することがあり、こちらもpythonのpandasで関数として用意されているので実装が簡単にできます。

for w in SMA_WINDOWS:
    sma = df['Close'].rolling(w, min_periods=w).mean()
    df[f'close_sma_ratio_{w}'] = df['Close'] / sma
for w in EMA_WINDOWS:
    ema = df['Close'].ewm(span=w, adjust=False, min_periods=w).mean()
    df[f'close_ema_ratio_{w}'] = df['Close'] / ema

SMA(単純移動平均)

  • 過去w日間の平均
    例:5日SMA = (今日~5日前の終値の平均)

数式で書くと下式のようになります。

$$SMA_t = \frac{1}{N} \sum_{i=0}^{N-1} X_{t-i}$$

EMA(指数移動平均)

  • 直近ほど重みを多くする平均(反応が早い)

EMAの計算式は下式のようになります。

$$EMA_t = \alpha X_t + (1 – \alpha) EMA_{t-1}$$

ここで、$\alpha = \frac{2}{N + 1}$となり、Nは平均する日数を表します。この変数により重みを調整します。この式からわかるように、平均日数を小さくすることで最新値側の影響が強まり、大きくすることで長期の長期の平均に近づきます。

わかりやすく、式を展開してみましょう。
$$EMA_t = \alpha X_t + \alpha(1-\alpha)X_{t-1} + \alpha(1-\alpha)^2 X_{t-2} + \cdots$$

このように過去の値には減衰係数が指数的に乗るため直前の値動きを重視した計算ができます。

SMAとEMAはどちらも次のような見方で使われます。
短期売買を目的とするのであれば、直近の値動きが反映されやすいEMAを使用した方がいいですね。

  • トレンドを平滑化して「今の価格が平均からどれくらい離れているか」を見る。
  • 「価格÷平均」で比率をとることで、銘柄スケールに依存しない相対的な強さを表現。

具体例
Close/SMA20 > 1.05 → 「平均より5%高い=強気」

Close/SMA20 < 0.95 → 「平均より5%低い=弱気」

2.6 RSI(相対力指数)

RSIは買われすぎや売られすぎを判断する指標です。
価格変化$\Delta_t = Close_t – Close_{t-1}$と定義し、上昇と下落をそれぞれ
$Gain_t = \max(\Delta_t, 0)$, $Loss_t = \max(-\Delta_t, 0)$で定義してあげることで、平均上昇幅や下落幅を下式のように表せます。

$$\text{AvgGain}_{t} = \frac{1}{N} \sum{i=0}^{N-1} Gain_{t-i}$$
$$\text{AvgLoss}_{t} = \frac{1}{N} \sum{i=0}^{N-1} Loss_{t-i}$$

相対強度は$RS_t = \frac{\text{AvgGain}_t}{\text{AvgLoss}_t}$と表せ、RSIは$RS$を使用して

$$RSI_t = 100 – \frac{100}{1 + RS_t}$$

と表せます。

delta = df['Close'].diff()
gain = delta.clip(lower=0).rolling(RSI_PERIOD, min_periods=RSI_PERIOD).mean()
loss = (-delta.clip(upper=0)).rolling(RSI_PERIOD, min_periods=RSI_PERIOD).mean()
rs = gain / (loss.replace(0, np.nan))
df['rsi'] = 100 - (100 / (1 + rs))
  • RSIは「上がった日の強さ」と「下がった日の強さ」を比較する指標。
  • 0〜100の範囲で、70以上は買われすぎ30以下は売られすぎとされる。
  • 直近14日で10日上昇・4日下落なら RSI ≈ 10/(10+4)=71 → やや過熱。

RSIは「モメンタム(勢い)」を直接表す指標。
短期予測には非常に効きやすいです。

2.7 ATR(平均真の変動幅)と正規化

ATRとは「一日の値動きの大きさ(変動幅)」を表す指標です。
変動幅といえば、「高値ー安値」だけを想像すると思いますが、前日の終値からのギャップ(窓)も考慮した指標になります。

これにより、実際に投資がが感じる1日の振れ幅というものを定量的に表現できます。

ATRの基本単位は「TR(True Range)」で、ある日の真の変動はばTRtは以下の三つうち最大値を取ります。

$$
TR_t = \max \left(
High_t – Low_t,\;
\left| High_t – Close_{t-1} \right|,\;
\left| Low_t – Close_{t-1} \right|
\right)
$$

これを実装したコード例がこちら。

tr = pd.concat([
    (df['High'] - df['Low']),
    (df['High'] - df['Close'].shift(1)).abs(),
    (df['Low']  - df['Close'].shift(1)).abs()
], axis=1).max(axis=1)
df['atr_norm'] = tr.rolling(ATR_WINDOW, min_periods=ATR_WINDOW).mean() / df['Close']
  • ATR = 「どれくらい値動きが激しいか」の指標。
  • 価格に対して正規化(/Close)することで銘柄間比較が可能。

「揺れやすさ」を表す。ATRが大きい=ボラティリティが高い=不安定相場。
機械学習にとって、ATRは「次の変動の大きさ」を予測するためのコンテキストになる。


2.8 ヒストリカルボラティリティ

2.7に記載したATRが「1日の値幅の絶対値」を見ているのに対し、HV(ヒストリカルボラティリティ)は過去N日間のリターン(変化率)の標準偏差をベースに算出します。

日次リターンの定義(対数リターン版)

$$r_t = \ln\left( \frac{Close_t}{Close_{t-1}} \right)$$
1日ごとの変化率をとっています。終値での比を取ればいいのですが、株式市場では対数リターンを用いるのが一般的だそうです。

標準偏差としてのHV(日次ボラティリティ)
過去N日間のリターン$r_{t}$の標準偏差を求めます。

$$\sigma_t = \sqrt{
\frac{1}{N-1} \sum_{i=0}^{N-1} (r_{t-i} – \bar{r})^2
}$$
ばらつきが大きいほど値動きが荒く、ばらつきが小さいほど安定している

年率換算としてのHV(年次ボラティリティ)
$$HV_{annual} = \sigma_t \times \sqrt{252}$$
年次ボラティリティは長期的な目線の時に使用します。1年間の取引日数が252日と仮定した時には日次ボラティリティに取引日数の平方根を掛け合わせることで計算できます。

df['ret_hv'] = df['ret_1'].rolling(HV_WINDOW, min_periods=HV_WINDOW).std()
  • 過去20日間のリターン標準偏差
  • ボラティリティ(価格変動率)の履歴。
  • ATRが「絶対的変動幅」なのに対し、こちらは「リターンの揺れ幅」。

2.9 出来高変化率と曜日循環

出来高変化率については、日次リターンと同様に1日の出来高の比率から求められます。
$$Volchg_{t} = \frac{Vol_{t} – Vol_{t-1}}{Vol_{t-1}}$$
出来高変化率は投資家の注目度が高まっているか判断する指標になります。

また、株式やFXなどで週単位で繰り返すパターンをモデルに学習させる必要があります。そこで使用されるのが曜日循環です。日付データについて、周期性を保ったまま連続値に変換するために三角関数を用います。
金曜日と月曜日はカレンダー上では3日空いてしまいますが、株式市場では1日しか差がありません。下図のように円周上に日付を設定して循環させることで、帳尻が合うように工夫できます。


数式で書くと以下のようになります。
なお、下式は単位円となるように振幅を1としてます。


$$\text{dowsin}_t = \sin\left( 2\pi \frac{dt}{P} \right)$$
$$\text{dowcos}_t = \cos\left( 2\pi \frac{dt}{P} \right)$$

df['vol_chg'] = df['Volume'].pct_change(1)
dow = df['Date'].dt.dayofweek
df['dow_sin'] = np.sin(2*np.pi*dow/5)
df['dow_cos'] = np.cos(2*np.pi*dow/5)

出来高変化率

  • 投資家の関心が急に高まった/低下した兆候。
  • 特にニュース発表日などでスパイクすることが多く、短期変動を予測する上で有効。

曜日循環(sin/cos)

  • 月曜安・金曜高など、週内リズムをモデルに教える。
  • sin/cosにすることで、曜日を円周上の連続値として扱う(距離の概念を保つ)。

2.10 ラグ特徴量(過去データの遅れ値)

タイムラグなどの言葉で使われるようにラグとは「遅れ」を意味します。
この特徴量においても同じく「遅れ」を意味しており、時系列データがあるときにラグ特徴量を用いることで、過去の値を呼び出すことができます。

終値や出来高について、ラグ特徴量を導入することを考えると、下式のようになります。(t日という日付からk日遡る)

$$\text{Closelag}k(t) = \text{Close}{t – k}$$

for i in LAGS_CLOSE:
    df[f'Close_lag_{i}'] = df['Close'].shift(i)
for i in LAGS_VOL:
    df[f'Volume_lag_{i}'] = df['Volume'].shift(i)
df['ret_1_lag1'] = df['ret_1'].shift(1)
df['ret_1_lag5'] = df['ret_1'].shift(5)
  • 価格やリターンの「過去値」を入力として与える。
  • これにより「昨日上がって今日も上がる」などの自己相関構造を学習できる。

具体例
Close_lag_1 = 昨日の終値
ret_1_lag1 = 昨日の上昇率
ret_1_lag5 = 5日前の変化率(トレンド反転の兆候を拾う)


2.11 外れ値対策と欠損処理

  • 移動平均やATRなどは最初の数日間でNaNが出る。
    → dropna()で学習データから除外。
  • 外れ値クリップ済みなので、追加の正規化は不要。

2.12 特徴量の全体マップ

これまでたくさん特徴量について説明してきたので、一覧表にして整理しました。種類・コード上の変数例・目的で簡潔に記載しました。すべての特徴量を使用すると過学習が発生する可能性があるため、用途に応じて適切に特徴量を絞って使用する必要があります。

種類変数例目的
トレンド系close_sma_ratio_5close_ema_ratio_5平均との乖離・勢い
モメンタム系rsiret_1_lag1上昇・下落の勢い
ボラティリティ系atr_normret_hv揺れやすさ
価格位置系bb_z平均からの距離(過熱感)
出来高系vol_chgVolume_lag_1投資家注目度の変化
時間・周期系dow_sindow_cos曜日リズム
遅延情報Close_lag_1Close_lag_5価格の慣性効果

2.14 この章のまとめ

  • 機械学習に渡す前に、「チャート感覚」を数値化する工程が前処理
  • 特徴量の設計次第で、モデルの8割の性能が決まる
  • 特に株価では、トレンド・勢い・ボラティリティ・周期性・出来高をどう表現するかが鍵。
  • LightGBMやRFはスケーリング不要で扱いやすいが、過剰特徴量に注意。
  • 今回の設計は、短期予測に効くテクニカル要素+時系列文脈をバランス良く組み合わせている。
shota_py

メーカー勤務のエンジニアです。 自分の趣味である、「電気回路」、「ガジェット」「株式投資」、「Python」に関する記事をつらつらと書いています

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


ABOUT US
shota_py
メーカー勤務のエンジニアです。 自分の趣味である、「電気回路」、「ガジェット」「株式投資」、「Python」に関する記事をつらつらと書いています