コンテンツにスキップ

プラットフォームと 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 連携MetaTrader5 Python パッケージ(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 PythonPython + MQL510-50ms(localhost)tickありログイン○(楽天/OANDA JP 等)
MT4 通常MQL410-50mstick(broker filter)ありログイン○(減少中)
cTrader Open APIC#/JS/gRPC20-100mstick + L2ありOAuth△(海外業者経由)
OANDA v20 RESTHTTP100-300mspricing snapshotstreaming 別Personal token○(OANDA JP)
OANDA v20 streamingHTTP50-150mstickありPersonal token
IB TWS APIPython(ib_insync)等100-500ms250ms snapshotありログイン△(US 中心)
FIX各種1-10mstickあり契約認証△(機関投資家寄り)
TradingView WebhookHTTP1-10sbar closepollingwebhook URL○(alert 発報のみ)

VPS / 実行環境

自動売買を 24 時間稼働させるには、VPS または類似環境が必要です。

VPS 選定基準

  1. ブローカーサーバーとの物理距離

    • MT4/MT5 の実行サーバー(ブローカー側)の所在地に近い VPS
    • LD5(London)、NY4(Equinix New Jersey)、TY3(Tokyo Equinix)が主要三大拠点
    • 物理距離 1,000km で RTT +10-15ms、10,000km で RTT +100-150ms
  2. スペック

    • MT5 1 チャート 1 EA:CPU 1 core / RAM 1GB で足りる
    • 複数チャート・複数 EA:CPU 2-4 core / RAM 4-8GB
    • Python + numpy 系:4 core / 8GB 推奨
    • SSD 必須(HDD ではレイテンシ増加)
  3. ネットワーク

    • 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 mt5
import 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 results

order_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% slippage
cerebro.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 np
import 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.TimeSeriesSplitmlfinlab の PurgedKFold を組み合わせます。

参考