最終更新: 2026年06月
FX EAに機械学習を使う——というアプローチは、2022年頃から個人トレーダーの間でも現実的になってきた。Pythonの機械学習ライブラリが整備され、MT5がPython連携APIを公式にサポートしたことで、「PythonでMLモデルを訓練し、MT5から呼び出して自動売買する」という構成が個人でも作れるようになった。
ただし、「機械学習を使えば勝てるEAができる」という期待は正確ではない。機械学習も過去データから学習するため、過剰最適化(カーブフィッティング)の問題は従来のEAより深刻なケースもある。
本稿では、Python×MT5で機械学習EAを作る具体的な手順を、よくある失敗パターンと合わせて整理する。
免責事項: FX取引には元本割れリスクがあります。機械学習モデルも損失を生む可能性があります。実運用前は十分な検証が必要です。
機械学習EAの全体アーキテクチャ
まず全体像を把握する。機械学習FX EAの基本構成は以下の通りだ。
【データ収集フェーズ】
MT5またはデータプロバイダーから価格データ取得
↓
【特徴量エンジニアリング】
テクニカル指標の計算・正規化
↓
【モデル訓練】
PythonでMLモデルを訓練(sklearn / LightGBM / PyTorch等)
↓
【モデル保存】
pickle / ONNX形式で保存
↓
【EA統合】
PythonサーバーまたはONNX直接読み込みでMT5と連携
↓
【バックテスト・フォワードテスト】
MT5 Strategy Testerで検証
↓
【本番運用・モニタリング】
パフォーマンス追跡・定期的な再訓練
各フェーズで注意点がある。順番に解説する。
ステップ1:MT5からデータを取得する(Python)
まず、MetaTrader5のPythonライブラリをインストールし、価格データを取得する。
import MetaTrader5 as mt5
import pandas as pd
from datetime import datetime
def get_ohlcv_data(
symbol: str,
timeframe: int,
start_date: datetime,
end_date: datetime
) -> pd.DataFrame:
"""
MT5から価格データを取得する
Args:
symbol: 通貨ペア(例: "USDJPY")
timeframe: mt5.TIMEFRAME_H1 等
start_date: 開始日時
end_date: 終了日時
"""
if not mt5.initialize():
raise ConnectionError(f"MT5初期化失敗: {mt5.last_error()}")
rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
if rates is None or len(rates) == 0:
mt5.shutdown()
raise ValueError(f"データ取得失敗: {symbol}")
df = pd.DataFrame(rates)
df['time'] = pd.to_datetime(df['time'], unit='s')
df.set_index('time', inplace=True)
df.rename(columns={
'open': 'Open', 'high': 'High',
'low': 'Low', 'close': 'Close',
'tick_volume': 'Volume'
}, inplace=True)
mt5.shutdown()
print(f"取得完了: {symbol} {len(df)}本 "
f"({df.index[0]} 〜 {df.index[-1]})")
return df
# 使用例: USD/JPYの1時間足を5年分取得
df = get_ohlcv_data(
symbol="USDJPY",
timeframe=mt5.TIMEFRAME_H1,
start_date=datetime(2019, 1, 1),
end_date=datetime(2024, 12, 31)
)
ステップ2:特徴量エンジニアリング
価格データから機械学習モデルの入力特徴量を作成する。これが予測精度に最も影響する工程だ。
import pandas_ta as ta # pip install pandas_ta
import numpy as np
def create_features(df: pd.DataFrame, forward_bars: int = 5) -> pd.DataFrame:
"""
機械学習用特徴量を生成する
Args:
df: OHLCV DataFrame
forward_bars: 予測対象の先読みバー数
"""
features = df.copy()
# --- テクニカル指標 ---
# モメンタム系
features['rsi_14'] = ta.rsi(df['Close'], length=14)
features['rsi_9'] = ta.rsi(df['Close'], length=9)
macd_df = ta.macd(df['Close'], fast=12, slow=26, signal=9)
features['macd'] = macd_df['MACD_12_26_9']
features['macd_signal'] = macd_df['MACDs_12_26_9']
features['macd_hist'] = macd_df['MACDh_12_26_9']
# トレンド系
features['ema_20'] = ta.ema(df['Close'], length=20)
features['ema_50'] = ta.ema(df['Close'], length=50)
features['ema_20_50_ratio'] = features['ema_20'] / features['ema_50'] - 1
# ボラティリティ系
features['atr_14'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)
bb_df = ta.bbands(df['Close'], length=20)
features['bb_width'] = (bb_df['BBU_20_2.0'] - bb_df['BBL_20_2.0']) / bb_df['BBM_20_2.0']
features['bb_pct'] = bb_df['BBP_20_2.0']
# 価格変化率
for lag in [1, 3, 5, 10, 20]:
features[f'return_lag{lag}'] = df['Close'].pct_change(lag)
# ボリューム
features['volume_ratio'] = df['Volume'] / df['Volume'].rolling(20).mean()
# --- 目的変数の作成 ---
# N本先のリターン方向(1: 上昇, 0: 下落)
future_return = df['Close'].shift(-forward_bars) / df['Close'] - 1
features['target'] = (future_return > 0).astype(int)
# --- クリーニング ---
features.dropna(inplace=True)
print(f"特徴量生成完了: {len(features.columns)-1}特徴量, "
f"{len(features)}サンプル")
return features
df_features = create_features(df, forward_bars=5)
重要な設計判断: 目的変数を「N本先の方向」にするか「N本先のリターン大きさ」にするかで、モデルの性質が変わる。分類問題(方向)と回帰問題(大きさ)どちらにするかは、後続のトレードロジックと合わせて決める。
ステップ3:モデル訓練(LightGBMを例に)
FXデータには「時系列」という制約がある。通常の交差検証(KFold)は**未来データでの学習(リークアップ)**が起きるため使えない。時系列用の分割が必須だ。
import lightgbm as lgb
from sklearn.model_selection import TimeSeriesSplit
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report
import pickle
FEATURE_COLS = [
'rsi_14', 'rsi_9', 'macd', 'macd_signal', 'macd_hist',
'ema_20_50_ratio', 'atr_14', 'bb_width', 'bb_pct',
'return_lag1', 'return_lag3', 'return_lag5', 'return_lag10', 'return_lag20',
'volume_ratio'
]
def train_model(df_features: pd.DataFrame) -> tuple:
"""
時系列分割を使ってLightGBMモデルを訓練する
"""
X = df_features[FEATURE_COLS]
y = df_features['target']
# 時系列分割(最後の20%をテスト用として予約)
split_idx = int(len(X) * 0.8)
X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:]
y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:]
# ウォークフォワード評価(訓練データ内)
tscv = TimeSeriesSplit(n_splits=5)
cv_scores = []
for train_idx, val_idx in tscv.split(X_train):
X_cv_train = X_train.iloc[train_idx]
X_cv_val = X_train.iloc[val_idx]
y_cv_train = y_train.iloc[train_idx]
y_cv_val = y_train.iloc[val_idx]
model_cv = lgb.LGBMClassifier(
n_estimators=300,
max_depth=4,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.8,
min_child_samples=50, # 過学習防止
random_state=42
)
model_cv.fit(X_cv_train, y_cv_train,
eval_set=[(X_cv_val, y_cv_val)],
callbacks=[lgb.early_stopping(20, verbose=False)])
pred = model_cv.predict(X_cv_val)
cv_scores.append(accuracy_score(y_cv_val, pred))
print(f"CV精度(平均): {np.mean(cv_scores):.3f} ± {np.std(cv_scores):.3f}")
# 訓練データ全体で最終モデルを訓練
final_model = lgb.LGBMClassifier(
n_estimators=300,
max_depth=4,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.8,
min_child_samples=50,
random_state=42
)
final_model.fit(X_train, y_train)
# テストセットで評価
y_pred = final_model.predict(X_test)
y_prob = final_model.predict_proba(X_test)[:, 1]
print(f"\n=== テストセット評価 ===")
print(f"精度: {accuracy_score(y_test, y_pred):.3f}")
print(classification_report(y_test, y_pred))
return final_model, X_test, y_test, y_prob
model, X_test, y_test, y_prob = train_model(df_features)
# モデルを保存
with open("fx_ml_model.pkl", "wb") as f:
pickle.dump(model, f)
print("モデル保存完了: fx_ml_model.pkl")
精度の解釈: FXの方向予測で54〜56%の精度が出れば「まずまず」だ。60%以上は強く疑う必要がある。過去データへの過学習(カーブフィッティング)の可能性が高い。
ステップ4:MT5との連携(Pythonサーバー方式)
訓練したモデルをMT5のEAから呼び出す。最もシンプルな方式はHTTPサーバーを立て、MQL5からHTTPリクエストを送る方法だ。
# prediction_server.py
from fastapi import FastAPI
from pydantic import BaseModel
import pickle
import numpy as np
app = FastAPI()
# モデルの読み込み
with open("fx_ml_model.pkl", "rb") as f:
model = pickle.load(f)
class PredictionRequest(BaseModel):
rsi_14: float
rsi_9: float
macd: float
macd_signal: float
macd_hist: float
ema_20_50_ratio: float
atr_14: float
bb_width: float
bb_pct: float
return_lag1: float
return_lag3: float
return_lag5: float
return_lag10: float
return_lag20: float
volume_ratio: float
class PredictionResponse(BaseModel):
probability_up: float
direction: str # "LONG" / "SHORT" / "NEUTRAL"
confidence_level: str # "HIGH" / "MEDIUM" / "LOW"
@app.post("/predict", response_model=PredictionResponse)
def predict(req: PredictionRequest):
features = np.array([[
req.rsi_14, req.rsi_9, req.macd, req.macd_signal, req.macd_hist,
req.ema_20_50_ratio, req.atr_14, req.bb_width, req.bb_pct,
req.return_lag1, req.return_lag3, req.return_lag5,
req.return_lag10, req.return_lag20, req.volume_ratio
]])
prob_up = float(model.predict_proba(features)[0][1])
# 閾値を設けて中立ゾーンを作る(重要)
if prob_up >= 0.60:
direction = "LONG"
confidence = "HIGH" if prob_up >= 0.70 else "MEDIUM"
elif prob_up <= 0.40:
direction = "SHORT"
confidence = "HIGH" if prob_up <= 0.30 else "MEDIUM"
else:
direction = "NEUTRAL"
confidence = "LOW"
return PredictionResponse(
probability_up=prob_up,
direction=direction,
confidence_level=confidence
)
# 起動: uvicorn prediction_server:app --host 0.0.0.0 --port 8000
// MQL5側:HTTPリクエストでPrediction APIを呼び出す
#include <Trade\Trade.mqh>
CTrade trade;
const string API_URL = "http://127.0.0.1:8000/predict";
struct MLPrediction {
double probability_up;
string direction;
string confidence_level;
};
MLPrediction GetMLPrediction() {
MLPrediction result;
result.direction = "NEUTRAL";
// 特徴量をJSONで構築(簡略化)
string features_json = StringFormat(
"{\"rsi_14\": %.2f, \"rsi_9\": %.2f, ...}",
iRSI(Symbol(), PERIOD_CURRENT, 14, PRICE_CLOSE),
iRSI(Symbol(), PERIOD_CURRENT, 9, PRICE_CLOSE)
// ... 他の特徴量
);
char post_data[], response[];
StringToCharArray(features_json, post_data, 0, StringLen(features_json));
string headers = "Content-Type: application/json\r\n";
int timeout = 5000; // 5秒タイムアウト
int res = WebRequest("POST", API_URL, headers, timeout,
post_data, response, headers);
if (res == 200) {
string resp_str = CharArrayToString(response);
// JSONパース(json4mql等のライブラリ推奨)
// ...
}
return result;
}
過学習を防ぐための設計原則
機械学習FX EAで最もよくある失敗が過学習(カーブフィッティング)だ。対策を整理する。
1. アウトオブサンプルテストの徹底
訓練・検証・テストを時系列順に分割する。テストデータはモデル開発中に一切触れない。
2015-2021年: 訓練データ
2022-2023年: 検証データ(ハイパーパラメータ調整)
2024年以降: テストデータ(最後の1回だけ確認)
2. シンプルなモデルから始める
複雑なモデル(深層学習・多層アンサンブル)ほど過学習しやすい。まずロジスティック回帰・決定木などのシンプルなモデルをベースラインにする。
3. 特徴量数を絞る
特徴量が多いほど過学習リスクが上がる。15〜20特徴量を上限の目安にする。重要度が低い特徴量は削除する。
# 特徴量重要度の確認
import matplotlib.pyplot as plt
importances = model.feature_importances_
sorted_idx = np.argsort(importances)[::-1]
print("重要度上位10特徴量:")
for i in range(10):
print(f" {FEATURE_COLS[sorted_idx[i]]}: {importances[sorted_idx[i]]:.4f}")
# 重要度が0.01未満の特徴量は削除候補
low_importance = [FEATURE_COLS[i] for i in range(len(FEATURE_COLS))
if importances[i] < 0.01]
print(f"\n削除候補: {low_importance}")
4. ウォークフォワード最適化
パラメータを固定したまま、時系列順に訓練窓をスライドさせて評価する。
def walk_forward_evaluation(
df: pd.DataFrame,
train_years: int = 3,
test_years: int = 1
):
"""
ウォークフォワード評価の実装
"""
results = []
start = df.index[0]
end = df.index[-1]
current = start + pd.DateOffset(years=train_years)
while current + pd.DateOffset(years=test_years) <= end:
train_start = current - pd.DateOffset(years=train_years)
train_end = current
test_end = current + pd.DateOffset(years=test_years)
train_df = df[(df.index >= train_start) & (df.index < train_end)]
test_df = df[(df.index >= train_end) & (df.index < test_end)]
# モデル訓練・評価(省略)
score = evaluate_period(train_df, test_df)
results.append({
"train_period": f"{train_start.date()} - {train_end.date()}",
"test_period": f"{train_end.date()} - {test_end.date()}",
"accuracy": score
})
current += pd.DateOffset(years=test_years)
return pd.DataFrame(results)
現実的な期待値の設定
機械学習FX EAについて、よくある誤解と現実を整理する。
| 期待 | 現実 | |---|---| | 精度60〜70%は達成可能 | アウトオブサンプルで55〜57%なら優秀。60%以上は過学習を強く疑う | | 訓練精度が高ければ使える | 訓練精度は参考にならない。テスト精度のみが重要 | | 一度作れば半永久的に動く | 市場環境の変化で性能が劣化する。定期的な再訓練が必要 | | MLは従来EAより必ず優れる | テクニカルベースの単純なEAに負けることは珍しくない |
モデルドリフトへの対処が特に重要だ。2019年のデータで訓練したモデルが2024年のコロナ後相場や2025年以降の金融環境に適応できない、というケースは実際に起きている。「本番運用開始から3〜6ヶ月で精度が落ちてきたら再訓練を検討する」というルールを設けることを推奨する。
まとめ:機械学習FX EAのチェックリスト
開発・運用の各フェーズで確認すべき項目:
開発フェーズ
- [ ] テストデータを訓練・検証中に一切参照していない
- [ ] TimeSeriesSpritによる時系列分割を実施した
- [ ] ウォークフォワード評価で複数期間の精度を確認した
- [ ] 特徴量のリークアップ(未来データの混入)がない
バックテストフェーズ
- [ ] スプレッドをリアルに設定した
- [ ] スリッページを考慮した
- [ ] プロフィットファクター ≥ 1.3(過学習でない前提)
- [ ] 最大DD ≤ 20%
- [ ] トレード数 ≥ 100件
本番移行前
- [ ] デモ口座で3ヶ月以上のフォワードテストを実施した
- [ ] フォワードとバックテストの乖離が許容範囲内
- [ ] モニタリングと再訓練のトリガーを定義した
免責事項: 機械学習を使ったEAも損失を生む可能性があります。本記事のコードはサンプルであり、投資収益を保証するものではありません。実運用前は十分な検証と適切なリスク管理を行ってください。バックテスト結果は将来の運用成績を保証しません。
