Paradex自動売買ボットの作り方【2026年実運用記録】Claude Codeで実装・Season3 XPを稼ぎながら自動化する完全手順
「DEXで手数料0%のまま24時間自動売買しながらSeason3 XPも稼ぐ方法を、実装コードで解説します」
実は私自身、ParadexでXP蓄積を目的としたファームボットを数ヶ月間稼働させてきました。DEXにありがちな面倒な認証や独特のAPIレイヤーに最初はハマったが、一度動き出せばCEXより安定している印象です。Season3が継続中の今、ボットで24時間稼働させることで手動では到底集めきれないXPが自然と積み上がっていく。
この記事ではParadexボットをゼロから作る手順を、実際に動くPythonコードで解説します。Claude Codeを使えば実装速度が格段に上がるので、その活用法も合わせて紹介します。
本記事は自動売買ボットの実装手順を解説するものであり、投資を勧誘するものではありません。仮想通貨取引は元本割れのリスクがあります。ボットの誤作動・バグ・API障害による損失について筆者は責任を負いません。少額から検証し、リスク管理を徹底してください。Paradexはデリバティブ取引所であり、日本居住者はご自身の判断と責任でご利用ください。
Paradexの基本的な使い方をまだ知らない方は先にこちらを読んでおくと実装がスムーズになります。
→ Paradexの使い方・登録方法【2026年版】
・paradex_pyライブラリを使ったAPIへの接続方法
・L2ウォレットキーを使った安全な認証の実装
・EMAゴールデンクロスに基づくエントリー・エグジットロジック
・Season3 XPを効率的に積み上げる取引頻度の設定
・本番環境でも使えるリスク遮断器の実装パターン
・実際に遭遇したエラーと対処法
Paradexボットを作る前に知っておくべきこと
ボット運用のメリット:手数料0%・XP自動蓄積・24時間稼働
Paradexでボットを動かす最大のメリットはMaker手数料が0%であることだ(2026年7月時点)。指値注文でポジションを建てる限りコストがかからないため、高頻度な売買でも手数料がPnLを圧迫しません。CEXのボットと比べてコスト構造が根本的に違います。
加えてSeason3が進行中の現在、取引ボリュームに応じてXPが付与されます。手動取引ではどうしても夜間や休日が手薄になるが、ボットは文字通り24時間稼働します。その差が数週間で大きなXP差になり、エアドロップ報酬に直結します。
前提知識と必要なもの
実装に入る前に以下を揃えておく必要があります。
- Paradexアカウント:MetaMaskなどのEVMウォレットで登録済みであること
- USDC証拠金:最低でも50 USDC程度。テスト段階は小額で
- Python 3.10以上:バージョン依存のエラーを避けるため3.10以上を推奨
- 基本的なPython知識:async/awaitの概念が分かると実装しやすい
Paradexには「L1ウォレットキー(MetaMaskの秘密鍵)」と「L2エージェントキー」の2種類があります。ボット実装ではL2エージェントキーのみを使います。L1キーはオンチェーン資産すべてのコントロールを持つため、コード・.envファイル・GitHubに書かれた時点で全財産喪失のリスクがあります。絶対に使わないこと。
STEP1: 開発環境の構築とParadex APIキー発行
Pythonバージョンの確認とvenv作成
# バージョン確認
python --version # Python 3.10.x 以上であることを確認
# プロジェクトディレクトリを作成
mkdir paradex-bot
cd paradex-bot
# 仮想環境を作成・有効化
python -m venv venv
# Windowsの場合
venv\Scripts\activate
# Mac/Linuxの場合
source venv/bin/activate
paradex_pyのインストール
Paradex公式のPythonクライアントライブラリを使います。これにより認証の複雑な処理が抽象化され、APIアクセスが簡潔に書けます。
pip install paradex_py python-dotenv
L2エージェントキーの発行手順
ParadexのダッシュボードからAPI設定を開きます。「Generate API Key」でL2エージェントキーのペア(公開鍵・秘密鍵)が生成されます。この画面を閉じると秘密鍵は二度と表示されないので、必ずコピーして保管すること。
環境変数の設定(.envファイル)
秘密鍵をコードに直書きするのは厳禁です。.envファイルに分離し、.gitignoreに追加します。
# .envファイルの内容
PARADEX_ACCOUNT_ADDRESS=0xYOUR_ACCOUNT_ADDRESS
PARADEX_L2_PRIVATE_KEY=0xYOUR_L2_PRIVATE_KEY
PARADEX_NETWORK=prod # または testnet
# .gitignoreに追加(絶対に忘れない)
echo ".env" >> .gitignore
STEP2: Paradex APIへの接続と認証テスト
接続サンプルコード
以下が最小限の接続テストコードです。実際に私がParadexファームボット実装時に最初に確認したのがこのパターンに近い。
import asyncio
import os
from dotenv import load_dotenv
from paradex_py import Paradex
from paradex_py.environment import Environment
load_dotenv()
async def test_connection():
# 環境設定 (prod または testnet)
env = Environment.PROD if os.getenv("PARADEX_NETWORK") == "prod" else Environment.TESTNET
# Paradexクライアントの初期化
# L1キーは渡さない - L2キーのみ使用
pdx = Paradex(
env=env,
l2_private_key=os.getenv("PARADEX_L2_PRIVATE_KEY"),
)
# JWT認証トークンを取得
await pdx.account.get_jwt_token()
print("[OK] 認証成功")
# アカウント情報を取得
account_info = await pdx.account.fetch_account_summary()
print(f"[OK] アカウント取得")
print(f" 総残高: {account_info.get('account_value', 'N/A')} USDC")
print(f" フリーマージン: {account_info.get('free_collateral', 'N/A')} USDC")
# マーケット一覧を取得
markets = await pdx.markets.fetch_markets()
print(f"[OK] マーケット数: {len(markets)}件")
return pdx
if __name__ == "__main__":
asyncio.run(test_connection())
ポジションと残高の確認コード
async def check_positions(pdx):
# 現在のオープンポジションを確認する
positions = await pdx.positions.fetch_positions()
if not positions:
print("[INFO] オープンポジションなし")
return
for pos in positions:
market = pos.get("market", "")
side = pos.get("side", "")
size = pos.get("size", 0)
entry_price = pos.get("avg_entry_price", 0)
unrealized_pnl = pos.get("unrealized_pnl", 0)
print(f" {market}: {side} {size} @ {entry_price} | 含み損益: {unrealized_pnl}")
「Paradex APIを使って、ETH-USD-PERPのEMA(14)とEMA(28)のゴールデンクロス時にLongエントリー、デッドクロス時にShortエントリーするボットを作って。ポジションサイズは証拠金の10%、SLは2%で設定して。.envからL2キーを読み込んで、既存ポジションがある場合は新規エントリーしないようにして」
STEP3: 基本的なオーダー戦略の実装
EMAベースの方向性判断ロジック
Paradexボットで方向性判断を行う際の一例として、EMAクロスを使った実装を紹介します。以下のコードは実際に動くものだが、パラメータはバックテストで調整すること。
from collections import deque
from typing import Optional
class EMACalculator:
# 指数移動平均を計算するクラス
def __init__(self, fast_period: int = 14, slow_period: int = 28):
self.fast_period = fast_period
self.slow_period = slow_period
self.prices = deque(maxlen=slow_period + 10)
self.fast_ema: Optional[float] = None
self.slow_ema: Optional[float] = None
def _calc_ema(self, prices: list, period: int) -> float:
# EMA計算(初回はSMAで初期化)
if len(prices) < period:
return sum(prices) / len(prices)
k = 2.0 / (period + 1)
ema = sum(prices[:period]) / period # 初期値をSMAで設定
for price in prices[period:]:
ema = price * k + ema * (1 - k)
return ema
def update(self, price: float) -> dict:
# 新しい価格でEMAを更新し、シグナルを返す
self.prices.append(price)
prices_list = list(self.prices)
prev_fast = self.fast_ema
prev_slow = self.slow_ema
if len(prices_list) >= self.slow_period:
self.fast_ema = self._calc_ema(prices_list, self.fast_period)
self.slow_ema = self._calc_ema(prices_list, self.slow_period)
signal = "HOLD"
if prev_fast is not None and prev_slow is not None:
if prev_fast <= prev_slow and self.fast_ema > self.slow_ema:
signal = "BUY" # ゴールデンクロス
elif prev_fast >= prev_slow and self.fast_ema < self.slow_ema:
signal = "SELL" # デッドクロス
return {
"signal": signal,
"fast_ema": self.fast_ema,
"slow_ema": self.slow_ema,
"price": price,
}
指値注文の送信とポジション管理
async def place_limit_order(
pdx,
market: str,
side: str,
size: float,
price: float,
) -> Optional[str]:
# 指値注文を送信してオーダーIDを返す
try:
order = await pdx.orders.create_order(
market=market,
side=side,
type="LIMIT",
size=str(size),
price=str(round(price, 2)),
time_in_force="GTC", # Good Till Cancel
)
order_id = order.get("id")
print(f"[ORDER] {side} {size} {market} @ {price} | ID: {order_id}")
return order_id
except Exception as e:
print(f"[ERROR] 注文送信失敗: {e}")
return None
async def calculate_position_size(pdx, risk_pct: float = 0.10) -> float:
# 証拠金に対するポジションサイズを計算する
account = await pdx.account.fetch_account_summary()
free_collateral = float(account.get("free_collateral", 0))
position_size = free_collateral * risk_pct
return max(10.0, round(position_size, 1)) # 最低10 USDC
ポジションのクローズ確認パターン
実運用で痛感したのが、クローズ注文を送っただけでポジションが消えたと思い込む事故です。私の実装では「送信→確認→確認失敗時リトライ」のパターンを必ず使っています。
import asyncio
async def close_position_with_verify(
pdx,
market: str,
max_retries: int = 3,
) -> bool:
# ポジションをクローズし、消滅を確認する
for attempt in range(max_retries):
positions = await pdx.positions.fetch_positions()
pos = next((p for p in positions if p.get("market") == market), None)
if pos is None:
print(f"[OK] {market} ポジションなし(クローズ済みまたは未保有)")
return True
close_side = "SELL" if pos["side"] == "LONG" else "BUY"
size = abs(float(pos["size"]))
current_price = float(pos.get("mark_price", 0))
# 成行に近い指値でクローズ
close_price = current_price * 0.995 if close_side == "SELL" else current_price * 1.005
await pdx.orders.create_order(
market=market,
side=close_side,
type="LIMIT",
size=str(size),
price=str(round(close_price, 2)),
time_in_force="IOC", # Immediate or Cancel
reduce_only=True,
)
# 3秒待ってポジション消滅を確認
await asyncio.sleep(3)
positions_after = await pdx.positions.fetch_positions()
still_open = any(p.get("market") == market for p in positions_after)
if not still_open:
print(f"[OK] {market} クローズ確認完了")
return True
print(f"[WARN] クローズ確認失敗 ({attempt + 1}/{max_retries})、リトライ中...")
print(f"[ERROR] {market} クローズ失敗、手動対応が必要")
return False
STEP4: XP最大化のための最適化
取引ボリュームとXPの関係
Season3のXP計算は「取引ボリューム(USD建て)」がベースになっています。つまり、ポジションサイズが大きいほど・取引回数が多いほどXPが増えます。ただし証拠金残高に見合わないレバレッジは清算リスクを高めるため、ボリュームを稼ぐための無謀なポジション拡大は避けます。
・Maker注文(指値)を使う:手数料0%でボリュームだけが積み上がります
・スプレッドが狭いペアを選ぶ:ETH-USD-PERP、BTC-USD-PERPが安定
・紹介コードで5%XPボーナスを確実に受け取る(登録時のみ適用)
・ポジションを短い周期でロールオーバーすることでボリュームを積み上げます
効率的な取引頻度の設定
import time
from datetime import datetime
class BotScheduler:
# 取引頻度を管理するスケジューラー
def __init__(self, scan_interval_sec: int = 60):
self.scan_interval = scan_interval_sec
self.last_scan = 0
def should_scan(self) -> bool:
# スキャンのタイミングかどうかを返す
now = time.time()
if now - self.last_scan >= self.scan_interval:
self.last_scan = now
return True
return False
def log(self, msg: str):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{ts}] {msg}")
紹介コードによる5%XPボーナスの活用
Paradexには紹介制度があり、紹介リンク経由で登録した場合に5%のXPボーナスが付与されます。ボットを複数ウォレットで運用する場合、新しいウォレットを登録する際は必ず紹介リンクを経由すること。後から適用することはできません。
STEP5: リスク管理と遮断器の実装
最大ロスリミットと遮断器
ファームボットをParadexで稼働させた際、一番設計に気を使ったのがここです。ボットは止まらないから、損失も止めなければ永遠に続きます。
class RiskCircuitBreaker:
# ボット全体のリスク遮断器。
# 設計不変条件: 遮断器は実際に起動されるランナーに配線すること。
def __init__(
self,
max_daily_loss_usd: float = 50.0,
max_consecutive_losses: int = 3,
max_position_usd: float = 200.0,
):
self.max_daily_loss = max_daily_loss_usd
self.max_consecutive_losses = max_consecutive_losses
self.max_position = max_position_usd
self.daily_pnl = 0.0
self.consecutive_losses = 0
self.is_halted = False
self.halt_reason = ""
def record_trade(self, pnl: float):
# 取引結果を記録し、必要なら遮断する
self.daily_pnl += pnl
if pnl < 0:
self.consecutive_losses += 1
else:
self.consecutive_losses = 0
if abs(self.daily_pnl) >= self.max_daily_loss and self.daily_pnl < 0:
self._halt(f"日次損失上限 {self.max_daily_loss} USD 到達 (現在: {self.daily_pnl:.2f})")
if self.consecutive_losses >= self.max_consecutive_losses:
self._halt(f"連続損失 {self.consecutive_losses} 回到達")
def check_position_size(self, size_usd: float) -> bool:
# ポジションサイズが上限内かチェックする
if size_usd > self.max_position:
print(f"[RISK] ポジションサイズ超過: {size_usd:.2f} > {self.max_position:.2f} USD")
return False
return True
def _halt(self, reason: str):
self.is_halted = True
self.halt_reason = reason
print(f"[CIRCUIT BREAKER] 遮断発動: {reason}")
def reset_daily(self):
# 毎日0時にリセット(デイリーカウンタのみ)
self.daily_pnl = 0.0
# 連続損失とhalted状態は手動リセット
ループ保護とエラーハンドリング
while True の中でscan()がtry/except外にあると、未捕捉例外でプロセスが全停止します。実運用ボットでは必ずループ全体をtry/exceptで包む。
async def main_loop(pdx, risk: RiskCircuitBreaker):
# メインループ(ループ保護付き)
ema_calc = EMACalculator(fast_period=14, slow_period=28)
scheduler = BotScheduler(scan_interval_sec=60)
market = "ETH-USD-PERP"
open_order_id = None
print("[START] Paradexボット起動")
while True:
try:
# 遮断器チェック
if risk.is_halted:
print(f"[HALT] 遮断中: {risk.halt_reason}")
await asyncio.sleep(60)
continue
if not scheduler.should_scan():
await asyncio.sleep(1)
continue
# 価格取得
ticker = await pdx.markets.fetch_ticker(market)
price = float(ticker.get("mark_price", 0))
if price <= 0:
scheduler.log("[WARN] 価格取得失敗")
continue
# EMAシグナル計算
signal_data = ema_calc.update(price)
signal = signal_data["signal"]
scheduler.log(
f"価格: {price:.2f} | "
f"EMA14: {signal_data['fast_ema']:.2f if signal_data['fast_ema'] else 'N/A'} | "
f"シグナル: {signal}"
)
# エントリーシグナル処理
if signal in ("BUY", "SELL") and open_order_id is None:
size_usd = await calculate_position_size(pdx, risk_pct=0.10)
if risk.check_position_size(size_usd):
size = size_usd / price
entry_price = price * 0.999 if signal == "BUY" else price * 1.001
open_order_id = await place_limit_order(
pdx, market, signal, round(size, 4), round(entry_price, 2)
)
except KeyboardInterrupt:
print("[STOP] 手動停止")
break
except Exception as e:
# 予期しないエラーはログを出してループを継続(プロセスを落とさない)
print(f"[ERROR] 予期しないエラー: {type(e).__name__}: {e}")
await asyncio.sleep(10)
ボットの状態をDiscordに通知する場合は、
urllib.requestを使って送信する際に必ずUser-Agent: DiscordBot (crypto-beat-bot, 1.0)ヘッダーを付けること。これがないとDiscord側で一定確率でブロックされます。また通知関数が実際にsend()を呼んでいるか、スタブ化されていないかを必ず確認すること。
動作確認とトラブルシューティング
実際に遭遇したエラーと対処法
Paradexボットの実装時に実際にハマったエラーをまとめた。
| エラー | 原因 | 対処法 |
|---|---|---|
401 Unauthorized |
JWTトークンの期限切れ(約1時間) | get_jwt_token()を定期的に再呼び出し(45分ごと推奨) |
InvalidSignature |
L2キーのフォーマット不正(0x有無) | 0xプレフィックスを付けた形式で渡す |
InsufficientMargin |
注文サイズが証拠金に対して大きすぎる | free_collateralを確認してサイズを減らします |
OrderNotFound |
約定済みまたはキャンセル済みの注文をキャンセルしようとしました | キャンセル前にオーダーステータスを確認します |
| 注文が通らない(無音) | WebSocket接続が切断されています | REST APIで直接確認、接続を再初期化します |
JWTトークン自動更新の実装
import time
class TokenManager:
# JWTトークンを自動更新するマネージャー
def __init__(self, pdx, refresh_interval: int = 2700): # 45分
self.pdx = pdx
self.refresh_interval = refresh_interval
self.last_refresh = 0
async def ensure_valid_token(self):
# 必要な場合にトークンを更新する
now = time.time()
if now - self.last_refresh >= self.refresh_interval:
await self.pdx.account.get_jwt_token()
self.last_refresh = now
print("[TOKEN] JWTトークン更新完了")
よくある質問
Q: ボットを動かすにはどれくらいの資金が必要ですか?
Q: XPはボットの取引でも獲得できますか?
Q: Paradex APIは無料で使えますか?
Q: ボットが停止したらポジションはどうなりますか?
Q: Claude Codeを使わずに実装できますか?
まとめ:ParadexボットでXPを稼ぎながら自動売買します
・paradex_pyでAPIへ接続できます。L2キーのみ使用、L1キーは絶対に使いません
・EMAクロス戦略で方向性を判断し、指値注文でMaker手数料0%を維持します
・close_with_verifyパターンでクローズ確認を必ず行う(送信だけでは不十分)
・RiskCircuitBreakerで日次損失上限・連続損失上限を設定して遮断器を配線します
・while Trueの全体をtry/exceptで包み、未捕捉例外でプロセスが落ちないようにします
・JWTトークンは45分ごとに更新する(1時間で期限切れになります)
・紹介コード経由での登録で5%XPボーナス(登録時のみ適用)
Paradexは手数料構造とAPIの品質の両面でDEXボット向きのプラットフォームだと実感しています。CEXと違ってウォレット管理は自己責任だが、その分カストディリスクがなく、Season3が続く限りXPというリターンもあります。
最初のボットを動かすのに数時間もあれば十分です。まずは接続テストから始めて、testnetで動作確認してから本番に移行する順番を守れば、大きなミスは避けられます。
