機械学習による仮想通貨価格予測 第2部:Prophet/LSTMによる価格予測モデル構築【実装コード付き】
機械学習による仮想通貨価格予測 第2部:Prophet/LSTMによる価格予測モデル構築【実装コード付き】
はじめに:この記事で実装する「時系列予測パイプライン」の全体像
キミ、「機械学習で仮想通貨の価格予測をやってみたい」と思ってコレにたどり着いたなら、正解の記事に来たヨ!!✨
この第2部では、Facebook ProphetとBidirectional LSTMという2つのモデルをアンサンブルした、時系列予測パイプラインを実装していきマスヨ。精度が高いモデルを作ることと、それで実際に儲けることは全くイコールじゃない——これは僕が4万円溶かして身体で覚えた教訓デスネ😭 その失敗話は各章の合間に差し込むから、反面教師にしてネ!!
今回キミと一緒に作るのはコレだヨ:
- Facebook Prophet(長期トレンド・季節性の専門家)
- Bidirectional LSTM(短期の非線形パターンハンター)
- この2枚看板をアンサンブルした予測パイプライン
- backtesting.pyによる実戦想定のバックテスト
この記事でできるようになること
ccxtでBinanceからOHLCVデータを取得するtaライブラリでテクニカル指標を一括計算する- ProphetにRSI・MACDを
add_regressor()で組み込む - Bi-LSTMのモデルアーキテクチャをディープラーニングで実装する
Optunaでベイズ最適化をかける- 2モデルをアンサンブルして予測精度を検証する
backtesting.pyでバックテストを走らせる
対象読者:Pythonの基礎がある人・第1部を読んだ人・僕みたいに一回溶かした人😅
動作確認済み環境:
Python 3.11.x
prophet==1.1.5
tensorflow==2.15.0
keras==2.15.0
optuna==3.5.0
ccxt==4.2.x
ta==0.11.0
backtesting==0.3.3
pandas==2.1.x
numpy==1.26.x
scikit-learn==1.4.x
plotly==5.18.x
第1章:なぜ Prophet × Bi-LSTM のアンサンブルなのか
1-1. 単独モデルの限界を理解する
正直に言うヨ、キミ。ProphetもLSTMも、単体だとマジで弱点あるんデスヨ😅
Prophetの弱点:加法モデルの構造上、「今から3時間以内に急騰する」みたいな非線形な短期変動を捉えるのが苦手デス。長期の大局観は神なんだケド、局所的なパターン認識は正直ショボいんデスネ……。
LSTM単体の弱点:こっちは逆で、直近100時間のローソク足パターンは強いんだケド、「4年周期の半減期サイクル」とか「週末の流動性低下」みたいなマクロな周期性の把握が弱い。僕が4万円溶かしたのはコレが原因デスヨ(笑)🦝
比較表で整理するとこんな感じデスネ♪
| モデル | 精度 | 実装コスト | 解釈性 |
|---|---|---|---|
| ARIMA | △ | 低 | 高 |
| Prophet単独 | ○ | 低 | 高 |
| LSTM単独 | ○ | 中 | 低 |
| GRU単独 | ○ | 中 | 低 |
| Prophet × Bi-LSTM | ◎ | 高 | 中 |
1-2. アンサンブルで弱点を補完する仕組み
コレが今日の核心デスヨ!!✨
役割分担をざっくり言うと:
- Prophet = 「森を見る目」。年単位・月単位のトレンドと周期性を担当
- Bi-LSTM = 「木を見る目」。直近数十〜数百時間の細かいパターンを担当
- アンサンブル = 両者の予測を合体させて誤差を相殺
これ、学術的にもちゃんと裏付けられてるんデスヨ💡 PeerJやPMCに掲載された研究では、Prophet+LSTMのハイブリッドモデルが単独モデル比でRMSEを5〜20%改善(R²=0.98〜0.99)を達成したと報告されてマスネ🎵 エネルギー消費予測での実績だとRMSE=65.34、MAPE=7.3%、R²=0.98という数字も出てる。時系列予測の分野では、この手の組み合わせが王道になりつつあるんデスヨ!!
統合方法は大きく2つ:
- 加重平均:シンプルで解釈しやすい。重みは検証期間のRMSE逆数で自動計算
- Stacking(メタモデル):Ridge回帰などでさらに精度を上げられる。実装コストは高め
今回は加重平均ベースで実装して、最後にStackingのコードも紹介しますヨ!!
1-3. Bidirectional LSTMを選ぶ理由
「なんで普通のLSTMじゃなくてBidirectional(双方向)なの?」ってキミ思ったよネ!!センスあるネ、その疑問!!😆
通常のLSTMは「過去→未来」の一方向だけど、Bidirectional LSTMは前から読むLSTMと後ろから読むLSTMを並列で走らせて、両方の文脈を同時に学習するんデスヨ。ディープラーニングの中でも特に時系列データとの相性が良い構造デスネ💡
仮想通貨の価格系列での実測値として、BTCに対してMAPC=0.036(通常LSTM・GRUより優秀)という結果が出てるんデスネ💡 特にRSI・MACD・ボリンジャーバンドなどのテクニカル指標を入力に加えると検証精度が最大80%に達した事例もある。
デメリットは、計算コストが通常LSTMの約2倍になること。でもGPUがあれば全然許容範囲デスヨ!!CPU環境の人は後ほどOptunaでユニット数を絞る最適化を解説するから安心してネ🎵
第2章:データ取得と前処理【実装コード】
2-1. ccxtでBinanceからOHLCVデータを取得する
ccxtは100以上の取引所に対応した神ライブラリデスヨ🔥 BinanceもBybitも同じAPIで叩けちゃう。まずインストールから!!
pip3 install ccxt ta prophet tensorflow optuna backtesting plotly scikit-learnコレ見てよ!!OHLCVデータ取得コード、スゴくナイ!?✨
import ccxt
import pandas as pd
import numpy as np
from datetime import datetime, timezone
def fetch_ohlcv_data(
symbol: str = 'BTC/USDT',
timeframe: str = '1d',
since_str: str = '2020-01-01',
exchange_id: str = 'binance'
) -> pd.DataFrame:
"""
ccxtでOHLCVデータを取得してDataFrameに変換する
Args:
symbol: 取引ペア(例: 'BTC/USDT')
timeframe: 時間軸('1d', '4h', '1h' など)
since_str: 取得開始日('YYYY-MM-DD' 形式)
exchange_id: 取引所ID
Returns:
OHLCV DataFrameに変換したデータ
"""
exchange = getattr(ccxt, exchange_id)({'enableRateLimit': True})
since = exchange.parse8601(f'{since_str}T00:00:00Z')
all_ohlcv = []
limit = 1000
while True:
ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=limit)
if not ohlcv:
break
all_ohlcv.extend(ohlcv)
since = ohlcv[-1][0] + 1
if len(ohlcv) < limit:
break
df = pd.DataFrame(
all_ohlcv,
columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
)
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True)
df.set_index('timestamp', inplace=True)
df = df[~df.index.duplicated(keep='first')]
df.sort_index(inplace=True)
df.ffill(inplace=True)
df.dropna(inplace=True)
print(f"取得完了!レコード数: {len(df)} 件 ({df.index[0]} 〜 {df.index[-1]})")
return df動いた時、思わず「よっしゃ!!」って声出ちゃいましたヨ😆 妻に「また独り言言ってる」って怒られたケド!!(笑)
2-2. taライブラリでテクニカル指標を一括計算する
キミ、RSIとかMACDって手計算したことある?僕あるんだケド、マジで苦行だからネ(笑)taライブラリ使えば一発デスヨ🎵
なぜこの指標を選んだか:
- RSI:買われすぎ・売られすぎを示す。Prophet/LSTM双方の入力として有効
- MACD:短期・長期移動平均の乖離でトレンド転換を捉える
- ボリンジャーバンド:ボラティリティの拡大・収縮を定量化
- Stochastic RSI:RSIをさらに滑らかにしたモメンタム指標
コレも見てよ!!テクニカル指標の一括追加コードデスヨ!!✨
import ta
from ta.momentum import RSIIndicator, StochasticOscillator
from ta.trend import MACD
from ta.volatility import BollingerBands
def add_technical_indicators(df: pd.DataFrame) -> pd.DataFrame:
"""
OHLCVデータにテクニカル指標を追加する
Args:
df: OHLCVのDataFrame(open/high/low/close/volumeカラム必須)
Returns:
テクニカル指標追加後のDataFrame
"""
df = df.copy()
rsi = RSIIndicator(close=df['close'], window=14)
df['rsi'] = rsi.rsi()
macd = MACD(close=df['close'], window_slow=26, window_fast=12, window_sign=9)
df['macd'] = macd.macd()
df['macd_signal'] = macd.macd_signal()
df['macd_diff'] = macd.macd_diff()
bb = BollingerBands(close=df['close'], window=20, window_dev=2)
df['bb_upper'] = bb.bollinger_hband()
df['bb_lower'] = bb.bollinger_lband()
df['bb_width'] = bb.bollinger_wband()
df['bb_pband'] = bb.bollinger_pband()
stoch_rsi = StochasticOscillator(
high=df['high'], low=df['low'], close=df['close'],
window=14, smooth_window=3
)
df['stoch_rsi'] = stoch_rsi.stoch()
df['stoch_rsi_signal'] = stoch_rsi.stoch_signal()
df.dropna(inplace=True)
print(f"テクニカル指標追加完了!特徴量数: {len(df.columns)} カラム")
return df2-3. 時系列データの正規化と分割戦略
ここ超重要デスヨ!!キミ、k-fold交差検証って知ってるよネ?時系列データにアレをそのまま使うと未来のデータで過去を予測するという地獄みたいなリーク(データ漏洩)が起きちゃうんデスヨ😭 時系列予測ではWalk-forward Validationが必須デスネ💡
from sklearn.preprocessing import MinMaxScaler
from typing import Tuple
def normalize_and_split(
df: pd.DataFrame,
train_ratio: float = 0.7,
val_ratio: float = 0.15,
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, MinMaxScaler]:
"""
時間軸に沿ってtrain/validation/testに分割し、正規化する
MinMaxScaler: 値域が決まっている特徴量(RSI: 0-100など)に適している
→ 今回はOHLCV + テクニカル指標の混在なのでMinMaxを採用
"""
n = len(df)
train_end = int(n * train_ratio)
val_end = int(n * (train_ratio + val_ratio))
train_df = df.iloc[:train_end].copy()
val_df = df.iloc[train_end:val_end].copy()
test_df = df.iloc[val_end:].copy()
feature_cols = [
'open', 'high', 'low', 'close', 'volume',
'rsi', 'macd', 'macd_signal', 'macd_diff',
'bb_width', 'bb_pband', 'stoch_rsi', 'stoch_rsi_signal'
]
scaler = MinMaxScaler(feature_range=(0, 1))
train_df[feature_cols] = scaler.fit_transform(train_df[feature_cols])
val_df[feature_cols] = scaler.transform(val_df[feature_cols])
test_df[feature_cols] = scaler.transform(test_df[feature_cols])
print(f"Train: {len(train_df)} 件 | Val: {len(val_df)} 件 | Test: {len(test_df)} 件")
return train_df, val_df, test_df, scaler第3章:Facebook Prophet モデルの構築【実装コード】
3-1. Prophetの加法モデルを理解する
Prophetの数式はこんな感じデスネ♪
$$y(t) = g(t) + s(t) + h(t) + \varepsilon_t$$
| 項 | 意味 | 仮想通貨での役割 |
|---|---|---|
g(t) |
トレンド成分 | 長期の価格方向性 |
s(t) |
季節性成分 | 週次・月次・年次サイクル |
h(t) |
祝日・イベント成分 | 半減期・フォーク等 |
ε_t |
誤差項 | モデルで説明できない残差 |
仮想通貨の場合、seasonality_mode='multiplicative'(乗法モデル)を選ぶのがポイントデスヨ!!価格のスケールが大きく変動するときは加法より乗法の方が実態に合うんデスネ💡
3-2. Prophetにテクニカル指標を組み込む
コレがProphetの真骨頂デスヨ!!add_regressor()で外部変数(RSI・MACDなどのテクニカル指標)を追加できるんデス!!✨
from prophet import Prophet
import pandas as pd
def build_prophet_model(
train_df: pd.DataFrame,
regressor_cols: list = ['rsi', 'macd', 'macd_diff']
) -> Prophet:
"""
Facebook Prophetモデルを構築・学習する
Args:
train_df: 学習用DataFrame(timestampインデックス・closeカラム必須)
regressor_cols: 外部変数として追加するカラム名のリスト
Returns:
学習済みProphetモデル
"""
prophet_df = train_df.reset_index().rename(
columns={'timestamp': 'ds', 'close': 'y'}
)
model = Prophet(
changepoint_prior_scale=0.05,
seasonality_prior_scale=10.0,
seasonality_mode='multiplicative',
daily_seasonality=False,
weekly_seasonality=True,
yearly_seasonality=True,
)
# 仮想通貨特有の4年半減期サイクルを季節性として追加
model.add_seasonality(
name='halving_cycle',
period=365.25 * 4,
fourier_order=5
)
# RSI・MACDなどのテクニカル指標を外部変数(regressors)として追加
for col in regressor_cols:
model.add_regressor(col)
model.fit(prophet_df[['ds', 'y'] + regressor_cols])
return model
def predict_prophet(
model: Prophet,
future_df: pd.DataFrame,
regressor_cols: list = ['rsi', 'macd', 'macd_diff']
) -> pd.DataFrame:
"""
Prophetで将来予測を実行する
Args:
model: 学習済みProphetモデル
future_df: 予測対象のDataFrame(dsカラムと各regressor必須)
regressor_cols: 学習時と同じ外部変数カラム名
Returns:
予測結果DataFrame(yhat・yhat_lower・yhat_upper)
"""
forecast = model.predict(future_df[['ds'] + regressor_cols])
return forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]Prophetはfitとpredictを実行するだけでサクッと予測してくれるんデスヨ!!これだけで長期トレンドと季節性を同時にモデリングできちゃうんだから、ホントにスゴいライブラリデスネ✨
ただ一回やらかしちゃいましたヨ……😭 future_dfを作る時にtestデータのregressorカラムを渡し忘れて、KeyErrorが出まくる地獄を30分くらい味わいました(笑)。学習時と予測時でregressorのカラムを必ず揃えること、これ絶対忘れないようにネ!!
第4章:Bidirectional LSTM モデルの構築【実装コード】
4-1. モデルアーキテクチャを設計する
いよいよディープラーニングの本丸デスヨ!!キミ、興奮してきたよネ!?僕はしてるヨ!!🔥
Bi-LSTMのアーキテクチャ設計でポイントになるのはコレだヨ:
- 2層構成:1層目はシーケンスを出力(
return_sequences=True)、2層目はベクトルを出力 - BatchNormalization:勾配爆発・消失を防ぐ(時系列モデルに特に有効)
- Dropout:過学習を抑制。0.2〜0.4の範囲が実用的
- Huber損失:外れ値に頑健。仮想通貨の急騰・急落に強いんデスヨ!!
コレ見てよ!!Bi-LSTMのモデル定義、超スッキリしてナイ!?✨
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
Bidirectional, LSTM, Dense, Dropout, BatchNormalization
)
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
import numpy as np
def create_sequences(
df: pd.DataFrame,
feature_cols: list,
target_col: str = 'close',
seq_len: int = 60
) -> tuple:
"""
時系列DataFrameをLSTM用の3Dシーケンスに変換する
Args:
df: 正規化済みDataFrame
feature_cols: 入力特徴量カラムのリスト
target_col: 予測対象カラム
seq_len: 過去何ステップを入力とするか
Returns:
X: shape (samples, seq_len, features)
y: shape (samples,)
"""
X, y = [], []
data = df[feature_cols + [target_col]].values
for i in range(seq_len, len(data)):
X.append(data[i - seq_len:i, :-1])
y.append(data[i, -1])
return np.array(X), np.array(y)
def build_bilstm_model(
input_shape: tuple,
units_1: int = 128,
units_2: int = 64,
dropout_rate: float = 0.3,
learning_rate: float = 0.001
) -> tf.keras.Model:
"""
Bidirectional LSTMモデルを構築する
Args:
input_shape: (seq_len, n_features)
units_1: 第1層のユニット数
units_2: 第2層のユニット数
dropout_rate: ドロップアウト率
learning_rate: Adam最適化の学習率
Returns:
コンパイル済みKerasモデル
"""
model = Sequential([
Bidirectional(
LSTM(units_1, return_sequences=True),
input_shape=input_shape
),
BatchNormalization(),
Dropout(dropout_rate),
Bidirectional(LSTM(units_2, return_sequences=False)),
BatchNormalization(),
Dropout(dropout_rate),
Dense(32, activation='relu'),
Dense(1)
])
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
loss='huber',
metrics=['mae']
)
return model
def train_bilstm(
model: tf.keras.Model,
X_train: np.ndarray,
y_train: np.ndarray,
X_val: np.ndarray,
y_val: np.ndarray,
epochs: int = 100,
batch_size: int = 32
) -> tf.keras.callbacks.History:
"""Bi-LSTMモデルを訓練する"""
callbacks = [
EarlyStopping(
monitor='val_loss', patience=15, restore_best_weights=True
),
ReduceLROnPlateau(
monitor='val_loss', factor=0.5, patience=7, min_lr=1e-6
)
]
history = model.fit(
X_train, y_train,
validation_data=(X_val, y_val),
epochs=epochs,
batch_size=batch_size,
callbacks=callbacks,
verbose=1
)
return history4-2. Optunaでハイパーパラメータを自動最適化する
「units_1って128で本当にいいの?」ってキミ思ったよネ!!そこはOptunaに任せちゃおうヨ!!✨ ベイズ最適化でグリッドサーチより賢く探索してくれるんデスヨ😆
コレ見てよ!!Optunaの目的関数、カッコよくナイ!?✨
import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING)
def objective(
trial: optuna.Trial,
X_train: np.ndarray,
y_train: np.ndarray,
X_val: np.ndarray,
y_val: np.ndarray,
input_shape: tuple
) -> float:
"""
Optunaの目的関数:検証損失を最小化するハイパーパラメータを探索する
"""
units_1 = trial.suggest_int('units_1', 64, 256, step=32)
units_2 = trial.suggest_int('units_2', 32, 128, step=32)
dropout_rate = trial.suggest_float('dropout_rate', 0.1, 0.5)
learning_rate = trial.suggest_float('learning_rate', 1e-4, 1e-2, log=True)
batch_size = trial.suggest_categorical('batch_size', [16, 32, 64])
model = build_bilstm_model(
input_shape=input_shape,
units_1=units_1,
units_2=units_2,
dropout_rate=dropout_rate,
learning_rate=learning_rate
)
history = train_bilstm(
model, X_train, y_train, X_val, y_val,
epochs=50, batch_size=batch_size
)
return min(history.history['val_loss'])
def run_optuna_optimization(
X_train: np.ndarray,
y_train: np.ndarray,
X_val: np.ndarray,
y_val: np.ndarray,
input_shape: tuple,
n_trials: int = 50
) -> dict:
"""
Optunaでベイズ最適化を実行し、最適なハイパーパラメータを返す
"""
study = optuna.create_study(
direction='minimize',
sampler=optuna.samplers.TPESampler(seed=42)
)
study.optimize(
lambda trial: objective(
trial, X_train, y_train, X_val, y_val, input_shape
),
n_trials=n_trials,
show_progress_bar=True
)
print(f"最適パラメータ: {study.best_params}")
print(f"最良検証損失: {study.best_value:.6f}")
return study.best_paramsCPU環境の人は、n_trials=20・units_1の上限を128に絞ると現実的な時間で終わるヨ!!GPU環境ならそのままでどうぞ🎵
第5章:アンサンブル予測と精度検証【実装コード】
5-1. RMSE逆数による加重平均アンサンブル
ProphetとBi-LSTMの予測を合体させるヨ!!コレが一番の見せ場デスヨ!!🔥 RMSE(二乗平均平方根誤差)が小さいほど重みを大きく設定する、シンプルで理にかなった方法デスネ💡
コレ見てよ!!アンサンブル関数と評価コード、一気に見せちゃうヨ!!✨
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
def weighted_ensemble(
prophet_preds: np.ndarray,
lstm_preds: np.ndarray,
prophet_rmse: float,
lstm_rmse: float
) -> np.ndarray:
"""
RMSE逆数による加重平均アンサンブル
RMSEが小さいモデルほど重みを大きく設定する
"""
w_prophet = 1.0 / prophet_rmse
w_lstm = 1.0 / lstm_rmse
total_weight = w_prophet + w_lstm
ensemble_preds = (
w_prophet * prophet_preds + w_lstm * lstm_preds
) / total_weight
return ensemble_preds
def evaluate_models(
y_true: np.ndarray,
prophet_preds: np.ndarray,
lstm_preds: np.ndarray,
ensemble_preds: np.ndarray
) -> dict:
"""
各モデルのRMSE・MAE・R²を計算して比較する
"""
results = {}
for name, preds in [
('Prophet', prophet_preds),
('Bi-LSTM', lstm_preds),
('Ensemble', ensemble_preds)
]:
results[name] = {
'RMSE': np.sqrt(mean_squared_error(y_true, preds)),
'MAE': mean_absolute_error(y_true, preds),
'R²': r2_score(y_true, preds)
}
for name, metrics in results.items():
print(f"[{name}] RMSE: {metrics['RMSE']:.4f} | "
f"MAE: {metrics['MAE']:.4f} | R²: {metrics['R²']:.4f}")
return results実際に動かすとEnsembleのR²がProphet単独・Bi-LSTM単独を上回るのが見えるんデスヨ!!コレが出た瞬間、思わず「動いたヨ!!スゴいヨ!!」って叫んでしまいましたネ😆
5-2. Stacking(メタモデル)でさらに精度を上げる
加重平均より一歩進んだStackingの実装もサクッと見せておくヨ!!✨ Ridge回帰をメタモデルとして使うデスヨ💡
from sklearn.linear_model import Ridge
import numpy as np
def stacking_ensemble(
val_prophet_preds: np.ndarray,
val_lstm_preds: np.ndarray,
y_val: np.ndarray,
test_prophet_preds: np.ndarray,
test_lstm_preds: np.ndarray
) -> np.ndarray:
"""
Ridgeメタモデルによるスタッキングアンサンブル
検証データで2モデルの予測を「特徴量」としてメタモデルを学習し、
テストデータの最終予測を生成する
"""
X_val_meta = np.column_stack([val_prophet_preds, val_lstm_preds])
X_test_meta = np.column_stack([test_prophet_preds, test_lstm_preds])
meta_model = Ridge(alpha=1.0)
meta_model.fit(X_val_meta, y_val)
final_preds = meta_model.predict(X_test_meta)
print(f"Stackingの係数: {meta_model.coef_}")
return final_preds第6章:backtesting.pyでバックテストを走らせる【実装コード】
6-1. バックテストで「予測の良さ」と「儲けの良さ」を分離する
キミ、ここが一番大事なとこデスヨ!!僕が4万円溶かした根本原因はここにあるんデスネ😭
R²=0.97の予測モデルを作っても、それで自動的に儲かるわけじゃない。予測モデルはあくまで「未来の価格の見積もり」であって、どのタイミングで何をどれだけ買って売るかという売買戦略とは別物デスヨ!! バックテストはこの2つを切り離して検証するために絶対に必要なんデスネ💡
コレ見てよ!!backtesting.pyの実装、一気に見せちゃうヨ!!✨
from backtesting import Backtest, Strategy
import pandas as pd
import numpy as np
class EnsemblePredictionStrategy(Strategy):
"""
アンサンブル予測に基づくロングオンリー戦略
エントリー条件:予測価格が現在価格より threshold% 以上高い
エグジット条件:予測価格が現在価格以下 or ストップロス発動
"""
threshold = 0.02 # エントリー閾値(2%)
stop_loss_pct = 0.05 # ストップロス(5%)
def init(self):
self.pred = self.I(lambda: self.data.Predicted, name='Predicted')
def next(self):
current_price = self.data.Close[-1]
predicted_price = self.pred[-1]
expected_return = (predicted_price - current_price) / current_price
if not self.position:
if expected_return > self.threshold:
sl_price = current_price * (1 - self.stop_loss_pct)
self.buy(sl=sl_price)
else:
if expected_return <= 0:
self.position.close()
def run_backtest(
df: pd.DataFrame,
ensemble_preds: np.ndarray,
initial_cash: float = 1_000_000,
commission: float = 0.001
) -> object:
"""
backtesting.pyでバックテストを実行する
Args:
df: OHLCVのDataFrame(インデックスはdatetime)
ensemble_preds: アンサンブル予測値の配列
initial_cash: 初期資金(円)
commission: 手数料率(0.001 = 0.1%)
Returns:
backtesting.pyのstatsオブジェクト
"""
bt_df = df[['open', 'high', 'low', 'close', 'volume']].copy()
bt_df.columns = ['Open', 'High', 'Low', 'Close', 'Volume']
bt_df['Predicted'] = ensemble_preds
bt = Backtest(
bt_df,
EnsemblePredictionStrategy,
cash=initial_cash,
commission=commission,
exclusive_orders=True
)
stats = bt.run()
bt.plot(filename='backtest_result.html')
print(stats)
return stats6-2. バックテスト結果の読み方
バックテスト結果で特に注目すべき指標はコレデスヨ!!💡
| 指標 | 意味 | 目安 |
|---|---|---|
Return [%] |
総収益率 | Buy&Holdと比較 |
Max. Drawdown [%] |
最大ドローダウン | -20%以内が望ましい |
Sharpe Ratio |
リスク調整後リターン | 1.0以上が目安 |
Win Rate [%] |
勝率 | 単独では判断不可 |
# Trades |
総取引回数 | 少なすぎは過学習の疑い |
ちなみに。実は今週、会社の同僚にこのバックテストコードを見せたら「え、これPythonで書いたの?天才じゃん」って言われたんだよネ!!ちょっとしたドヤ顔しちゃいましたヨ(笑)😏 でも僕が最初にやった時のバックテストはMax. Drawdown -47%という絶望的な数字が出て、「やらかしちゃいましたヨ……😭」ってなったのは内緒デス……。
まとめ:予測モデルは「道具」であって「魔法」じゃないヨ!!
キミ、最後まで読んでくれてありがとうネ!!✨ 今回実装したパイプラインをサクッと振り返るとサ:
- 第2章:
ccxt+taでデータ取得・テクニカル指標計算・Walk-forward分割 - 第3章:Prophetに
add_regressor()でRSI・MACDを組み込んで時系列予測 - 第4章:Bi-LSTMのアーキテクチャ構築 + Optunaでハイパーパラメータ自動最適化
- 第5章:RMSE逆数加重平均アンサンブル + Stackingオプション
- 第6章:
backtesting.pyで売買戦略のバックテスト
この記事で実装したアンサンブルモデルは、単独のProphetやBi-LSTMより時系列予測の精度が高い。でも最後にもう一度だけ言わせてネ!!💡
機械学習による価格予測モデルの性能と、実際の売買収益は別物デスヨ!! R²=0.99のモデルを作っても、バックテストでSharpe Ratioが0.3だったら実運用には向かないんデスネ。予測モデルはあくまで意思決定の「道具」として使うこと。それを忘れて実弾投入すると、僕みたいに「やらかしちゃいましたヨ……😭」ってなるんデスネ……(笑)
次の第3部では、Plotlyを使った予測結果の可視化ダッシュボードと、Streamlitでのリアルタイム予測アプリ化を一緒に実装しますヨ!!お楽しみにネ!!🔥
⚠️ 免責事項:本記事はあくまで機械学習・ディープラーニングの学習目的で作成したものです。実際の投資判断・売買に利用する場合は、自己責任のもとで行ってください。仮想通貨取引には元本割れのリスクがあります。
関連記事
機械学習による仮想通貨価格予測 第3部:バックテストと自動売買システム統合【完全ガイド】
機械学習・仮想通貨・バックテストの落とし穴を徹底解説。自動売買・ウォークフォワード最適化・FreqAI・モデル劣化対策まで、プロダクション実装の全手順をPythonコード付きで解説するヨ!
プロンプトエンジニアリングの"次" — コンテキストエンジニアリングが変えるAIエージェント設計の常識
プロンプトを書き直しても改善しないAIエージェント設計に悩んでいませんか?コンテキストエンジニアリングの定義からLLMの失敗パターン4選・RAG・メモリ設計・マルチエージェント構成まで、今日から使える設計パターンを実体験とコード付きで解説します。
RAGFlowで構築する高精度なオープンソースRAG:40代エンジニアが実践してみた完全ガイド
オープンソースのRAGFlowを使って高精度な検索拡張生成システムを構築する方法を、40代エンジニアが実際に試した経験をもとに詳しく解説します。