機械学習による仮想通貨価格予測 第1部:時系列データの前処理とテクニカル指標【実践ガイド】

約24分で読めます by ぽんたぬき
機械学習による仮想通貨価格予測 第1部:時系列データの前処理とテクニカル指標【実践ガイド】

機械学習による仮想通貨価格予測 第1部:時系列データの前処理とテクニカル指標【実践ガイド】


はじめに:なぜ仮想通貨価格予測に機械学習が必要なのか

仮想通貨市場が「予測困難」とされる3つの理由

仮想通貨市場は、従来の金融市場とは一線を画す独特の性質を持っています。機械学習モデルを構築する前に、この「難しさ」の本質を正確に把握しておくことが重要です。

① 24時間365日の高ボラティリティ 株式市場と異なり、仮想通貨市場は取引所が閉まる時間帯がありません。BTCは1日で±30%の価格変動を記録することもあり、フラッシュクラッシュ(瞬間的な急落と回復)がモデルの学習を著しく不安定にさせます。

② 市場ノイズと外部要因の影響 イーロン・マスク氏のツイート一つで価格が数十パーセント動いた事例が示す通り、SNSの投稿、規制ニュース、ハッキング事件など、価格チャート以外の情報が市場を大きく揺さぶります。

③ 非線形な価格変動パターン ARIMA や単純な回帰モデルが前提とする線形性は、仮想通貨価格には当てはまりません。価格変動は複数の時間スケールにわたる複雑なパターンを持ち、従来の統計手法では捉えきれません。

機械学習アプローチが有効な理由

これらの課題に対し、深層学習を中心とした機械学習アプローチが有効性を発揮しています。

  • LSTM・GRU:長期的な時系列依存関係を学習できるアーキテクチャ
  • Transformer:自己注意機構により長距離の依存関係を効率的に捉える
  • XGBoost:価格の方向性分類(上昇/下降)に優れた勾配ブースティング

最新の研究成果を見ると、GRU による高頻度予測で MAPE=0.09%XGBoost による方向性分類で BTC の正解率 55.9% が報告されています(2024〜2025年の実験値)。テクニカル指標との組み合わせにより、単純な終値データのみを使った場合と比較して予測精度が向上することも実証されています。

この記事シリーズの全体像と第1部の位置づけ

本シリーズは以下の3部で構成されています。

テーマ
第1部(本記事) データ取得・前処理・テクニカル指標
第2部 LSTM モデル構築と学習
第3部 バックテストと実運用

第1部では「ゴミを入れればゴミが出る(Garbage In, Garbage Out)」という原則に基づき、モデル精度を左右するデータ品質の確保に集中します。どれほど高度なモデルを使っても、前処理の品質がボトルネックになることは実際の開発現場でも繰り返し確認されています。


第1章:OHLCVデータの取得と基本構造を理解する

OHLCVとは何か:仮想通貨予測の基本データ

機械学習による価格予測の基盤となるのが OHLCV データです。

カラム 名称 予測における意味
O 始値(Open) その時間足の開始価格。前の足からのギャップを示す
H 高値(High) 買い圧力の上限。ATR 計算に使用
L 安値(Low) 売り圧力の下限。ATR・ボリンジャーバンド計算に使用
C 終値(Close) その足で確定した価格。多くの指標の基準値
V 出来高(Volume) 市場参加者の熱量。価格変動の信頼度を示す

「終値だけあれば十分では?」と思われる方もいますが、High と Low は ATR(ボラティリティ指標)の計算に不可欠であり、Volume は価格変動の強度を裏付ける重要な特徴量です。OHLCV の5つすべてを特徴量として活用することがベストプラクティスです。

Pythonでのデータ取得方法:ccxt と yfinance の使い方

ccxt(CryptoCurrency eXchange Trading) は、Binance・Bybit・Coinbase など 100 以上の取引所を統一 API で操作できるライブラリです。流動性が高く欠損の少ない Binance をデータソースとして推奨します。

import ccxt
import pandas as pd
import time

def fetch_ohlcv_all(
    symbol: str = 'BTC/USDT',
    timeframe: str = '1d',
    start_date: str = '2022-01-01',
    exchange_name: str = 'binance'
) -> pd.DataFrame:
    """ページネーションを使った全件 OHLCV 取得"""
    exchange = getattr(ccxt, exchange_name)({
        'rateLimit': 1200,
        'enableRateLimit': True,
    })

    since = exchange.parse8601(f'{start_date}T00:00:00Z')
    all_ohlcv = []
    limit = 1000  # Binance の 1 リクエスト上限

    while True:
        try:
            ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=limit)
            if not ohlcv:
                break
            all_ohlcv.extend(ohlcv)
            since = ohlcv[-1][0] + 1
            exchange.sleep(1000)  # レート制限対策
            if since > exchange.milliseconds():
                break
        except ccxt.NetworkError as e:
            print(f"ネットワークエラー: {e} → リトライ")
            time.sleep(3)

    df = pd.DataFrame(all_ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True)
    df = df.set_index('timestamp').sort_index()
    df = df[~df.index.duplicated(keep='first')]
    return df.astype(float)

時間足の選択はユースケースに合わせて調整します。

時間足 適したユースケース
1h 短期予測(1〜3日先)・デイトレ補助
4h 中期予測(3〜7日先)
1d 長期トレンド分析(7〜30日先)

手軽に試したい場合は yfinance も選択肢です。pip install yfinance のみで導入でき、yf.download('BTC-USD', start='2022-01-01') のワンライナーで日足データを取得できます。ただし取得できる時間足の粒度は ccxt より粗いため、実践的な開発では ccxt を推奨します。

データ品質チェック:取得後に必ず確認すべき3項目

データ取得直後に以下の3点を必ず確認します。

# ① 欠損値の確認
print(df.isnull().sum())

# ② タイムスタンプの連続性チェック
expected_freq = pd.tseries.frequencies.to_offset('1D')
gap = df.index.to_series().diff().dropna()
print(gap[gap > expected_freq])  # 欠損している時間帯を検出

# ③ 異常値(スパイク)の検出
z_score = (df['close'] - df['close'].mean()) / df['close'].std()
print(df[z_score.abs() > 4])  # 4σ 以上の外れ値

第2章:時系列データの前処理【実装コード付き】

ステップ1:欠損値処理の正しいアプローチ

仮想通貨データでは、取引所のメンテナンスや障害により特定の時間足が丸ごと欠損するケースがあります。

# 前後値補完(Forward Fill → Backward Fill)
df = df.fillna(method='ffill').fillna(method='bfill')

# 短期の欠損には線形補完が自然な近似となる
df = df.interpolate(method='linear')

絶対に避けるべき処理:欠損値を 0 で埋めることです。価格が 0 になったと誤学習させてしまい、指標計算も崩壊します。

ステップ2:定常性の確保(差分変換・対数変換)

LSTM を含む多くの機械学習モデルは、定常時系列(平均・分散が時間によらず一定)を前提としています。価格データは基本的に非定常なため、変換が必要です。

from statsmodels.tsa.stattools import adfuller
import numpy as np

# ADF 検定で定常性を確認
result = adfuller(df['close'].dropna())
print(f'ADF 統計量: {result[0]:.4f}, p値: {result[1]:.4f}')
# p値 > 0.05 → 非定常(変換が必要)

# 対数変換でスケールを安定化
df['log_close'] = np.log(df['close'])

# 差分変換でトレンドを除去
df['diff_close'] = df['close'].diff()

ステップ3:正規化・スケーリングの実装

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(0, 1))

# ⚠️ 重要:fit はトレーニングデータのみで行う
train_size = int(len(df) * 0.7)
scaler.fit(df[:train_size][feature_cols])

df_scaled = scaler.transform(df[feature_cols])

⚠️ データリーク防止の鉄則scaler.fit() をデータ全体に対して行うと、テストデータの統計情報が訓練に漏れ込みます(データリーク)。必ず訓練データのみfit してください。

MinMaxScalerStandardScaler の使い分け基準は、ニューラルネットワーク(LSTM・GRU)には 0〜1 スケールの MinMaxScaler、外れ値に対してロバストにしたい場合は RobustScaler が適しています。

ステップ4:スライドウィンドウによる入力系列の生成

時系列予測では「過去 N ステップのデータで翌ステップを予測する」という構造を作ります。

import numpy as np

def create_sequences(data: np.ndarray, window_size: int = 10):
    """スライドウィンドウで入力系列とターゲットを生成"""
    X, y = [], []
    for i in range(window_size, len(data)):
        X.append(data[i - window_size:i])   # 過去 window_size ステップ
        y.append(data[i, 3])                 # 翌ステップの終値(Close = index 3)
    return np.array(X), np.array(y)

# LSTM 用 3D テンソル: (samples, timesteps, features)
X, y = create_sequences(df_scaled, window_size=10)
print(X.shape)  # → (samples, 10, n_features)

ウィンドウサイズは予測対象の時間足によって調整します。日足なら 10〜30日、時間足なら 24〜72 時間が一般的な出発点です。

ステップ5:時系列データの正しい学習/テスト分割

total = len(X)
train_end = int(total * 0.70)
val_end   = int(total * 0.85)

X_train, y_train = X[:train_end],       y[:train_end]
X_val,   y_val   = X[train_end:val_end], y[train_end:val_end]
X_test,  y_test  = X[val_end:],          y[val_end:]

絶対 NGtrain_test_split(X, y, shuffle=True) などのランダム分割。未来のデータが訓練データに混入するため、実際には使えない「水増し精度」が生じます。時系列データは必ず**時間順(Chronological Split)**で分割してください。


第3章:テクニカル指標の種類と特徴量エンジニアリング

テクニカル指標を特徴量に使う理由と研究的根拠

複数の研究で、テクニカル指標を特徴量に加えることで終値のみの場合と比較してモデル精度が向上することが確認されています。特に MA(複数期間)+ RSI + MACD の組み合わせは実証実験での採用頻度が高く、方向性分類精度の改善に寄与する傾向があります。

トレンド系指標:移動平均線とMACD

# 単純移動平均(SMA)
df['MA_25']  = df['close'].rolling(window=25).mean()
df['MA_75']  = df['close'].rolling(window=75).mean()
df['MA_200'] = df['close'].rolling(window=200).mean()

# 指数移動平均(EMA):直近の価格に高いウェイト
df['EMA_12'] = df['close'].ewm(span=12, adjust=False).mean()
df['EMA_26'] = df['close'].ewm(span=26, adjust=False).mean()

# MACD:EMA の差でトレンドの強さと方向を計測
df['MACD']        = df['EMA_12'] - df['EMA_26']
df['MACD_signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
df['MACD_hist']   = df['MACD'] - df['MACD_signal']

オシレーター系指標:RSIとストキャスティクス

# RSI(14期間が標準)
def compute_rsi(series: pd.Series, period: int = 14) -> pd.Series:
    delta = series.diff()
    gain  = delta.clip(lower=0).rolling(period).mean()
    loss  = (-delta.clip(upper=0)).rolling(period).mean()
    rs    = gain / loss
    return 100 - (100 / (1 + rs))

df['RSI'] = compute_rsi(df['close'])

# ストキャスティクス(%K・%D)
low_14  = df['low'].rolling(14).min()
high_14 = df['high'].rolling(14).max()
df['stoch_k'] = 100 * (df['close'] - low_14) / (high_14 - low_14)
df['stoch_d'] = df['stoch_k'].rolling(3).mean()

RSI を機械学習の入力とする際は、0〜100 の値域をそのまま使うか、/ 100 で 0〜1 に正規化して他の特徴量とスケールを合わせることが推奨されます。

ボラティリティ系指標:ボリンジャーバンドとATR

# ボリンジャーバンド(20期間・2σ)
rolling_mean = df['close'].rolling(20).mean()
rolling_std  = df['close'].rolling(20).std()
df['BB_upper']  = rolling_mean + (2 * rolling_std)
df['BB_middle'] = rolling_mean
df['BB_lower']  = rolling_mean - (2 * rolling_std)
df['BB_width']  = (df['BB_upper'] - df['BB_lower']) / df['BB_middle']  # バンド幅

# ATR(Average True Range):ボラティリティの絶対値
df['TR'] = pd.concat([
    df['high'] - df['low'],
    (df['high'] - df['close'].shift()).abs(),
    (df['low']  - df['close'].shift()).abs()
], axis=1).max(axis=1)
df['ATR'] = df['TR'].rolling(14).mean()

ATR は仮想通貨の高ボラティリティ環境で特に有効です。ポジションサイズの調整やストップロス設計にも応用できます。

独自特徴量の作り方

# 価格内部特徴量
df['HL_range']   = df['high'] - df['low']          # 値幅
df['CO_diff']    = df['close'] - df['open']         # 終値-始値
df['HC_diff']    = df['high'] - df['close']         # 高値-終値(上ひげ)

# 出来高変化率
df['vol_change'] = df['volume'].pct_change()

# 相関分析で重要特徴量を確認
corr = df[feature_cols + ['close']].corr()['close'].sort_values(ascending=False)
print(corr.head(10))

ta-lib vs ta ライブラリ:選び方と実装比較

ライブラリ 特徴 推奨ユーザー
ta-lib 150以上の指標、高速処理、C言語バインディング 上級者(conda 推奨)
ta pip install ta のみで導入可能 初心者・プロトタイプ段階

ta ライブラリでは一行ですべての指標を追加できます。

from ta import add_all_ta_features

df = add_all_ta_features(
    df, open='open', high='high', low='low', close='close', volume='volume',
    fillna=True
)

プロトタイプ段階では ta、本番環境のパフォーマンスが重要になる段階で ta-lib へ移行する段階的なアプローチが実際の開発現場ではベストプラクティスとされています。


第4章:よくある失敗パターンと対策

落とし穴1:LSTMが「予測」ではなく「追従」するラグ問題

症状:予測グラフが実際の価格より1ステップ遅れているように見える。

原因:入力系列とターゲットのタイムシフト設計ミス、または input_shape の誤りによりデータリークが発生し、モデルが「現在値をそのまま出力する」という最も簡単な解を学習してしまう。

対策

# ✅ 正しい設計:t〜t+window-1 で t+window を予測
X.append(data[i - window_size:i])  # 過去データ
y.append(data[i])                  # 翌ステップのターゲット(1つ先)

落とし穴2:データリーク(未来情報の混入)

特に注意が必要なのは以下の2ケースです。

  • 正規化のリーク:全データに対して scaler.fit() すると、テストデータの統計値が訓練に混入
  • 移動平均のリーク:ウィンドウが未来のデータにかかっている場合(center=True に注意)

対策として sklearn.pipeline.Pipeline を使い、変換処理をパイプライン化することでリークを構造的に防止できます。

落とし穴3:特徴量の過剰追加による過学習

指標を増やせば増やすほど精度が上がるわけではありません。相関の高い特徴量が大量に加わると、モデルが汎化できなくなります。

# 相関フィルタリング:閾値以上の相関を持つ特徴量を除去
corr_matrix = df[feature_cols].corr().abs()
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
to_drop = [col for col in upper.columns if any(upper[col] > 0.95)]
df = df.drop(columns=to_drop)

落とし穴4:予測精度への過大な期待

技術的に正確な表現を心がけると、方向性分類で 55〜60% が現実的な上限です。これは「ランダムよりわずかに良い」水準に見えますが、取引コストを考慮した上でバックテストで収益が出るかどうかが実用上の判断基準です。MAPE や RMSE だけで精度を評価せず、方向正解率(Directional Accuracy) を必ず確認しましょう。


まとめ:第1部の要点と第2部への準備

前処理チェックリスト:モデル構築前に確認すべき7項目

モデル構築に進む前に、以下のチェックリストを必ず確認してください。

  • ① 欠損値処理済みか(fillna() または interpolate() を適用済み)
  • ② ADF 検定で定常性を確認したか(p値 < 0.05 が目安)
  • ③ 正規化はトレーニングデータのみfit したか(データリーク防止)
  • ④ スライドウィンドウのサイズは適切か(予測ホライズンと整合している)
  • ⑤ データ分割は**時間順(Chronological Split)**か(ランダム分割は NG)
  • ⑥ データリークがないか(移動平均・正規化の両方を確認)
  • ⑦ テクニカル指標の計算に未来データが混入していないか

次回予告:第2部「LSTMモデルの構築と学習」

第2部では、本記事で整備したデータを用いて Keras/TensorFlow で LSTM モデルを実装する手順を段階的に理解を深めていきます。具体的には以下のトピックを扱います。

  • Keras で LSTM モデルを実装する基本構造
  • ハイパーパラメータチューニング(層数・ユニット数・Dropout 率)
  • GRU・Transformer との比較実験とアーキテクチャ選択の基準

本記事の前処理パイプラインが正しく構築されていれば、第2部のモデル構築は格段にスムーズになります。チェックリストの全項目を確認した上で次のステップへ進んでください。


参考ライブラリ・リソース

使用ライブラリ

ライブラリ 用途 インストール
ccxt 取引所 API からのデータ取得 pip install ccxt
yfinance 履歴データの簡易取得 pip install yfinance
ta テクニカル指標(初心者向き) pip install ta
ta-lib テクニカル指標(高機能) conda install -c conda-forge ta-lib
scikit-learn 正規化・前処理 pip install scikit-learn
statsmodels ADF 検定 pip install statsmodels
Keras / TensorFlow LSTM モデル構築(第2部で使用) pip install tensorflow

関連記事

NumPyだけで作る強化学習エージェント:Q学習を「コードから逆引き」で完全理解する実装ハンズオン
機械学習・AI

NumPyだけで作る強化学習エージェント:Q学習を「コードから逆引き」で完全理解する実装ハンズオン

PyTorch不要!NumPyだけでQ学習エージェントをスクラッチ実装。ベルマン方程式・Qテーブル・ε-greedy戦略をコードと1対1で完全理解。RLHF理解にも直結する強化学習の本質を学ぶ実践ハンズオン。

機械学習による仮想通貨価格予測(第4部):強化学習で収益を最大化するトレードAIの実装ガイド
機械学習・AI

機械学習による仮想通貨価格予測(第4部):強化学習で収益を最大化するトレードAIの実装ガイド

強化学習(PPO・DQN・SAC)を使った仮想通貨トレードAIの実装を解説。カスタムGym環境の構築からstable-baselines3による学習まで、Pythonコードで段階的に説明します。

機械学習による仮想通貨価格予測(第3部):予測モデルの検証と自動売買システムへの統合完全ガイド
機械学習・AI

機械学習による仮想通貨価格予測(第3部):予測モデルの検証と自動売買システムへの統合完全ガイド

バックテスト成功でも本番失敗する原因を解説。ウォークフォワード検証・CPCVなどプロの検証手法と、自動売買システムへの統合・運用まで実装コード付きで完全解説。

コメント

0/2000