AI活用

Claude CodeでBitget自動売買ボットを作る完全手順【2026年版】ファンディングレート裁定&ペアトレード

⚡ Claude Code × Bitget API

Claude CodeでBitget自動売買ボットを作る
完全手順【2026年版】

日本人利用可・TradingView不要・Pythonで24時間稼働。
ファンディングレート裁定 & BTC/ETHペアトレードの実動コードを完全公開。

日本人利用OK
ccxt ライブラリ使用
Claude Code Desktop前提

Bitgetアカウントを開設する →

この記事が解説する戦略

当サイトの既存記事(TradingViewシリーズ)では「TradingViewのPineスクリプトでシグナルを生成→Webhookで自動注文」という手法を解説しました。この方式はTradingViewのチャートを常時開いておく必要があり、VPSで24時間稼働させるのが難しいという制約があります。

本記事ではTradingViewをまったく使わない、PythonがBitget APIを直接叩いて動く自律型ボットを解説します。

🌏

なぜBitget? — 日本人が使える大手取引所
Bitgetは現時点で日本居住者が利用できる数少ない大手デリバティブ取引所のひとつです。取引量・流動性ともに世界トップ5クラスで、USDT無期限先物でのファンディングレート裁定に十分な銘柄数が揃っています。
TradingView方式(既存記事) 本記事(Python直接方式)
取引所 Bitget Bitget(同じ取引所・別アプローチ)
シグナル源 TradingView Pineスクリプト Pythonが直接計算
動作方式 Webhook → GAS中継 → API Python → Bitget API 直接
24時間稼働 TradingViewを開いておく必要あり VPS / クラウドで完全自律
戦略 MAクロス・RSIシグナル ファンディングレート裁定・ペアトレード
⚠️

免責事項
本記事で紹介するコードは教育目的です。仮想通貨取引はリスクを伴います。実際に運用する際は必ず少額から始め、自己責任で行ってください。

STEP 1前提条件と環境構築

必要なもの

  • Claude Code Desktop版(インストール済み)
  • Python 3.10 以上
  • Bitgetアカウント(無料・日本人OK)
  • 最低資金:ファンディングレート戦略は200 USDT〜、ペアトレードは500 USDT〜
🟢

Bitget — 日本人が使えるデリバティブ取引所

USDT無期限先物・スポット同時注文対応。API利用無料。日本語サポートあり。

Bitgetで無料登録 →

プロジェクト構成

bitget_bot/
├── .env # APIキー(Gitにコミットしない)
├── requirements.txt
├── config.py # 共通設定・取引所インスタンス
├── fr_screener.py # 高FRスクリーナー(単独実行可)
├── funding_bot.py # 戦略①:ファンディングレート裁定
└── pairs_bot.py # 戦略②:ペアトレード

ライブラリのインストール

本記事ではBitget・Bybit・OKXなど主要取引所を統一APIで扱える ccxt ライブラリを使用します。Bitget公式SDKよりも更新頻度が高く、V2 APIに完全対応しています。

requirements.txt

ccxt==4.4.0
python-dotenv==1.0.1
pandas==2.2.2
numpy==1.26.4
statsmodels==0.14.2
pip install ccxt python-dotenv pandas numpy statsmodels

.env ファイル(APIキー管理)

🔑

Bitgetは「パスフレーズ」が必要 — Bybitとの最大の違い
Bitget APIは APIキー・APIシークレット・パスフレーズ の3つを要求します。APIキー作成時に設定したパスフレーズは後から確認できないので、必ず安全な場所にメモしておいてください。
.env

BITGET_API_KEY=your_api_key_here
BITGET_API_SECRET=your_api_secret_here
BITGET_PASSPHRASE=your_passphrase_here   # ← Bitget固有の第3の認証情報

共通設定ファイル

config.py

import os
import ccxt
from dotenv import load_dotenv

load_dotenv()

# ── 認証情報 ─────────────────────────────────────────
API_KEY    = os.getenv("BITGET_API_KEY",    "")
API_SECRET = os.getenv("BITGET_API_SECRET", "")
PASSPHRASE = os.getenv("BITGET_PASSPHRASE", "")

_creds = {
    "apiKey":   API_KEY,
    "secret":   API_SECRET,
    "password": PASSPHRASE,   # ccxtでは password = Bitgetのパスフレーズ
}

# ── 取引所インスタンス ──────────────────────────────
# USDT無期限先物(swap)用
futures_ex = ccxt.bitget({
    **_creds,
    "options": {"defaultType": "swap"},
})

# スポット用(ファンディングレート裁定のヘッジに使用)
spot_ex = ccxt.bitget({
    **_creds,
    "options": {"defaultType": "spot"},
})

# マーケット情報を事前読み込み(ロットサイズ取得に必要)
futures_ex.load_markets()
spot_ex.load_markets()

# ── リスク設定(共通) ───────────────────────────────
MAX_DAILY_LOSS_USDT = 50   # 1日の最大損失上限(USDT)
TRADE_USDT          = 100  # 1取引あたりの投入額(USDT)

STEP 2Bitget APIキーの取得と設定

APIキー作成手順

  1. bitget.com にログイン後、右上アイコン →「API管理」
  2. 「APIキーを作成」をクリック
  3. パスフレーズ(自分で決める文字列)を入力 — 後から変更不可のため必ずメモ
  4. 権限:「取引」のみチェック(出金権限は絶対に付与しない)
  5. IPホワイトリスト:VPSのIPアドレスを入力(セキュリティ強化に推奨)
  6. 生成された APIキーAPIシークレットパスフレーズ.env に保存
🔐

APIシークレットとパスフレーズは作成時の1回しか表示されません
必ずすぐにコピーして安全な場所に保存してください。.envファイルは絶対にGitHubにアップロードしないこと。.gitignoreに追加必須。出金権限は絶対に付与しないでください。

接続テスト

接続確認コード(config.py の末尾に追加して実行)

if __name__ == "__main__":
    try:
        # 先物残高確認
        balance = futures_ex.fetch_balance()
        usdt = balance.get("USDT", {}).get("free", 0)
        print(f"✅ 接続成功 | 先物USDT残高: {usdt:.2f}")

        # スポット残高確認
        s_balance = spot_ex.fetch_balance()
        s_usdt = s_balance.get("USDT", {}).get("free", 0)
        print(f"✅ スポットUSDT残高: {s_usdt:.2f}")
    except Exception as e:
        print(f"❌ 接続失敗: {e}")
        print("→ .env の API_KEY / API_SECRET / PASSPHRASE を確認してください")
python config.py

戦略①ファンディングレート裁定ボット

💰 ファンディングレート裁定とは

無期限先物のファンディングレートが高いとき、先物でショート(レートを受け取る側)しながらスポットでロングしてデルタニュートラルを維持。価格変動リスクをヘッジしながらファンディングレートの収益だけを取り続ける戦略。

仕組みと収益計算

① FRが高い銘柄を検出

② 先物:ショート
+
③ スポット:ロング

④ 8時間ごとにFR受取

⑤ FR低下で決済

収益性の目安(100 USDT投入時、Bitget Taker手数料 0.06%):

ファンディングレート 年換算(単純計算) 手数料後(Taker 0.06%×4回) 判定
0.02% / 8h ~21.9% / 年 ▲ 手数料負け ✗ スキップ
0.05% / 8h ~54.7% / 年 損益ギリギリ〜微益 △ 要確認
0.10% / 8h ~109.5% / 年 +約85% ◎ 推奨
0.20% / 8h ~219% / 年 +約195% ◎ 積極的に狙う
ℹ️

本ボットの最低閾値は 0.001(0.1%/8h)に設定しています
BitgetのTaker手数料は0.06%(往復0.12%×先物スポット=計0.24%)のため、Bybitより高めの閾値を設定します。ファンディングタイム(JST)は 01:00 / 09:00 / 17:00 の1日3回です。アルトコインに高FR銘柄が多く出現します。

ステップ①:高FRスクリーナー(単体で使える)

fr_screener.py

from config import futures_ex

def get_top_funding_rates(min_rate: float = 0.001, min_vol_usdt: float = 1_000_000, top_n: int = 10):
    """高ファンディングレートのUSDT無期限先物銘柄をスクリーニング"""

    # ① 全銘柄のファンディングレートを一括取得
    funding_rates = futures_ex.fetch_funding_rates()

    # ② 全ティッカー(価格・出来高)を一括取得
    tickers = futures_ex.fetch_tickers()

    results = []
    for symbol, fr_data in funding_rates.items():
        # USDT無期限のみ(例:'BTC/USDT:USDT')
        if not symbol.endswith("/USDT:USDT"):
            continue

        fr  = fr_data.get("fundingRate") or 0.0
        ticker     = tickers.get(symbol, {})
        last_price = ticker.get("last")  or 0.0
        volume     = ticker.get("quoteVolume") or 0.0

        # 最低FR・最低流動性フィルター
        if fr >= min_rate and volume >= min_vol_usdt:
            annual = fr * 3 * 365 * 100   # 年換算(%)
            results.append({
                "symbol":     symbol,
                "fundingRate": fr,
                "annual_pct": annual,
                "lastPrice":  last_price,
                "vol_M":      volume / 1_000_000,
            })

    # FR降順でソート
    results.sort(key=lambda x: x["fundingRate"], reverse=True)

    print(f"\n{'='*68}")
    print(f"{'銘柄':<22} {'FR/8h':>8} {'年換算':>10} {'価格':>12} {'出来高(M$)':>12}")
    print(f"{'='*68}")
    for r in results[:top_n]:
        print(
            f"{r['symbol']:<22} {r['fundingRate']*100:>7.4f}% "
            f"{r['annual_pct']:>9.1f}% {r['lastPrice']:>12.4f} {r['vol_M']:>11.1f}M"
        )
    print(f"{'='*68}\n")
    return results

if __name__ == "__main__":
    get_top_funding_rates()

ステップ②:ファンディングレート裁定ボット(フルコード)

funding_bot.py

import time
import logging
from config import futures_ex, spot_ex, TRADE_USDT, MAX_DAILY_LOSS_USDT

# ── 設定 ─────────────────────────────────────────────
MIN_FUNDING_RATE  = 0.001      # 最低ファンディングレート(0.1%/8h)
EXIT_FUNDING_RATE = 0.0005     # この値を下回ったら撤退
MIN_VOLUME_USDT   = 1_000_000  # 24h出来高 1M USDT以上
CHECK_INTERVAL    = 3600       # チェック間隔(秒)= 1時間
MAX_POSITIONS     = 2          # 同時保有最大銘柄数

# ── ロギング ─────────────────────────────────────────
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("funding_bot.log", encoding="utf-8"),
        logging.StreamHandler(),
    ],
)
logger = logging.getLogger(__name__)

# ポジション管理辞書: {symbol: 数量}
open_positions: dict[str, float] = {}
_start_balance: float = 0.0


def get_usdt_balance() -> float:
    """先物口座のUSDT残高を取得"""
    b = futures_ex.fetch_balance()
    return float(b.get("USDT", {}).get("free", 0))


def check_daily_loss() -> bool:
    """開始残高との差分が日次上限を超えていないか確認"""
    current = get_usdt_balance()
    loss = current - _start_balance
    if loss < -MAX_DAILY_LOSS_USDT:
        logger.warning(f"⚠️ 日次損失上限到達(損益: {loss:.2f} USDT)→ ボット停止")
        return False
    return True


def calc_qty(symbol: str, price: float) -> float:
    """USDT額から注文数量を計算し、取引所のロットサイズに丸める"""
    raw = TRADE_USDT / price
    return float(futures_ex.amount_to_precision(symbol, raw))


def get_high_fr_symbols() -> list:
    """高FRかつ十分な流動性のあるUSDT無期限銘柄を返す"""
    funding_rates = futures_ex.fetch_funding_rates()
    tickers       = futures_ex.fetch_tickers()

    result = []
    for symbol, fr_data in funding_rates.items():
        if not symbol.endswith("/USDT:USDT"):
            continue
        fr     = fr_data.get("fundingRate") or 0.0
        ticker = tickers.get(symbol, {})
        vol    = ticker.get("quoteVolume") or 0.0
        price  = ticker.get("last")        or 0.0

        if fr >= MIN_FUNDING_RATE and vol >= MIN_VOLUME_USDT and price > 0:
            result.append({"symbol": symbol, "fundingRate": fr, "lastPrice": price})

    result.sort(key=lambda x: x["fundingRate"], reverse=True)
    return result


def get_current_fr(symbol: str) -> float:
    """指定銘柄の現在のファンディングレートを取得"""
    data = futures_ex.fetch_funding_rate(symbol)
    return data.get("fundingRate") or 0.0


def open_position(symbol: str, price: float):
    """
    先物ショート(FRを受け取る) + スポットロング(価格変動ヘッジ)
    Bitget: side='sell', tradeSide='open' → 先物ショート新規
    """
    qty = calc_qty(symbol, price)
    # スポット用シンボル: 'BTC/USDT:USDT' → 'BTC/USDT'
    base_coin   = symbol.split("/")[0]       # 'BTC'
    spot_symbol = f"{base_coin}/USDT"

    # ① 先物ショート新規
    futures_ex.create_order(
        symbol, "market", "sell", qty,
        params={"tradeSide": "open"}
    )

    # ② スポットロング(同一銘柄でデルタニュートラル)
    spot_qty = float(spot_ex.amount_to_precision(spot_symbol, qty))
    spot_ex.create_order(spot_symbol, "market", "buy", spot_qty)

    open_positions[symbol] = qty
    logger.info(f"[OPEN] {symbol} | 先物SHORT + スポットLONG | qty={qty} | 価格≈{price:.4f}")


def close_position(symbol: str):
    """先物ショート決済 + スポット売却"""
    qty = open_positions.get(symbol, 0)
    if qty <= 0:
        return

    base_coin   = symbol.split("/")[0]
    spot_symbol = f"{base_coin}/USDT"

    # ① 先物ショートを決済(buy + close)
    futures_ex.create_order(
        symbol, "market", "buy", qty,
        params={"tradeSide": "close"}
    )

    # ② スポット売却(実際の保有残高を取得してから)
    s_balance = spot_ex.fetch_balance()
    spot_free  = float(s_balance.get(base_coin, {}).get("free", 0))

    if spot_free > 0:
        sell_qty = float(spot_ex.amount_to_precision(spot_symbol, spot_free))
        if float(sell_qty) > 0:
            spot_ex.create_order(spot_symbol, "market", "sell", sell_qty)

    open_positions.pop(symbol, None)
    logger.info(f"[CLOSE] {symbol} | ポジション全決済 | qty={qty}")


def run():
    """メインループ"""
    global _start_balance
    _start_balance = get_usdt_balance()
    logger.info(f"🚀 ファンディングレートボット起動")
    logger.info(f"   開始残高={_start_balance:.2f} USDT | 最低FR={MIN_FUNDING_RATE*100:.3f}% | 投入={TRADE_USDT}USDT")

    while True:
        try:
            if not check_daily_loss():
                break

            active = set(open_positions.keys())
            logger.info(f"保有中: {active if active else 'なし'}")

            # 既存ポジションのFRを確認 → 低下していたら撤退
            for sym in list(active):
                current_fr = get_current_fr(sym)
                if current_fr < EXIT_FUNDING_RATE:
                    logger.info(f"FR低下({current_fr*100:.4f}%)→ {sym} 撤退")
                    close_position(sym)
                else:
                    logger.info(f"保持中: {sym} | FR={current_fr*100:.4f}%")
                time.sleep(0.3)

            # 新規エントリー(最大ポジション数に達していない場合)
            if len(open_positions) < MAX_POSITIONS:
                high_fr = get_high_fr_symbols()
                logger.info(
                    f"📊 高FR銘柄 上位3件: "
                    f"{[f\"{x['symbol']} {x['fundingRate']*100:.4f}%\" for x in high_fr[:3]]}"
                )
                for coin in high_fr:
                    sym = coin["symbol"]
                    if sym not in open_positions:
                        open_position(sym, coin["lastPrice"])
                        if len(open_positions) >= MAX_POSITIONS:
                            break
                    time.sleep(0.5)

            logger.info(f"次のチェックまで {CHECK_INTERVAL}秒 待機...\n")
            time.sleep(CHECK_INTERVAL)

        except Exception as e:
            logger.error(f"エラー発生: {e} | 60秒後に再試行")
            time.sleep(60)


if __name__ == "__main__":
    run()
💡

Bitget の tradeSide パラメータ
Bitgetでは注文の方向を side(buy/sell)と tradeSide(open/close)の組み合わせで指定します。先物ショートを新規に建てるときは side="sell" + tradeSide="open"、決済するときは side="buy" + tradeSide="close" です。ccxt経由では params={"tradeSide": "open"} として渡します。

戦略②BTC/ETH ペアトレードボット

📐 ペアトレード(統計的裁定)とは

高い相関を持つ2銘柄(BTC/ETH)のスプレッドを監視し、乖離(Z-score)が統計的な範囲を超えたら逆張りエントリー。スプレッドが平均に回帰したら利確する市場中立戦略。

なぜ「相関」より「共和分」が重要か

BTCとETHの30日相関係数は 0.94(2026年時点)と非常に高いですが、「相関が高い」だけでは平均回帰の保証になりません。共和分(コインテグレーション)が成立しているペアは、短期的に乖離しても必ず収束する統計的根拠があります。

検証方法 使うツール 判定基準
相関係数 numpy.corrcoef() 0.8以上で高相関(参考程度)
ADF検定(共和分) statsmodels adfuller() p値 < 0.05 で共和分成立 ✓
ヘッジ比率 OLS回帰 BTC変動に対するETHの感応度を算出
① BTCとETHの価格取得

② ADF検定(共和分確認)

③ Z-score計算

④ ±2.0超えでエントリー

⑤ ±0.5以内で利確

ペアトレードボット(フルコード)

pairs_bot.py

import time
import logging
import numpy as np
from statsmodels.tsa.stattools import adfuller
from statsmodels.regression.linear_model import OLS
from statsmodels.tools import add_constant
from config import futures_ex, TRADE_USDT, MAX_DAILY_LOSS_USDT

# ── 設定 ─────────────────────────────────────────────
SYMBOL_A       = "BTC/USDT:USDT"  # 基準銘柄(Bitgetのccxtシンボル形式)
SYMBOL_B       = "ETH/USDT:USDT"  # 相手銘柄
KLINE_INTERVAL = "1h"            # 1時間足(ccxt形式: '1m','5m','1h','4h','1d')
LOOKBACK       = 500             # 過去データ数
ADF_PVALUE     = 0.05           # 共和分判定のp値閾値
ZSCORE_ENTRY   = 2.0            # エントリーZ-score絶対値
ZSCORE_EXIT    = 0.5            # エグジットZ-score絶対値
ZSCORE_STOP    = 3.5            # 損切りZ-score絶対値
CHECK_INTERVAL = 3600           # チェック間隔(秒)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("pairs_bot.log", encoding="utf-8"),
        logging.StreamHandler(),
    ],
)
logger = logging.getLogger(__name__)

# ポジション状態管理
position_state = {
    "open":      False,
    "direction": None,    # "long_b" or "short_b"
    "qty_a":     0.0,
    "qty_b":     0.0,
}
_start_balance: float = 0.0


def get_kline_closes(symbol: str, interval: str, limit: int) -> np.ndarray:
    """
    OHLCVを取得して終値配列を返す。
    ccxtは古い順(時系列順)でデータを返すため reversed() は不要。
    各要素: [timestamp, open, high, low, close, volume]
    """
    ohlcv = futures_ex.fetch_ohlcv(symbol, interval, limit=limit)
    return np.array([row[4] for row in ohlcv])  # index 4 = close


def calc_hedge_ratio(prices_a: np.ndarray, prices_b: np.ndarray) -> float:
    """
    OLS回帰でヘッジ比率(β)を計算
    log(B) = α + β * log(A) + ε
    """
    X = add_constant(np.log(prices_a))
    y = np.log(prices_b)
    model = OLS(y, X).fit()
    return float(model.params[1])


def calc_spread(prices_a: np.ndarray, prices_b: np.ndarray, hedge_ratio: float) -> np.ndarray:
    """対数スプレッドを計算: spread = log(B) - β * log(A)"""
    return np.log(prices_b) - hedge_ratio * np.log(prices_a)


def calc_zscore(spread: np.ndarray) -> float:
    """最新のZ-scoreを計算"""
    std = np.std(spread)
    return 0.0 if std == 0 else float((spread[-1] - np.mean(spread)) / std)


def adf_test(spread: np.ndarray) -> float:
    """ADF検定でp値を返す(p < 0.05 → 共和分成立)"""
    return float(adfuller(spread, autolag="AIC")[1])


def calc_qty(symbol: str, usdt_amount: float) -> float:
    """USDT額から注文数量を計算"""
    price = futures_ex.fetch_ticker(symbol)["last"]
    raw   = usdt_amount / price
    return float(futures_ex.amount_to_precision(symbol, raw))


def place_order(symbol: str, side: str, trade_side: str, qty: float):
    """先物成行注文(Bitget: tradeSide='open'/'close' 必須)"""
    futures_ex.create_order(
        symbol, "market", side, qty,
        params={"tradeSide": trade_side}
    )
    action = "新規" if trade_side == "open" else "決済"
    logger.info(f"  [{action}] {symbol} {side} qty={qty}")


def open_pair_position(direction: str, hedge_ratio: float):
    """
    direction='long_b'  → ETH(B)ロング・BTC(A)ショート(スプレッド割安)
    direction='short_b' → ETH(B)ショート・BTC(A)ロング(スプレッド割高)
    """
    qty_b = calc_qty(SYMBOL_B, TRADE_USDT)
    qty_a = float(futures_ex.amount_to_precision(
        SYMBOL_A,
        calc_qty(SYMBOL_A, TRADE_USDT * hedge_ratio)
    ))

    if direction == "long_b":
        logger.info(f"📉 エントリー: ETHロング・BTCショート(スプレッド割安)")
        place_order(SYMBOL_B, "buy",  "open", qty_b)
        place_order(SYMBOL_A, "sell", "open", qty_a)
    else:
        logger.info(f"📈 エントリー: ETHショート・BTCロング(スプレッド割高)")
        place_order(SYMBOL_B, "sell", "open", qty_b)
        place_order(SYMBOL_A, "buy",  "open", qty_a)

    position_state.update({
        "open":      True,
        "direction": direction,
        "qty_a":     qty_a,
        "qty_b":     qty_b,
    })


def close_pair_position(reason: str):
    """両ポジションをクローズ"""
    d    = position_state["direction"]
    qa   = position_state["qty_a"]
    qb   = position_state["qty_b"]
    logger.info(f"✅ {reason} → ポジション決済")

    if d == "long_b":
        place_order(SYMBOL_B, "sell", "close", qb)
        place_order(SYMBOL_A, "buy",  "close", qa)
    else:
        place_order(SYMBOL_B, "buy",  "close", qb)
        place_order(SYMBOL_A, "sell", "close", qa)

    position_state.update({
        "open":      False,
        "direction": None,
        "qty_a":     0.0,
        "qty_b":     0.0,
    })


def run():
    global _start_balance
    bal = futures_ex.fetch_balance()
    _start_balance = float(bal.get("USDT", {}).get("free", 0))
    logger.info(f"🚀 ペアトレードボット起動 | {SYMBOL_A}/{SYMBOL_B}")
    logger.info(f"   エントリーZ=±{ZSCORE_ENTRY} | エグジットZ=±{ZSCORE_EXIT} | 損切りZ=±{ZSCORE_STOP}")

    while True:
        try:
            # ── データ取得 ──────────────────────────────────
            prices_a = get_kline_closes(SYMBOL_A, KLINE_INTERVAL, LOOKBACK)
            prices_b = get_kline_closes(SYMBOL_B, KLINE_INTERVAL, LOOKBACK)

            if len(prices_a) < 100:
                logger.warning("データ不足 → スキップ")
                time.sleep(60)
                continue

            # ── 統計計算 ──────────────────────────────────
            hedge_ratio = calc_hedge_ratio(prices_a, prices_b)
            spread      = calc_spread(prices_a, prices_b, hedge_ratio)
            zscore      = calc_zscore(spread)
            p_value     = adf_test(spread)

            logger.info(
                f"Z-score={zscore:+.3f} | ヘッジ比={hedge_ratio:.3f} | "
                f"ADF p値={p_value:.4f} | 共和分={'✓' if p_value < ADF_PVALUE else '✗'}"
            )

            if p_value >= ADF_PVALUE:
                logger.warning(f"⚠️ 共和分不成立(p={p_value:.4f})→ スキップ")
                time.sleep(CHECK_INTERVAL)
                continue

            # ── 日次損失チェック ──────────────────────────
            cur_bal = float(futures_ex.fetch_balance().get("USDT", {}).get("free", 0))
            if cur_bal - _start_balance < -MAX_DAILY_LOSS_USDT:
                logger.warning("⚠️ 日次損失上限 → ボット停止")
                if position_state["open"]:
                    close_pair_position("損失上限による強制決済")
                break

            # ── 売買判定 ──────────────────────────────────
            if not position_state["open"]:
                if zscore > ZSCORE_ENTRY:
                    open_pair_position("short_b", hedge_ratio)
                elif zscore < -ZSCORE_ENTRY:
                    open_pair_position("long_b", hedge_ratio)
                else:
                    logger.info("待機中(エントリー条件未達)")
            else:
                if abs(zscore) > ZSCORE_STOP:
                    close_pair_position(f"損切り(Z={zscore:+.3f})")
                elif abs(zscore) < ZSCORE_EXIT:
                    close_pair_position(f"利確(Z={zscore:+.3f})")
                else:
                    logger.info(f"ポジション保有中(Z={zscore:+.3f})")

            time.sleep(CHECK_INTERVAL)

        except Exception as e:
            logger.error(f"エラー: {e} | 60秒後に再試行")
            time.sleep(60)


if __name__ == "__main__":
    run()
ℹ️

ccxt の OHLCV は時系列順(古い順)で返る
Bybitの pybit ライブラリと異なり、ccxt の fetch_ohlcv()古いデータが先頭(インデックス0が最古、末尾が最新)で返ります。reversed() は不要です。インデックス4が終値です:[timestamp, open, high, low, close, volume]

STEP 3リスク管理の考え方

日次損失上限

50 USDT
両ボット共通。超えたら自動停止

1取引投入額

100 USDT
ポジション上限の目安

FR裁定:最低レート

0.1%
8時間あたり。Bitget手数料を考慮した閾値

ペアトレード:損切り

Z ±3.5
想定外の乖離拡大時に強制決済

やってはいけないこと

  • APIキーに「出金権限」を付与する(資金を全額抜かれるリスク)
  • .envをGitHubにアップロードする
  • 最初から大きな資金(1,000 USDT以上)で本番稼働させる
  • ペアトレードで損切りを設定せず放置する(デカップリング時に大損)
  • FR裁定でスポットを忘れて先物だけ決済する(価格変動リスクが残る)
  • Bitgetの証拠金モードを「クロス」にしたまま運用する(アイソレーテッド推奨)
🧪

テスト方法:最小ロットで実弾テスト
Bitgetには専用テストネット(別URL)がありません。最初は TRADE_USDT = 10 などの最小金額(最低証拠金は取引所の最小ロット次第で通常5〜10 USDT相当)でコードの動作を確認してから段階的に増額してください。Bitget UIには「デモトレード」機能もありますが、これはAPIとは別アカウントです。

STEP 4VPSで24時間稼働させる

ローカルPCでは電源断・スリープによりボットが停止します。本番運用にはVPS(仮想専用サーバー)が必須です。

🖥️

XServer VPS — ボット運用に最適なVPS

月額880円〜(税込)でLinuxサーバーを即日開設。Python対応・高速回線・24時間365日稼働。初期費用0円・最短10分でセットアップ完了。

XServer VPSをチェック →

VPS セットアップ手順(Ubuntu 22.04)

# 1. Pythonとpipを最新化
sudo apt update && sudo apt install -y python3.11 python3-pip python3-venv git

# 2. プロジェクトをVPSに転送(ローカルで実行)
scp -r ./bitget_bot user@YOUR_VPS_IP:~/

# 3. VPS上で仮想環境を作成
cd ~/bitget_bot
python3 -m venv venv
source venv/bin/activate
pip install ccxt python-dotenv pandas numpy statsmodels

# 4. .envをVPS上で直接作成(scpは避けてサーバー上で編集)
nano .env   # キーを入力して保存

# 5. 接続テスト
python config.py

systemd で常駐化(推奨)

/etc/systemd/system/funding-bot.service

[Unit]
Description=Bitget Funding Rate Bot
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/bitget_bot
ExecStart=/home/ubuntu/bitget_bot/venv/bin/python funding_bot.py
Restart=always
RestartSec=10
StandardOutput=append:/home/ubuntu/bitget_bot/funding_bot.log
StandardError=append:/home/ubuntu/bitget_bot/funding_bot.log

[Install]
WantedBy=multi-user.target
# サービス登録・起動
sudo systemctl daemon-reload
sudo systemctl enable funding-bot
sudo systemctl start  funding-bot

# 動作確認
sudo systemctl status funding-bot
tail -f ~/bitget_bot/funding_bot.log
💡

systemd を使う理由
nohup より安全で、VPSが再起動された場合や、スクリプトがクラッシュした場合に自動で再起動されます。ログも自動で追記されるため管理が楽です。

VPSが未契約の方はこちら

国産VPSのXServerは速度・安定性ともに定評あり。自動売買ボットの運用実績も多数。月880円〜でPythonボットを24時間動かせます。

XServer VPSの詳細を見る →

STEP 5Claude Codeへの依頼プロンプト集

以下のプロンプトをClaude Codeのチャット欄に貼り付けてカスタマイズできます。

ファンディングレート戦略をカスタマイズする

funding_bot.py のファンディングレート裁定ボットに以下の改良を加えてください:

1. MIN_FUNDING_RATE を 0.002(0.2%/8h)に引き上げ
2. エントリーする最大銘柄数を3銘柄に増やす
3. 各ポジションの未実現損益を1時間ごとにログ出力する
4. FRが48時間以上連続して MIN_FUNDING_RATE を上回った場合のみエントリーする
   (短命な高FRを避けるため)

Bitget API は ccxt ライブラリ(fetch_funding_rate / fetch_positions)を使用してください。

ペアトレードの対象ペアを追加する

pairs_bot.py を改良して、複数の通貨ペアを同時監視できるようにしてください。

対象ペアリスト(ccxtシンボル形式):
- 'BTC/USDT:USDT' / 'ETH/USDT:USDT'(現在の実装)
- 'BTC/USDT:USDT' / 'SOL/USDT:USDT'
- 'ETH/USDT:USDT' / 'SOL/USDT:USDT'

各ペアでADF検定を実施し、共和分が成立しているペアの中で
最もZ-scoreの絶対値が大きいペアのみエントリーする設計にしてください。
同時保有は1ペアまでとします。

LINE通知を追加する

funding_bot.py と pairs_bot.py に LINE Messaging API による通知機能を追加してください。

通知タイミング:
- エントリー時(銘柄・方向・価格・FR)
- エグジット時(損益・保有時間)
- 日次損失上限到達時
- ボット起動・停止時

LINE_CHANNEL_ACCESS_TOKEN と LINE_USER_ID を .env から読み込む設計にしてください。

Bitget APIエラー時のリトライ処理を強化する

config.py の futures_ex と spot_ex に対して、以下のリトライ設定を追加してください:

- レートリミットエラー(ccxt.RateLimitExceeded)時に自動で待機・リトライ
- ネットワークエラー(ccxt.NetworkError)時は3回まで再試行
- リトライ間隔は指数バックオフ(1秒→2秒→4秒)
- すべてのリトライ失敗時はLINE通知を送信してボットを停止

ccxtの enableRateLimit オプションと、カスタムデコレータで実装してください。

よくある質問

Q. Bitgetは日本居住者が使えますか?

本記事執筆時点(2026年5月)では、Bitgetは日本居住者のアカウント開設・取引に対応しています。ただし各自で利用規約と最新の規制状況を確認した上でご利用ください。Bybitが2023年以降に日本向けサービスを停止したことを踏まえ、本記事ではBitgetを選択しています。

Q. ファンディングレートが負になったらどうなる?

FR裁定のポジション(先物ショート)を保有中にFRが負転すると、今度はショートポジションがFRを支払う側になります。本ボットはFRがEXIT_FUNDING_RATE(デフォルト0.05%)を下回った時点でポジションを決済します。FRのモニタリングは1時間ごとなので、急変時には若干の損失が出る可能性があります。

Q. ペアトレードでBTCとETHが突然デカップリングしたら?

Z-scoreがZSCORE_STOP(±3.5)を超えた時点で強制決済します。ETF承認やBTC単独急騰局面では、BTC/ETHのペアトレードが機能しなくなる可能性があります。マクロイベント前後はボットを手動停止することを検討してください。

Q. Bitgetのスポットと先物で別のウォレットになる?

はい、Bitgetはスポットウォレットと先物ウォレットが分離されています。FR裁定で先物とスポットを同時に建てる場合、それぞれのウォレットに資金を移しておく必要があります。先物口座に100 USDT、スポット口座にBTC購入資金として100 USDT分を準備してください。

Q. APIレートリミットはどのくらい?

Bitget V2 APIのレートリミット:マーケットデータ系は20リクエスト/秒、注文系は10/秒(UID単位)です。本ボットは1時間ごとのチェックで設計されているため、通常の使用では問題ありません。連続APIコール時はtime.sleep(0.3)を挟んでいます。ccxtではenableRateLimit: Trueオプションで自動管理もできます。

Q. ccxtのバージョンによってAPIが変わることはある?

あります。本記事のコードは ccxt==4.4.0 時点で検証しています。バージョンアップ時にメソッド名や引数が変わる場合があります。pip install ccxt==4.4.0 で固定バージョンを使用し、requirements.txt に明記することを推奨します。

日本人利用可・APIキー無料発行・USDT無期限先物とスポットを同一取引所で運用できます。

Bitgetで無料登録する →

⚠️

最終免責事項
本記事のコードは教育・情報提供を目的としています。仮想通貨取引は元本割れのリスクがあります。実際の運用は自己責任で、余剰資金の範囲内で行ってください。筆者は運用結果に対して一切の責任を負いません。