XM 500ドル入金ボーナスの詳細はこちら ▶︎

無料・高収益グリッドbot「Stop-Grid Trader(Chisiki Grid)」の公開ソースコード・使い方

  • URLをコピーしました!

Chisikiさんが超優秀なグリッドEAのソースコードを無料公開しました。収益性・リスクリワードが非常に優秀で、しかも口座縛りなしで無料入手できるため、EA初心者の練習台にぴったりです。

ただトレードロジックがやや複雑なので、備忘録として軽くまとめておきます。

この記事では無料EA「Stop-Grid Trader(Chisiki Grid)」の使い方を解説していきます。

米国政府閉鎖により、主要な統計レポートが時間通りに発表されなくなりました。(情報ソース)閉鎖が解除されるまで指標トレードは避けた方がいいです。

目次

Stop-Grid Traderとは?

Stop-Grid TraderはChisikiさんが無料配布しているグリッドトレードのサンプルコードです。

Chisikiさんは1日で数千ドル-数万ドルの利益を出している腕利きのトレーダーです。レイテンシー取引を使わずに大きな利益を出していることから、海外FX上級者の間で参考にしている人も多いです。

Stop-Grid Traderのトレードロジック・パラメーター概要

Stop-Grid Traderのトレードロジックは「ストラドル+ピラミッディング」です。起動するとBuy Stopを10段、Sell Stopを10段それぞれ設置します。トレンド相場で大きな利益を狙えます。

ピラミッディングの利確タイミングは、最後のグリッド段数+1段のところです。グリッド段数が10段なら、11段目に相当するところでピラミッディングのポジションを一括決済します。

またこのbotではロットを2分割して、それぞれ異なるトレードロジックで運用します。

  • ピラミッディングトレード
  • グリッドごとの順張りトレード+ドテントレード

トレンドが弱くてピラミッディングで利益が得られなかったとしても、他のトレードロジックで利益を確保できます。

ゴールドは1日でおよそ4000point上下します。レンジの半分2000pointで利確するには、グリッド間隔は200point、ポジション段数は片側10段にしておくといいでしょう。

このbotのグリッド間隔は「現在のRAWスプレッド*係数(DEF_MULTIPLIER)」で決まります。例えばゴールドのスプレッドが20pointなら、係数は10倍に設定すると、グリッド間隔は200pointになります。

デフォルトの2倍だとグリッド間隔が50-80pointの高頻度設定になり、手数料負けしやすくなります。初めて使う場合は、係数は5-10倍に設定するといいでしょう。

1日あたりの収益の目安は?

以下のツイートでは、botを6時間運用して77ドル稼ぐことに成功しています。(設定ロットは0.02ロット)

Chisikiさんのように1日で数千ドル稼ぎたいなら、運用ロットを大きくするか、グリッド間隔を狭くして高頻度運用するかしましょう。リスクリワードは非常に優秀なので、使いこなせれば爆発的な利益を得られるはずです。

Stop-Grid Traderの公開ソースコード(Pythonスクリプト)

Stop-Grid Traderのソースコードはこちら。右上のボードボタンでソースコードをコピペできます。

"""
Stop-Grid Trader  – version 3.1
------------------------------------------------------------
Mini-GUI bot for MetaTrader 5 that trades a symmetric
stop-grid around the current mid-price.

Key workflow (unchanged trading logic, polished GUI):
  1.  Select a running MT5 terminal.
  2.  Enter parameters, then press **Start**:
        • Symbol name (incl. suffix)
        • Price-digits to round to
        • Base lot (even, ≥ 0.02)
        • Orders per side
        • Grid multiplier  (= spread × factor)
        • Loop count (#restarts after a full close)
  3.  The bot places alternating BUY_STOP / SELL_STOP orders
      at ±multiplier·spread intervals, outermost stops include
      a TP one grid-step farther out.
  4.  Every 0.02-lot fill is partially closed at +1 grid step
      (0.01 lot).  The remainder:
          – SL is moved to break-even (both grid & BE-REV).
          – A 0.02-lot reverse STOP is placed at the BE price.
          – If the reverse position’s 0.01 lot remainder finds
            price already beyond the initial mid-price, it is
            closed instantly; otherwise TP = initial mid-price.
  5.  Once the current mid-price touches the outer TP level,
      all pending orders are removed, every position is closed,
      and (optionally) the grid restarts up to *loop count*.
"""

import tkinter as tk
from tkinter import ttk, messagebox
import threading, time, sys
import MetaTrader5 as mt5
try:
    import psutil
except ImportError:
    psutil = None

# ── default GUI values ──────────────────────────────────────────
DEF_SYMBOL        = "XAUUSD"
DEF_DIGITS        = 2
DEF_LOT           = 0.02
DEF_ORDERS_SIDE   = 10
DEF_MULTIPLIER    = 2.0
DEF_LOOP          = 0              # 0 ⇒ run once
# ── constants (rarely changed) ─────────────────────────────────
DEVIATION         = 100
MAGIC_NUMBER      = 0
GRID_TAG          = "basic grid"
CHECK_INTERVAL    = 1.0
# ───────────────────────────────────────────────────────────────


# ═════════════════════════ GUI HELPERS ═════════════════════════
def _discover_terminals() -> list[str]:
    paths = []
    if psutil:
        for p in psutil.process_iter(attrs=["name", "exe"]):
            if "terminal64.exe" in (p.info.get("name") or "").lower():
                exe = p.info.get("exe") or ""
                if exe and exe not in paths:
                    paths.append(exe)
    return paths


def choose_terminal() -> str | None:
    """Modal: pick an MT5 terminal."""
    root = tk.Tk(); root.withdraw()
    win = tk.Toplevel(root); win.title("Select MT5 terminal"); win.grab_set()

    cols = ("exe", "login", "server", "balance", "currency", "name")
    tree = ttk.Treeview(win, columns=cols, show="headings", height=8)
    for c, w in zip(cols, (340, 80, 170, 100, 70, 150)):
        tree.heading(c, text=c); tree.column(c, width=w, anchor="w")

    for exe in _discover_terminals():
        mt5.initialize(path=exe)
        acc, _ = mt5.account_info(), mt5.terminal_info()
        if acc:
            tree.insert(
                "", tk.END,
                values=(
                    exe, acc.login, acc.server,
                    f"{acc.balance:.2f}", acc.currency, acc.name
                )
            )
        mt5.shutdown()

    tree.grid(row=0, column=0, columnspan=2, padx=6, pady=6)

    sel: dict[str, str | None] = {"path": None}

    def _use() -> None:
        if tree.selection():
            sel["path"] = tree.item(tree.selection()[0], "values")[0]
            win.destroy()

    ttk.Button(win, text="Use", command=_use)\
        .grid(row=1, column=1, pady=(0, 6), padx=6, sticky="e")

    if tree.get_children():
        tree.selection_set(tree.get_children()[0])

    win.wait_window(); root.destroy()
    return sel["path"]


class ParamDialog(tk.Toplevel):
    """Gather user parameters; returns None if canceled."""

    def __init__(self, parent: tk.Tk):
        super().__init__(parent)
        self.title("Grid parameters"); self.grab_set()
        self.res: tuple | None = None

        rows = (
            ("Symbol",          DEF_SYMBOL),
            ("Price digits",    str(DEF_DIGITS)),
            ("Base lot",        f"{DEF_LOT:.2f}"),
            ("Orders / side",   str(DEF_ORDERS_SIDE)),
            ("Grid multiplier", str(DEF_MULTIPLIER)),
            ("Loop count",      str(DEF_LOOP)),
        )
        self.vars: list[tk.StringVar] = []
        for r, (label, default) in enumerate(rows):
            ttk.Label(self, text=label).grid(row=r, column=0, sticky="w", padx=6, pady=4)
            var = tk.StringVar(value=default); self.vars.append(var)
            ttk.Entry(self, textvariable=var, width=15)\
                .grid(row=r, column=1, sticky="w", padx=6, pady=4)

        ttk.Button(self, text="Start", command=self._ok)\
            .grid(row=len(rows), column=1, pady=8, sticky="e")

    def _ok(self) -> None:
        try:
            sym   = self.vars[0].get().strip()
            digs  = int(self.vars[1].get())
            lot   = float(self.vars[2].get())
            nside = int(self.vars[3].get())
            mult  = float(self.vars[4].get())
            loops = int(self.vars[5].get())

            if digs < 0:               raise ValueError("digits ≥ 0")
            if lot < 0.02 or int(round(lot * 100)) % 2:
                raise ValueError("lot must be even ×0.01 and ≥0.02")
            if nside < 1:              raise ValueError("orders / side ≥ 1")
            if mult <= 0:              raise ValueError("multiplier > 0")
            if loops < 0:              raise ValueError("loop count ≥ 0")
        except Exception as err:
            messagebox.showerror("Invalid input", str(err), parent=self)
            return

        self.res = (sym, digs, lot, nside, mult, loops)
        self.destroy()


# ═════════════════════════ TRADER CLASS ════════════════════════
class StopGridTrader:

    def __init__(
        self,
        terminal_path: str,
        symbol: str,
        digits: int,
        base_lot: float,
        orders_side: int,
        multiplier: float,
        loop_count: int
    ):
        self.path   = terminal_path
        self.symbol = symbol
        self.digits = digits
        self.lot    = base_lot
        self.side   = orders_side
        self.mult   = multiplier
        self.loopN  = loop_count
        self.done   = 0  # loops completed

        # runtime
        self.mid: float | None = None
        self.step_pts: int | None = None
        self.tp_high = self.tp_low = None
        self.running = False

        # status window
        self.root = tk.Tk(); self.root.title("Stop-Grid Trader")
        self.status = tk.StringVar(value="Initializing…")
        ttk.Label(self.root, textvariable=self.status)\
            .grid(padx=12, pady=10)
        ttk.Button(self.root, text="Abort", command=self._abort)\
            .grid(pady=(0, 10))

    # ── MetaTrader 5 init ────────────────────────────────────
    def _mt5_init(self) -> None:
        if not mt5.initialize(path=self.path):
            c, m = mt5.last_error(); raise RuntimeError(f"MT5 init: {c} {m}")
        if not mt5.symbol_select(self.symbol, True):
            raise RuntimeError(f"Cannot select symbol {self.symbol}")

    # ── helpers ──────────────────────────────────────────────
    def _norm_vol(self, vol: float) -> float:
        info = mt5.symbol_info(self.symbol); step = info.volume_step or 0.01
        return round(max(info.volume_min, min(vol, info.volume_max)) / step) * step

    # place pending order
    def _pend(
        self, ord_type: int, price: float,
        sl: float, tp: float = 0.0,
        vol: float | None = None,
        tag: str = GRID_TAG
    ) -> None:
        if vol is None: vol = self.lot
        mt5.order_send({
            "action": mt5.TRADE_ACTION_PENDING,
            "symbol": self.symbol,
            "volume": self._norm_vol(vol),
            "type":   ord_type,
            "price":  price,
            "sl":     sl,
            "tp":     tp,
            "deviation": DEVIATION,
            "magic":  MAGIC_NUMBER,
            "comment": tag,
            "type_time": mt5.ORDER_TIME_GTC,
        })

    # ── grid creation ────────────────────────────────────────
    def _build_grid(self) -> None:
        tick = mt5.symbol_info_tick(self.symbol); info = mt5.symbol_info(self.symbol)
        self.mid      = round((tick.bid + tick.ask) / 2, self.digits)
        raw_spd_pts   = int(round((tick.ask - tick.bid) / info.point))
        self.step_pts = int(raw_spd_pts * self.mult)
        pt            = info.point

        self.tp_high = self.tp_low = None
        for i in range(1, self.side + 1):
            buy  = self.mid + i * self.step_pts * pt
            sell = self.mid - i * self.step_pts * pt
            if i == self.side:                       # outer layer
                self.tp_high = buy  + self.step_pts * pt
                self.tp_low  = sell - self.step_pts * pt
                tp_b, tp_s   = self.tp_high, self.tp_low
            else:
                tp_b = tp_s = 0.0
            self._pend(mt5.ORDER_TYPE_BUY_STOP , buy , self.mid, tp=tp_b)
            self._pend(mt5.ORDER_TYPE_SELL_STOP, sell, self.mid, tp=tp_s)

        self.status.set(f"Grid ready   (loop {self.done}/{self.loopN})")

    # place reverse stop at break-even
    def _place_be_rev(self, pos) -> None:
        info = mt5.symbol_info(self.symbol); pt = info.point
        be   = round(pos.price_open, self.digits)
        if pos.type == mt5.POSITION_TYPE_BUY:
            otype, sl = mt5.ORDER_TYPE_SELL_STOP, be + self.step_pts * pt
        else:
            otype, sl = mt5.ORDER_TYPE_BUY_STOP,  be - self.step_pts * pt
        self._pend(otype, be, round(sl, self.digits), vol=self.lot, tag="BE-REV")

    # after partial TP
    def _handle_partial(self, pos) -> None:
        tick = mt5.symbol_info_tick(self.symbol); bid, ask = tick.bid, tick.ask
        half = self.lot / 2
        be_price = round(pos.price_open, self.digits)

        if pos.comment.startswith("BE-REV"):
            beyond = (
                pos.type == mt5.POSITION_TYPE_BUY  and bid >= self.mid or
                pos.type == mt5.POSITION_TYPE_SELL and ask <= self.mid
            )
            if beyond:
                mt5.order_send({
                    "action": mt5.TRADE_ACTION_DEAL, "symbol": self.symbol,
                    "position": pos.ticket, "volume": half,
                    "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY
                           else mt5.ORDER_TYPE_BUY,
                    "price": bid if pos.type == mt5.POSITION_TYPE_BUY else ask,
                    "deviation": DEVIATION, "magic": MAGIC_NUMBER,
                    "comment": "mid-instant TP"
                })
                return
            mt5.order_send({
                "action":  mt5.TRADE_ACTION_SLTP, "symbol": self.symbol,
                "position": pos.ticket,
                "sl": be_price,                           # BE SL
                "tp": round(self.mid, self.digits),
                "deviation": DEVIATION
            })
        else:
            mt5.order_send({
                "action":  mt5.TRADE_ACTION_SLTP, "symbol": self.symbol,
                "position": pos.ticket,
                "sl": be_price,
                "tp": 0.0,
                "deviation": DEVIATION
            })
            self._place_be_rev(pos)

    # ── monitoring loop ──────────────────────────────────────
    def _monitor(self) -> None:
        info = mt5.symbol_info(self.symbol); pt = info.point
        half = self.lot / 2

        while self.running:
            time.sleep(CHECK_INTERVAL)
            tick = mt5.symbol_info_tick(self.symbol)
            mid_now = (tick.bid + tick.ask) / 2

            # global exit condition
            if (self.tp_high and mid_now >= self.tp_high) or \
               (self.tp_low  and mid_now <= self.tp_low):
                self._full_close()
                continue

            # partial-TP check
            for pos in mt5.positions_get(symbol=self.symbol) or []:
                trg = (
                    pos.price_open + self.step_pts * pt
                    if pos.type == mt5.POSITION_TYPE_BUY
                    else pos.price_open - self.step_pts * pt
                )
                hit = (
                    pos.type == mt5.POSITION_TYPE_BUY  and tick.bid >= trg or
                    pos.type == mt5.POSITION_TYPE_SELL and tick.ask <= trg
                )
                if hit and abs(pos.volume - self.lot) < 1e-6:
                    mt5.order_send({
                        "action": mt5.TRADE_ACTION_DEAL, "symbol": self.symbol,
                        "position": pos.ticket, "volume": half,
                        "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY
                               else mt5.ORDER_TYPE_BUY,
                        "price": tick.bid if pos.type == mt5.POSITION_TYPE_BUY else tick.ask,
                        "deviation": DEVIATION, "magic": MAGIC_NUMBER,
                        "comment": "partial TP"
                    })
                    time.sleep(0.3)
                    self._handle_partial(pos)

    # ── cancel orders & close positions ──────────────────────
    def _full_close(self) -> None:
        # cancel pending
        for o in mt5.orders_get(symbol=self.symbol) or []:
            if hasattr(mt5, "order_delete"):
                mt5.order_delete(o.ticket)
            else:
                mt5.order_send({
                    "action": mt5.TRADE_ACTION_REMOVE,
                    "order":  o.ticket,
                    "symbol": o.symbol
                })

        # close all positions
        tick = mt5.symbol_info_tick(self.symbol)
        for p in mt5.positions_get(symbol=self.symbol) or []:
            mt5.order_send({
                "action": mt5.TRADE_ACTION_DEAL, "symbol": self.symbol,
                "position": p.ticket, "volume": p.volume,
                "type": mt5.ORDER_TYPE_SELL if p.type == mt5.POSITION_TYPE_BUY
                       else mt5.ORDER_TYPE_BUY,
                "price": tick.bid if p.type == mt5.POSITION_TYPE_BUY else tick.ask,
                "deviation": DEVIATION, "magic": MAGIC_NUMBER,
                "comment": "grid exit"
            })

        self.done += 1
        if self.done <= self.loopN:
            self.status.set(f"Loop {self.done} finished – restarting…")
            time.sleep(2)
            self._build_grid()
        else:
            self.status.set("All loops done – exit")
            self.running = False
            self.root.after(800, self.root.quit)

    # ── GUI: abort button ────────────────────────────────────
    def _abort(self) -> None:
        if messagebox.askyesno("Abort", "Stop trading and exit?", parent=self.root):
            self.running = False
            self._full_close()

    # ── run bot ──────────────────────────────────────────────
    def run(self) -> None:
        self._mt5_init()
        self._build_grid()
        self.running = True
        threading.Thread(target=self._monitor, daemon=True).start()
        self.root.mainloop()
        mt5.shutdown()


# ══════════════════════════ MAIN ══════════════════════════════
def main() -> None:
    term = choose_terminal()
    if not term:
        sys.exit("No MT5 terminal selected – exiting.")

    root = tk.Tk(); root.withdraw()
    pd = ParamDialog(root); pd.wait_window(); root.destroy()
    if pd.res is None:
        sys.exit("Parameters dialog canceled – exiting.")

    sym, digs, lot, nside, mult, loops = pd.res
    StopGridTrader(
        terminal_path = term,
        symbol        = sym,
        digits        = digs,
        base_lot      = lot,
        orders_side   = nside,
        multiplier    = mult,
        loop_count    = loops
    ).run()


if __name__ == "__main__":
    main()

追加のソースコード(監視ループ)

以下のコードは、特定の金融商品(通貨ペアなど)の価格を常に監視し、特定の条件を満たしたときに注文の再構築を行う「監視ループ」の役割を担っています。

グリッドトレード戦略において、ポジションがない状態で待機注文の半数以上が約定した場合、残りの待機注文をすべてキャンセルし、グリッドを再設定するというロジックを実装しています。

例えばポジションが片側10段、両面20段の場合、その半分の10ポジションが既に約定している場合、既存の予約注文をすべてキャンセルして、また新たにストップ注文を設置し直すわけです。

これにより、レンジ相場が続く状況に適応し、取引機会を最適化することを目的としています。

# -- monitoring loop
def _monitor(self) -> None:
    info = mt5.symbol_info(self.symbol); pt = info.point
    half = self.lot / 2

    while self.running:
        time.sleep(CHECK_INTERVAL)
        tick = mt5.symbol_info_tick(self.symbol)
        mid_now = (tick.bid + tick.ask) / 2

        # ===== Measures against range-bound markets =====
        if not mt5.positions_total():
            pendings = [o for o in (mt5.orders_get(
                symbol=self.symbol) or []) if o.comment.startswith(GRID_TAG)]
            executed = self.side * 2 - len(pendings)
            if executed >= self.side // 2 and pendings:
                self.status.set("Half grid consumed - restarting...")
                for o in pendings:
                    if hasattr(mt5, "order_delete"):
                        mt5.order_delete(o.ticket)
                    else:
                        mt5.order_send({
                            "action": mt5.TRADE_ACTION_REMOVE,
                            "order": o.ticket,
                            "symbol": o.symbol
                        })
                time.sleep(1)
                self._build_grid()
                continue
        # =================================================

上記2つを合成したソースコード

上記2つを雑に合成したソースコードです。

デフォルトの銘柄は「XAUUSD」ですが、ビットコイン(BTCUSD)の方がボラティリティが激しくて、利益を出しやすいです。

"""
Stop-Grid Trader  – version 3.1
------------------------------------------------------------
Mini-GUI bot for MetaTrader 5 that trades a symmetric
stop-grid around the current mid-price.

Key workflow (unchanged trading logic, polished GUI):
  1.  Select a running MT5 terminal.
  2.  Enter parameters, then press **Start**:
        • Symbol name (incl. suffix)
        • Price-digits to round to
        • Base lot (even, ≥ 0.02)
        • Orders per side
        • Grid multiplier  (= spread × factor)
        • Loop count (#restarts after a full close)
  3.  The bot places alternating BUY_STOP / SELL_STOP orders
      at ±multiplier·spread intervals, outermost stops include
      a TP one grid-step farther out.
  4.  Every 0.02-lot fill is partially closed at +1 grid step
      (0.01 lot).  The remainder:
          – SL is moved to break-even (both grid & BE-REV).
          – A 0.02-lot reverse STOP is placed at the BE price.
          – If the reverse position’s 0.01 lot remainder finds
            price already beyond the initial mid-price, it is
            closed instantly; otherwise TP = initial mid-price.
  5.  Once the current mid-price touches the outer TP level,
      all pending orders are removed, every position is closed,
      and (optionally) the grid restarts up to *loop count*.
"""

import tkinter as tk
from tkinter import ttk, messagebox
import threading, time, sys
import MetaTrader5 as mt5
try:
    import psutil
except ImportError:
    psutil = None

# ── default GUI values ──────────────────────────────────────────
DEF_SYMBOL        = "XAUUSD"
DEF_DIGITS        = 2
DEF_LOT           = 0.02
DEF_ORDERS_SIDE   = 10
DEF_MULTIPLIER    = 2.0
DEF_LOOP          = 0              # 0 ⇒ run once
# ── constants (rarely changed) ─────────────────────────────────
DEVIATION         = 100
MAGIC_NUMBER      = 0
GRID_TAG          = "basic grid"
CHECK_INTERVAL    = 1.0
# ───────────────────────────────────────────────────────────────


# ═════════════════════════ GUI HELPERS ═════════════════════════
def _discover_terminals() -> list[str]:
    paths = []
    if psutil:
        for p in psutil.process_iter(attrs=["name", "exe"]):
            if "terminal64.exe" in (p.info.get("name") or "").lower():
                exe = p.info.get("exe") or ""
                if exe and exe not in paths:
                    paths.append(exe)
    return paths


def choose_terminal() -> str | None:
    """Modal: pick an MT5 terminal."""
    root = tk.Tk(); root.withdraw()
    win = tk.Toplevel(root); win.title("Select MT5 terminal"); win.grab_set()

    cols = ("exe", "login", "server", "balance", "currency", "name")
    tree = ttk.Treeview(win, columns=cols, show="headings", height=8)
    for c, w in zip(cols, (340, 80, 170, 100, 70, 150)):
        tree.heading(c, text=c); tree.column(c, width=w, anchor="w")

    for exe in _discover_terminals():
        mt5.initialize(path=exe)
        acc, _ = mt5.account_info(), mt5.terminal_info()
        if acc:
            tree.insert(
                "", tk.END,
                values=(
                    exe, acc.login, acc.server,
                    f"{acc.balance:.2f}", acc.currency, acc.name
                )
            )
        mt5.shutdown()

    tree.grid(row=0, column=0, columnspan=2, padx=6, pady=6)

    sel: dict[str, str | None] = {"path": None}

    def _use() -> None:
        if tree.selection():
            sel["path"] = tree.item(tree.selection()[0], "values")[0]
            win.destroy()

    ttk.Button(win, text="Use", command=_use)\
        .grid(row=1, column=1, pady=(0, 6), padx=6, sticky="e")

    if tree.get_children():
        tree.selection_set(tree.get_children()[0])

    win.wait_window(); root.destroy()
    return sel["path"]


class ParamDialog(tk.Toplevel):
    """Gather user parameters; returns None if canceled."""

    def __init__(self, parent: tk.Tk):
        super().__init__(parent)
        self.title("Grid parameters"); self.grab_set()
        self.res: tuple | None = None

        rows = (
            ("Symbol",          DEF_SYMBOL),
            ("Price digits",    str(DEF_DIGITS)),
            ("Base lot",        f"{DEF_LOT:.2f}"),
            ("Orders / side",   str(DEF_ORDERS_SIDE)),
            ("Grid multiplier", str(DEF_MULTIPLIER)),
            ("Loop count",      str(DEF_LOOP)),
        )
        self.vars: list[tk.StringVar] = []
        for r, (label, default) in enumerate(rows):
            ttk.Label(self, text=label).grid(row=r, column=0, sticky="w", padx=6, pady=4)
            var = tk.StringVar(value=default); self.vars.append(var)
            ttk.Entry(self, textvariable=var, width=15)\
                .grid(row=r, column=1, sticky="w", padx=6, pady=4)

        ttk.Button(self, text="Start", command=self._ok)\
            .grid(row=len(rows), column=1, pady=8, sticky="e")

    def _ok(self) -> None:
        try:
            sym   = self.vars[0].get().strip()
            digs  = int(self.vars[1].get())
            lot   = float(self.vars[2].get())
            nside = int(self.vars[3].get())
            mult  = float(self.vars[4].get())
            loops = int(self.vars[5].get())

            if digs < 0:               raise ValueError("digits ≥ 0")
            if lot < 0.02 or int(round(lot * 100)) % 2:
                raise ValueError("lot must be even ×0.01 and ≥0.02")
            if nside < 1:              raise ValueError("orders / side ≥ 1")
            if mult <= 0:              raise ValueError("multiplier > 0")
            if loops < 0:              raise ValueError("loop count ≥ 0")
        except Exception as err:
            messagebox.showerror("Invalid input", str(err), parent=self)
            return

        self.res = (sym, digs, lot, nside, mult, loops)
        self.destroy()


# ═════════════════════════ TRADER CLASS ════════════════════════
class StopGridTrader:

    def __init__(
        self,
        terminal_path: str,
        symbol: str,
        digits: int,
        base_lot: float,
        orders_side: int,
        multiplier: float,
        loop_count: int
    ):
        self.path   = terminal_path
        self.symbol = symbol
        self.digits = digits
        self.lot    = base_lot
        self.side   = orders_side
        self.mult   = multiplier
        self.loopN  = loop_count
        self.done   = 0  # loops completed

        # runtime
        self.mid: float | None = None
        self.step_pts: int | None = None
        self.tp_high = self.tp_low = None
        self.running = False

        # status window
        self.root = tk.Tk(); self.root.title("Stop-Grid Trader")
        self.status = tk.StringVar(value="Initializing…")
        ttk.Label(self.root, textvariable=self.status)\
            .grid(padx=12, pady=10)
        ttk.Button(self.root, text="Abort", command=self._abort)\
            .grid(pady=(0, 10))

    # ── MetaTrader 5 init ────────────────────────────────────
    def _mt5_init(self) -> None:
        if not mt5.initialize(path=self.path):
            c, m = mt5.last_error(); raise RuntimeError(f"MT5 init: {c} {m}")
        if not mt5.symbol_select(self.symbol, True):
            raise RuntimeError(f"Cannot select symbol {self.symbol}")

    # ── helpers ──────────────────────────────────────────────
    def _norm_vol(self, vol: float) -> float:
        info = mt5.symbol_info(self.symbol); step = info.volume_step or 0.01
        return round(max(info.volume_min, min(vol, info.volume_max)) / step) * step

    # place pending order
    def _pend(
        self, ord_type: int, price: float,
        sl: float, tp: float = 0.0,
        vol: float | None = None,
        tag: str = GRID_TAG
    ) -> None:
        if vol is None: vol = self.lot
        mt5.order_send({
            "action": mt5.TRADE_ACTION_PENDING,
            "symbol": self.symbol,
            "volume": self._norm_vol(vol),
            "type":   ord_type,
            "price":  price,
            "sl":     sl,
            "tp":     tp,
            "deviation": DEVIATION,
            "magic":  MAGIC_NUMBER,
            "comment": tag,
            "type_time": mt5.ORDER_TIME_GTC,
        })

    # ── grid creation ────────────────────────────────────────
    def _build_grid(self) -> None:
        tick = mt5.symbol_info_tick(self.symbol); info = mt5.symbol_info(self.symbol)
        self.mid      = round((tick.bid + tick.ask) / 2, self.digits)
        raw_spd_pts   = int(round((tick.ask - tick.bid) / info.point))
        self.step_pts = int(raw_spd_pts * self.mult)
        pt            = info.point

        self.tp_high = self.tp_low = None
        for i in range(1, self.side + 1):
            buy  = self.mid + i * self.step_pts * pt
            sell = self.mid - i * self.step_pts * pt
            if i == self.side:                       # outer layer
                self.tp_high = buy  + self.step_pts * pt
                self.tp_low  = sell - self.step_pts * pt
                tp_b, tp_s   = self.tp_high, self.tp_low
            else:
                tp_b = tp_s = 0.0
            self._pend(mt5.ORDER_TYPE_BUY_STOP , buy , self.mid, tp=tp_b)
            self._pend(mt5.ORDER_TYPE_SELL_STOP, sell, self.mid, tp=tp_s)

        self.status.set(f"Grid ready   (loop {self.done}/{self.loopN})")

    # place reverse stop at break-even
    def _place_be_rev(self, pos) -> None:
        info = mt5.symbol_info(self.symbol); pt = info.point
        be   = round(pos.price_open, self.digits)
        if pos.type == mt5.POSITION_TYPE_BUY:
            otype, sl = mt5.ORDER_TYPE_SELL_STOP, be + self.step_pts * pt
        else:
            otype, sl = mt5.ORDER_TYPE_BUY_STOP,  be - self.step_pts * pt
        self._pend(otype, be, round(sl, self.digits), vol=self.lot, tag="BE-REV")

    # after partial TP
    def _handle_partial(self, pos) -> None:
        tick = mt5.symbol_info_tick(self.symbol); bid, ask = tick.bid, tick.ask
        half = self.lot / 2
        be_price = round(pos.price_open, self.digits)

        if pos.comment.startswith("BE-REV"):
            beyond = (
                pos.type == mt5.POSITION_TYPE_BUY  and bid >= self.mid or
                pos.type == mt5.POSITION_TYPE_SELL and ask <= self.mid
            )
            if beyond:
                mt5.order_send({
                    "action": mt5.TRADE_ACTION_DEAL, "symbol": self.symbol,
                    "position": pos.ticket, "volume": half,
                    "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY
                           else mt5.ORDER_TYPE_BUY,
                    "price": bid if pos.type == mt5.POSITION_TYPE_BUY else ask,
                    "deviation": DEVIATION, "magic": MAGIC_NUMBER,
                    "comment": "mid-instant TP"
                })
                return
            mt5.order_send({
                "action":  mt5.TRADE_ACTION_SLTP, "symbol": self.symbol,
                "position": pos.ticket,
                "sl": be_price,                           # BE SL
                "tp": round(self.mid, self.digits),
                "deviation": DEVIATION
            })
        else:
            mt5.order_send({
                "action":  mt5.TRADE_ACTION_SLTP, "symbol": self.symbol,
                "position": pos.ticket,
                "sl": be_price,
                "tp": 0.0,
                "deviation": DEVIATION
            })
            self._place_be_rev(pos)

    # ── monitoring loop ──────────────────────────────────────
    def _monitor(self) -> None:
        info = mt5.symbol_info(self.symbol); pt = info.point
        half = self.lot / 2

        while self.running:
            time.sleep(CHECK_INTERVAL)
            tick = mt5.symbol_info_tick(self.symbol)
            mid_now = (tick.bid + tick.ask) / 2

            # ===== Measures against range-bound markets (CODE ADDED HERE) =====
            if not mt5.positions_total():
                pendings = [o for o in (mt5.orders_get(
                    symbol=self.symbol) or []) if o.comment.startswith(GRID_TAG)]
                executed = self.side * 2 - len(pendings)
                if executed >= self.side // 2 and pendings:
                    self.status.set("Half grid consumed - restarting...")
                    for o in pendings:
                        if hasattr(mt5, "order_delete"):
                            mt5.order_delete(o.ticket)
                        else:
                            mt5.order_send({
                                "action": mt5.TRADE_ACTION_REMOVE,
                                "order": o.ticket,
                                "symbol": o.symbol
                            })
                    time.sleep(1)
                    self._build_grid()
                    continue
            # ====================================================================

            # global exit condition
            if (self.tp_high and mid_now >= self.tp_high) or \
               (self.tp_low  and mid_now <= self.tp_low):
                self._full_close()
                continue

            # partial-TP check
            for pos in mt5.positions_get(symbol=self.symbol) or []:
                trg = (
                    pos.price_open + self.step_pts * pt
                    if pos.type == mt5.POSITION_TYPE_BUY
                    else pos.price_open - self.step_pts * pt
                )
                hit = (
                    pos.type == mt5.POSITION_TYPE_BUY  and tick.bid >= trg or
                    pos.type == mt5.POSITION_TYPE_SELL and tick.ask <= trg
                )
                if hit and abs(pos.volume - self.lot) < 1e-6:
                    mt5.order_send({
                        "action": mt5.TRADE_ACTION_DEAL, "symbol": self.symbol,
                        "position": pos.ticket, "volume": half,
                        "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.POSITION_TYPE_BUY
                               else mt5.ORDER_TYPE_BUY,
                        "price": tick.bid if pos.type == mt5.POSITION_TYPE_BUY else tick.ask,
                        "deviation": DEVIATION, "magic": MAGIC_NUMBER,
                        "comment": "partial TP"
                    })
                    time.sleep(0.3)
                    self._handle_partial(pos)

    # ── cancel orders & close positions ──────────────────────
    def _full_close(self) -> None:
        # cancel pending
        for o in mt5.orders_get(symbol=self.symbol) or []:
            if hasattr(mt5, "order_delete"):
                mt5.order_delete(o.ticket)
            else:
                mt5.order_send({
                    "action": mt5.TRADE_ACTION_REMOVE,
                    "order":  o.ticket,
                    "symbol": o.symbol
                })

        # close all positions
        tick = mt5.symbol_info_tick(self.symbol)
        for p in mt5.positions_get(symbol=self.symbol) or []:
            mt5.order_send({
                "action": mt5.TRADE_ACTION_DEAL, "symbol": self.symbol,
                "position": p.ticket, "volume": p.volume,
                "type": mt5.ORDER_TYPE_SELL if p.type == mt5.POSITION_TYPE_BUY
                       else mt5.ORDER_TYPE_BUY,
                "price": tick.bid if p.type == mt5.POSITION_TYPE_BUY else tick.ask,
                "deviation": DEVIATION, "magic": MAGIC_NUMBER,
                "comment": "grid exit"
            })

        self.done += 1
        if self.done <= self.loopN:
            self.status.set(f"Loop {self.done} finished – restarting…")
            time.sleep(2)
            self._build_grid()
        else:
            self.status.set("All loops done – exit")
            self.running = False
            self.root.after(800, self.root.quit)

    # ── GUI: abort button ────────────────────────────────────
    def _abort(self) -> None:
        if messagebox.askyesno("Abort", "Stop trading and exit?", parent=self.root):
            self.running = False
            self._full_close()

    # ── run bot ──────────────────────────────────────────────
    def run(self) -> None:
        self._mt5_init()
        self._build_grid()
        self.running = True
        threading.Thread(target=self._monitor, daemon=True).start()
        self.root.mainloop()
        mt5.shutdown()


# ══════════════════════════ MAIN ══════════════════════════════
def main() -> None:
    term = choose_terminal()
    if not term:
        sys.exit("No MT5 terminal selected – exiting.")

    root = tk.Tk(); root.withdraw()
    pd = ParamDialog(root); pd.wait_window(); root.destroy()
    if pd.res is None:
        sys.exit("Parameters dialog canceled – exiting.")

    sym, digs, lot, nside, mult, loops = pd.res
    StopGridTrader(
        terminal_path = term,
        symbol        = sym,
        digits        = digs,
        base_lot      = lot,
        orders_side   = nside,
        multiplier    = mult,
        loop_count    = loops
    ).run()


if __name__ == "__main__":
    main()

Stop Grid Traderのトレードロジック

この「Stop-Grid Trader」のトレードロジックを、各ステップに分けてわかりやすく解説します。

このボットは何をするのか? (全体像)

このボットは、現在の価格を中心として、上下に等間隔で「罠」を仕掛けるようなグリッドトレードを行います。価格がどちらに動いても利益を狙えるように設計されています。

一番の特徴は、一度ポジションを持つと、一部を早めに利益確定し、残りはリスクを無くした上で、価格が反転した場合にも利益を狙う「リバース注文」を仕掛けるという、非常に巧妙なロジックを持っている点です。

以下で、具体的な流れを詳しく見ていきましょう。

トレードの基本的な流れ

ステップ1:グリッドの設置 (トレード開始時)

ボットをスタートさせると、まず以下の動作でグリッド(売買注文の網)を構築します。

  1. 中心価格の決定: 現在の買値(Ask)と売値(Bid)の平均価格を「グリッドの中心価格」とします。
  2. 間隔(グリッドステップ)の計算: スプレッド × Grid multiplier (設定値) で、注文を出す価格の間隔を決定します。例えば、スプレッドが10ポイントで、Multiplierが2.0なら、間隔は20ポイントになります。
  3. ストップ注文の配置:
    • 中心価格よりに、設定された本数分だけBUY_STOP(逆指値買い)注文を等間隔で配置します。
    • 中心価格よりに、設定された本数分だけSELL_STOP(逆指値売り)注文を等間隔で配置します。
  4. 利確(TP)と損切り(SL)の設定:
    • すべてのストップ注文の損切り(SL)は、グリッドの「中心価格」に設定されます。
    • 一番外側に置かれたBUY_STOPSELL_STOP注文にだけ、特別な利確(TP)が設定されます。このTPは、さらに1グリッドステップ分外側の価格です。このTPに価格が到達すると、グリッド全体の終了トリガーとなります。

【イメージ図】

      ↑ TP (グリッド全体の終了ポイント)
      ↑ BUY_STOP (一番外側、TPあり)
      ↑ BUY_STOP
      ↑ BUY_STOP
<---- 現在の価格 (グリッド中心) ----> SLはすべてここ
      ↓ SELL_STOP
      ↓ SELL_STOP
      ↓ SELL_STOP (一番外側、TPあり)
      ↓ TP (グリッド全体の終了ポイント)

ステップ2:注文が約定した時の複雑な処理

価格が動き、いずれかのストップ注文が約定(ポジションになる)と、以下の賢い処理が自動的に行われます。ここではBUY_STOPが約定した例で説明します。

  1. 一部利益確定 (Partial Take Profit)
    • ポジションができてから、価格がさらに1グリッドステップ分上昇すると、ポジションの半分(例:0.02ロットなら0.01ロット)を利益確定します。これにより、早めに利益を確保します。
  2. 残りのポジションの処理
    • 損切りを建値に移動 (Break-Even): 残った半分のポジションの損切り(SL)を、元のエントリー価格(建値)に移動します。これで、このポジションは最悪でもプラスマイナスゼロになり、負けることがなくなります
    • リバース注文の設置 (Reverse Order): ここが最も特徴的な部分です。ポジションのエントリー価格と同じ価格に、反対方向のストップ注文(この例ではSELL_STOP)を、元のロット数(0.02ロット)で新たに設置します。
      • 目的: もし価格が反転してエントリー価格まで戻ってきた場合、残りの買いポジションは建値で決済され(損益ゼロ)、同時に新しい売りポジションが作られ、今度は下落方向で利益を狙うことができます。

ステップ3:リバース注文が約定した後の特殊な処理

もし価格が反転し、ステップ2で設置したリバース注文(例:SELL_STOP)が約定した場合、さらに特殊な処理が行われます。

  • ケースA:価格がグリッドの「中心価格」を超えて反対側に行っていた場合
    • リバース注文が約定した時点で、価格がすでにグリッド全体の「中心価格」よりも下落していた場合。
    • これは「反転の勢いが弱い」と判断し、リバースポジションの半分を即座に決済します。深追いはしないという戦略です。
  • ケースB:価格がまだグリッドの「中心価格」に達していない場合
    • リバース注文が約定したが、価格はまだグリッドの「中心価格」まで戻っていない場合。
    • これは「中心価格への回帰が期待できる」と判断し、残ったリバースポジションの利益確定(TP)をグリッドの「中心価格」に設定します。

ステップ4:グリッド全体の終了条件

以下のいずれかの条件が満たされると、1回のトレードサイクルが終了します。

  1. 全体の利確: 価格が、ステップ1で設定した一番外側の注文のTPレベルに到達する。
  2. 手動停止: ユーザーがGUIの「Abort」ボタンを押す。

終了条件が満たされると、ボットはすべての保有ポジションを決済し、すべての待機注文をキャンセルします。

その後、Loop count(ループ回数)が設定されていれば、現在の価格で新しいグリッドを構築し、トレードを再開します。

パラメータの解説

  • Symbol name: トレードする通貨ペアや銘柄名 (例: XAUUSD, USDJPY)。
  • Price-digits: 価格の小数点以下の桁数。
  • Base lot: 基本となるロット数。ロジック上、0.02以上の偶数(0.02, 0.04, 0.06…)である必要があります。
  • Orders per side: 中心価格の片側に設置する注文の数。
  • Grid multiplier: グリッドの間隔を決めるための係数。スプレッド × この値 が間隔になります。
  • Loop count: グリッド全体が終了した後に、自動で再スタートする回数。0なら1回のみ実行。

まとめ

このボットは、単なるグリッドトレードではなく、

  • 早期の一部利確で着実に利益を確保し、
  • 残りを建値決済にすることでリスクを管理し、
  • 価格が反転しても利益を狙えるリバース注文を仕掛ける、

という複数の戦略を組み合わせた、非常に洗練されたロジックで動作します。

Stop Grid Traderの設定方法

このPythonスクリプトで書かれたbotの設定方法と実行手順を、ステップバイステップで解説します。

このbotはMT5のEA(Expert Advisor)ではなく、Pythonスクリプトとして外部からMT5を操作するタイプです。そのため、設定はMT5内ではなく、PC上で行います。

準備するもの (前提条件)

  1. MetaTrader 5 (MT5) ターミナル
  2. Pythonのインストール
    • このスクリプトはPythonで書かれているため、PCにPythonがインストールされている必要があります。
    • もしまだインストールしていなければ、Python公式サイトからダウンロードしてインストールしてください。
  3. 必要なPythonライブラリ
    • スクリプトがMT5と通信したり、PCの情報を取得したりするために、いくつかのライブラリが必要です。コマンドプロンプトやターミナルを開き、以下のコマンドを1行ずつ実行してインストールしてください。
    pip install MetaTrader5 pip install psutil
    • MetaTrader5はMT5と連携するための公式ライブラリです。
    • psutilは実行中のMT5ターミナルを自動で検出するために使われます。

設定と実行の手順

ステップ1:スクリプトファイルを保存する

  1. 提供されたPythonコード全体をコピーします。
  2. メモ帳やVSCodeなどのテキストエディタを開きます。
  3. コピーしたコードを貼り付けます。
  4. ファイルに名前を付けて保存します。名前は自由ですが、必ず拡張子を .py にしてください。(例: stop_grid_trader.py)

ステップ2:MetaTrader 5の設定

  1. PCでMT5ターミナルを起動し、取引したい口座にログインしておきます。(XM MT5AXIORY MT5Tradeview MT5
  2. MT5の上部メニューから ツールオプション を選択します。
  3. 開いたウィンドウで エキスパートアドバイザ タブを選択します。
  4. 「自動売買を許可する」 にチェックを入れてください。これは外部からのプログラムがMT5を操作するために必須の設定です。
  5. OK をクリックしてオプションウィンドウを閉じます。
  6. MT5のツールバーにある「自動売買」ボタンが緑色になっていることを確認します。(赤色の場合はクリックして緑色にします)

ステップ3:スクリプトを実行する

  1. コマンドプロンプト(またはPowerShell, ターミナル)を起動します。
  2. cd コマンドを使って、ステップ1で保存したPythonファイルがあるフォルダに移動します。
  3. 以下のコマンドを入力して、スクリプトを実行します。 python stop_grid_trader.py (ファイル名を変更した場合は、そのファイル名を入力してください)

ステップ4:MT5ターミナルを選択する

スクリプトを実行すると、最初に「Select MT5 terminal」というウィンドウが表示されます。

  • 現在PCで実行されているMT5ターミナルが一覧で表示されます。
  • 取引に使用したいMT5ターミナルを選択し、「Use」ボタンをクリックします。

ステップ5:パラメータを入力する

次に「Grid parameters」というウィンドウが表示されます。ここでトレードの具体的な設定を行います。

  • Symbol:
    • 取引したい通貨ペアや銘柄名を入力します。ブローカーのシンボル名と完全に一致させる必要があります。(例: XAUUSD, USDJPY.m など、お使いのMT5の気配値表示に表示されている通りに入力してください)
  • Price digits:
    • 価格の小数点以下の桁数です。ドル円なら3、ゴールドなら2、ユーロドルなら5が一般的です。
  • Base lot:
    • 1回の注文の基本ロット数です。このロジックでは必ず0.02以上の偶数(0.02, 0.04, 0.06…)に設定してください。半分に分割する処理があるためです。
  • Orders / side:
    • 価格の中心から片側(上または下)に何本の注文を出すかを設定します。10なら、上下に10本ずつ、合計20本のストップ注文が設置されます。
  • Grid multiplier:
    • グリッドの間隔を決める係数です。スプレッド × この値 が注文間の価格差になります。
      • 値を小さくする → グリッドが密になり、頻繁に約定する(レンジ相場向け)
      • 値を大きくする → グリッドが広くなり、約定しにくくなる(トレンド相場向け、リスク低減)
  • Loop count:
    • グリッド全体が終了した後に、何回自動で再スタートするかを設定します。0にすると1回実行したら終了します。5にすると、5回グリッドを繰り返します。

すべてのパラメータを入力したら、「Start」ボタンをクリックします。

ステップ6:取引開始とモニタリング

「Start」をクリックすると、パラメータ入力ウィンドウが消え、「Stop-Grid Trader」という小さなウィンドウが表示されます。

  • このウィンドウには現在のステータス(例: “Grid ready”)が表示されます。
  • この状態になったら、MT5のターミナルを確認してください。設定した通りにBUY_STOPSELL_STOP注文が発注されているはずです。
  • あとはbotが自動でロジックに従って取引を管理します。

ステップ7:取引を停止する方法

取引を途中で完全に停止したい場合は、「Stop-Grid Trader」ウィンドウの「Abort」ボタンをクリックします。

  • 確認ダイアログが表示されるので「はい」を選択すると、botはすべての待機注文をキャンセルし、すべての保有ポジションを決済して、安全にプログラムを終了します。
  • (注意)コマンドプロンプトやステータスウィンドウを直接閉じると、ポジションや注文が残ったままになる可能性があるので、必ず「Abort」ボタンを使ってください。

非常に重要な注意点

  • 必ずデモ口座で試す: このような自動売買botは、必ず最初にデモ口座で十分なテストを行い、そのロジックとリスクを完全に理解してから、自己責任でリアル口座に使用してください。
  • PCの電源: このbotはPC上で実行されている間だけ動作します。PCがスリープしたり、シャットダウンしたりするとbotも停止します。24時間稼働させたい場合は、VPS(仮想専用サーバー)の利用を検討してください。

Stop Grid Traderのおすすめ運用方法

この「Stop-Grid Trader」を最大限に活かすための、より具体的で実践的なおすすめ運用方法を提案します。

このbotは「放置して資産を増やす」タイプのEAではなく、「特定の相場状況を見極めて、短時間だけ稼働させる」ことで真価を発揮するツールです。猟師が獲物の通り道に罠を仕掛けるようなイメージで使いましょう。

おすすめ運用戦略:「重要指標発表」を狙ったスキャルピング

最もおすすめなのが、経済指標発表時のようなボラティリティが急上昇するが、方向感が定まらない(上下に荒れやすい)相場を狙う方法です。

なぜ指標発表時が有効なのか?

  • 高いボラティリティ: 発表直後は価格が乱高下しやすく、グリッド注文が次々と約定します。
  • 往復運動: しばしば価格は急騰した後に急落する(またはその逆)「往って来い」の動きを見せます。これは、このbotの「一部利確」と「リバース注文」のロジックが最も輝く瞬間です。
  • 短時間で決着: 指標トレードは数分~数十分で終わることが多く、長時間PCに張り付く必要がありません。

具体的な手順とパラメータ設定例

対象とする指標の例:

  • 米国の雇用統計 (NFP)
  • 消費者物価指数 (CPI)
  • 連邦公開市場委員会 (FOMC) の政策金利発表

トレードする銘柄:

  • XAUUSD (ゴールド): 指標発表時に最もボラティリティが高くなりやすく、この戦略に最適です。
  • USDJPY (ドル円): 日本円が絡む指標の場合。

【ステップ・バイ・ステップ運用法】

  1. 準備 (発表10分前):
    • 経済指標カレンダーで、注目度の高い指標の時間を確認します。
    • MT5を起動し、チャートを5分足または1分足で表示しておきます。
  2. 設定 (発表1~2分前):
    • botを起動し、パラメータを入力します。このタイミングが非常に重要です。
    • 直前の価格でグリッドを設置するため、発表ギリギリに起動します。
    ▼ パラメータ設定例 (XAUUSDの場合) ▼
    • Symbol: XAUUSD
    • Price digits: 2
    • Base lot: 0.02 (※最初は必ず最小ロットで。リスク許容度に応じて調整)
    • Orders / side: 15 (上下に15本ずつ。乱高下の範囲を広めにカバー)
    • Grid multiplier: 2.5
      • 解説: ゴールドのスプレッドが平常時20pips程度だと仮定すると、20 * 2.5 = 50pips ($0.5) の間隔になります。指標発表時はスプレッドが広がるため、これくらいが適切です。狭すぎるとスプレッド拡大で即座に不利なポジションを持ってしまう可能性があります。
    • Loop count: 0
      • 解説: 指標発表という「一時的なお祭り」だけを狙うため、ループはさせません。1回のグリッドで終了させます。
  3. 実行 (発表直前):
    • 「Start」ボタンを押して、グリッドを設置します。
    • MT5のチャートに、現在の価格を中心にストップ注文がずらっと並んだことを確認します。
  4. 監視 (発表後):
    • 指標が発表されると、価格が激しく動き、注文が次々と約定し、決済されていきます。
    • botが自動で処理するので、基本的には見ているだけでOKです。
    • 価格が一番外側のTP(利確ライン)に達するか、相場の動きが落ち着いたら、botは自動ですべてのポジションと注文をクローズします。
  5. 終了:
    • もし動きが収まったのにポジションが残っている場合は、手動で「Abort」ボタンを押して終了させても良いでしょう。

経済指標カレンダーはみんかぶがおすすめ。フィルターを「主要国+重要度4以上」だけにすることで、重要度の高いものだけを絞ることができます。

あるいはcTraderの経済指標カレンダーなら、表示通貨ペアに関連する経済指標だけ表示できます。例えばXAUUSDならアメリカ関連の指標だけ表示できます。(Tradeview cTraderはこちら

Screenshot

実際のbot運用はMT5口座で行いますが、指標確認用にcTraderのデモ口座を作っておいてもいいでしょう。

もう一つの運用戦略:「レンジ相場」でのデイトレード

東京時間の午前中や、大きな指標がない日のNY市場の深夜など、一定の値幅を行ったり来たりしているレンジ相場で稼働させる方法です。

▼ パラメータ設定例 (USDJPY レンジ相場) ▼

  • Symbol: USDJPY
  • Price digits: 3
  • Base lot: 0.02
  • Orders / side: 8 (レンジ幅が狭いので、本数は少なめでOK)
  • Grid multiplier: 1.8
    • 解説: レンジ相場ではボラティリティが低いため、multiplierを小さくしてグリッドを密にし、小さな値動きでも約定するように狙います。スプレッドが狭い通貨ペア(ドル円など)が向いています。
  • Loop count: 0 または 1
    • 解説: 「このレンジ相場が続くであろう数時間だけ」と決めて稼働させます。

非常に重要な注意点

  • リスク管理の徹底: この手法はハイリスク・ハイリターンです。必ず余剰資金の範囲内で行い、失っても問題ない金額で運用してください。
  • スプレッド拡大とスリッページ: 指標発表時はスプレッドが通常時の数倍に広がり、注文した価格と実際に約定する価格がずれる「スリッページ」が発生しやすくなります。これらはコストとなり、利益を圧迫する要因です。Grid multiplierを極端に小さくしてはいけないのはこのためです。
  • ブローカーの選定: スプレッドが狭く、約定力が高い(スリッページが少ない)ブローカーを選ぶことが、この戦略の成功率に直結します。
  • デモ口座での練習は必須: いきなりリアル口座で試すのは絶対にやめてください。最低でも数回、異なる指標でデモ口座で練習し、botの動き、スプレッドの広がり方、想定される損益を体感してください。

このbotは、相場の特性を理解した上で「使いどころ」を絞れば、非常に強力な武器になり得ます。ぜひデモ口座で試してみてください。

Stop Grid Traderのおすすめパラメーター(指標トレード向け)

Stop Grid Traderのおすすめパラメーターはこちら。指標トレード向けの高頻度運用です。

DEF_SYMBOL(運用銘柄)XAUUSD
DEF_LOT(ロット)0.04(レバレッジ200倍)
0.1(レバレッジ400倍)
0.2(レバレッジ1000倍)
DEF_ORDERS_SIDE(片側のグリッド段数)10-20
DEF_MULTIPLIER(グリッド倍率)2.0
DEF_LOOP(ループ回数)0

運用銘柄は「ゴールド(XAUUSD)」がおすすめ。ボラティリティが非常に激しく、ピラミディングトレードで利益を出しやすいです。原油・ビットコインも同様にボラティリティは激しいですが、適用レバレッジが低いため高頻度運用には適していません。

グリッド倍率については「2.0倍」がおすすめ。グリット倍率が低ければ低いほど、グリットの幅が狭くなるため、少ないpipsのオーバーシュートでも確実にTP決済できるようになります。

グリッド倍率が低すぎるとレンジ相場で大きな損を出してしまいますが、オーバーシュートの発生しやすい指標タイミングだけに絞るなら問題ないでしょう。

グリッド段数については「10段から20段」がおすすめ。アメリカの経済指標が発表されると、ゴールドはおよそ1000ポイントから2000ポイントほど動くため、その値動きで利確しやすくなります。

ゴールドのスプレッドが20ポイント、グリッド幅が40ポイントの場合、10段なら400ポイント、20段なら800ポイントでTP決済ができます。逆に50段以上になるとTP決済まで2000ポイント以上の利幅を稼ぐ必要があるため、利確ハードルが高くなります。

ゴールドのスプレッドが30ポイントと広めの場合、グリッド幅も60ポイントと広めになるため、グリッド段数は10段と控えめにする。(60ポイント*10段=600ポイント)

運用ロットについては、資金10,000ドルあたり「0.2ロット」がおすすめ。(口座レバレッジ200倍の場合)口座レバレッジが400倍の場合は、その2倍(0.4ロット)にします。

証拠金維持率は200%前後をキープしたいため、必要証拠金は有効証拠金の半分になるようにします。例えば有効証拠金が10,000ドルなら、必要証拠金は5,000ドル前後にしておきます。

合計ロット必要証拠金(レバレッジ200倍)必要証拠金(レバレッジ400倍)
0.02ロット*10ポジション360ドル180ドル
0.04ロット*10ポジション720ドル360ドル
0.1ロット*10ポジション1800ドル900ドル
0.2ロット*10ポジション3600ドル1800ドル
0.4ロット*10ポジション7200ドル3600ドル

ピラミッディングトレードは証拠金維持率が圧迫されにくいので、ロスカットされにくいです。もっとハイリスクな運用をするなら、必要証拠金の割合を増やしてもいいでしょう。

資本効率をもっと高めたいなら、レバレッジの高い口座を運用します。レバレッジが高ければ高いほど、必要証拠金は少なくなり、大きなロットで取引してもロスカットされにくくなります。

ただ1000倍以上のレバレッジを提供しているブローカーは、経済指標前後にレバレッジを制限したり、スプレッドを恣意的に広げたりすることがあります。

資金に余裕のないブローカーほど、こうした嫌がらせリスクは高くなります。B-bookブローカーで指標トレードをするなら、XMなど資金に余裕のあるブローカーを選びましょう。(XM 公式サイトはこちら

収益性を高めるためのソースコード改善案

Stop Grid Traderの基本ロジックはピラミッディングトレードです。ボラティリティが激しいトレンド相場では大きな利益を出せますが、レンジ相場では手数料負けしやすくなります。なるべくボラティリティが高いタイミングで運用しましょう。

ソースコードの改善案としては、以下のものが挙げられます。

  • APIで出来高引用する(ソース
  • ボラティリティの高いタイミング(経済指標など)でピンポイント稼働する(ソース
  • 短期分析で倍率を動的に調整する(ソース
  • 機械学習を入れつつ、鮮度の高いデータだけを利用する(ソース
  • 稼働ウォレット率と資金量の推移から日上限の出来高探ってトラップ注文の到達率を上げる(ソース

プログラミングが苦手なら、LLMに指示を出してバイブコーティングで修正するといいでしょう。特にGoogle Geminiなら無料でお手軽にソースコードの修正ができます。

運用口座のおすすめは?

運用口座はTradeview ILC口座がおすすめ。

スプレッド・取引手数料が非常に優秀で、高頻度運用でも利益を出しやすいです。またTradeviewはA-bookブローカーでもあるので、経済指標前後でスプレッドが恣意的に広げられる可能性も低いです。

さらにTradeviewはBTCUSDの取引も可能です。ただし銘柄レバレッジは10倍と低めなので、資金10,000ドルあたりで0.02-0.04ロットの低ロット運用をすることが前提となります。

Tradeview ILC口座の最小ロットは0.1ロット。設定ロットは0.2ロットが一般的になる。

もっと取引コストを抑えたいなら、他の海外FXブローカーを探しても良いでしょう。特に海外FX調査兵団さんが高く評価しているブローカーは、取引コストが低めで、約定力にも優れている傾向があります。

Stop-Grid Traderはバックテストできる?

Stop-Grid TraderのPythonスクリプトは、GUI (tkinter) を持ち、リアルタイムでMT5ターミナルと連携して動作するライブトレーディング(またはフォワードテスト)用のボットです。

そのため、このスクリプトをそのままバックテストすることはできません

バックテストを行うには、スクリプトを大幅にリファクタリング(再構成)し、取引ロジックと**実行環境(GUIやMT5との通信)**を分離する必要があります。

バックテスト化の課題 (なぜそのままでは動かないか)

  1. GUI (tkinter): バックテストは過去のデータに対して自動で高速に実行されるべきです。パラメータ入力を求めるGUIや、実行中のステータスを表示するウィンドウは不要であり、処理を妨げます。
  2. リアルタイムのMT5関数: mt5.order_send, mt5.positions_get, mt5.symbol_info_tick などの関数は、現在稼働しているMT5ターミナルに依存します。バックテストでは、これらの関数を、過去のデータ上で仮想的に動作するシミュレーターに置き換える必要があります。
  3. 時間とイベント駆動: threading や time.sleep はリアルタイムでの監視に使われます。バックテストは、ローソク足データを1本ずつ進めるシーケンシャルなループで時間をシミュレートするため、これらの仕組みは不要です。

バックテスト化への戦略

  1. シミュレーターの作成: MT5の動作を模倣するクラス(BacktestSimulator)を作成します。このクラスは以下の役割を担います。
    • 過去の価格データ(OHLC)を保持する。
    • 仮想の口座残高、ポジション、待機注文(Pending Order)を管理する。
    • order_sendやpositions_getのような関数をシミュレートするメソッドを提供する。
    • 時間を1本ずつ進め、その間の価格変動(高値・安値)で注文がトリガーされるかを判定する。
  2. 取引ロジックの抽出と改造: StopGridTraderクラスから、純粋な取引ロジック(グリッドをどう作り、どう決済するか)を抜き出し、新しいバックテスト用のクラス(StopGridTraderBacktest)に移植します。
    • GUI、スレッド、time.sleepに関するコードをすべて削除します。
    • mt5.* で始まる関数呼び出しを、上記で作成したBacktestSimulatorのメソッド呼び出しに置き換えます。
    • _monitorメソッドは、継続的にループするのではなく、ローソク足が1本進むごとに呼び出されるon_barのようなメソッドに改造します。

他のMT5口座にスケール化するには?

Stop Grid Traderのおすすめ運用方法は、経済指標の前後で手動運用することです。ただこの運用方法だとMT4/MT5のEAのように自動化できないので、他の口座にスケール化することは難しいです。

その場合はコピートレードプラットフォームを使いましょう。マスター口座とフォロワー口座をリンクさせておくことで、マスター口座の取引をフォロワー口座にコピーできます。

特にB-bookブローカーはスプレッドが非常に狭い代わりに、派手に稼ぎすぎるとスプレッドやマイナススリッページを広げたり、口座凍結・利益没収してきたりするケースがあります。そういったブローカーで資金10万円+10口座で分散運用して、目立たないように稼ぐことも可能となります。

おすすめはFX BlueのPersonal Trade Copierで、会員登録すれば無料ダウンロードできます。

Personal Trade Copierの使い方については、こちらの記事で詳しくまとめられています。

コピートレードをするなら、Copygram、MetaCopier、Traders Connectなどのコピートレードプラットフォームも候補に入ります。

クラウドタイプのコピートレードツールなら、ソフトウェア版MT5を同時起動する必要もなくなるため、RAM容量の低いVPSでも10-100口座で運用しやすくなります。

役に立った記事はSNSシェア!
  • URLをコピーしました!
  • URLをコピーしました!

海外FXの稼ぎ方

海外FXの効率的な稼ぎ方はこちら。

  1. XM スタンダード口座を開設し、3種ボーナスを獲得する
  2. 海外FXの稼ぎ方を体得する
  3. 取引コストの低いTradeview cTrader口座/ILC口座に乗り換える

海外FX初心者の運用口座には、XM スタンダード口座がおすすめ。口座の最大レバレッジが1000倍で、ゴールドの銘柄レバレッジも1000倍なため、ゴールドのハイリスクトレードが可能となります。

またXMは3種類のボーナスを提供しており、10万円の入金額を17万円ほどに増やせます。ボーナスは損失カバー機能があるので、ハイリスクなトレードに使いましょう。

海外FXの稼ぎ方は、億トレーダー(Xアカウント)のコピートレード、無料bot「Stop Grid Trader」による自動売買などがおすすめ。

裁量トレードがメインなら、運用口座はTradeview cTrader口座がおすすめ。高性能FXプラットフォーム「cTrader」に対応しており、リミット注文・ストップ注文を設置しやすくなります。

botによる自動売買をするなら、運用口座はTradeview ILC口座がおすすめ。スプレッドが非常に狭く、取引手数料も1ロット往復5ドルと最安値クラスなので、取引回数の多いトレードロジックで利益を出しやすくなります。

この記事を書いた人

海外FXの情報を備忘録としてまとめています。
運用は自己責任でお願いします。
Twitterで「海外FXの有益情報bot」も運用してます。

目次