成行注文が使えない取引所で自動売買botを組んだ話
暗号資産の自動売買botを作ろうとして、いきなり詰まった経験がある人向けの記事です。
「成行注文(market order)を投げたら弾かれた」「ドキュメント読んでも成行に対応してるのか判断できない」、そういう状態でコードだけ書き進めて後で気づく、というのを自分は何度かやった。
結論から書く。国内の取引所には成行注文に対応していない、もしくは実質使えないところがある。これを知らずに設計すると、注文ロジックを後から全部作り直すことになる。自分は指値注文で約定を待つ方式に切り替えて、約定監視とタイムアウト処理を別レイヤーで作ることで解決した。この記事ではその手順と、途中でハマった箇所を書く。
なぜ成行注文が使えない取引所があるのか
まず前提として、取引所APIのドキュメントには「market注文対応」と書いてあっても、実際に投げるとエラーが返ってくることがある。自分が最初に触った取引所がまさにこれで、APIリファレンスには order_type に market が選べると書いてあるのに、実際にリクエストすると invalid parameter が返ってきた。
サポートに問い合わせて分かったのは、板の薄い通貨ペアでは成行注文自体を無効化しているケースがあるということだった。流動性が低いと成行注文で価格が跳ねすぎるため、取引所側でリスク回避的にブロックしている。
もう一つ厄介なのが、成行注文自体はAPI上存在するのに、実際の約定価格が想定と大きくズレるケースだ。スプレッドが広い時間帯に成行を投げると、想定の1.5倍のスリッページが出たことがあった。これは月の損益に響く数字で、自分の場合は月間の想定利益の3割近くがスリッページで溶けた月もあった。
つまり「成行が使えない」には2パターンある。
- APIレベルでそもそも受け付けない
- 受け付けるが約定価格の劣化が大きすぎて実質使えない
どちらのパターンかを最初に見極めないと、対処法を間違える。
解決手順
ステップ1:対象取引所の注文種別を実際に叩いて確認する
ドキュメントを信用せず、テスト環境かごく少額の本番環境で実際にorder_typeを変えてリクエストを投げてみる。自分はこれをやらずにドキュメントだけ読んで実装を進め、後で全部書き直す羽目になった。1回のテストで20分もかからない作業だったので、最初にやるべきだったと今は思う。
エラーコードとメッセージは必ずログに残しておく。取引所によってエラーメッセージのフォーマットがバラバラで、後から見返す時に地味に困る。
ステップ2:指値注文+約定監視のロジックに切り替える
成行が使えない、または使い物にならないと判断したら、指値注文を出して約定を待つ方式にする。ここで必要になるのが以下の3つの状態管理だ。
- 注文が板に乗っているか(未約定)
- 一部約定しているか(部分約定)
- 約定または注文がキャンセルされたか
自分は最初これを1つのフラグで管理しようとして失敗した。部分約定の状態を考慮していなかったため、残数量の計算がずれてポジションサイズが狂った。状態は最低でも4種類(新規、部分約定、全約定、キャンセル)で管理する必要がある。
ステップ3:タイムアウトと再指値のロジックを入れる
指値注文は約定しないリスクがある。板から離れた価格で指値を出すと、いつまでも約定しないまま相場が動いてしまう。自分は当初、注文を出したら約定するまで待ち続ける実装にしていたが、これだと相場が大きく動いた時にエントリーチャンスを逃す。
対処として、一定時間(自分は30秒を目安にした)約定しなければ注文をキャンセルして、その時点の板の状態を見て指値価格を再計算し、再度注文を出すロジックを入れた。この再指値ロジックを入れてから、約定率がそれまでの体感で6割程度から9割前後まで改善した実感がある。
ステップ4:異常検知を必ず組み込む
これは成行・指値に関わらずだが、APIのレスポンスが想定外の形式で返ってきたり、注文がタイムアウトしたまま応答が来ないケースがある。自分のbotは一度、キャンセルリクエストへの応答が来ないまま二重に注文を出してしまい、想定の2倍のポジションを持ってしまったことがある。金額にして数万円分の含み損を余計に抱えた。
この経験から、注文状態の確認は必ずポーリングで二重チェックし、タイムアウト時は強制的に注文照会APIを叩いて実際の状態を取得するようにした。
コード例(Python、簡略版)
実際の取引所固有のパラメータは省略し、ロジックの骨組みだけ示す。
import time
import requests
ORDER_TIMEOUT_SEC = 30
def place_limit_order(client, pair, side, price, amount):
resp = client.create_order(
pair=pair,
order_type="limit",
side=side,
price=price,
amount=amount,
)
return resp["order_id"]
def wait_for_fill(client, order_id, timeout=ORDER_TIMEOUT_SEC):
start = time.time()
while time.time() - start < timeout:
status = client.get_order_status(order_id)
if status["state"] == "filled":
return status
if status["state"] == "partially_filled":
# 部分約定分は記録しておき、残量だけ再計算する
pass
time.sleep(2)
return None
def execute_with_retry(client, pair, side, target_price, amount, max_retry=3):
remaining = amount
for attempt in range(max_retry):
order_id = place_limit_order(client, pair, side, target_price, remaining)
result = wait_for_fill(client, order_id)
if result and result["state"] == "filled":
return result
# タイムアウトまたは未約定ならキャンセルして板を見直す
client.cancel_order(order_id)
order_book = client.get_order_book(pair)
target_price = recalculate_price(order_book, side)
remaining = amount - get_filled_amount(order_id, client)
raise TimeoutError("約定に至らず再指値の上限に達した")
ここでのポイントは、約定待ちのポーリング間隔(例では2秒)を取引所のレートリミットと相談しながら決めることだ。bitFlyerなどはリクエスト回数の制限が厳しく、ポーリング間隔を詰めすぎるとリミットに引っかかる。自分は最初1秒間隔でポーリングしていてリミットオーバーのエラーを何度も食らった。
落とし穴と対処
一つ目は、取引所ごとにキャンセル済み注文のレスポンス形式が違う点だ。ある取引所は既にキャンセル済みの注文を再度キャンセルしようとするとエラーを返すが、別の取引所は何も起きずに200が返る。この違いを吸収する層を作らないと、エラーハンドリングが取引所の数だけ分岐する羽目になる。
二つ目は、注文IDの扱いだ。取引所によっては注文IDが0や空文字で返ってくることがあり、これを「注文失敗」として扱うロジックを組んでいると誤判定してしまう。実際に自分はこれで一度、正常に発注できているのに失敗と誤認してキャンセル処理を走らせてしまったことがある。IDの仕様は取引所ごとにドキュメントの隅まで確認するしかない。
三つ目は、部分約定の扱いだ。部分約定を考慮せずにポジション管理をすると、実際の保有量と管理上の数値がズレる。自分のケースでは、このズレが原因で損切りラインの計算が狂い、想定より大きい損失を出したことがある。金額としては1回あたり数千円だが、これが積み重なると月次の収支に無視できない影響が出る。
関連ツール
この手の「取引所ごとのクセを吸収する設計」を毎回一から組むのは正直しんどい作業だった。自分はこの経験を元に、複数取引所のAPI連携パターンと注文状態管理、異常検知のロジックをまとめた実装学習キットを作った。
Zaifのorder_id=0仕様やbitFlyerのレートリミット回避など、今回書いたような個別の詰まりどころをサンプルコード付きでまとめている。FastAPIとReact Nativeで実装しているので、自分のbotに組み込む形でも、設計の参考としてでも使える内容にしている。
まとめ
成行注文が使えない取引所は珍しくなく、事前確認をせずに実装を進めると手戻りが大きい。指値注文+約定監視+タイムアウト再指値という構成にすれば、成行が使えない環境でも実用的な約定率は確保できるという実感がある。部分約定の状態管理と異常検知だけは、最初から設計に組み込んでおいた方がいい。
この記事で触れている自動売買の仕組みは、自社プロダクトの AutoTrader として運用しています。対応取引所・プラン・稼働状況は こちら にまとめてあります。
著者:ぽん(@pon_freelance)
C言語実務23年、組み込み/制御系。
副業で技術記事販売と自作ツール販売をやっている。
書いているもの:
- AutoTrader 実装学習キット - FastAPI × React Native で作る外部 API 連携アプリ実装学習キット
(その他:(なし))
開発の裏側を購読できます — AutoTrader のリリースごとに「何を・なぜ・どう変えたか」を 2,000〜4,000 字で書き残しています。バグの原因、取引所 API 変更への追従、設計判断のトレードオフまで。
→ AutoTrader開発ログ(月500円・いつでも解約可)