CCXTで仮想通貨の注文を実行しよう ― 基本戦略の実装まで徹底解説【第2回】

約20分で読めます by ぽんたぬき
CCXTで仮想通貨の注文を実行しよう ― 基本戦略の実装まで徹底解説【第2回】

CCXTで仮想通貨の注文を実行しよう ― 基本戦略の実装まで徹底解説【第2回】

⚠️ 免責事項:本記事のコードはあくまで技術的な解説を目的としています。実際の取引はご自身の判断と責任のもとで行ってください。仮想通貨取引には元本割れのリスクが伴います。


はじめに ― 第1回のおさらいと今回のゴール

第1回記事では、CCXTのインストールから取引所への接続、ティッカー・OHLCV(ローソク足)データの取得までを解説しました。「データは取れるようになったけれど、実際に注文を出すにはどうすればいいのか」という疑問を持った方も多いのではないでしょうか。

第2回となる本記事では、以下の内容を段階的に解説します。

  • 成行・指値・逆指値注文の実装方法
  • 残高確認とポジション管理の実践
  • 移動平均クロス・グリッドトレード・RSIを使ったシンプルな自動売買戦略

サンドボックス環境で安全に検証する

本番資金を使った検証は絶対に避けましょう。ベストプラクティスとして、まずBinance TestnetBybit Testnetのサンドボックス環境を利用します。

import ccxt

exchange = ccxt.binance({
    'apiKey': 'YOUR_TESTNET_API_KEY',
    'secret': 'YOUR_TESTNET_SECRET',
})
exchange.set_sandbox_mode(True)  # サンドボックス有効化

set_sandbox_mode(True) を呼び出すだけで、エンドポイントが自動的にテストネット用に切り替わります。


CCXTにおける注文の基本構造を理解する

注文を出す前に、CCXTが統一する注文パラメータと返り値の構造を把握しておきましょう。

注文に必要な4つのパラメータ

パラメータ 説明
symbol 取引ペア 'BTC/USDT'
side 売買方向 'buy' / 'sell'
type 注文タイプ 'market' / 'limit'
amount 注文数量(基軸通貨) 0.001(BTC)
price 指値価格(limit時のみ) 30000

Orderオブジェクトの主要フィールド

CCXTはすべての取引所で共通のOrderオブジェクトを返します。

フィールド 意味
id 取引所固有の注文ID
status open / closed / canceled
filled 約定済み数量
remaining 未約定数量
cost 約定金額(filled × price)
fee 手数料情報

注文ステータスは open(未約定)→ closed(全約定)または canceled(キャンセル済み)、場合によっては rejected(拒否)へと遷移します。


成行注文(Market Order)を実装する

成行注文は、現在の市場価格でただちに約定させる注文です。実装がシンプルな反面、**スリッページ(想定価格とのズレ)**が発生する点に注意が必要です。

# 残高確認
balance = exchange.fetch_balance()
usdt_free = balance['USDT']['free']
print(f"利用可能USDT: {usdt_free}")

# BTC/USDTを成行買い(0.001 BTC)
order = exchange.create_market_buy_order('BTC/USDT', 0.001)
print(f"注文ID: {order['id']}")
print(f"ステータス: {order['status']}")
print(f"約定数量: {order['filled']}")
print(f"約定金額: {order['cost']} USDT")

流動性の低いアルトコインペアでは、成行注文により大きなスリッページが生じる可能性があります。実際の開発現場では、流動性の高い主要ペア以外には指値注文を使うことを推奨します。


指値注文(Limit Order)を実装する

指値注文は、あらかじめ指定した価格でのみ約定する注文です。スリッページはありませんが、価格に届かない場合は未約定のまま残ります。

# 現在価格を取得
ticker = exchange.fetch_ticker('BTC/USDT')
current_price = ticker['last']

# 現在値より1%安い価格で指値買い
limit_price = round(current_price * 0.99, 2)
order = exchange.create_limit_buy_order('BTC/USDT', 0.001, limit_price)
order_id = order['id']
print(f"指値注文を発行: {limit_price} USDT")

注文一覧の管理とキャンセル

import time

# 未約定注文の確認
open_orders = exchange.fetch_open_orders('BTC/USDT')

# ポーリングで約定待ち(最大60秒)
timeout = 60
start = time.time()
while time.time() - start < timeout:
    order = exchange.fetch_order(order_id, 'BTC/USDT')
    if order['status'] == 'closed':
        print("約定しました!")
        break
    time.sleep(5)
else:
    # タイムアウト → キャンセル
    exchange.cancel_order(order_id, 'BTC/USDT')
    print("タイムアウト:注文をキャンセルしました")

逆指値注文(Stop Order)とその他の注文タイプ

逆指値注文(ストップロス)は、損失を限定するために欠かせない仕組みです。CCXTでは取引所ごとに対応状況が異なるため、事前に機能チェックを行います。

# 逆指値注文のサポート確認
if exchange.has['createStopOrder']:
    # 価格が29000 USDTを下回ったら成行売り
    stop_order = exchange.create_order(
        symbol='BTC/USDT',
        type='stop_market',
        side='sell',
        amount=0.001,
        params={'stopPrice': 29000}
    )
else:
    print("この取引所はstop_market注文に未対応です")

OCO注文(利確注文とストップロスを同時に出し、どちらかが約定したらもう一方をキャンセルする仕組み)については、exchange.has['createOCOOrder'] で対応確認をしてください。現時点ではBinanceなど一部の取引所のみ対応しています。


残高・ポジション管理の実装

スポット残高の取得

balance = exchange.fetch_balance()

# free: 注文に使用可能な残高
# used: 注文で拘束中の残高
# total: free + used
for currency in ['BTC', 'USDT', 'ETH']:
    b = balance.get(currency, {})
    print(f"{currency}: 利用可能={b.get('free', 0):.6f}, 拘束中={b.get('used', 0):.6f}")

先物ポジションの確認

positions = exchange.fetch_positions(['BTC/USDT:USDT'])
for pos in positions:
    if pos['contracts'] > 0:
        print(f"側面: {pos['side']}, 数量: {pos['contracts']}, "
              f"未実現PnL: {pos['unrealizedPnl']}, レバレッジ: {pos['leverage']}x")

基本的なトレード戦略を実装する

戦略① 移動平均クロス(MA Cross)

ゴールデンクロス(短期MAが長期MAを上抜け)で買い、デッドクロスで売るシンプルな戦略です。

import pandas as pd

def fetch_ohlcv_df(exchange, symbol, timeframe='1h', limit=100):
    ohlcv = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit)
    df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    return df

def ma_cross_signal(df, short=7, long=25):
    df['ma_short'] = df['close'].rolling(short).mean()
    df['ma_long'] = df['close'].rolling(long).mean()
    # ゴールデンクロス: short が long を上抜けた瞬間
    df['signal'] = 0
    df.loc[(df['ma_short'] > df['ma_long']) & (df['ma_short'].shift(1) <= df['ma_long'].shift(1)), 'signal'] = 1
    df.loc[(df['ma_short'] < df['ma_long']) & (df['ma_short'].shift(1) >= df['ma_long'].shift(1)), 'signal'] = -1
    return df

# 実行例
df = fetch_ohlcv_df(exchange, 'BTC/USDT')
df = ma_cross_signal(df)
latest_signal = df['signal'].iloc[-1]

if latest_signal == 1:
    exchange.create_market_buy_order('BTC/USDT', 0.001)
    print("ゴールデンクロス → 買い注文")
elif latest_signal == -1:
    exchange.create_market_sell_order('BTC/USDT', 0.001)
    print("デッドクロス → 売り注文")

戦略② RSIによる売買判断

RSI(Relative Strength Index)が70超で過買い(売りシグナル)、30未満で過売り(買いシグナル)と判断します。

def calc_rsi(series, period=14):
    delta = series.diff()
    gain = delta.clip(lower=0).rolling(period).mean()
    loss = (-delta.clip(upper=0)).rolling(period).mean()
    rs = gain / loss
    return 100 - (100 / (1 + rs))

df['rsi'] = calc_rsi(df['close'])
rsi_latest = df['rsi'].iloc[-1]

if rsi_latest < 30:
    print(f"RSI={rsi_latest:.1f} → 過売り、買いシグナル")
    exchange.create_market_buy_order('BTC/USDT', 0.001)
elif rsi_latest > 70:
    print(f"RSI={rsi_latest:.1f} → 過買い、売りシグナル")
    exchange.create_market_sell_order('BTC/USDT', 0.001)

戦略③ グリッドトレード

価格帯に等間隔で指値を並べ、相場の往来を収益化する戦略です。

def setup_grid(exchange, symbol, center_price, grid_count=5, grid_pct=0.01, amount=0.001):
    orders = []
    for i in range(1, grid_count + 1):
        buy_price = round(center_price * (1 - grid_pct * i), 2)
        sell_price = round(center_price * (1 + grid_pct * i), 2)
        buy_order = exchange.create_limit_buy_order(symbol, amount, buy_price)
        sell_order = exchange.create_limit_sell_order(symbol, amount, sell_price)
        orders.extend([buy_order, sell_order])
        print(f"グリッド{i}: 買い={buy_price}, 売り={sell_price}")
    return orders

ticker = exchange.fetch_ticker('BTC/USDT')
grid_orders = setup_grid(exchange, 'BTC/USDT', ticker['last'])

エラーハンドリングとリスク管理

実際の開発現場では、エラーハンドリングなしの自動売買は絶対に本番稼働させてはいけません。

CCXTでよく発生する例外と対処法

例外クラス 発生原因 対処法
InsufficientFunds 残高不足 注文量を再計算して再試行
InvalidOrder パラメータ不正(ロットサイズ違反等) exchange.marketsでlot/tick確認
NetworkError 通信断 指数バックオフでリトライ
RateLimitExceeded API制限超過 enableRateLimit=Trueを必ず設定
import ccxt, time, logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')

def safe_create_order(exchange, symbol, order_type, side, amount, price=None, max_retries=3):
    for attempt in range(max_retries):
        try:
            if order_type == 'market':
                return exchange.create_order(symbol, 'market', side, amount)
            else:
                return exchange.create_order(symbol, 'limit', side, amount, price)
        except ccxt.InsufficientFunds as e:
            logging.error(f"残高不足: {e}")
            return None
        except ccxt.NetworkError as e:
            wait = 2 ** attempt
            logging.warning(f"通信エラー({attempt+1}回目)、{wait}秒後リトライ: {e}")
            time.sleep(wait)
        except ccxt.BaseError as e:
            logging.error(f"注文エラー: {e}")
            return None
    return None

ポジションサイジング

1回の注文でリスクにさらす金額を制御することが、長期的な生存に直結します。

def calc_position_size(balance_usdt, risk_pct, entry_price, stop_price):
    """
    固定比率法:総資産の risk_pct% をリスクにさらす最大数量を算出
    """
    risk_amount = balance_usdt * risk_pct / 100
    price_diff = abs(entry_price - stop_price)
    size = risk_amount / price_diff
    return round(size, 6)

# 例:残高10,000 USDT、リスク1%、エントリー30,000、ストップ29,700
size = calc_position_size(10000, 1, 30000, 29700)
print(f"最大注文数量: {size} BTC")  # → 0.333333 BTC

本番環境への移行前チェックリスト

サンドボックスで動作確認が取れたら、本番移行前に以下を必ず確認しましょう。

  • APIキーの出金権限をOFFにする(読み取り・取引のみ許可)
  • set_sandbox_mode(True) を削除し、本番APIキーに差し替え
  • exchange.markets で最小注文数量(minAmount)・ティックサイズを確認
  • 取引手数料(exchange.fetch_trading_fee(symbol))を計算式に組み込む
  • APSchedulercron による定期実行を設定
  • 注文履歴をCSVまたはDBに記録するログ機能を実装
# APSchedulerによる1時間ごとの戦略実行例
from apscheduler.schedulers.blocking import BlockingScheduler

scheduler = BlockingScheduler()

@scheduler.scheduled_job('interval', hours=1)
def run_strategy():
    logging.info("戦略実行開始")
    df = fetch_ohlcv_df(exchange, 'BTC/USDT')
    df = ma_cross_signal(df)
    # ... 注文ロジック ...

scheduler.start()

まとめ ― 第2回で学んだこと

本記事では、CCXTを使った注文実行の基礎から実践的な戦略実装まで、段階的に解説しました。

学習項目 内容
注文の基本 market / limit / stop注文の実装
注文管理 ポーリング・キャンセル・一覧取得
残高管理 スポット残高・先物ポジションの確認
戦略実装 MAクロス・RSI・グリッドトレード
安全設計 エラーハンドリング・ポジションサイジング

次回の第3回では、**「バックテストとパラメータ最適化」**をテーマに、backtesting.pyvectorbt を使って戦略の有効性を過去データで検証する方法を解説します。「動くコードは書けたけれど、本当に儲かるのか?」という疑問に答えていきます。

⚠️ 再度の免責事項:本記事で紹介するコードおよび戦略は教育目的のものです。実際の取引で生じた損益について、筆者は一切の責任を負いません。


参考リンク・関連資料

関連記事

CCXTで作る仮想通貨自動取引ボット:環境構築から本番運用まで完全解説

CCXTで作る仮想通貨自動取引ボット:環境構築から本番運用まで完全解説

CCXTを使った仮想通貨自動取引ボットの作り方を完全解説。Python環境構築からAPIキーの安全な管理、市場データ取得まで、初心者から中級者向けにステップバイステップで紹介します。

CCXTを使って仮想通貨のトレードをしてみる(第4回):バックテストから本番運用へ

CCXTを使って仮想通貨のトレードをしてみる(第4回):バックテストから本番運用へ

CCXTを使った仮想通貨自動売買ボット構築シリーズ第4回。バックテストの最終検証からペーパートレード、本番運用への安全な移行手順を実装コード付きで解説します。

コメント

0/2000