Skip to content
Go back

API障害で自動売買を止めない:8サービス統合の自動復旧システム

Edit page

FX・日本株・米国株の3エンジンを24時間自動運用していると、外部APIの障害は「起きるかどうか」ではなく「いつ起きるか」の問題になる。GMO Coin API、Kabustation API、moomoo/Alpaca API——どれも稼働率100%ではない。

問題は、API障害が発生したときにシステムがどう振る舞うかだ。例外で落ちるのか、リトライして復旧するのか、復旧不能ならフェールセーフで安全に停止するのか。

本記事では、FX・JS(日本株)のday系8サービス(launchdでスケジュール実行されるバッチ処理群)を対象に構築した、API障害自動復旧システムの設計と実装を記録する。


自動売買における障害の種類

外部APIとの通信で発生する障害は大きく3種類に分類できる。

一時的障害(Transient)

これらは時間が経てば自動的に解消する。リトライすれば成功する可能性が高い。

半永続的障害(Semi-Persistent)

数時間〜数日で解消するが、リトライだけでは対処できない。代替データソースへの切り替えやフォールバック動作が必要になる。

永続的障害(Permanent)

人的介入が必須。自動復旧の対象外だが、検知と通知は自動化する。


8サービス統合の設計

FX・JSエンジンで毎日スケジュール実行されるバッチサービスを「day系サービス」と呼んでいる。バックテストデータの更新、シグナル分析レポート生成、パフォーマンス集計など8つのサービスがlaunchd(macOSのタスクスケジューラ)で管理されている。

統合前の問題

各サービスが個別にエラーハンドリングを実装しており、以下の問題があった。

  1. リトライロジックの重複: 8サービスそれぞれに try-except + time.sleep のリトライが散在
  2. 障害通知の氾濫: 1つのAPI障害で8サービスから別々にSlack通知が飛ぶ
  3. 復旧判定の不在: リトライ成功後に「復旧した」ことを確認する仕組みがない
  4. フォールバック不統一: サービスによっては例外で即死し、残りのサービスも連鎖的に影響を受ける

統合後の設計

[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件(障害中にシグナルなし)

フェールセーフの設計原則

自動復旧が不可能な場合、システムは「安全な状態」に遷移する必要がある。

「安全な状態」の定義

自動売買における安全な状態とは以下を指す。

  1. 新規エントリーを停止する: 価格データが信頼できない状態でポジションを取らない
  2. 既存ポジションは保持する: API障害だけでは決済しない(相場は動いていない可能性がある)
  3. SL/TPは有効なまま: ブローカー側で設定済みのストップロス/テイクプロフィットは、API障害に関係なく機能する
  4. 障害解消後に自動復帰する: 人手による再起動は不要

「全ポジションを緊急決済する」は安全な対応に見えるが、実際には不利な価格で大量決済してしまうリスクがある。市場が開いていてSL/TPが機能している限り、「何もしない」が最善の選択肢であることが多い。


学んだこと

1. APIは必ず壊れるという前提で設計する

外部APIの稼働率が99.9%でも、年間8.7時間のダウンタイムがある。3つのAPIを使っていれば、年間26時間はどこかが落ちている計算だ。障害対応はオプションではなくシステムの必須機能だ。

2. リトライとフォールバックは別レイヤーで管理する

各サービスにリトライロジックを書くと保守が不可能になる。共通レイヤーでリトライ・サーキットブレーカー・フォールバックを管理し、個別サービスは「APIを呼ぶ」だけにする。

3. 通知の集約は運用の生命線

8サービスから8通のエラー通知が飛ぶと、本当に見るべき情報が埋もれる。障害通知は「何が起きたか」「影響範囲」「現在のステータス」を1通にまとめることで、深夜の障害でも即座に状況を把握できる。


まとめ

API障害自動復旧の設計で重要なのは以下の3点だ。

  1. 指数バックオフ + ジッター: 一時的障害は自動リトライで解消。ジッターでリトライの衝突を回避
  2. サーキットブレーカー: 障害中のAPIへの無意味なリクエストを遮断し、フォールバックに切り替え
  3. フェールセーフ = 「何もしない」: 新規エントリー停止 + 既存ポジション保持が最も安全な状態

自動売買システムのインフラは地味な仕事だが、ここが壊れると取引ロジックがどれだけ優秀でも意味がない。障害対応の自動化は、利益を生むための「守りの投資」だ。


Edit page
Share this post on:

Previous Post
コード重複3,700行を52%削減:3モード統合リファクタリングの実践
Next Post
複利再投資を自動提案するシステム:毎日23時にSlackで最適ロットを通知