GMO コイン API で FX 自動売買 Bot を作る完全ガイド
はじめに
GMO コインは外国為替 FX の API を公開しており、Python で自動売買 Bot を構築できます。この記事では、実際に Bot を開発・運用している筆者が、API の使い方を開発フロー順に解説します。
前提知識:
- Python の基本
- REST API の概念
- FX 取引の基本用語(ロング、ショート、SL/TP)
API の全体像
GMO コイン外国為替 FX の API は 3 種類あります:
| 種類 | 認証 | 用途 |
|---|---|---|
| Public REST API | 不要 | レート取得、ローソク足、マーケット状態 |
| Private REST API | HMAC-SHA256 | 注文、ポジション管理、口座情報 |
| Public WebSocket | 不要 | リアルタイム価格配信 |
ベース URL:
REST (Public): https://forex-api.coin.z.com/public/v1
REST (Private): https://forex-api.coin.z.com/private/v1
WebSocket: wss://forex-api.coin.z.com/ws/public/v1
注意: 暗号資産 API (api.coin.z.com) とはサブドメインが異なります。外国為替は forex-api です。
Step 1: 認証 (HMAC-SHA256)
Private API へのリクエストには 3 つのヘッダーが必要です:
import hmac
import hashlib
import time
import requests
API_KEY = os.environ["GMO_API_KEY"]
API_SECRET = os.environ["GMO_API_SECRET"]
def auth_headers(method: str, path: str, body: str = "") -> dict:
timestamp = str(int(time.time() * 1000))
text = timestamp + method + path + body
sign = hmac.new(
API_SECRET.encode(),
text.encode(),
hashlib.sha256,
).hexdigest()
return {
"API-KEY": API_KEY,
"API-TIMESTAMP": timestamp,
"API-SIGN": sign,
"Content-Type": "application/json",
}
ポイント:
- タイムスタンプはミリ秒(
time.time() * 1000) - 署名文字列:
タイムスタンプ + HTTPメソッド + パス + ボディ - API キーは環境変数から読む(コードに書かない)
Step 2: マーケットデータ取得
レート (Ticker)
def get_ticker(symbol: str = "USD_JPY") -> dict:
url = f"https://forex-api.coin.z.com/public/v1/ticker?symbol={symbol}"
resp = requests.get(url, timeout=10).json()
data = resp["data"][0]
return {"bid": float(data["bid"]), "ask": float(data["ask"])}
ローソク足 (Klines)
def get_candles(symbol: str, interval: str, date: str) -> list:
"""
interval: 1min, 5min, 15min, 30min, 1hour, 4hour, 1day
date: YYYYMMDD 形式
"""
url = (
f"https://forex-api.coin.z.com/public/v1/klines"
f"?symbol={symbol}&priceType=BID&interval={interval}&date={date}"
)
resp = requests.get(url, timeout=10).json()
return resp.get("data", [])
マーケット状態確認
def is_market_open() -> bool:
url = "https://forex-api.coin.z.com/public/v1/status"
resp = requests.get(url, timeout=5).json()
return resp.get("data", {}).get("status") == "OPEN"
取引前に必ず確認。メンテナンス中に注文を出すとエラーになります。
Step 3: 注文を出す
成行注文 (Market Order)
import json
def place_market_order(symbol: str, side: str, size: int) -> str:
path = "/order"
data = {
"symbol": symbol,
"side": side, # "BUY" or "SELL"
"executionType": "MARKET",
"size": str(size), # 文字列で渡す
}
body = json.dumps(data)
headers = auth_headers("POST", path, body)
url = f"https://forex-api.coin.z.com/private/v1{path}"
resp = requests.post(url, headers=headers, data=body, timeout=10).json()
if resp.get("status") == 0:
return str(resp.get("data", "")) # order_id
else:
raise Exception(f"Order failed: {resp.get('messages')}")
注意点:
sizeは文字列で渡す(数値だとエラー)- USD/JPY の最小注文単位は 100 通貨
- レスポンスに約定価格は含まれない。別途
/latestExecutionsで取得が必要
最小注文単位
| 通貨ペア | 最小単位 |
|---|---|
| USD/JPY, EUR/JPY 等 | 100 通貨 |
| TRY/JPY, ZAR/JPY, MXN/JPY | 100,000 通貨 |
約定価格の取得
成行注文のレスポンスには約定価格が入っていません。注文後に取得します:
def get_fill_price(order_id: str, retries: int = 3) -> float:
for i in range(retries):
path = f"/latestExecutions?orderId={order_id}"
headers = auth_headers("GET", path)
url = f"https://forex-api.coin.z.com/private/v1{path}"
resp = requests.get(url, headers=headers, timeout=10).json()
executions = resp.get("data", {}).get("list", [])
if executions:
# 部分約定の場合は加重平均
total_value = sum(float(e["price"]) * float(e["size"]) for e in executions)
total_size = sum(float(e["size"]) for e in executions)
return total_value / total_size if total_size > 0 else 0.0
time.sleep(0.3 * (i + 1)) # バックオフ
return 0.0
Step 4: ポジション管理
ポジション一覧の取得
def get_open_positions() -> list:
path = "/openPositions"
headers = auth_headers("GET", path)
url = f"https://forex-api.coin.z.com/private/v1{path}"
resp = requests.get(url, headers=headers, timeout=10).json()
return resp.get("data", {}).get("list", [])
レスポンスに含まれる主要フィールド: positionId, symbol, side, size, price(エントリー価格)
ポジション決済 (Market Close)
def close_position(symbol: str, side: str, position_id: int, size: int) -> str:
path = "/closeOrder"
# side は「反対売買」の方向
close_side = "SELL" if side == "BUY" else "BUY"
data = {
"symbol": symbol,
"side": close_side,
"executionType": "MARKET",
"settlePosition": [
{"positionId": position_id, "size": str(size)}
],
}
body = json.dumps(data)
headers = auth_headers("POST", path, body)
url = f"https://forex-api.coin.z.com/private/v1{path}"
resp = requests.post(url, headers=headers, data=body, timeout=10).json()
if resp.get("status") == 0:
return str(resp.get("data", ""))
raise Exception(f"Close failed: {resp.get('messages')}")
OCO 注文 (SL/TP 同時設定)
Bot の安全装置として最も重要な機能。SL と TP を同時に設定し、どちらか約定したらもう一方をキャンセルします:
def place_oco_close(symbol: str, side: str, position_id: int,
size: int, take_profit: float, stop_loss: float) -> str:
path = "/closeOrder"
close_side = "SELL" if side == "BUY" else "BUY"
precision = 3 # JPY ペアは小数点3桁
data = {
"symbol": symbol,
"side": close_side,
"executionType": "OCO",
"limitPrice": f"{take_profit:.{precision}f}",
"stopPrice": f"{stop_loss:.{precision}f}",
"settlePosition": [
{"positionId": position_id, "size": str(size)}
],
}
body = json.dumps(data)
headers = auth_headers("POST", path, body)
url = f"https://forex-api.coin.z.com/private/v1{path}"
resp = requests.post(url, headers=headers, data=body, timeout=10).json()
if resp.get("status") == 0:
return str(resp.get("data", ""))
raise Exception(f"OCO failed: {resp.get('messages')}")
OCO のメリット: Bot がクラッシュしても SL/TP は取引所側で執行されるため、損失が無制限に膨らむリスクを防げます。
Step 5: WebSocket でリアルタイム価格取得
ポーリングだと API レートリミットに引っかかるので、リアルタイム価格は WebSocket で受信します:
import aiohttp
import asyncio
async def stream_prices(symbol: str = "USD_JPY"):
url = "wss://forex-api.coin.z.com/ws/public/v1"
async with aiohttp.ClientSession() as session:
async with session.ws_connect(url, heartbeat=15) as ws:
await ws.send_json({
"command": "subscribe",
"channel": "ticker",
"symbol": symbol,
})
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
data = msg.json()
bid = float(data.get("bid", 0))
ask = float(data.get("ask", 0))
print(f"BID: {bid}, ASK: {ask}")
接続断の対策
WebSocket は切断されることがあります。3 層の対策を入れると安定します:
- PING/PONG:
heartbeat=15で aiohttp が自動送信 - 受信タイムアウト: 30 秒データが来なければ再接続
- リコネクトループ: 切断時に指数バックオフで再接続
Step 6: 口座情報の取得
def get_account() -> dict:
path = "/account/assets"
headers = auth_headers("GET", path)
url = f"https://forex-api.coin.z.com/private/v1{path}"
resp = requests.get(url, headers=headers, timeout=10).json()
d = resp.get("data", {})
return {
"balance": float(d.get("balance", 0)), # 入金額
"equity": float(d.get("equity", 0)), # 純資産額
"unrealized_pl": float(d.get("positionLossGain", 0)), # 未決済損益
"margin_used": float(d.get("margin", 0)), # 使用証拠金
"available": float(d.get("availableAmount", 0)), # 利用可能額
}
ポジションサイジング(リスク管理)に available を使って、1 トレードあたりのリスク額を算出できます。
実装上のハマりポイント
開発中に遭遇した問題をまとめます:
1. size は文字列
"size": 1000 ではなく "size": "1000" でないとエラー。数値フィールドなのに文字列で渡す必要がある。
2. 約定価格がレスポンスに入っていない
成行注文のレスポンスは order_id のみ。約定価格は /latestExecutions を別途コールして取得する。部分約定の場合は加重平均を自分で計算する。
3. JPY ペアの小数点は 3 桁
OCO 注文の limitPrice / stopPrice は、JPY ペアなら小数点 3 桁(例: 155.123)。USD 系ペアは 5 桁(例: 1.08567)。精度を間違えるとエラー。
4. 暗号資産 API とサブドメインが違う
暗号資産: api.coin.z.com、外国為替: forex-api.coin.z.com。間違えると 404。
5. メンテナンス時間
外国為替は土日・年末年始は取引停止。注文前に /status で OPEN を確認すること。
まとめ
GMO コインの外国為替 FX API は、個人開発者でも本格的な自動売買 Bot を構築できる十分な機能を備えています。特に OCO 注文による SL/TP のサーバーサイド管理は、Bot の安全性に直結する重要機能です。
API ドキュメント: GMO コイン API リファレンス
免責事項: この記事は技術的な解説を目的としており、投資助言ではありません。FX 取引にはリスクが伴います。投資判断はご自身の責任で行ってください。