この記事は、GRVT APIを使ったマーケットデータ取得とMaker注文ボットの技術解説です。手数料リベートやポイント、運用益を保証するものではありません。

以下のコード例は学習用の参考です。筆者の実運用成績や再現可能な収益を示すものではなく、GRVT API仕様との整合性も実行前にご自身で検証してください。

API接続、公開されているサンプル形式のコード、リスク管理上の注意を扱います。ポイント計算や参加資格は変更されることがあり、現在の報酬を約束する内容ではありません。

⚠️ 注意
本記事はGRVTボット開発のチュートリアルです。自動売買には元本損失リスクが伴います。海外事業者であることだけを理由に、日本の法令や規制の適用外とは判断できません。利用可能性や法的な扱いは利用者の状況によって異なるため、公式規約と必要に応じて専門家の助言を確認してください。運用は損失を許容できる範囲に限定してください。GRVTの基本的な使い方はGRVT使い方ガイドを先にご参照ください。
📝 この記事でわかること
・Maker注文・手数料・報酬を確認する際の注意点
・Python + GRVT APIで自動売買ボットを構築する手順
・D_makerパターンによるMaker両建て戦略の実装コード
・ポイント制度終了後の確認事項とリスク管理
・リスク管理・遮断器・エラーハンドリングの実装方法

GRVTボットの仕組みと注意点

Maker注文と報酬について

Maker注文が報酬対象になるか、料率や条件がどうなっているかは、注文時点の公式Fee Scheduleとアカウント条件を確認してください。Maker注文でも約定しない、逆選択や価格変動で損失になる、手数料等が発生する可能性があり、リベートや利益は保証されません。

ポイントプログラムの状況

Season 2のポイント集計・登録受付は終了しています。GRVTは2026年7月28日にTGEを発表し、TGEは7月30日に実施されました。登録は8月6日に締め切られ、配布は12か月にわたる分割配布です。各回のトークン解除後、30日以内に請求する必要があります。現在の参加資格や請求状況はGRVT公式TGE案内で確認してください。過去のポイント獲得を新たな配布や報酬とみなさないでください。

Earn on Equityの概要

GRVT公式ヘルプによると、2026年8月11日からの基本利率はUSDT・USDCで年率3.5%、紹介特典を含む上限は4.5%です。ボーナスは最大50 USDT、4週間サイクルで、Vault残高は対象外です。利率・適用条件は変更され得る変動制で、将来の利回りを保証しません。最新条件はGRVT公式Earn on Equity案内を確認してください。

ZKsyncが可能にするもの

GRVTはハイブリッド型の取引プラットフォームです。注文処理や決済の具体的な構成・リスクは公式の技術資料で確認してください。「カストディはDEX」など、単純化した表現だけで資産保全性を判断しないでください。

API認証情報の漏えいは口座内の取引や資産に影響し得ます。最小権限の認証情報を使い、秘密鍵をコードやログに含めず、保管・失効方法を確認してください。

STEP1: 開発環境の構築とGRVT API認証設定

重要: 掲載コードは未検証の参考例であり、そのまま実行・本番接続しないでください。まず公式ドキュメントでAPIエンドポイント、署名方式、注文仕様、環境変数名を照合し、テストネットまたはモック環境で読み取り専用テストとペーパー運用を行ってください。テストネットが利用できない場合は本番資金を使わず、ローカルのモックで検証してください。

Python環境の準備

Python 3.10以上を推奨します。必要なライブラリは最小限に絞る:

# 仮想環境の作成と有効化
python -m venv grvt_bot
source grvt_bot/bin/activate   # Windows: grvt_bot\Scripts\activate

# 依存ライブラリのインストール
pip install requests websocket-client eth_account python-dotenv

GRVT APIの認証方法

GRVTのAPI認証はウォレット署名ベースです。CEXのような単純なAPIキー/シークレットとは異なり、秘密鍵で署名したメッセージを使ってセッショントークンを取得します。

認証フローは以下の通り:

  1. GRVT公式でAPIキー(L2エージェント鍵)を発行
  2. 署名でセッショントークンを取得
  3. 以降のAPIコールはトークンをヘッダーに付与
⚠️ 注意:L1鍵は絶対に使いません
GRVTのAPIには「L1鍵(ウォレットのマスター秘密鍵)」と「L2/エージェント鍵」があります。ボットに渡すのはL2鍵のみ。L1鍵を.envに書くと全資産が危険にさらされます。

環境変数設定

# .env ファイル
GRVT_PRIVATE_KEY=0xYOUR_L2_AGENT_PRIVATE_KEY
GRVT_ACCOUNT_ADDRESS=0xYOUR_WALLET_ADDRESS
GRVT_ENV=mainnet   # または testnet
💡 Claude Codeへの指示例

「.envから GRVT_PRIVATE_KEY と GRVT_ACCOUNT_ADDRESS を読み込み、eth_accountで署名してGRVT Mainnet APIのセッショントークンを取得するPythonモジュールを作って。トークンの有効期限を自動で監視して、切れる前に再取得する機能も入れて」

STEP2: GRVT APIへの接続とマーケットデータ取得

接続テストコード

import os
import requests
from dotenv import load_dotenv
from eth_account import Account
from eth_account.messages import encode_defunct
import time

load_dotenv()

BASE_URL = "https://trades.grvt.io"  # Mainnet
# BASE_URL = "https://dev.trades.grvt.io"  # Testnet

def get_session_token():
    # ウォレット署名でセッショントークンを取得
    private_key = os.environ["GRVT_PRIVATE_KEY"]
    account = Account.from_key(private_key)

    timestamp = int(time.time() * 1000)
    message = f"GRVT login:{timestamp}"
    msg = encode_defunct(text=message)
    signed = Account.sign_message(msg, private_key=private_key)

    payload = {
        "wallet": account.address,
        "signature": signed.signature.hex(),
        "timestamp": timestamp
    }

    resp = requests.post(f"{BASE_URL}/auth/login", json=payload, timeout=10)
    resp.raise_for_status()
    return resp.json()["token"]

def get_orderbook(token, instrument="BTC_USDC_Perp"):
    # 板情報を取得
    headers = {"Authorization": f"Bearer {token}"}
    params = {"instrument": instrument, "depth": 5}
    resp = requests.get(
        f"{BASE_URL}/full/v1/book",
        headers=headers,
        params=params,
        timeout=10
    )
    resp.raise_for_status()
    return resp.json()

if __name__ == "__main__":
    token = get_session_token()
    print(f"Token acquired: {token[:20]}...")

    book = get_orderbook(token)
    bids = book["result"]["bids"]
    asks = book["result"]["asks"]

    best_bid = float(bids[0]["price"]) if bids else 0
    best_ask = float(asks[0]["price"]) if asks else 0
    mid_price = (best_bid + best_ask) / 2

    print(f"BTC Mid Price: ${mid_price:,.2f}")
    print(f"Spread: {(best_ask - best_bid) / mid_price * 100:.4f}%")

WebSocket購読サンプル

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    if data.get("type") == "trade":
        print(f"Trade: {data['price']} x {data['size']}")

def subscribe_trades(token, instrument="BTC_USDC_Perp"):
    # 取引をリアルタイム購読
    ws_url = "wss://trades.grvt.io/ws"

    def on_open(ws):
        ws.send(json.dumps({"type": "auth", "token": token}))
        ws.send(json.dumps({
            "type": "subscribe",
            "channel": f"trades:{instrument}"
        }))

    ws = websocket.WebSocketApp(
        ws_url,
        on_message=on_message,
        on_open=on_open
    )
    ws.run_forever()
💡 Claude Codeへの指示例

「GRVT APIを使って、BTC-USD-PERPで現在の中値から0.1%上にSell Limit、0.1%下にBuy Limitを出し続けるMakerボットを作って。約定したら即座に反対側に再配置、ポジションが証拠金の50%を超えたら新規注文を止めて」

STEP3: Maker注文戦略の実装(D_makerパターン)

以下は注文管理の構造を示す参考例です。OI維持、ポイント獲得、リベート取得、損益改善を保証せず、特定の戦略を推奨するものでもありません。

スプレッドを使ったMaker両建て

中値(Mid Price)を基準に、上下対称に指値を置きます。スプレッドは板の薄さと必要リベートに応じて調整します。

import requests
import time
import os
from dotenv import load_dotenv

load_dotenv()

BASE_URL = "https://trades.grvt.io"
INSTRUMENT = "BTC_USDC_Perp"
SPREAD_PCT = 0.001   # 0.1% 上下に指値
ORDER_SIZE = 0.001   # BTC建てのサイズ

def place_limit_order(token, side, price, size):
    # Maker指値注文を発行
    headers = {"Authorization": f"Bearer {token}"}
    payload = {
        "instrument": INSTRUMENT,
        "side": side,          # "BUY" or "SELL"
        "type": "LIMIT",
        "price": str(round(price, 2)),
        "size": str(size),
        "time_in_force": "GTC",   # Good Till Cancel
        "post_only": True          # Makerのみ。Takerになったら拒否
    }
    resp = requests.post(
        f"{BASE_URL}/full/v1/order",
        headers=headers,
        json=payload,
        timeout=10
    )
    resp.raise_for_status()
    return resp.json()

def cancel_all_orders(token):
    # 全注文をキャンセル
    headers = {"Authorization": f"Bearer {token}"}
    payload = {"instrument": INSTRUMENT}
    resp = requests.delete(
        f"{BASE_URL}/full/v1/orders",
        headers=headers,
        json=payload,
        timeout=10
    )
    resp.raise_for_status()

def get_position(token):
    # 現在のネットポジション取得
    headers = {"Authorization": f"Bearer {token}"}
    resp = requests.get(
        f"{BASE_URL}/full/v1/positions",
        headers=headers,
        timeout=10
    )
    resp.raise_for_status()
    positions = resp.json().get("result", [])
    for pos in positions:
        if pos["instrument"] == INSTRUMENT:
            return float(pos["size"])
    return 0.0

def run_maker_bot(token):
    # メインループ: 板を張り続けるMakerボット
    print("D_maker Bot 起動 -- GRVT BTC-USDC-Perp")

    while True:
        try:
            book = get_orderbook(token)
            bids = book["result"]["bids"]
            asks = book["result"]["asks"]

            if not bids or not asks:
                print("板が薄い、10秒待機")
                time.sleep(10)
                continue

            best_bid = float(bids[0]["price"])
            best_ask = float(asks[0]["price"])
            mid = (best_bid + best_ask) / 2

            buy_price  = mid * (1 - SPREAD_PCT)
            sell_price = mid * (1 + SPREAD_PCT)

            net_pos = get_position(token)

            cancel_all_orders(token)

            if abs(net_pos) * mid < 5000:   # ポジション制限: $5,000未満なら両側配置
                place_limit_order(token, "BUY",  buy_price,  ORDER_SIZE)
                place_limit_order(token, "SELL", sell_price, ORDER_SIZE)
                print(f"[{time.strftime('%H:%M:%S')}] Mid:{mid:.2f} "
                      f"BUY@{buy_price:.2f} / SELL@{sell_price:.2f} | Net:{net_pos:+.4f}")
            else:
                if net_pos > 0:
                    place_limit_order(token, "SELL", sell_price, ORDER_SIZE)
                    print(f"[{time.strftime('%H:%M:%S')}] 過剰ロング -- Sellのみ配置")
                else:
                    place_limit_order(token, "BUY", buy_price, ORDER_SIZE)
                    print(f"[{time.strftime('%H:%M:%S')}] 過剰ショート -- Buyのみ配置")

            time.sleep(30)  # 30秒ごとに再配置

        except requests.exceptions.RequestException as e:
            print(f"APIエラー: {e}、60秒後リトライ")
            time.sleep(60)
        except Exception as e:
            print(f"予期せぬエラー: {e}、30秒後リトライ")
            time.sleep(30)
✅ D_makerパターンのポイント
・post_only: True を必ず指定。Takerになるとリベートが取れないどころか手数料を払います
・30秒ごとの再配置で、板から外れた注文を常に最新の中値にリセット
・ネットポジションが偏ったら片側のみ配置してリバランス誘導

STEP4: 運用前に確認する項目

Season 2は終了しており、過去のポイント算定条件を現在も適用できるとは限りません。ポイントや報酬を目的に取引を増やしたり、不要なポジションを保有したりしないでください。報酬対象性は公式ルールで確認し、取引手数料、スプレッド、資金調達料、スリッページ、清算リスクを含めて評価します。

紹介リンクを利用する場合、紹介関係が発生する可能性があります。リンク経由での登録は任意であり、報酬条件や資格を保証しません。

STEP5: リスク管理と遮断器の実装

最大ドローダウン制限

ボットが暴走してもアカウントが致命傷を受けないよう、遮断器を実装します。

class RiskBreaker:
    # シンプルなリスク遮断器

    def __init__(self, max_daily_loss_usd=100.0):
        self.max_daily_loss = max_daily_loss_usd
        self.daily_realized_pnl = 0.0
        self.trade_count = 0
        self.is_breaker_tripped = False

    def record_trade(self, pnl):
        # 約定時にPnLを記録
        self.daily_realized_pnl += pnl
        self.trade_count += 1

        if self.daily_realized_pnl < -self.max_daily_loss:
            self.is_breaker_tripped = True
            print(f"[RISK] 日次損失上限 ${self.max_daily_loss} を超過 -- ボット停止")

    def can_trade(self):
        return not self.is_breaker_tripped

risk = RiskBreaker(max_daily_loss_usd=200.0)

エラーハンドリングとループ保護

ネットワーク切断やAPIレート制限は必ず発生します。while Trueの中の処理全体をtry/exceptで包み、未捕捉例外でプロセスが落ちないようにします。

def run_with_protection(token_getter, bot_func):
    # プロセス全体を保護するラッパー
    consecutive_errors = 0
    MAX_CONSECUTIVE_ERRORS = 10

    while True:
        try:
            token = token_getter()
            bot_func(token)
            consecutive_errors = 0

        except KeyboardInterrupt:
            print("手動停止")
            break

        except Exception as e:
            consecutive_errors += 1
            print(f"トップレベルエラー ({consecutive_errors}/{MAX_CONSECUTIVE_ERRORS}): {e}")

            if consecutive_errors >= MAX_CONSECUTIVE_ERRORS:
                print("連続エラー上限に達したためプロセス終了")
                break

            # バックオフ待機
            wait = min(60 * consecutive_errors, 600)
            print(f"{wait}秒後に再起動")
            time.sleep(wait)
📝 ZKsync手数料コストについて
GRVTの決済はZKsyncオンチェーンで確定するが、GASコストはGRVTが負担する設計になっており、ユーザーが別途ETH/ZKを準備する必要はない(2026年7月時点)。ただし出金時にはZKsync手数料が発生するため、取引と出金のコストは分けて計算すること。

本番利用に進む前に: テストネットまたはペーパー環境で注文・取消・部分約定・切断復旧・重複注文・ポジション上限・停止処理を試してください。テストに通らない場合やAPI仕様を確認できない場合は、本番へ接続しないでください。

動作確認とトラブルシューティング

認証エラーの対処

「401 Unauthorized」が返ってくる場合:

  • .envのPRIVATE_KEYが正しいL2エージェント鍵かを確認(L1鍵は使いません)
  • ウォレットアドレスとL2鍵のペアが合致しているか確認
  • トークンの有効期限切れ(取得から一定時間後)→ 再取得ロジックを実装します

注文が通らない場合

「post_only rejection」エラー:指値価格がMid付近すぎてTakerになりそうと判断されています。スプレッドを少し広げる(0.08%→0.12%)。

「Insufficient margin」エラー:証拠金残高不足。GRVTのポートフォリオページで残高を確認します。

ポイントが反映されない場合

Season 2は終了済みです。過去のポイント表示や配布資格・請求状況は公式ポータルとTGE案内を確認してください。将来の報酬が得られる前提で取引しないでください。

よくある質問

Q: Maker注文で報酬を受け取れますか?
A: 報酬の有無や料率はアカウントや現行の公式Fee Scheduleによります。Maker注文でも約定・損益は保証されません。注文前に公式情報を確認してください。
Q: ボット取引は現在のポイント対象ですか?
A: Season 2は終了しています。現在の報酬制度や参加条件がある場合は、公式告知で確認してください。掲載コードがポイント対象になるとは限りません。
Q: TGE後の配布スケジュールは?
A: GRVTの公式発表では、TGEは2026年7月30日、登録締切は8月6日です。配布は12か月にわたる分割配布で、各回のトークン解除後30日以内に請求します。個別の資格と期限は公式案内で確認してください。
Q: GRVTと他の取引所のボットはどちらが有利ですか?
A: 市場環境、手数料、流動性、実装、リスク許容度によって異なります。比較可能な検証データなしに優劣や収益性は断定できません。
Q: どれくらいの資金が必要ですか?
A: 必要額は市場、最小注文数量、証拠金要件、手数料などで異なります。公式仕様を確認し、失っても生活に影響しない範囲を超える資金を投入しないでください。固定の推奨額は示せません。

まとめ:コードは検証環境で安全に確認する

このページはGRVT APIとMaker注文ボットの技術参考資料です。コードは未検証のため、公式仕様に照らしたレビューとテストネットまたはペーパー運用を行い、注文取消や異常時停止まで確認するまでは本番資金に接続しないでください。取引による損失があり得るほか、ポイント・リベート・利回り・利益はいずれも保証されません。

GRVTのTGE・配布条件は公式TGE案内、Earn on Equityは公式ヘルプで最新情報を確認してください。

関連記事: