FXの自動売買を調べると「MT4」「MT5」「EA」「リピート系」といった用語が一気に出てきます。この記事では、それぞれの仕組みと違いを整理したうえで、Pythonから実際にMT5を動かすコードと、リピート系の必要資金をシミュレーションするコードを掲載します。
先に結論を書くと、初心者にリピート系が勧められる理由は「簡単だから」ですが、簡単なのは設定だけで、リスク管理は株より難しいです。レンジを外れたときにいくら含み損を抱えるかを計算せずに始めると、確実に痛い目に遭います。その計算をPythonでやります。
📘 外部参考:MetaTrader5 Python統合(公式ドキュメント) / MetaTrader 5(公式)
3つの方式
| 方式 | 書く言語 | 難易度 | 自由度 | 向いている人 |
|---|---|---|---|---|
| MT4/MT5 + EA | MQL4 / MQL5 | ★★★ | 高い | EAを配布・販売したい人 |
| Python + MT5 API | Python | ★★★★ | 最高 | 分析もPythonでやる人 |
| リピート系サービス | 不要 | ★ | 低い | 設定だけで始めたい人 |
MT4とMT5、どちらを選ぶか
| 項目 | MT4 | MT5 |
|---|---|---|
| リリース | 2005年 | 2010年 |
| 対応市場 | FXのみ | FX・株・先物・CFD |
| 言語 | MQL4 | MQL5 |
| バックテスト | 単一通貨・シングルスレッド | マルチ通貨・マルチスレッド |
| Python連携 | 公式には無し | 公式パッケージあり |
| 既存EAの数 | 非常に多い | 増加中 |
| iOSアプリ | 提供終了 | あり |
これから始めるならMT5一択です。理由は単純で、MT4にはPythonから接続する公式手段がないからです。MT4を使うなら、分析もMQL4で書くか、ファイル経由やソケット経由で無理やり橋渡しすることになります。
MT4のEA資産が豊富なのは事実ですが、それは他人が作ったものを使う場合の話です。自分で書くなら関係ありません。国内でMT4を提供している業者もまだありますが、新規開発の対象としては役目を終えつつあります。
PythonからMT5に接続する
始める前に、重要な制約が2つあります。
MetaTrader5パッケージはWindows専用です。Macでは動きません(Wineや仮想マシン、VPSが必要)- MT5本体を起動してログインしておく必要があります。Pythonは端末経由で注文を出しています
# pip install MetaTrader5 pandas
import MetaTrader5 as mt5
import pandas as pd
def connect() -> None:
if not mt5.initialize():
raise RuntimeError(f"MT5の初期化に失敗: {mt5.last_error()}")
info = mt5.account_info()
if info is None:
mt5.shutdown()
raise RuntimeError("口座情報が取得できません。MT5にログインしてください")
print(f"口座: {info.login} 残高: {info.balance:,.0f} {info.currency}")
print(f"レバレッジ: 1:{info.leverage} 証拠金維持率: {info.margin_level}")
def get_rates(symbol: str = "USDJPY", n: int = 500) -> pd.DataFrame:
rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_H1, 0, n)
if rates is None:
raise RuntimeError(f"{symbol}: {mt5.last_error()}")
df = pd.DataFrame(rates)
df["time"] = pd.to_datetime(df["time"], unit="s", utc=True)
return df.set_index("time")
connect()
print(get_rates().tail(3))
mt5.shutdown()
mt5.last_error()を必ず出してください。MT5のPython APIは失敗しても例外を投げず、Noneを返すだけです。何も表示されないまま次に進んで、原因が分からなくなります。
注文を出す(フィリングモードの罠)
Pythonから発注するとき、初心者が必ずぶつかるのがリターンコード10030「Unsupported filling mode」です。type_fillingに指定できる値はブローカーと銘柄によって違います。決め打ちでORDER_FILLING_IOCと書くと弾かれます。
def pick_filling(symbol: str) -> int:
"""その銘柄が対応しているフィリングモードを自動で選ぶ。"""
info = mt5.symbol_info(symbol)
if info is None:
raise RuntimeError(f"{symbol}: 銘柄情報なし")
mode = info.filling_mode
if mode & 1: # SYMBOL_FILLING_FOK
return mt5.ORDER_FILLING_FOK
if mode & 2: # SYMBOL_FILLING_IOC
return mt5.ORDER_FILLING_IOC
return mt5.ORDER_FILLING_RETURN
def market_order(symbol: str, lot: float, side: str,
sl_pips: float = 30, tp_pips: float = 60,
magic: int = 20260101) -> dict:
info = mt5.symbol_info(symbol)
if not info.visible:
mt5.symbol_select(symbol, True) # 気配値表示に出ていないと発注できない
tick = mt5.symbol_info_tick(symbol)
point = info.point
pip = point * (10 if info.digits in (3, 5) else 1) # 3桁/5桁業者の補正
if side == "BUY":
price, otype = tick.ask, mt5.ORDER_TYPE_BUY
sl, tp = price - sl_pips * pip, price + tp_pips * pip
else:
price, otype = tick.bid, mt5.ORDER_TYPE_SELL
sl, tp = price + sl_pips * pip, price - tp_pips * pip
request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": lot,
"type": otype,
"price": price,
"sl": round(sl, info.digits),
"tp": round(tp, info.digits),
"deviation": 20, # 許容スリッページ(ポイント)
"magic": magic, # 自分のBOTの注文を識別する番号
"comment": "py-bot",
"type_time": mt5.ORDER_TIME_GTC,
"type_filling": pick_filling(symbol),
}
result = mt5.order_send(request)
if result is None:
raise RuntimeError(f"order_send失敗: {mt5.last_error()}")
if result.retcode != mt5.TRADE_RETCODE_DONE:
raise RuntimeError(f"注文拒否 retcode={result.retcode} "
f"{result.comment}")
return {"ticket": result.order, "price": result.price,
"volume": result.volume}
ポイントを3つ挙げます。
pipの計算。3桁・5桁表示の業者では、1pipがpointの10倍です。ここを間違えると損切り幅が10分の1になりますslとtpを注文と同時に置く。BOTが落ちてもサーバー側の損切りは生き残りますmagic番号。手動の注文や他のEAと区別するために必須です
def my_positions(symbol: str, magic: int = 20260101) -> list:
"""自分のBOTが建てたポジションだけを取り出す。"""
pos = mt5.positions_get(symbol=symbol) or []
return [p for p in pos if p.magic == magic]
リピート系(グリッド)の正体
リピート系は、一定の値幅ごとに買い注文を並べ、少し上がったら利確することを繰り返す仕組みです。国内では次のようなサービスがあります。
| サービス | 運営 | 最小単位 | カスタマイズ性 |
|---|---|---|---|
| トラリピ | マネースクエア | 1,000通貨 | 高い(値幅・本数を自分で設計) |
| トライオートFX | インヴァスト証券 | 1,000通貨 | 高い(ビルダー機能) |
| ループイフダン | アイネット証券 | 1,000通貨 | 低い(用意された組合せから選択) |
| 松井証券FX 自動売買 | 松井証券 | 1通貨 | 中程度 |
| iサイクル2取引 | 外為オンライン | 1,000通貨 | 中程度(方式を選択) |
この仕組みには、はっきりした特徴があります。レンジ相場では細かい利益を積み上げ、トレンドが出ると含み損が積み上がる。勝率が非常に高く見えるのに、1回の負けで全部持っていかれる構造です。
そしてここがいちばん重要なのですが、必要資金は「注文本数 × 値幅 × 数量」で決まります。設定画面の分かりやすさに騙されず、自分で計算してください。
必要資金をPythonで計算する
import numpy as np
def grid_requirement(upper: float, lower: float, step: float,
lot_units: int = 1000, leverage: int = 25,
worst_price: float | None = None) -> dict:
"""買いグリッドの必要資金を計算する。worst_priceまで下げた前提。"""
grids = np.arange(lower, upper + 1e-9, step)
n = len(grids)
worst = worst_price if worst_price is not None else lower
# 全本数が約定した状態での含み損(円建て通貨ペアを想定)
unreal_loss = float(np.sum((worst - grids) * lot_units))
# 必要証拠金は約定価格ベース(国内個人はレバレッジ25倍が上限)
margin = float(np.sum(grids * lot_units) / leverage)
return {
"本数": n,
"総建玉": n * lot_units,
"必要証拠金": round(margin),
"最大含み損": round(unreal_loss),
"推奨資金": round(margin + abs(unreal_loss)),
}
r = grid_requirement(upper=160, lower=140, step=0.5, lot_units=1000)
for k, v in r.items():
print(f"{k:12s}: {v:,}" if isinstance(v, (int, float)) else f"{k}: {v}")
ドル円を140〜160円に0.5円刻み、1,000通貨ずつで組むと41本になります。全部約定して140円まで下げた時点で、含み損は40万円を超えます。「1,000通貨だから少額」という感覚とはかけ離れた数字です。
さらに、レンジの下限を割ったらどうなるかを見ておく必要があります。
def stress(upper=160, lower=140, step=0.5, lot_units=1000,
breaks=(140, 135, 130, 125, 120)) -> pd.DataFrame:
rows = []
for w in breaks:
r = grid_requirement(upper, lower, step, lot_units,
worst_price=w)
rows.append({"下落到達": w, "含み損": r["最大含み損"],
"推奨資金": r["推奨資金"]})
return pd.DataFrame(rows).set_index("下落到達")
print(stress())
レンジ外に20円動いただけで含み損が倍以上に膨らみます。ロスカットは含み損の絶対額ではなく証拠金維持率で判定されるので、資金が薄いほど早く強制決済されます。しかもそれは「いちばん安いところ」で起きます。
過去データで最大含み損を測る
想定レンジは希望的観測になりがちです。実際の値動きで検証しましょう。
import yfinance as yf
def historical_grid(symbol: str = "USDJPY=X", years: str = "20y",
upper: float = 160, lower: float = 140,
step: float = 0.5, lot_units: int = 1000) -> None:
df = yf.download(symbol, period=years, auto_adjust=True, progress=False)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
low = df["Low"].dropna()
grids = np.arange(lower, upper + 1e-9, step)
# 各時点で「約定済みかつ含み損」の合計を計算する
running_min = low.cummin()
losses = [float(np.sum(np.clip(grids - m, 0, None) * lot_units))
for m in running_min]
s = pd.Series(losses, index=low.index)
print(f"期間: {low.index[0].date()} 〜 {low.index[-1].date()}")
print(f"最安値: {low.min():.2f}円")
print(f"最大含み損: {s.max():,.0f}円")
print(f"レンジ下限({lower}円)を割った日数: {int((low < lower).sum())}日")
historical_grid()
ドル円の20年を見れば、75円台があります。140〜160円のグリッドをその時代に置いていたら、含み損は数百万円規模です。「レンジ内で回り続ける」という前提が崩れた瞬間に何が起きるかを、数字で確認してから始めてください。
安全に始めるための順序
- デモ口座で1ヶ月。ループイフダンや外為オンラインにはデモがあります
- 必要資金を先に計算する。上のコードで「推奨資金」を出し、それを下回るなら本数を減らす
- レンジ外の想定を持つ。過去10〜20年の最安値まで耐えられるか
- 最小単位で始める。松井証券なら1通貨から試せます
- 損失上限の仕組みを使う。ループイフダンの「マイセーフティ」など
MT5+Pythonで自作する場合も同じです。デモ口座(mt5.account_info().trade_modeで確認できます)で通しで動かしてから実弾へ。祝日、週末のギャップ、スワップ、ブローカーのメンテナンス時間。想定していないところで必ず止まります。
def assert_demo() -> None:
"""実弾口座で誤って動かさないためのガード。"""
info = mt5.account_info()
if info.trade_mode != mt5.ACCOUNT_TRADE_MODE_DEMO:
raise RuntimeError("デモ口座ではありません。実行を中止します")
print("デモ口座であることを確認しました")
レバレッジと業者選びの注意
国内のFX業者は、個人向けレバレッジが25倍までに規制されています。海外業者は数百倍を提示していますが、その多くは金融庁の登録を受けていません。出金トラブルや破綻時に法的な保護がないため、自動売買の器として使うのは推奨できません。
MT5を提供している国内業者を選べば、レバレッジは25倍でも、信託保全と日本語サポートが得られます。自動売買では想定外の事態が起きるので、連絡が取れることの価値は大きいです。
まとめ
- これから自作するならMT5一択。MT4にはPython公式連携がない
MetaTrader5パッケージはWindows専用。MT5本体の起動が必要- 発注では
type_fillingを銘柄から自動判定する。決め打ちは弾かれる - 3桁・5桁業者では1pip = point × 10。損切り幅の計算に注意
- リピート系は必要資金を自分で計算する。本数×値幅×数量で決まる
- 過去20年の最安値で耐えられるかを検証してから始める
- 海外の高レバレッジ業者は法的保護がない
自動売買の失敗は、たいてい戦略ではなく資金管理で起きます。特にリピート系は「勝率95%」のような数字が並びますが、その勝率は残り5%の大きさを何も語っていません。始める前に、最悪の日にいくら失うかを計算しておいてください。

