レイテンシーアービトラージはFXの中でもトップクラスに期待値の高いトレード手法です。かつては数ヶ月で数百万円から数千万円以上の利益を出すことも可能でした。
ただ現在ではブローカー側の対策が厳しくなっており、この手の超高速売買をすると不正取引として口座凍結・利益没収されるリスクが高くなっています。
なおこれは国内/海外FXブローカーの話です。レイテンシー対策が共有されていないバイナリーオプション業者であれば付け入る隙はあるかもしれません。
この記事ではバイナリーオプション業者向けのレイテンシー取引ツールを紹介していきます。無料公開しているソースコードなのでLLMで改造することも可能です。
レイテンシーツールの無料公開ソースコード
バイナリー業者向けのレイテンシー取引ツールのソースコードはこちら。(ソースコード引用元)
これはいわゆるクリック系ツールで、Chromeブラウザを手動操作に見せかけて自動売買することができます。
import json
import asyncio
import re
import requests
import websockets
import pychrome
import time
from collections import deque
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import MetaTrader5 as mt5
import psutil
import sys
import pyautogui # ▼▼▼ 追加: pyautogui をインポート ▼▼▼
# --- ▼ ユーザー設定 ▼ ---
YOUR_HASH = "" # 自身のハッシュ値に書き換えてください
TRADE_AMOUNT = "500"
TIMEFRAME_ID = "15" # 30秒取引:"15", 1分取引:"16", ...
TRADE_MODE = "demo"
MT5_SYMBOL = "USDJPY"
# ▼▼▼ 追加・変更箇所: マウスクリック用の座標設定 ▼▼▼
# 【超重要】これらの座標は**仮の値**です。
# 実際の取引プラットフォームの「PUT」と「CALL」ボタンの正確な中心座標に設定してください。
# 設定を誤るとクリックが全く機能しません。
#
# ★ 座標特定の手順 ★
# 1. 取引プラットフォームのページをChromeで開く。
# 2. Chromeの**ズームレベルを必ず100%**に設定。
# 3. ブラウザの**ウィンドウサイズを固定**し、常にその位置に配置する。
# 4. マウスカーソルを「PUT」または「CALL」ボタンの中心に手動で合わせる。
# 5. Pythonのインタラクティブシェルや簡単なスクリプトで `import pyautogui; pyautogui.position()` を実行し、
# その時点のマウスカーソルのX, Y座標を取得する。
# 例:
# >>> import pyautogui
# >>> pyautogui.position()
# Point(x=900, y=500)
#
# ★ その他重要な注意点 (pyautogui 使用時) ★
# ・プログラム実行中は**ブラウザウィンドウを最小化せず、フォアグラウンドに表示**してください。
# ・プログラム実行中に**マウスやキーボードを操作しないでください**。誤動作の原因になります。
# ・これはOSレベルでの物理クリックであり、画面に表示されているものをクリックします。
PUT_BUTTON_COORD_X = 1742 # 例: PUTボタンのX座標 (画面左上を(0,0)とするピクセル値)
PUT_BUTTON_COORD_Y = 575 # 例: PUTボタンのY座標
CALL_BUTTON_COORD_X = 1732 # 例: CALLボタンのX座標
CALL_BUTTON_COORD_Y = 359 # 例: CALLボンのY座標
# --- ▼ エントリーロジック設定 ▼ ---
# 価格差の移動平均を計算するために、何回分のデータを保持するか
AVERAGE_PERIOD = 50
# 平均の価格差から、何pips乖離したらエントリーするか (例: 0.5pips = 0.005)
ENTRY_THRESHOLD_PIPS = 0.005
# 一度エントリーしてから、次にエントリーするまでの待機時間(秒)
ENTRY_COOLDOWN_SECONDS = 15
# 注文失敗時に再試行する最大回数
MAX_ORDER_RETRY_ATTEMPTS = 3
# 注文再試行までの待機時間(秒)
ORDER_RETRY_DELAY_SECONDS = 5
# --- ▲ エントリーロジック設定 ▲ ---
# --- 以下は基本的に編集不要 ---
# グローバル変数
diff_history = deque(maxlen=AVERAGE_PERIOD)
last_entry_time = 0
def log_message(message):
print(f"[INFO] {message}")
def find_mt5_instances():
instances = []
for proc in psutil.process_iter(['name', 'exe']):
try:
if proc.info['name'] in ['terminal64.exe', 'terminal.exe']:
if proc.info['exe'] and proc.info['exe'] not in instances:
instances.append(proc.info['exe'])
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return instances
def select_mt5_instance(instances):
if not instances:
log_message("MT5: 起動しているMT5が見つかりませんでした。")
return None
if len(instances) == 1:
log_message(f"MT5: 起動中のMT5を1つ検出しました: {instances[0]}")
return instances[0]
print("-" * 50)
log_message("複数のMT5が起動しています。接続するMT5を選択してください:")
for i, path in enumerate(instances):
print(f" [{i + 1}] {path}")
while True:
try:
choice = int(input(f"番号を入力してください (1-{len(instances)}): ")) - 1
if 0 <= choice < len(instances): return instances[choice]
else: print("無効な番号です。")
except ValueError: print("数値を入力してください。")
except (KeyboardInterrupt, EOFError): return None
# --- ブラウザへの接続処理 ---
# SeleniumとPyChromeはWebSocketによる価格データ取得のために引き続き使用します。
# クリックにはpyautoguiを使用するため、pychromeでのInput.dispatchMouseEventは不要になります。
try:
chrome_options = Options()
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
driver = webdriver.Chrome(options=chrome_options)
log_message("Selenium: 既存のChromeセッションに接続しました (port 9222)")
except Exception as e:
log_message(f"Selenium: 接続に失敗しました: {e}"); exit()
try:
browser = pychrome.Browser(url="http://127.0.0.1:9222")
tabs = browser.list_tab()
tab = tabs[0] if tabs else None
if tab: tab.start(); log_message("PyChrome: 接続に成功しました (port 9222)")
else: log_message("PyChrome: 利用可能なタブが見つかりません")
except Exception as e:
log_message(f"PyChrome: 接続に失敗しました: {e}"); tab = None
async def setup_websocket_connection():
# この関数は、pyautoguiによるクリックとは直接関係ありませんが、
# 価格データ取得のために既存のWebSocket接続設定を維持します。
if not tab:
log_message("BO: PyChrome タブが利用できないためWebSocket設定をスキップします。")
return False
js_websocket_setup = f"""
(async function() {{
if (typeof window.boWebSocket !== 'undefined' && window.boWebSocket.readyState !== WebSocket.CLOSED) {{
console.log("Closing existing BO WebSocket before re-setup.");
window.boWebSocket.close();
await new Promise(r => setTimeout(r, 100));
}}
window.boWebSocket = new WebSocket("wss://bo.zentrader.com:27017/");
window.boWebSocket.onclose = (event) => {{
console.warn("BO WebSocket closed. Code:", event.code, "Reason:", event.reason);
}};
window.boWebSocket.onerror = (error) => {{
console.error("BO WebSocket error:", error);
}};
let timeout = 10000;
let interval = 500;
let elapsed = 0;
while (window.boWebSocket.readyState !== WebSocket.OPEN && elapsed < timeout) {{
await new Promise(r => setTimeout(r, interval));
elapsed += interval;
}}
if (window.boWebSocket.readyState === WebSocket.OPEN) {{
console.log("BO WebSocket opened and ready for initial commands.");
const now = Math.floor(Date.now() / 1000), tE = now, tB = now - 500;
window.boWebSocket.send(JSON.stringify({{
command: "connect",
bo: "{TRADE_MODE}",
hash: "{YOUR_HASH}",
platform: "mt4",
source: "site"
}}));
console.log("connect sent");
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{ command: "get_user_data" }}));
console.log("get_user_data sent");
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{ command: "hook_time", enable: "true" }}));
console.log("hook_time sent");
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{ command: "get_cfg_trade", language: "ja" }}));
console.log("get_cfg_trade sent");
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{ command: "get_user_settings" }}));
console.log("get_user_settings sent");
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{
command: "hook_timeframes",
option_kind: "1",
tool_id: "4",
timeframe_id: "{TIMEFRAME_ID}",
bo: "{TRADE_MODE}",
enable: "true",
interval: "1000",
source: "site"
}}));
console.log("hook_timeframes sent");
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{
command: "get_quotes_history",
tool_id: "4",
time_begin: tB.toString(),
time_end: tE.toString(),
time_size: "S1",
source: "site",
request_id: "4_500"
}}));
console.log("get_quotes_history sent", {{
time_begin: new Date(tB * 1000).toISOString(),
time_end: new Date(tE * 1000).toISOString(),
duration_sec: tE - tB
}});
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{ command: "hook_user_status", enable: "true" }}));
console.log("hook_user_status sent");
await new Promise(r => setTimeout(r, 300));
window.boWebSocket.send(JSON.stringify({{
command: "hook_options",
source: "site",
enable: "true",
interval: "100"
}}));
console.log("hook_options sent");
return true;
}} else {{
console.error("BO WebSocket failed to open within timeout. readyState:", window.boWebSocket.readyState);
return false;
}}
}})();
"""
try:
result_eval = tab.Runtime.evaluate(expression=js_websocket_setup, awaitPromise=True)
js_return_value = result_eval.get('result', {}).get('value')
if js_return_value is True:
log_message("BO: WebSocket接続スクリプトの実行とOPEN状態への待機に成功しました。")
return True
else:
log_message("BO: WebSocket接続スクリプトの実行に失敗、またはOPEN状態になりませんでした。")
if js_return_value is False:
pass
else:
log_message(f"BO: JavaScriptからの予期せぬ戻り値: {js_return_value}")
return False
except Exception as e:
log_message(f"BO: WebSocket接続スクリプトの実行中にエラー: {e}")
return False
def send_bo_order(direction, price_open):
"""バイナリーオプションの注文をPC画面上のマウスクリックで送信する関数"""
target_x = 0
target_y = 0
button_name = ""
if direction == "PUT":
target_x = PUT_BUTTON_COORD_X
target_y = PUT_BUTTON_COORD_Y
button_name = "PUT"
elif direction == "CALL":
target_x = CALL_BUTTON_COORD_X
target_y = CALL_BUTTON_COORD_Y
button_name = "CALL"
else:
log_message(f"BO: 不明な注文方向: {direction}")
return False
log_message(f"BO: {button_name} ボタンを物理座標 ({target_x}, {target_y}) でクリックします。現在の価格表示(参考): {price_open}")
log_message(f"【超重要】ブラウザのウィンドウ位置、ズームレベル100%を確認し、実行中はPCを操作しないでください。")
try:
# pyautoguiで指定された座標をクリック
pyautogui.click(x=target_x, y=target_y)
log_message(f"★★★ BO: {button_name} ボタンの物理クリックイベント送信成功 (座標: {target_x}, {target_y}) ★★★")
return True
except pyautogui.FailSafeException:
log_message(f"BO: エラー - マウスが画面隅に移動したため、pyautoguiがFail-Safeをトリガーしました。")
log_message(f"BO: Fail-Safeは緊急停止機能です。プログラムを終了します。")
sys.exit(1) # プログラムを強制終了
except Exception as e:
log_message(f"BO: エラー - {button_name} ボタンの物理クリック中にエラーが発生しました: {e}")
return False
def get_price_from_mt5():
tick = mt5.symbol_info_tick(MT5_SYMBOL)
return tick.ask if tick else None
async def price_monitoring_and_trading_logic():
"""ブラウザ価格を監視し、乖離エントリーロジックを実行する"""
global last_entry_time
port = 9222
try:
response = requests.get(f'http://localhost:{port}/json')
ws_debugger_url = next(item['webSocketDebuggerUrl'] for item in response.json() if 'webSocketDebuggerUrl' in item and item['type'] == 'page')
except Exception as e:
log_message(f"CDP: DevTools WebSocket URL 取得に失敗: {e}");
log_message("CDP: プログラムを終了します。ブラウザが起動していないか、デバッグポートが利用できません。")
sys.exit(1) # 強制終了
log_message("CDP: DevTools WebSocket に接続試行中...")
try:
websocket_cdp = await websockets.connect(ws_debugger_url)
await websocket_cdp.send(json.dumps({"id": 1, "method": "Network.enable"}))
log_message("CDP: DevTools WebSocket に接続成功。価格差の監視を開始...")
except Exception as e:
log_message(f"CDP: DevTools WebSocket への接続に失敗しました: {e}")
log_message("CDP: プログラムを終了します。")
sys.exit(1) # 強制終了
latest_browser_price = 0.0
while True:
try:
message = await websocket_cdp.recv() # CDP WebSocketを使用
data = json.loads(message)
if data.get("method") != "Network.webSocketFrameReceived": continue
# WebSocketフレームのペイロードから価格情報を抽出
payload = data["params"]["response"].get("payloadData", "")
if not payload.startswith("U,1,1,USD/JPY"): continue
match = re.search(r"USD/JPY\|([\d.]+)\|", payload)
if not match: continue
browser_price = float(match.group(1))
if latest_browser_price == browser_price: continue
latest_browser_price = browser_price
mt5_ask = get_price_from_mt5()
if not mt5_ask: continue
current_diff = mt5_ask - browser_price
diff_history.append(current_diff)
if len(diff_history) < AVERAGE_PERIOD:
log_message(f"[データ収集中 {len(diff_history)}/{AVERAGE_PERIOD}] MT5: {mt5_ask} | Browser: {browser_price} | 差: {current_diff:+.5f}")
continue
average_diff = sum(diff_history) / len(diff_history)
log_message(f"[監視中] MT5: {mt5_ask} | Browser: {browser_price} | 現在差: {current_diff:+.5f} | 平均差: {average_diff:+.5f}")
# --- ▼▼▼ エントリー判定 ▼▼▼ ---
if time.time() - last_entry_time < ENTRY_COOLDOWN_SECONDS:
log_message(f"【エントリー対象外】クールダウン中 ({ENTRY_COOLDOWN_SECONDS - (time.time() - last_entry_time):.1f}秒残り) ✕")
continue
order_triggered = False
order_direction = None
order_price = None
if current_diff > average_diff + ENTRY_THRESHOLD_PIPS:
log_message(f"【PUT条件成立】現在差({current_diff:+.5f}) > 平均差({average_diff:+.5f}) + 閾値({ENTRY_THRESHOLD_PIPS}) ○")
order_direction = "PUT"
order_price = browser_price
order_triggered = True
elif current_diff < average_diff - ENTRY_THRESHOLD_PIPS:
log_message(f"【CALL条件成立】現在差({current_diff:+.5f}) < 平均差({average_diff:+.5f}) - 閾値({ENTRY_THRESHOLD_PIPS}) ○")
order_direction = "CALL"
order_price = browser_price
order_triggered = True
else:
log_message(f"【エントリー対象外】条件不成立 (現在差: {current_diff:+.5f}, 平均差: {average_diff:+.5f}, 閾値: {ENTRY_THRESHOLD_PIPS}) ✕")
# 注文がトリガーされた場合の再試行ロジック
if order_triggered:
order_successful = False
for attempt in range(MAX_ORDER_RETRY_ATTEMPTS):
log_message(f"BO: 注文試行中... ({attempt + 1}/{MAX_ORDER_RETRY_ATTEMPTS}回目)")
# pyautoguiによる物理クリックを試行
order_status = send_bo_order(order_direction, order_price)
if order_status is True: # クリックイベント送信が成功
order_successful = True
break # 再試行ループを抜ける
else: # クリックイベント送信が失敗した場合 (FailSafeなどで中断された場合も含む)
if attempt < MAX_ORDER_RETRY_ATTEMPTS - 1:
log_message(f"BO: 注文クリック失敗。{ORDER_RETRY_DELAY_SECONDS}秒後に再試行します。")
await asyncio.sleep(ORDER_RETRY_DELAY_SECONDS)
else:
log_message(f"BO: 注文クリック失敗。最大試行回数 ({MAX_ORDER_RETRY_ATTEMPTS}) に達しました。")
if order_successful:
log_message(f"BO: 注文クリック処理が正常に完了しました。クールダウンを開始します。")
last_entry_time = time.time() # 注文成功時のみ最終エントリー時間を更新
else:
log_message(f"BO: 注文の再試行がすべて失敗しました。次のティックで処理を継続します。")
except websockets.exceptions.ConnectionClosed as e:
log_message(f"CDP: DevTools WebSocket接続が閉じられました: {e}。これは致命的な問題です。")
log_message("CDP: プログラムを終了します。ブラウザのデバッグ接続が失われました。")
break
except Exception as e:
log_message(f"CDP: WebSocket 受信または処理中にエラー: {e}");
await asyncio.sleep(1)
continue
async def main():
mt5_path = select_mt5_instance(find_mt5_instances())
if not mt5_path: log_message("プログラムを終了します。"); return
if not mt5.initialize(path=mt5_path):
log_message(f"MT5: initialize() failed, error code = {mt5.last_error()}"); return
log_message(f"MT5: 接続成功 (Path: {mt5_path}, Version: {mt5.version()})")
log_message("BO: 初期WebSocket接続設定を開始します。")
if not await setup_websocket_connection():
log_message("BO: WebSocketの初期接続設定に失敗しました。プログラムを終了します。"); return
log_message("BO: 初期WebSocket接続が正常に確立されました。")
await price_monitoring_and_trading_logic()
if __name__ == "__main__":
# pyautogui のFail-Safe機能を有効にする (デフォルトで有効ですが念のため)
# マウスを画面の四隅のいずれかに移動させると、pyautoguiの制御が停止します。
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.1 # 各pyautogui呼び出し間の0.1秒の一時停止を設定
try:
asyncio.run(main())
except KeyboardInterrupt:
log_message("プログラムが中断されました。(KeyboardInterrupt)")
finally:
if mt5.terminal_info():
mt5.shutdown(); log_message("MT5: 接続をシャットダウンしました。")
calibrate用のファイルはこちら。
# -*- coding: utf-8 -*-
import pyautogui
import time
def calibrate():
print("=== Calibration Mode ===")
time.sleep(0.2)
input("Move the mouse cursor over the UP button and press Enter�c")
up = pyautogui.position()
print(f"UP_POS = ({up.x}, {up.y})")
input("Move the mouse cursor over the DOWN button and press Enter�c")
down = pyautogui.position()
print(f"DOWN_POS = ({down.x}, {down.y})")
print("\nCopy these tuples into auto_trader.py as UP_POS and DOWN_POS.")
if __name__ == "__main__":
calibrate()
説明書・使い方はこちら。
1.PowerShellを立ち上げます。
2.cd C:\Users\自分のフォルダ\Desktop\tradebot
3.
pip install requests
pip install websockets
pip install pychrome
pip install selenium
pip install MetaTrader5
pip install psutil
- Chrome のリモートデバッグ起動
自動接続のため、別ウィンドウで次を実行し、デバッグモードの Chrome を立ち上げます:& “C:/Program Files/Google/Chrome/Application/chrome.exe”
--remote-debugging-port=9222
–user-data-dir=”C:/chromebot_profile”5.https://demo.zentrader.com/を、立ち上げたURL欄に貼る。
(デモ口座登録してない人はしてください)6.py tradebot.py
エントリー部分は、ChatGPTと話しながらするといいと思います。
抜けてるところあるかもなので、わからないことあったらDMで聞いてくださいHASHの見つけ方
ステップ1でデバッグモードで起動したChromeで、取引サイトにログインします。
キーボードの F12 キーを押して、「デベロッパーツール(開発者ツール)」を開きます。
「ネットワーク(Network)」タブを選択し、その中にある「WS (WebSocket)」をクリックして絞り込みます。
ページをリロード(再読み込み)すると、WebSocketの通信が表示されます。bo.zentrader.com のような名前の通信を探してください。
その通信名をクリックし、「メッセージ(Messages)」タブを開きます。
緑色の上向き矢印(送信されたデータ)の中に、”command”: “connect” から始まるデータがあります。その中にある “hash”: “xxxxxxxx…” の、xxxx… の部分があなたのログイン情報です。これをコピーします。
ソースコードの使い方(Gemini Pro説明バージョン)
このPythonコードは、MetaTrader 5 (MT5) の価格と、ブラウザ上のバイナリーオプションプラットフォームの価格差を利用して、自動でエントリー(取引注文)を行うためのツールです。
注文は、pyautogui
というライブラリを使って、PCの画面上のボタンを物理的にマウスクリックすることで行われます。
以下に、このコードを使用するための準備から実行までの手順を説明します。
1. このコードの目的と仕組み
- 目的: MT5から配信されるレートと、ブラウザ上の取引プラットフォームのレートの間に発生するわずかな「ズレ(乖離)」を検知し、そのズレが元に戻る動きを狙って自動で取引します。
- 仕組み:
- MT5とブラウザの両方からリアルタイムに価格を取得します。
- 2つの価格の差を常に計算し、その差の「移動平均値」を算出します。
- 現在の価格差が、計算された移動平均値から設定した閾値(
ENTRY_THRESHOLD_PIPS
)以上に広がった瞬間に、エントリーチャンスと判断します。 - エントリーチャンスと判断すると、
pyautogui
が事前に設定された座標(PUTボタンまたはCALLボタンの位置)を自動でマウスクリックして注文を出します。
2. 必要なもの(実行環境)
- Python環境: PythonがPCにインストールされている必要があります。
- 必要なライブラリ: 以下のライブラリをインストールしてください。コマンドプロンプトやターミナルで以下のコマンドを実行します。
bash pip install requests websockets pychrome-async selenium MetaTrader5 psutil pyautogui
- MetaTrader 5 (MT5): PCにインストールされ、起動している必要があります。
- Google Chrome: PCにインストールされている必要があります。
- バイナリーオプションプラットフォームのアカウント: コードはZentraderを想定しているようですが、他のプラットフォームでも座標設定を正しく行えば利用できる可能性があります。
3. 準備手順(プログラム実行前)
プログラムを実行する前に、以下の準備を必ず行ってください。
Step 1: Chromeをデバッグモードで起動する
このコードは、起動中のChromeに接続して情報を取得する必要があります。
- 現在開いているChromeをすべて閉じます。
- コマンドプロンプト(Windowsの場合)またはターミナル(Macの場合)を開き、以下のコマンドを実行してChromeを起動します。
- Windowsの場合:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
※Chromeのインストール場所が異なる場合は、パスを修正してください。 - Macの場合:
bash /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
- Windowsの場合:
Step 2: 取引プラットフォームにログインする
デバッグモードで起動したChromeで、取引したいバイナリーオプションのプラットフォームのページを開き、ログインを済ませておきます。
Step 3: MT5を起動する
取引に使用するMT5を起動し、ログインしておきます。
Step 4: 【最重要】コードのユーザー設定を行う
コードの冒頭にある「ユーザー設定」セクションを、ご自身の環境に合わせて編集します。
YOUR_HASH = ""
: あなたの取引アカウントのハッシュ値を設定します。これはプラットフォームにログインした際の認証情報の一部です。ブラウザの開発者ツール(F12キー)の「ネットワーク」タブなどで確認する必要があります。TRADE_AMOUNT = "500"
: 1回あたりの取引金額を設定します。TIMEFRAME_ID = "15"
: 取引時間を選択します。コードのコメントによると"15"
は30秒取引のようです。TRADE_MODE = "demo"
: デモ口座を使うか、リアル口座を使うかを設定します。("demo"
または"real"
)MT5_SYMBOL = "USDJPY"
: MT5で価格を取得する通貨ペアを設定します。- 【超重要】マウスクリック用の座標設定
このプログラムが正しく動作するための最も重要な設定です。PUT
ボタンとCALL
ボタンの画面上の正確な座標を設定する必要があります。- ウィンドウサイズとズームレベルの固定:
- 取引プラットフォームを開いているChromeのズームレベルを必ず100%にしてください。
- ブラウザのウィンドウサイズと位置を固定し、プログラム実行中はその状態を維持してください。
- 座標の特定方法:
- マウスカーソルを取引画面の「PUT」ボタンの真ん中に手で合わせます。
- コマンドプロンプトやターミナルでPythonを起動し(
python
と入力)、以下のコマンドを実行して座標(X, Y)を取得します。python >>> import pyautogui >>> print(pyautogui.position()) # Point(x=1742, y=575) のように表示されます
- 表示されたX座標とY座標を、コードの
PUT_BUTTON_COORD_X
とPUT_BUTTON_COORD_Y
に設定します。 - 同様に、「CALL」ボタンの座標も特定し、
CALL_BUTTON_COORD_X
とCALL_BUTTON_COORD_Y
に設定します。
- ウィンドウサイズとズームレベルの固定:
4. 実行方法
すべての準備が完了したら、コマンドプロンプトやターミナルで、このPythonファイルが保存されているフォルダに移動し、以下のコマンドでプログラムを実行します。
python <ファイル名>.py
(<ファイル名>
は、このコードを保存したファイル名に置き換えてください)
- MT5の選択: 複数のMT5を起動している場合、どのMT5に接続するかを選択するためのプロンプトが表示されます。番号を入力してEnterキーを押してください。
- 監視開始: 正常に起動すると、ターミナルにログメッセージが表示され、価格差の監視が始まります。
5. 実行中の注意点
- 【絶対にPCを操作しない】: プログラム実行中は、マウスやキーボードに触らないでください。予期せぬ場所をクリックしてしまい、誤った取引や操作につながる危険があります。
- 【ブラウザを前面に表示】: 取引画面のChromeウィンドウは、常に最前面に表示し、最小化しないでください。
pyautogui
は画面に見えているものをクリックするため、ウィンドウが隠れていると機能しません。 - プログラムの停止方法:
- 通常停止: プログラムを実行しているターミナルを選択し、
Ctrl + C
キーを押します。 - 緊急停止(フェイルセーフ): マウスカーソルを画面の四隅(右上、右下、左上、左下)のいずれかに素早く移動させると、
pyautogui
の安全機能が作動し、プログラムが強制的に停止します。
- 通常停止: プログラムを実行しているターミナルを選択し、
このコードは高度な自動化ツールであり、設定ミスや不適切な使用は意図しない損失につながる可能性があります。まずはデモ口座で十分にテストし、仕組みをよく理解した上でご利用ください。
calibrateファイルの使い方
このPythonスクリプトは、画面上の特定の位置の座標(X, Y)を正確に取得するための補助ツールです。
具体的には、pyautogui
を使った自動化スクリプト(例えば、先ほど質問された自動取引ボット)で、クリックさせたいボタン(このコードでは「UPボタン」と「DOWNボタン」)の正確な座標を簡単に見つけ出すために使います。
手動で座標を調べる手間を省き、より正確な設定を支援することが目的の「キャリブレーション(位置調整)用スクリプト」です。
コードの主な機能と解説
このコードは非常にシンプルで、以下の3つの主要な部分から成り立っています。
- ライブラリのインポート
import pyautogui import time
pyautogui
: マウスやキーボードを自動で操作するためのライブラリです。このコードでは、マウスカーソルの現在位置を取得するpyautogui.position()
という機能を使っています。time
: 時間に関連する機能を提供します。time.sleep(0.2)
でプログラムの開始をわずかに遅らせ、ユーザーが準備する時間を与えています。
- 対話的な座標取得
input("Move the mouse cursor over the UP button and press Enter…") up = pyautogui.position() print(f"UP_POS = ({up.x}, {up.y})")
input(...)
: 画面に(...)
内のメッセージを表示し、ユーザーがEnterキーを押すまでプログラムの実行を一時停止します。- 処理の流れ:
- 「UPボタンの上にマウスカーソルを移動させて、Enterキーを押してください」というメッセージが表示されます。
- ユーザーは、自動クリックさせたいアプリケーションの「UP」ボタンの上にマウスカーソルを合わせます。
- その後、このスクリプトを実行しているターミナル(黒い画面)に戻り、Enterキーを押します。
- Enterキーが押された瞬間に
pyautogui.position()
が実行され、その時点でのマウスカーソルのX座標とY座標が変数up
に保存されます。 print(...)
で、取得した座標をUP_POS = (123, 456)
のような形式で画面に表示します。
- この一連の処理が、「DOWN」ボタンについても繰り返されます。
- 実行部分
python if __name__ == "__main__": calibrate()
- これはPythonの決まり文句で、「このスクリプトが直接実行された場合に
calibrate()
関数を呼び出す」という意味です。これにより、このファイルを実行するだけでキャリブレーションが開始されます。
- これはPythonの決まり文句で、「このスクリプトが直接実行された場合に
使い方
このスクリプトの使い方は以下の通りです。
- 準備:
- 自動クリックさせたいアプリケーション(例: 取引プラットフォームの画面)をPCの画面に表示させます。
- このコードを
calibrate.py
のような名前で保存します。
- 実行:
- コマンドプロンプトやターミナルを開き、保存したファイルがあるフォルダに移動します。
python calibrate.py
と入力してスクリプトを実行します。
- 操作:
- ターミナルに
Move the mouse cursor over the UP button and press Enter
と表示されます。 - あなたはマウスを操作して、クリックさせたい「UP」ボタン(例えば、取引プラットフォームの「CALL」ボタン)の真ん中にカーソルを合わせます。
- カーソルを合わせたまま、ターミナルに戻って Enterキー を押します。
- すると、ターミナルに
UP_POS = (1732, 359)
のような座標が表示されます。 - 次に
Move the mouse cursor over the DOWN button and press Enter
と表示されるので、今度は「DOWN」ボタン(例えば「PUT」ボタン)で同じ操作を繰り返します。
- ターミナルに
- 結果の利用:
- すべての操作が終わると、ターミナルに以下のような最終結果が表示されます。
UP_POS = (1732, 359) DOWN_POS = (1742, 575)
- この
(1732, 359)
や(1742, 575)
という座標を、あなたのメインの自動取引スクリプト(auto_trader.py
など)の座標設定部分にコピー&ペーストします。
- すべての操作が終わると、ターミナルに以下のような最終結果が表示されます。
まとめ
このスクリプトは、自動化プログラムの「目」や「指」となるマウスカーソルの位置を正確に教えるための、非常に便利で重要な準備ツールです。これにより、設定ミスを防ぎ、自動化の信頼性を大幅に向上させることができます。
派生元のソースコード
抜けるバイナリー&ソースコード
天国草さんが最初に紹介したバイナリー業者向けのレイテンシーアービトラージツールです。
該当noteは削除済みで、購入者のみ閲覧できます。
# ---------------------------------------------------------------------
# ENTRY TIMER & ODDS FETCH (SELENIUM)
# ---------------------------------------------------------------------
def init_driver_with_iframe(self):
"""Attach to an existing Chrome (debug port 9230) and switch to iframe containing odds."""
opts = Options()
opts.debugger_address = "localhost:9230" # assumes user started Chrome with --remote-debugging-port=9230
try:
self.driver = webdriver.Chrome(options=opts)
print("✅ Connected to Chrome debugging session")
self.find_and_switch_to_odds_iframe()
except Exception as exc:
print(f"❌ Could not connect to Chrome: {exc}")
self.driver = None
def find_and_switch_to_odds_iframe(self):
"""Loop through iframes until BUY_ODDS_CLASS element found."""
if self.driver is None:
return
while True:
try:
self.driver.switch_to.default_content()
iframes = self.driver.find_elements(By.TAG_NAME, "iframe")
for idx, iframe in enumerate(iframes):
try:
self.driver.switch_to.default_content()
self.driver.switch_to.frame(iframe)
if self.driver.find_elements(By.CLASS_NAME, BUY_ODDS_CLASS):
#print(f"✅ Odds found in iframe[{idx}]")
return
except Exception:
continue
time.sleep(0)
except Exception as exc:
print(f"frame scan error: {exc}")
time.sleep(0)
def get_latest_buy_odds(self):
if not self.driver:
return None
try:
elements = self.driver.find_elements(By.CLASS_NAME, BUY_ODDS_CLASS)
for el in elements:
txt = el.text.strip()
if txt:
return float(txt)
#print("⚠️ BUYオッズ取得できず → iframe再検索")
self.find_and_switch_to_odds_iframe()
except Exception:
self.find_and_switch_to_odds_iframe()
return None
def get_latest_sell_odds(self):
if not self.driver:
return None
try:
el = self.driver.find_element(By.XPATH, SELL_XPATH)
txt = el.text.strip()
return float(txt) if txt else None
except Exception:
self.find_and_switch_to_odds_iframe()
return None
def start_entry_timer(self):
"""Starts 40‑second cycle timer logic one minute in the future."""
if self.timer_active:
return
now = datetime.now()
self.entry_start_time = (now + timedelta(minutes=1)).replace(second=0, microsecond=0)
self.timer_active = True
self.label_overlay(f"Entry timer will start at {self.entry_start_time.strftime('%H:%M:%S')}")
threading.Thread(target=self._entry_timer_loop, daemon=True).start()
def _entry_timer_loop(self):
"""Runs forever updating allow flags based on time and odds."""
while self.timer_active:
time.sleep(0)
now = datetime.now()
if self.entry_start_time is None:
continue
delta = (now - self.entry_start_time).total_seconds()
if delta < 0:
self.allow_buy_click = self.allow_sell_click = False
self.label_overlay("⏳ Waiting for entry start …")
continue
# inside cycles 37is best
time_in_cycle = delta % CYCLE_SECONDS
if 37 <= time_in_cycle <= 40: # active 4‑second window each cycle
buy_odds = self.get_latest_buy_odds()
sell_odds = self.get_latest_sell_odds()
self.allow_buy_click = buy_odds is not None and buy_odds >= ODDS_THRESHOLD
self.allow_sell_click = sell_odds is not None and sell_odds >= ODDS_THRESHOLD
status = (
f"[{now.strftime('%H:%M:%S')}] Window 37‑40s BUY: {buy_odds} {'✅' if self.allow_buy_click else '❌'} | "
f"SELL: {sell_odds} {'✅' if self.allow_sell_click else '❌'}"
)
self.label_overlay(status)
else:
self.allow_buy_click = self.allow_sell_click = False
self.label_overlay(f"Wait cycle … {int(time_in_cycle)}s")
WS接続してエントリーするコード
天国草さんの2つ目のレイテンシー取引用ソースコードです。現在は有料noteになっています。
# Selenium ドライバ設定(port 9222)
chrome_options = Options()
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
driver = webdriver.Chrome(options=chrome_options)
# PyChrome 設定(port 9222 使用)
try:
browser = pychrome.Browser(url="http://127.0.0.1:9222")
tabs = browser.list_tab()
if tabs:
tab = tabs[0]
tab.start()
log_queue.put("PyChrome: 接続に成功しました (port 9222)")
else:
log_queue.put("PyChrome: 利用可能なタブが見つかりません")
tab = None
except Exception as e:
log_queue.put(f"PyChrome: 接続に失敗しました: {e}")
tab = None
# WebSocket イベントハンドラ
def on_ws_created(**kwargs):
log_queue.put(f"WS created: {kwargs}")
def on_ws_frame_sent(**kwargs):
response = kwargs.get("response", {})
payload = response.get("payloadData", "")
if "livechat" in payload and "disconnect_timeout" in payload:
log_queue.put("WS: livechat の disconnect_timeout を送信")
def on_ws_frame_received(**kwargs):
pass
# PyChrome 初期化
if tab:
try:
tab.Network.enable()
tab.Network.webSocketCreated = on_ws_created
tab.Network.webSocketFrameSent = on_ws_frame_sent
tab.Network.webSocketFrameReceived = on_ws_frame_received
log_queue.put("PyChrome: Network 監視を開始")
except Exception as e:
log_queue.put(f"PyChrome: Network 設定に失敗しました: {e}")
# BO 注文送信関数(CALL / PUT)
def send_bo_order(direction, price_open):
if not tab:
log_queue.put("BO: PyChrome 未接続のため送信をスキップ")
return
try:
# CALL(BUY)と PUT(SELL)で構造は同一(direction のみ差し替え)
if direction == "CALL":
order_data = {
"command": "open_option",
"plugin": "site",
"sum": "5000",
"tool_id": "4",
"direction": direction,
"price_open": str(price_open),
"timeframe_id": "15",
"option_kind": "1"
}
else:
order_data = {
"command": "open_option",
"plugin": "site",
"sum": "5000",
"tool_id": "4",
"direction": direction,
"price_open": str(price_open),
"timeframe_id": "15",
"option_kind": "1"
}
# JavaScript 経由で WebSocket 送信
js_code = f"""
if (typeof window.boWebSocket !== 'undefined' && window.boWebSocket.readyState === WebSocket.OPEN) {{
var orderData = {json.dumps(order_data)};
window.boWebSocket.send(JSON.stringify(orderData));
console.log('BO order sent:', orderData);
true;
}} else {{
console.log('BO WebSocket connection is not available');
false;
}}
"""
result = tab.Runtime.evaluate(expression=js_code)
if result.get('result', {}).get('value'):
log_queue.put(f"BO: 注文送信成功 - {direction} @ {price_open}")
log_queue.put(f"BO: 注文詳細 - {order_data}")
else:
log_queue.put("BO: 注文送信失敗(WebSocket 未接続)")
except Exception as e:
log_queue.put(f"BO: 注文送信エラー: {e}")
# WebSocket 接続設定(BO 用): 履歴データ取得 + hook_user_status / hook_options
js_websocket_setup = """
try {
if (typeof window.boWebSocket !== 'undefined') {
window.boWebSocket.close();
}
window.boWebSocket = new WebSocket("wss://bo.zentrader.com:27017/");
window.boWebSocket.onopen = () => {
const now = Math.floor(Date.now() / 1000);
const timeEnd = now;
const timeBegin = now - 500;
// 1) connect
window.boWebSocket.send(JSON.stringify({
command: "connect",
bo: "demo",
hash: "ここにログイン情報いれてください。",
platform: "mt4",
source: "site"
}));
console.log("connect sent");
// 2) get_user_data
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({ command: "get_user_data" }));
console.log("get_user_data sent");
}, 300);
// 3) hook_time
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({ command: "hook_time", enable: "true" }));
console.log("hook_time sent");
}, 600);
// 4) get_cfg_trade
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({ command: "get_cfg_trade", language: "ja" }));
console.log("get_cfg_trade sent");
}, 900);
// 5) get_user_settings
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({ command: "get_user_settings" }));
console.log("get_user_settings sent");
}, 1200);
// 6) hook_timeframes
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({
command: "hook_timeframes",
option_kind: "1",
tool_id: "4",
timeframe_id: "15",
bo: "demo",
enable: "true",
interval: "1000",
source: "site"
}));
console.log("hook_timeframes sent");
}, 1500);
// 7) get_quotes_history
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({
command: "get_quotes_history",
tool_id: "4",
time_begin: timeBegin.toString(),
time_end: timeEnd.toString(),
time_size: "S1",
source: "site",
request_id: "4_500"
}));
console.log("get_quotes_history sent", {
time_begin: new Date(timeBegin * 1000).toISOString(),
time_end: new Date(timeEnd * 1000).toISOString(),
duration_sec: timeEnd - timeBegin
});
}, 1800);
// 8) hook_user_status
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({ command: "hook_user_status", enable: "true" }));
console.log("hook_user_status sent");
}, 2100);
// 9) hook_options
setTimeout(() => {
window.boWebSocket.send(JSON.stringify({
command: "hook_options",
source: "site",
enable: "true",
interval: "100"
}));
console.log("hook_options sent");
}, 2400);
};
window.boWebSocket.onmessage = (event) => {
console.log("BO WS message:", event.data);
};
window.boWebSocket.onerror = (error) => {
console.log("BO WS error:", error);
};
window.boWebSocket.onclose = (event) => {
console.log("BO WS closed:", event.code, event.reason);
};
true;
} catch (error) {
console.log("BO WS setup error:", error);
false;
}
"""
# WebSocket でブラウザ価格を監視(port 9222)
def start_cdp_price_listener():
async def listen_price_from_cdp():
port = 9222
try:
response = requests.get(f'http://localhost:{port}/json')
targets = response.json()
ws_debugger_url = next(item['webSocketDebuggerUrl'] for item in targets if 'webSocketDebuggerUrl' in item)
except Exception as e:
log_queue.put(f"CDP: DevTools WebSocket URL 取得に失敗: {e}")
return
async with websockets.connect(ws_debugger_url) as websocket:
await websocket.send(json.dumps({"id": 1, "method": "Network.enable"}))
log_queue.put("CDP: DevTools WebSocket に接続。価格監視を開始 (port 9222)")
while True:
try:
message = await websocket.recv()
data = json.loads(message)
if data.get("method") == "Network.webSocketFrameReceived":
payload = data["params"]["response"].get("payloadData", "")
if payload.startswith("U,1,1,USDJPY.FXCM"):
match = re.search(r"USDJPY\\.FXCM\\|([\\d.]+)\\|", payload)
if match:
latest_price["ask"] = float(match.group(1))
except Exception as e:
log_queue.put(f"CDP: WebSocket 受信エラー: {e}")
break
asyncio.run(listen_price_from_cdp())
他の無料入手可能なレイテンシー取引ツール
レイテンシーアービトラージの取引ツールは他の方法でも無料入手できます。
上級者にはHFT Arbitrage Platformが人気です。以下のリンクでは無料の海賊版をダウンロードできます。
あるいはGitHubでも無料入手できます。ただMT4/MT5は約定速度が遅いため、メインで使われることは少ないです。あくまで勉強用のソースコードと考えましょう。
以下のクラックサイトでも無料入手できます。本来の価格は499ドルと高額ですが、クラックサイトなら良質なEAを無料で入手できます。

FX初心者にレイテンシーアービトラージをおすすめしない理由
レイテンシーアービトラージは利益率が非常に高いですが、以下の理由からFX初心者にはおすすめしていません。
- ブローカー・バイナリー業者から口座凍結・利益没収されやすい
- ロットを上げると口座凍結されるため、スケール化しにくい
- 大量のリアル口座を使い潰すため、新規口座登録などの事務的コストも大きい
- ブローカーごとのレイテンシーを計測する負担も大きい
- ノウハウの陳腐化が激しい(現在はレイテンシーアービトラージのノウハウを売るほうが稼げる)
- レートの遅い業者の情報は希少なので、他のトレーダーから情報を得られない
- 取引環境の良いA-bookブローカーでは利益を出せない
- リクイディティプロバイダー(LP)のラストルック(Last Look)仕様により、LPに不利なタイミングでは決済させてもらえない(約定が遅れやすい)
まず前提として、B-bookブローカーやバイナリー業者はトレーダーと利益相反の関係にあり、トレーダーが大きな利益を出すと、ブローカー・バイナリー業者側は損失を出すことになります。
そのためレイテンシーアービトラージのような利益率の高いトレードを行うトレーダーには、不正取引などの難癖をつけて口座凍結したり、利益没収してきたりすることがあります。
2015年から2020年あたりまでなら楽に稼げたかもしれませんが、2025年現在だとブローカー・バイナリー業者側の対策も進んでいます。初心者が運用しても、数日もしないうちに口座凍結されてしまうでしょう。
こういった裏事情はFX初心者の間では知られていません。現在はレイテンシー取引をするよりもノウハウを売った方が楽に稼げます。(ももんがさんのちゃっち倶楽部、ピル山めろ子さんのnote、うみさんのnoteなど)
レイテンシーアービトラージはレート配信の遅いブローカーを見つけたり、通貨ごとのレイテンシーを計測したり、口座凍結されないようパラメーターを調整したり、口座凍結された後に口座を作り直したりする必要があります。これらの作業も膨大な労力コストになります。
レイテンシー取引に精通しているFX上級者、あるいはブローカーの選定に時間をかけられる専業トレーダーなら続ける価値はあるでしょう。ただ兼業トレーダーやFX初心者が今から参入するのは労力的に割に合わないです。
今から高頻度売買で稼ぐなら、Stop Grid Traderによる指標トレードがおすすめ。こちらはレートのズレを利用しないため、A-bookブローカーでも利益が出せます。

A-bookブローカーの収益源は取引手数料で、トレーダーとはあまり利益相反しません。派手に稼いでもB-bookブローカーやバイナリー業者のように口座凍結・利益没収されるリスクが低いです。