FX・日本株・米国株の3エンジンを24時間自動運用していると、外部APIの障害は「起きるかどうか」ではなく「いつ起きるか」の問題になる。GMO Coin API、Kabustation API、moomoo/Alpaca API——どれも稼働率100%ではない。
問題は、API障害が発生したときにシステムがどう振る舞うかだ。例外で落ちるのか、リトライして復旧するのか、復旧不能ならフェールセーフで安全に停止するのか。
本記事では、FX・JS(日本株)のday系8サービス(launchdでスケジュール実行されるバッチ処理群)を対象に構築した、API障害自動復旧システムの設計と実装を記録する。
自動売買における障害の種類
外部APIとの通信で発生する障害は大きく3種類に分類できる。
一時的障害(Transient)
- ネットワークタイムアウト
- HTTP 500/502/503(サーバー側の一時的なエラー)
- レート制限(429 Too Many Requests)
これらは時間が経てば自動的に解消する。リトライすれば成功する可能性が高い。
半永続的障害(Semi-Persistent)
- APIメンテナンス(事前告知あり、数時間継続)
- APIキーの一時無効化
- データ遅延(マーケットデータの更新が止まる)
数時間〜数日で解消するが、リトライだけでは対処できない。代替データソースへの切り替えやフォールバック動作が必要になる。
永続的障害(Permanent)
- APIバージョンの廃止
- アカウントの停止
- エンドポイントの仕様変更
人的介入が必須。自動復旧の対象外だが、検知と通知は自動化する。
8サービス統合の設計
FX・JSエンジンで毎日スケジュール実行されるバッチサービスを「day系サービス」と呼んでいる。バックテストデータの更新、シグナル分析レポート生成、パフォーマンス集計など8つのサービスがlaunchd(macOSのタスクスケジューラ)で管理されている。
統合前の問題
各サービスが個別にエラーハンドリングを実装しており、以下の問題があった。
- リトライロジックの重複: 8サービスそれぞれに
try-except+time.sleepのリトライが散在 - 障害通知の氾濫: 1つのAPI障害で8サービスから別々にSlack通知が飛ぶ
- 復旧判定の不在: リトライ成功後に「復旧した」ことを確認する仕組みがない
- フォールバック不統一: サービスによっては例外で即死し、残りのサービスも連鎖的に影響を受ける
統合後の設計
[launchd scheduler]
├─ Service A: BT Data Update
├─ Service B: Signal Analysis
├─ Service C: Performance Report
├─ ...(計8サービス)
│
└─ 共通レイヤー: APIHealthManager
├─ リトライ制御(指数バックオフ)
├─ サーキットブレーカー
├─ フォールバック制御
├─ 障害集約通知(ErrorAggregator)
└─ 復旧検知・通知
指数バックオフによるリトライ
一時的障害に対しては、指数バックオフ(Exponential Backoff)でリトライする。これはリトライ間隔を1秒→2秒→4秒→8秒と指数的に伸ばしていく手法で、サーバー側の負荷を増やさずに復旧を待つ。
def retry_with_backoff(
func,
max_retries: int = 5,
base_delay: float = 1.0,
max_delay: float = 60.0,
retryable_exceptions: tuple = (ConnectionError, TimeoutError),
):
for attempt in range(max_retries):
try:
return func()
except retryable_exceptions as e:
if attempt == max_retries - 1:
raise
delay = min(base_delay * (2 ** attempt), max_delay)
# ジッター(ランダムな揺らぎ)を加えて同時リトライの衝突を回避
jittered_delay = delay * (0.5 + random.random() * 0.5)
time.sleep(jittered_delay)
ジッター(Jitter)を加えるのが重要だ。複数サービスが同時にリトライすると、APIサーバーに対して「リトライの嵐」を引き起こす可能性がある。ランダムな揺らぎを加えることで、リトライのタイミングを分散させる。
サーキットブレーカー
一定回数以上の連続失敗が発生したら、そのAPIへのアクセスを一時的に「遮断」する。サーキットブレーカー(Circuit Breaker)と呼ばれるパターンで、障害中のAPIに無意味なリクエストを送り続けることを防ぐ。
状態遷移:
CLOSED(正常) → 連続5回失敗 → OPEN(遮断)
OPEN(遮断) → 5分経過 → HALF_OPEN(試行)
HALF_OPEN → 成功 → CLOSED(正常に戻る)
HALF_OPEN → 失敗 → OPEN(再遮断)
OPEN状態ではAPIを呼び出さず、即座にフォールバック動作に切り替わる。5分後に1回だけ「試行」し、成功すれば通常動作に戻る。
障害集約通知
1つのAPI障害が8サービスに影響する場合、8通の通知を送る代わりに、ErrorAggregator(5分窓で集約する仕組み)を使って1通にまとめる。
❌ [API-RECOVERY] GMO Coin API障害検知
影響サービス: 8/8
最初の検知: 07:01:23 JST
エラー: ConnectionTimeout (api.coin.z.com)
状態: リトライ中(3/5回目)
復旧時には「復旧通知」も自動送信する。
✅ [API-RECOVERY] GMO Coin API復旧確認
障害期間: 07:01 - 07:08 JST(7分)
影響トレード: 0件(障害中にシグナルなし)
フェールセーフの設計原則
自動復旧が不可能な場合、システムは「安全な状態」に遷移する必要がある。
「安全な状態」の定義
自動売買における安全な状態とは以下を指す。
- 新規エントリーを停止する: 価格データが信頼できない状態でポジションを取らない
- 既存ポジションは保持する: API障害だけでは決済しない(相場は動いていない可能性がある)
- SL/TPは有効なまま: ブローカー側で設定済みのストップロス/テイクプロフィットは、API障害に関係なく機能する
- 障害解消後に自動復帰する: 人手による再起動は不要
「全ポジションを緊急決済する」は安全な対応に見えるが、実際には不利な価格で大量決済してしまうリスクがある。市場が開いていてSL/TPが機能している限り、「何もしない」が最善の選択肢であることが多い。
学んだこと
1. APIは必ず壊れるという前提で設計する
外部APIの稼働率が99.9%でも、年間8.7時間のダウンタイムがある。3つのAPIを使っていれば、年間26時間はどこかが落ちている計算だ。障害対応はオプションではなくシステムの必須機能だ。
2. リトライとフォールバックは別レイヤーで管理する
各サービスにリトライロジックを書くと保守が不可能になる。共通レイヤーでリトライ・サーキットブレーカー・フォールバックを管理し、個別サービスは「APIを呼ぶ」だけにする。
3. 通知の集約は運用の生命線
8サービスから8通のエラー通知が飛ぶと、本当に見るべき情報が埋もれる。障害通知は「何が起きたか」「影響範囲」「現在のステータス」を1通にまとめることで、深夜の障害でも即座に状況を把握できる。
まとめ
API障害自動復旧の設計で重要なのは以下の3点だ。
- 指数バックオフ + ジッター: 一時的障害は自動リトライで解消。ジッターでリトライの衝突を回避
- サーキットブレーカー: 障害中のAPIへの無意味なリクエストを遮断し、フォールバックに切り替え
- フェールセーフ = 「何もしない」: 新規エントリー停止 + 既存ポジション保持が最も安全な状態
自動売買システムのインフラは地味な仕事だが、ここが壊れると取引ロジックがどれだけ優秀でも意味がない。障害対応の自動化は、利益を生むための「守りの投資」だ。