プラットフォームと API
自動売買を実装する技術基盤は MT4、MT5、cTrader、TradingView、Python + Broker API に大別され、それぞれ API 特性、ティック品質、ラウンドトリップレイテンシ、バックテストフレームワークが異なります。 プラットフォーム選定は「戦略の性質」「許容レイテンシ」「ブローカー接続の柔軟性」の 3 軸で決まります。 同じ戦略ロジックでも、MT4、MT5、Python + API では結果が違います。 バックテスト、Walk-Forward、過剰最適化の抑制は、戦略評価の必須前段です。 このページは、どのプラットフォームで EA を書くかの判断、バックテスト設計、ライブとのギャップの把握、VPS 選定、Walk-Forward 実装に使います。
プラットフォームマップ
MetaTrader 4(MT4)
- 開発元:MetaQuotes Software(Cyprus / Russia)
- リリース:2005 年
- 言語:MQL4(C 風構文)
- 市場シェア:個人向け FX EA の事実上のデファクトスタンダード
- 強み:EA/インジケータの膨大な資産、書籍・情報が豊富、多くの海外業者が採用
- 弱み:単一スレッド、シングルチャート単位の実行、非時系列足(Renko/Point&Figure)非対応、開発は事実上停止(メンテのみ)
- バックテスト:Strategy Tester 内蔵。ティック品質は Every tick モードでも broker 提供の M1 データから内挿。実口座の実ティック再現には不十分
- 国内対応:楽天証券 MT4、OANDA JAPAN MT4、フィリップ証券など。国内業者の MT4 対応は減少傾向
- 判断:既存 EA 資産があるか、MT4 のみ対応の業者を使う場合以外、新規開発は非推奨
MetaTrader 5(MT5)
- 開発元:MetaQuotes Software
- リリース:2010 年
- 言語:MQL5(C++ 風、オブジェクト指向対応)
- 強み:マルチアセット(株式/先物対応)、複数注文一括(netting/hedging 選択可)、内蔵ストラテジーテスターがマルチスレッド並列、MQL5 Market 統合
- 弱み:MQL4 との後方互換性なし(移植コスト大)、netting mode がデフォルトで、hedging mode 使用にはブローカー側対応が必要
- バックテスト:内蔵 Strategy Tester がクラウド分散対応(MQL5 Cloud Network)、Real Ticks モードで broker 提供の実ティック使用可、遺伝的アルゴリズム最適化組み込み
- Python 連携:
MetaTrader5Python パッケージ(pip install MetaTrader5)で外部 Python から MT5 端末を操作可能。Windows のみサポート - 国内対応:楽天証券 MT5、OANDA JAPAN、外為ファイネスト、Avatrade Japan、ForexTrading.com など(対応業者増加中)
- 判断:新規 EA 開発はほぼ MT5 一択。Python 併用可能なのが決定的優位
cTrader
- 開発元:Spotware Systems(Cyprus)
- リリース:2011 年
- 言語:C#(.NET)、cAlgo フレームワーク
- 強み:DOM(Depth of Market)表示、Level II クオート、Level 2 板情報 API、UI が MT より近代的、cTrader Copy(コピートレード内蔵)
- 弱み:シェアが MT に劣り、対応ブローカーが限定(IC Markets、Pepperstone 等)、国内対応業者ほぼ無し
- バックテスト:内蔵 cAlgo Strategy Tester、Tick data 使用可、C# デバッガ完全対応
- 判断:ECN 系海外ブローカーで Level II を活用したい場合の第一候補、国内では現実的でない
TradingView + Webhook
- 開発元:TradingView(US / UK)
- 言語:Pine Script v6
- 強み:世界最大級のチャートユーザー基盤、シェア可能な strategy、実行不要のバックテスト UI が優秀
- 弱み:Pine Script は本格アルゴには機能不足、実際の発注は webhook 経由の別実装が必要、alert のレイテンシは webhook 経路で 1-10 秒
- 発注経路:TradingView alert → webhook 受信サーバー(自前 Flask/FastAPI)→ Broker API 発注、というパイプラインを組む
- バックテスト:Pine 内蔵 backtester。バーごと計算だがティック内価格を考慮せず、slippage/commission モデルは簡易
- 判断:発注はプロトタイプ止まり、リアルタイムアラート + 手動発注の半自動用途で優位
Python + Broker API
- 主要 API:
- MetaTrader5(Python):MT5 端末経由、Windows 限定、実質シェア最大
- OANDA v20:REST API + streaming、JP デモ対応、レイテンシ 100-300ms
- Interactive Brokers TWS API / ib_insync:米国株メイン、FX 対応、TWS/Gateway 起動必要
- cTrader Open API:gRPC ベース、FIX 相当の高機能
- FIX Protocol(Prime broker 級):遅延最小だが月額固定費 + 開発コスト大
- 国内 API:SBI FX Trade、GMO クリック証券は API 未提供、外為どっとコムの外貨ネクスト API は限定的
- 強み:汎用言語(numpy/pandas/scipy/scikit-learn 完全活用)、任意のフレームワーク採用可、Windows 制約なし(MT5 除く)、CI/CD・テスト・監視の一般的な OSS ツールが使える
- 弱み:実装コスト大、エラーハンドリング・再接続・状態管理を自作、バックテストフレームワークを別途組む
- 判断:中長期スパンの本格開発、統計裁定・ML 系戦略、既存 Python エコシステム活用したい場合
API 比較表
| API | 言語 | 平均レイテンシ | ティック粒度 | streaming | 認証 | 国内利用 |
|---|---|---|---|---|---|---|
| MT5 Python | Python + MQL5 | 10-50ms(localhost) | tick | あり | ログイン | ○(楽天/OANDA JP 等) |
| MT4 通常 | MQL4 | 10-50ms | tick(broker filter) | あり | ログイン | ○(減少中) |
| cTrader Open API | C#/JS/gRPC | 20-100ms | tick + L2 | あり | OAuth | △(海外業者経由) |
| OANDA v20 REST | HTTP | 100-300ms | pricing snapshot | streaming 別 | Personal token | ○(OANDA JP) |
| OANDA v20 streaming | HTTP | 50-150ms | tick | あり | Personal token | ○ |
| IB TWS API | Python(ib_insync)等 | 100-500ms | 250ms snapshot | あり | ログイン | △(US 中心) |
| FIX | 各種 | 1-10ms | tick | あり | 契約認証 | △(機関投資家寄り) |
| TradingView Webhook | HTTP | 1-10s | bar close | polling | webhook URL | ○(alert 発報のみ) |
VPS / 実行環境
自動売買を 24 時間稼働させるには、VPS または類似環境が必要です。
VPS 選定基準
-
ブローカーサーバーとの物理距離
- MT4/MT5 の実行サーバー(ブローカー側)の所在地に近い VPS
- LD5(London)、NY4(Equinix New Jersey)、TY3(Tokyo Equinix)が主要三大拠点
- 物理距離 1,000km で RTT +10-15ms、10,000km で RTT +100-150ms
-
スペック
- MT5 1 チャート 1 EA:CPU 1 core / RAM 1GB で足りる
- 複数チャート・複数 EA:CPU 2-4 core / RAM 4-8GB
- Python + numpy 系:4 core / 8GB 推奨
- SSD 必須(HDD ではレイテンシ増加)
-
ネットワーク
- MT4/MT5 の通信は暗号化 + 圧縮の独自プロトコル
- 帯域は月 10-50GB 程度で足りる(常時 streaming で 1GB/day 弱)
- ping 監視で 24 時間の平均・最大レイテンシを記録する
主要 VPS
- BeeksFX / ForexVPS:FX 特化、LD5/NY4/TY3 近接、月 3,000-10,000 円
- AWS EC2(t3.small):汎用、東京 / London / Virginia リージョン、月 2,000-4,000 円
- さくら VPS / ConoHa:国内低コスト、月 1,000-3,000 円、レイテンシ TY3 まで 3-5ms
- ブローカー無料 VPS:楽天証券 MT5(取引量条件付き)、XM(残高 $5,000 以上)等、条件を満たせば無料
バックテスト設計
データ品質
FX バックテストで最も重要かつ軽視されがちなのが、ティックデータ品質です。
- M1 データからの内挿:MT4 標準。M1 の O/H/L/C からティックを疑似生成。エントリーバー内の実際の高安順序が不明のため、ストップ・利確判定に誤差
- Real Ticks(broker 提供):MT5 の Every tick based on real ticks モード。broker 提供の bid ticks を使うが、ask/spread は近似
- サードパーティティックデータ:Dukascopy / TickData.com / HistData / TrueFX が代表的
- 推奨:HistData の分刻み無料 + Dukascopy / TrueFX の tick データを補完
In-Sample / Out-of-Sample
- IS(In-Sample):戦略設計・パラメータ最適化に使う期間
- OOS(Out-of-Sample):未使用データでの検証期間
- 推奨分割:50/50 か 70/30、時系列連続で
- 不可:OOS でパラメータ再調整(それは IS の一部)
Walk-Forward
固定的な IS/OOS 分割ではなく、時系列を移動しながら「直近 N 期間で最適化 → 次 M 期間で forward test」を繰り返します。
- 標準的パラメータ:最適化 12 か月 → 前進 3 か月、を繰り返し
- Anchored WF:最適化窓の左端固定(期間拡大)
- Rolling WF:最適化窓を丸ごと移動(期間一定)
- Anchored の方が overfitting 検出に厳しい
- 判定:全 Forward 期間の合計 Sharpe が Full IS Sharpe の 50% 以上残るか
モンテカルロシミュレーション
戦略が生成したトレード履歴(エントリー・エグジット系列)をランダムに並び替え、リターン分布を作ります。
- Trade shuffle:トレードをランダム並び替え → drawdown 分布
- Bootstrap:復元抽出でリターン系列を再構成 → 信頼区間
- Judgement:5,000-10,000 回試行、median drawdown と 95%tile drawdown を Live 想定値に
過剰最適化(Overfitting)の兆候
- Sharpe > 3 in IS:ほぼ確実に overfit
- Parameter space の凹凸が極端:最適値の 1 隣(n=14 → n=15)で Sharpe が半減する場合、overfit
- OOS Sharpe / IS Sharpe が 30% 未満:overfit の強い兆候
- 入手パラメータ数 / トレード数 > 1%:一般に overfit
- Deflated Sharpe Ratio(Bailey & López de Prado 2014):Multiple testing 補正で有意性判定
落とし穴
- バックテストの月利 10% とライブの -30% ギャップ:ティック品質・スプレッド見積・スリッページの 3 点で説明できる。バックテストで gap を予測できる
- M1 データバックテストで scalp EA を評価:秒足の値動きが再現されないので実勝率と大きく乖離。scalp なら real tick 必須
- Spread を固定 0.1 pip と仮定:実際は東京早朝・ロンドン fix・NY オープンでスプレッド 3-10 倍。時間帯別スプレッドをバックテストに組み込む
- Slippage を 0 pip と仮定:特に指標発表時。ATR ベースの動的 slippage モデルを組み込む
- Broker 差異の無視:同じ EA を A 業者と B 業者に載せて別結果になる。約定方式(STP/ECN)、reject 率、requote が違う
- Look-ahead bias:バックテスト中に翌足の情報を使ってしまう実装ミス。時刻同期を必ず 1 bar 遅らせる
- Survivor bias in vendor tick data:過去に破綻した業者のティックデータは公開されないことが多い。SNB ショック直前・直後のスプレッド爆発が再現されない
- Curve fitting via genetic algorithm:MT5 の遺伝的アルゴリズム最適化は IS で最良のパラメータを出すが、それは overfitting のことが多い
- Python 側の await 忘れ / スレッドセーフでない設計:MT5 Python は同期呼び出し。asyncio 混在に注意
- VPS 監視の欠落:EA が停止していても気付かない設計は破綻の入口。ping / EA heartbeat / P&L 監視を組む
Python + MetaTrader5 実装スケッチ
MT5 Python パッケージの基本的な使い方です(Windows 必須)。
import MetaTrader5 as mt5import pandas as pd
def connect(login: int, password: str, server: str) -> bool: """MT5 端末に接続する。""" if not mt5.initialize(): return False return mt5.login(login=login, password=password, server=server)
def fetch_ohlc(symbol: str, timeframe: int, n_bars: int) -> pd.DataFrame: """MT5 から OHLC を取得。timeframe は mt5.TIMEFRAME_M15 など。""" rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n_bars) if rates is None: return pd.DataFrame() df = pd.DataFrame(rates) df["time"] = pd.to_datetime(df["time"], unit="s") return df.set_index("time")
def place_market_order( symbol: str, side: str, # "buy" or "sell" volume: float, sl: float | None = None, tp: float | None = None, deviation: int = 20,) -> dict: """成行注文を発注。""" tick = mt5.symbol_info_tick(symbol) price = tick.ask if side == "buy" else tick.bid order_type = mt5.ORDER_TYPE_BUY if side == "buy" else mt5.ORDER_TYPE_SELL
request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_type, "price": price, "deviation": deviation, "type_filling": mt5.ORDER_FILLING_IOC, } if sl is not None: request["sl"] = sl if tp is not None: request["tp"] = tp
result = mt5.order_send(request) return result._asdict() if result else {}
def close_all_positions(symbol: str) -> list: """指定シンボルの全ポジションを閉じる。""" positions = mt5.positions_get(symbol=symbol) if not positions: return [] results = [] for pos in positions: tick = mt5.symbol_info_tick(symbol) close_type = ( mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY else mt5.ORDER_TYPE_BUY ) price = tick.bid if close_type == mt5.ORDER_TYPE_SELL else tick.ask request = { "action": mt5.TRADE_ACTION_DEAL, "position": pos.ticket, "symbol": symbol, "volume": pos.volume, "type": close_type, "price": price, "deviation": 20, "type_filling": mt5.ORDER_FILLING_IOC, } result = mt5.order_send(request) results.append(result._asdict() if result else {}) return resultsorder_send の戻り値の retcode を確認します。
成功は mt5.TRADE_RETCODE_DONE(10009)です。
それ以外はエラー処理を書きます。
バックテスト実装スケッチ(backtrader)
import backtrader as bt
class SmaCross(bt.Strategy): params = dict(fast=20, slow=50)
def __init__(self): self.fast = bt.indicators.SMA(self.data.close, period=self.p.fast) self.slow = bt.indicators.SMA(self.data.close, period=self.p.slow) self.crossover = bt.indicators.CrossOver(self.fast, self.slow)
def next(self): if not self.position and self.crossover > 0: self.buy(size=10000) elif self.position and self.crossover < 0: self.close()
cerebro = bt.Cerebro()cerebro.addstrategy(SmaCross)
data = bt.feeds.GenericCSVData( dataname="usdjpy_m15.csv", dtformat="%Y-%m-%d %H:%M:%S", timeframe=bt.TimeFrame.Minutes, compression=15,)cerebro.adddata(data)
cerebro.broker.setcash(1_000_000)cerebro.broker.setcommission(commission=0.00003) # 0.3 pip 相当cerebro.broker.set_slippage_perc(perc=0.0001) # 0.01% slippagecerebro.addanalyzer(bt.analyzers.SharpeRatio)cerebro.addanalyzer(bt.analyzers.DrawDown)cerebro.addanalyzer(bt.analyzers.TradeAnalyzer)
results = cerebro.run()commission と slippage は必ず現実的な値を入れます。 デフォルト 0 のままだと「紙 backtest」になります。
Walk-Forward 実装スケッチ
import numpy as npimport pandas as pd
def walk_forward( df: pd.DataFrame, strategy_fn, # (df, params) -> returns_series param_grid: list[dict], is_bars: int = 12 * 20 * 24, # 12 months of hourly oos_bars: int = 3 * 20 * 24, # 3 months of hourly anchored: bool = False,) -> pd.DataFrame: """Anchored / Rolling Walk-Forward を返す。""" records = [] start = 0 is_start = 0
while is_start + is_bars + oos_bars <= len(df): is_end = is_start + is_bars oos_end = is_end + oos_bars
is_slice = df.iloc[is_start:is_end] oos_slice = df.iloc[is_end:oos_end]
# IS でグリッドサーチ best_params = None best_sharpe = -np.inf for params in param_grid: ret = strategy_fn(is_slice, params) sharpe = ret.mean() / ret.std() * np.sqrt(252) if sharpe > best_sharpe: best_sharpe = sharpe best_params = params
# OOS で forward test oos_ret = strategy_fn(oos_slice, best_params) oos_sharpe = oos_ret.mean() / oos_ret.std() * np.sqrt(252)
records.append( { "is_start": is_slice.index[0], "is_end": is_slice.index[-1], "oos_start": oos_slice.index[0], "oos_end": oos_slice.index[-1], "best_params": best_params, "is_sharpe": best_sharpe, "oos_sharpe": oos_sharpe, } )
# 窓を進める if anchored: is_end = is_end # start 固定 else: is_start += oos_bars
is_start = is_start if anchored else is_start # is_start を進める (Rolling の場合のみ) if not anchored: pass # 上で進めた # anchored の場合 is_start は 0 固定 # 一貫させるため下記に整理 if anchored: is_bars += oos_bars # 期間を伸ばす # OOS の右端まで進んだら次 # (実装簡略化のため OOS 開始点を次の IS_end に)
return pd.DataFrame(records)anchored / rolling の切り替え・窓の進め方は用途に合わせて調整します。
実務では sklearn.model_selection.TimeSeriesSplit や mlfinlab の PurgedKFold を組み合わせます。
参考
- MetaQuotes — MQL5 Documentation https://www.mql5.com/en/docs
- MetaQuotes — MetaTrader 5 for Python https://www.mql5.com/en/docs/integration/python_metatrader5
- Spotware — cTrader Automate Guide https://help.ctrader.com/ctrader-automate/
- OANDA — v20 REST API https://developer.oanda.com/rest-live-v20/introduction/
- Interactive Brokers — TWS API https://interactivebrokers.github.io/tws-api/
- Ernie Chan, Quantitative Trading(2013 revised), Wiley(walk-forward, overfitting 章)
- Marcos López de Prado, Advances in Financial Machine Learning, Wiley, 2018(Combinatorial Purged CV, DSR)
- David H. Bailey & Marcos López de Prado, “The Deflated Sharpe Ratio” Journal of Portfolio Management, 2014
- backtrader documentation https://www.backtrader.com/docu/
- vectorbt documentation https://vectorbt.dev/
- 関連:戦略型別の期待値、検証方法論