🔥 cTrader Open APIで自動売買!Pythonで注文を出す完全ガイド【Market/Limit/SL/TP対応】
🔥 cTrader Open APIで自動売買!Pythonで注文を出す完全ガイド
ついに注文を出す時が来た!
ここまでの記事で:
- ✅ Open APIへの接続
- ✅ リアルタイム価格の取得
ができるようになりました。
そして今回、ついに実際に注文を出します!
MQLで言うところの OrderSend() ですね。ワクワクしますね 😆
⚠️ 重要: 必ずデモ口座でテストしてから本番に移行してください!
注文の種類を理解する
Open APIで出せる注文タイプ:
| 注文タイプ | MQL相当 | 説明 |
|---|---|---|
| MARKET | OP_BUY, OP_SELL | 成行注文(即時約定) |
| LIMIT | OP_BUYLIMIT, OP_SELLLIMIT | 指値注文 |
| STOP | OP_BUYSTOP, OP_SELLSTOP | 逆指値注文 |
| STOP_LIMIT | - | ストップリミット注文 |
Step 1: 成行注文(Market Order)
基本的な成行注文
from ctrader_open_api.messages.OpenApiMessages_pb2 import *
def sendMarketOrder(symbol_id, volume, is_buy): """ 成行注文を送信
Args: symbol_id: シンボルID(例:1 = EURUSD) volume: ロット数 × 100(0.01ロット = 1000) is_buy: True=買い, False=売り """ request = ProtoOANewOrderReq() request.ctidTraderAccountId = ACCOUNT_ID request.symbolId = symbol_id request.orderType = ProtoOAOrderType.MARKET request.tradeSide = ProtoOATradeSide.BUY if is_buy else ProtoOATradeSide.SELL request.volume = volume # 0.01ロット = 1000
deferred = client.send(request) deferred.addCallback(onOrderResult) deferred.addErrback(onOrderError)
def onOrderResult(result): print("✅ 注文成功!") print(f" ポジションID: {result.position.positionId}") print(f" 約定価格: {result.position.price}")
def onOrderError(failure): print(f"❌ 注文失敗: {failure}")🚨 超重要:ボリュームの単位
MQLでは 0.01 と書けばOKでしたが、Open APIでは整数値で指定します:
| ロット数 | Open APIのvolume値 |
|---|---|
| 0.01 | 1,000 |
| 0.1 | 10,000 |
| 1.0 | 100,000 |
# 例:0.01ロットを買うvolume = 1000 # 0.01 × 100000 = 1000sendMarketOrder(symbol_id=1, volume=volume, is_buy=True)Step 2: SL/TP付きの成行注文
実戦では、SL(ストップロス)とTP(テイクプロフィット)が必須ですよね。
def sendMarketOrderWithSLTP(symbol_id, volume, is_buy, sl_pips=None, tp_pips=None, current_price=None): """ SL/TP付き成行注文
Args: symbol_id: シンボルID volume: ボリューム(0.01ロット = 1000) is_buy: True=買い, False=売り sl_pips: SL幅(pips) tp_pips: TP幅(pips) current_price: 現在価格(SL/TP計算用) """ request = ProtoOANewOrderReq() request.ctidTraderAccountId = ACCOUNT_ID request.symbolId = symbol_id request.orderType = ProtoOAOrderType.MARKET request.tradeSide = ProtoOATradeSide.BUY if is_buy else ProtoOATradeSide.SELL request.volume = volume
# SL/TPを設定 if sl_pips and current_price: pip_value = 0.0001 # EURUSDの場合 if is_buy: sl_price = current_price - (sl_pips * pip_value) else: sl_price = current_price + (sl_pips * pip_value) request.stopLoss = sl_price
if tp_pips and current_price: pip_value = 0.0001 if is_buy: tp_price = current_price + (tp_pips * pip_value) else: tp_price = current_price - (tp_pips * pip_value) request.takeProfit = tp_price
deferred = client.send(request) deferred.addCallback(onOrderResult) return deferred
# 使用例:20pips SL、40pips TPで買いsendMarketOrderWithSLTP( symbol_id=1, # EURUSD volume=1000, # 0.01ロット is_buy=True, sl_pips=20, tp_pips=40, current_price=1.10500)Step 3: 指値注文(Limit Order)
指値注文は、指定した価格で約定させる注文です。
def sendLimitOrder(symbol_id, volume, is_buy, target_price, sl_price=None, tp_price=None): """ 指値注文を送信
Args: symbol_id: シンボルID volume: ボリューム is_buy: True=買い指値, False=売り指値 target_price: 指値価格 sl_price: ストップロス価格(オプション) tp_price: テイクプロフィット価格(オプション) """ request = ProtoOANewOrderReq() request.ctidTraderAccountId = ACCOUNT_ID request.symbolId = symbol_id request.orderType = ProtoOAOrderType.LIMIT request.tradeSide = ProtoOATradeSide.BUY if is_buy else ProtoOATradeSide.SELL request.volume = volume request.limitPrice = target_price
if sl_price: request.stopLoss = sl_price if tp_price: request.takeProfit = tp_price
deferred = client.send(request) deferred.addCallback(onPendingOrderResult) return deferred
def onPendingOrderResult(result): print("✅ 指値注文を設置しました!") print(f" 注文ID: {result.order.orderId}") print(f" 価格: {result.order.limitPrice}")
# 使用例:1.10000で買い指値sendLimitOrder( symbol_id=1, volume=1000, is_buy=True, target_price=1.10000, sl_price=1.09800, tp_price=1.10400)Step 4: 逆指値注文(Stop Order)
ブレイクアウト戦略で使う逆指値注文です。
def sendStopOrder(symbol_id, volume, is_buy, stop_price, sl_price=None, tp_price=None): """ 逆指値注文(ストップ注文)を送信
Args: symbol_id: シンボルID volume: ボリューム is_buy: True=買い逆指値, False=売り逆指値 stop_price: トリガー価格 """ request = ProtoOANewOrderReq() request.ctidTraderAccountId = ACCOUNT_ID request.symbolId = symbol_id request.orderType = ProtoOAOrderType.STOP request.tradeSide = ProtoOATradeSide.BUY if is_buy else ProtoOATradeSide.SELL request.volume = volume request.stopPrice = stop_price
if sl_price: request.stopLoss = sl_price if tp_price: request.takeProfit = tp_price
deferred = client.send(request) deferred.addCallback(onPendingOrderResult) return deferred
# 使用例:1.11000を上抜けたら買いsendStopOrder( symbol_id=1, volume=1000, is_buy=True, stop_price=1.11000, sl_price=1.10800, tp_price=1.11400)Step 5: ポジション決済
利確・損切り、または手動決済の方法です。
全決済
def closePosition(position_id, volume): """ ポジションを決済
Args: position_id: 決済するポジションのID volume: 決済数量(全決済なら元のvolume) """ request = ProtoOAClosePositionReq() request.ctidTraderAccountId = ACCOUNT_ID request.positionId = position_id request.volume = volume
deferred = client.send(request) deferred.addCallback(onCloseResult) return deferred
def onCloseResult(result): print("✅ 決済完了!") print(f" 決済価格: {result.position.price}")部分決済
# 元のポジションが0.1ロット(volume=10000)の場合# 半分だけ決済closePosition(position_id=123456, volume=5000) # 0.05ロット決済Step 6: SL/TPの変更
ポジション保有中にSL/TPを変更する方法です。
def modifySLTP(position_id, new_sl=None, new_tp=None): """ ポジションのSL/TPを変更
Args: position_id: ポジションID new_sl: 新しいSL価格 new_tp: 新しいTP価格 """ request = ProtoOAAmendPositionSLTPReq() request.ctidTraderAccountId = ACCOUNT_ID request.positionId = position_id
if new_sl: request.stopLoss = new_sl if new_tp: request.takeProfit = new_tp
deferred = client.send(request) deferred.addCallback(lambda r: print("✅ SL/TP変更完了!")) return deferred
# 使用例:トレーリングストップ的な使い方modifySLTP(position_id=123456, new_sl=1.10200, new_tp=1.10800)Step 7: 保有ポジション一覧の取得
現在の保有ポジションを確認します。
def getPositions(): """保有ポジション一覧を取得""" request = ProtoOAReconcileReq() request.ctidTraderAccountId = ACCOUNT_ID
deferred = client.send(request) deferred.addCallback(onPositionsList) return deferred
def onPositionsList(result): print(f"\n📊 保有ポジション: {len(result.position)} 件") print("=" * 60)
for pos in result.position: side = "BUY" if pos.tradeData.tradeSide == ProtoOATradeSide.BUY else "SELL" volume_lot = pos.tradeData.volume / 100000
print(f"🔹 ポジションID: {pos.positionId}") print(f" シンボルID: {pos.tradeData.symbolId}") print(f" 方向: {side}") print(f" ロット: {volume_lot:.2f}") print(f" エントリー価格: {pos.price}")
if pos.HasField('stopLoss'): print(f" SL: {pos.stopLoss}") if pos.HasField('takeProfit'): print(f" TP: {pos.takeProfit}")
print()完全版コード:自動売買ボット
全部を組み合わせた、実際に動く自動売買ボットです!
from ctrader_open_api import Client, Protobuf, TcpProtocol, EndPointsfrom ctrader_open_api.messages.OpenApiCommonMessages_pb2 import *from ctrader_open_api.messages.OpenApiMessages_pb2 import *from twisted.internet import reactor, taskimport time
# ============================================# 設定# ============================================CLIENT_ID = "あなたのClient ID"CLIENT_SECRET = "あなたのClient Secret"ACCESS_TOKEN = "取得したアクセストークン"ACCOUNT_ID = 12345678
# トレード設定SYMBOL_ID = 1 # EURUSD(あなたの口座で確認してください)TRADE_VOLUME = 1000 # 0.01ロットSL_PIPS = 20TP_PIPS = 40
# ============================================
client = Client(EndPoints.PROTOBUF_LIVE_HOST, EndPoints.PROTOBUF_PORT, TcpProtocol)
# 状態管理current_bid = Nonecurrent_ask = Nonehas_position = False
def onConnected(client): print("🔗 接続成功!") request = ProtoOAApplicationAuthReq() request.clientId = CLIENT_ID request.clientSecret = CLIENT_SECRET deferred = client.send(request) deferred.addCallback(onAppAuth)
def onAppAuth(result): print("✅ アプリ認証完了") request = ProtoOAAccountAuthReq() request.ctidTraderAccountId = ACCOUNT_ID request.accessToken = ACCESS_TOKEN deferred = client.send(request) deferred.addCallback(onAccountAuth)
def onAccountAuth(result): print("✅ アカウント認証完了")
# 現在のポジションを確認 request = ProtoOAReconcileReq() request.ctidTraderAccountId = ACCOUNT_ID deferred = client.send(request) deferred.addCallback(onReconcile)
def onReconcile(result): global has_position has_position = len(result.position) > 0 print(f"📊 現在のポジション数: {len(result.position)}")
# 価格をサブスクライブ request = ProtoOASubscribeSpotsReq() request.ctidTraderAccountId = ACCOUNT_ID request.symbolId.append(SYMBOL_ID) deferred = client.send(request) deferred.addCallback(lambda r: print("📡 価格監視を開始!"))
def sendBuyOrder(): """買い注文を送信""" global has_position, current_ask
if has_position: print("⏸️ 既にポジションがあるため注文をスキップ") return
if not current_ask: print("⏸️ 価格データがまだ取得できていません") return
print(f"\n🔥 買い注文を送信中...")
request = ProtoOANewOrderReq() request.ctidTraderAccountId = ACCOUNT_ID request.symbolId = SYMBOL_ID request.orderType = ProtoOAOrderType.MARKET request.tradeSide = ProtoOATradeSide.BUY request.volume = TRADE_VOLUME
# SL/TP設定 pip_value = 0.0001 # EURUSD用 request.stopLoss = current_ask - (SL_PIPS * pip_value) request.takeProfit = current_ask + (TP_PIPS * pip_value)
deferred = client.send(request) deferred.addCallback(onOrderResult) deferred.addErrback(onOrderError)
def sendSellOrder(): """売り注文を送信""" global has_position, current_bid
if has_position: print("⏸️ 既にポジションがあるため注文をスキップ") return
if not current_bid: print("⏸️ 価格データがまだ取得できていません") return
print(f"\n🔥 売り注文を送信中...")
request = ProtoOANewOrderReq() request.ctidTraderAccountId = ACCOUNT_ID request.symbolId = SYMBOL_ID request.orderType = ProtoOAOrderType.MARKET request.tradeSide = ProtoOATradeSide.SELL request.volume = TRADE_VOLUME
pip_value = 0.0001 request.stopLoss = current_bid + (SL_PIPS * pip_value) request.takeProfit = current_bid - (TP_PIPS * pip_value)
deferred = client.send(request) deferred.addCallback(onOrderResult) deferred.addErrback(onOrderError)
def onOrderResult(result): global has_position has_position = True print("✅ 注文が約定しました!") print(f" ポジションID: {result.position.positionId}") print(f" 約定価格: {result.position.price}")
def onOrderError(failure): print(f"❌ 注文エラー: {failure}")
def onMessageReceived(client, message): global current_bid, current_ask
# 価格更新 if message.payloadType == 2131: # ProtoOASpotEvent spot = Protobuf.extract(message) if spot.symbolId == SYMBOL_ID: if spot.HasField('bid'): current_bid = spot.bid / 100000 if spot.HasField('ask'): current_ask = spot.ask / 100000
timestamp = time.strftime("%H:%M:%S") print(f"[{timestamp}] Bid: {current_bid:.5f} Ask: {current_ask:.5f}", end='\r')
# ポジションクローズの検知 elif message.payloadType == 2126: # ProtoOAExecutionEvent global has_position event = Protobuf.extract(message) if event.executionType == ProtoOAExecutionType.ORDER_FILLED: pass # 新規約定 elif event.executionType == ProtoOAExecutionType.ORDER_CANCELLED: pass # キャンセル
# コマンド入力用def checkInput(): """標準入力をチェック(簡易的なコマンド処理)""" # 実際のボットでは、条件に基づいて自動で発注します pass
# コールバック設定client.setConnectedCallback(onConnected)client.setDisconnectedCallback(lambda c, r: print(f"\n切断: {r}"))client.setMessageReceivedCallback(onMessageReceived)
print("=" * 60)print("🤖 cTrader Open API 自動売買ボット")print("=" * 60)print(f" シンボルID: {SYMBOL_ID}")print(f" ロット: {TRADE_VOLUME / 100000:.2f}")print(f" SL: {SL_PIPS} pips")print(f" TP: {TP_PIPS} pips")print("=" * 60)print("\n接続中...\n")
client.startService()reactor.run()MQLとの比較:注文処理
| 操作 | MQL | Open API (Python) |
|---|---|---|
| 成行注文 | OrderSend(OP_BUY) | ProtoOANewOrderReq (MARKET) |
| 指値注文 | OrderSend(OP_BUYLIMIT) | ProtoOANewOrderReq (LIMIT) |
| 決済 | OrderClose() | ProtoOAClosePositionReq |
| SL/TP変更 | OrderModify() | ProtoOAAmendPositionSLTPReq |
| ロット指定 | 0.01 | 1000 |
| 価格指定 | 1.10500 | 1.10500(そのまま) |
エラーハンドリング
よくあるエラーと対処法:
「NOT_ENOUGH_MONEY」
def onOrderError(failure): error_message = str(failure) if "NOT_ENOUGH_MONEY" in error_message: print("❌ 証拠金不足です!ロット数を減らしてください") elif "SYMBOL_NOT_FOUND" in error_message: print("❌ シンボルIDが間違っています") elif "TRADING_DISABLED" in error_message: print("❌ 取引が無効です(市場クローズ or 口座制限)") else: print(f"❌ エラー: {error_message}")「INVALID_VOLUME」
ボリュームが口座の最小/最大ロットを超えていないか確認:
# シンボル詳細を取得して確認request = ProtoOASymbolByIdReq()request.ctidTraderAccountId = ACCOUNT_IDrequest.symbolId.append(SYMBOL_ID)注意点とベストプラクティス
1. 必ずデモ口座でテスト
# デモサーバーを使うHOST = EndPoints.PROTOBUF_DEMO_HOST # デモ# HOST = EndPoints.PROTOBUF_LIVE_HOST # ライブ(本番用)2. リスク管理を忘れずに
# 1トレードの最大リスクを口座の2%に制限account_balance = 10000 # ドルmax_risk = account_balance * 0.02 # 200ドルpip_value = 10 # 1ロットあたり1pipのドル価値sl_pips = 20
max_lots = max_risk / (sl_pips * pip_value) # 1.0ロット3. 重複注文を防ぐ
# フラグで管理if not has_position and not pending_order: sendBuyOrder()次のステップ
この記事で学んだこと:
- ✅ 成行注文の出し方
- ✅ SL/TP付き注文
- ✅ 指値・逆指値注文
- ✅ ポジション決済
- ✅ SL/TPの変更
- ✅ ポジション一覧取得
これで、Open APIで自動売買ができるようになりました!🎉
次の記事では、cTrader 5.4で導入されたネイティブPython cBotについて解説します。Open APIとの違いや使い分けも説明しますね。
まとめ
ついに注文が出せるようになりましたね!
MQLの OrderSend() に慣れていると、最初は「なんでこんなに複雑なの?」と思うかもしれません。
でも、この複雑さには理由があります:
- 非同期処理で複数の操作を同時に
- 詳細なエラー情報でデバッグが楽
- 柔軟な拡張性で何でも作れる
一度慣れてしまえば、MQLより快適に開発できるようになります。
さあ、あなたの自動売買ボットを作りましょう! 🤖
参考リンク
🎁おすすめツール・サービス
※上記リンクにはプロモーション(広告)が含まれています
量子相場謀略|ホソノP
ブログでは書けない本音のトレード戦略とEA開発の裏側を公開。 データに基づいた検証で、再現性のある手法だけをお届けします。