⚠️【非推奨】FIX API入門③|Python注文送信→ Python cBotを使おう
🚨 重要:FIX APIは非推奨です
この記事で紹介しているFIX APIは、以下の理由から非推奨です:
- 😱 17個のハマりポイントがあり、ドキュメントに書いてない罠が多すぎる
- 💀 実装に1週間以上かかる(Python cBotなら数時間)
- 🤯 800行以上のコードが必要(Python cBotなら数十行)
- ❌ コントラクトサイズ等の情報が取得できない
🐍 代わりにPython cBotを使いましょう!
cTrader 5.4以降、ネイティブPythonサポートが追加されました。
👉 Python cBot入門ガイド - 1行で注文できる世界へ
👉 cTrader 5.4 Python完全ガイド - 環境構築から実践まで
以下の内容は参考資料として残しています。FIX APIをどうしても使いたい方のみお読みください。
😤 「注文送るだけでしょ?」
ログオンが通った。
「あとは注文を送るだけ!」
# 0.01ロットでゴールドを買うnew_order("XAUUSD", "BUY", 0.01)…NOT_ENOUGH_MONEY?
口座に250万円あるのに??
🎯 この記事でできるようになること
- NewOrderSingle(35=D) で新規注文
- ExecutionReport(35=8) の約定確認
- 商品別のOrderQty計算
- ポジション決済
失敗談付きで完全解説。
📋 注文に必要な情報
シンボルID(数値!)
文字列「XAUUSD」では動かない。数値IDが必要。
| シンボル | 文字列 | ID(例) |
|---|---|---|
| ユーロドル | EURUSD | 1 |
| ポンドドル | GBPUSD | 2 |
| ドル円 | USDJPY | 3 |
| ゴールド | XAUUSD | 41 |
ブローカーによってIDが違う!
cTrader Web → 取引 → シンボル詳細で「FIX通貨ペアID」を確認。
ハマりポイント①: シンボルを文字列で指定
# ❌ 文字列(動かない)f"55=XAUUSD{SOH}"
# ✅ 数値IDf"55=41{SOH}"📝 NewOrderSingle(35=D)の構造
必須フィールド
| タグ | 名前 | 値 |
|---|---|---|
| 35 | MsgType | D(NewOrderSingle) |
| 11 | ClOrdID | 一意な注文ID |
| 55 | Symbol | シンボルID(数値) |
| 54 | Side | 1=BUY, 2=SELL |
| 38 | OrderQty | 数量(重要!) |
| 40 | OrdType | 1=成行, 2=指値 |
| 60 | TransactTime | UTC時刻 |
オプションフィールド
| タグ | 名前 | 値 |
|---|---|---|
| 44 | Price | 指値価格(OrdType=2時) |
| 99 | StopPx | 逆指値価格 |
| 59 | TimeInForce | 1=GTC, 3=IOC, 4=FOK |
😱 OrderQty(タグ38)の罠
ここが最大のハマりポイント!
間違いやすい考え方
# 「0.01ロット買いたい」
# ❌ ロット数をそのまま(動かない)f"38=0.01{SOH}"
# ❌ FXの計算式を全商品に適用(危険!)qty = 0.01 * 100000 # = 1000f"38=1000{SOH}"商品ごとに単位が違う!
| 商品 | 基準単位 | 1ロット | 0.01ロット | OrderQty |
|---|---|---|---|---|
| FX | 通貨 | 100,000 | 1,000 | 1000 |
| ゴールド | オンス | 100 | 1 | 1 |
| シルバー | オンス | 5,000 | 50 | 50 |
| 原油 | バレル | 1,000 | 10 | 10 |
実際に何が起きたか
【送信した内容】38=1000(1000オンス)
【ブローカーの解釈】1000オンス ÷ 100オンス/ロット = 10ロット
【必要証拠金】$4,450 × 1000オンス ÷ 100(レバレッジ) = $44,500約667万円
【口座残高】250万円
【結果】NOT_ENOUGH_MONEY 💀0.01ロットのつもりが10ロットになっていた!
🔧 正しいOrderQty計算
def calculate_order_qty(symbol_id: int, lot_size: float) -> int: """商品タイプに応じたOrderQty計算"""
# ゴールド (XAUUSD) - ID=41 if symbol_id == 41: return int(lot_size * 100) # 0.01 lot = 1
# シルバー (XAGUSD) - ID=42 elif symbol_id == 42: return int(lot_size * 5000) # 0.01 lot = 50
# 原油 (USOIL) - ID=43 elif symbol_id == 43: return int(lot_size * 1000) # 0.01 lot = 10
# FX通貨ペア (デフォルト) else: return int(lot_size * 100000) # 0.01 lot = 1000ハマりポイント②: コントラクトサイズが取得できない
FIX APIではコントラクトサイズを取得できない!
"Unfortunately this information is not available in FIX API. You can consider Open API instead."
事前にブローカーの仕様を調べるしかない。
🎯 注文送信の完全コード
def build_new_order(symbol_id: int, side: str, lot_size: float, order_type: int = 1): """ NewOrderSingle (35=D) を組み立て
Args: symbol_id: シンボルID(数値) side: "BUY" or "SELL" lot_size: ロット数(0.01など) order_type: 1=成行, 2=指値 """ global seq_num
# ClOrdID(一意な注文ID) cl_ord_id = f"{symbol_id}_{int(time.time() * 1000)}"
# Side: 1=BUY, 2=SELL side_code = "1" if side.upper() == "BUY" else "2"
# OrderQty(商品別計算) qty = calculate_order_qty(symbol_id, lot_size)
# ボディ組み立て body = ( f"35=D{SOH}" f"49={SENDER_COMP_ID}{SOH}" f"56={TARGET_COMP_ID}{SOH}" f"57=TRADE{SOH}" f"34={seq_num}{SOH}" f"52={get_utc_timestamp()}{SOH}" f"11={cl_ord_id}{SOH}" # ClOrdID f"55={symbol_id}{SOH}" # Symbol(数値) f"54={side_code}{SOH}" # Side f"38={qty}{SOH}" # OrderQty(重要!) f"40={order_type}{SOH}" # OrdType f"60={get_utc_timestamp()}{SOH}" # TransactTime )
# ヘッダー + チェックサム body_length = len(body.encode('ascii')) header = f"8=FIX.4.4{SOH}9={body_length}{SOH}" msg_without_checksum = header + body checksum = calc_checksum(msg_without_checksum)
seq_num += 1 return msg_without_checksum + checksum, cl_ord_id使用例
# ゴールド0.01ロット BUYmsg, order_id = build_new_order(symbol_id=41, side="BUY", lot_size=0.01)send_message(trade_sock, msg)
# GBPUSD 0.05ロット SELLmsg, order_id = build_new_order(symbol_id=2, side="SELL", lot_size=0.05)send_message(trade_sock, msg)📨 ExecutionReport(35=8)の解析
注文送信後に返ってくる
← 8=FIX.4.4|...|35=8|11=41_1704520800000|150=0|39=0|...← 8=FIX.4.4|...|35=8|11=41_1704520800000|150=F|39=2|31=4452.02|721=1001179|...重要なタグ
| タグ | 名前 | 意味 |
|---|---|---|
| 11 | ClOrdID | 送信した注文ID |
| 150 | ExecType | 実行タイプ |
| 39 | OrdStatus | 注文ステータス |
| 31 | LastPx | 約定価格 |
| 32 | LastQty | 約定数量 |
| 721 | PosMaintRptID | ポジションID |
| 58 | Text | エラーメッセージ |
ExecType(150)の値
| 値 | 意味 | 説明 |
|---|---|---|
| 0 | New | 注文受付 |
| 4 | Canceled | キャンセル |
| 8 | Rejected | 拒否 |
| F | Trade | 約定! |
OrdStatus(39)の値
| 値 | 意味 |
|---|---|
| 0 | New(新規) |
| 1 | PartiallyFilled(部分約定) |
| 2 | Filled(全約定) |
| 4 | Canceled |
| 8 | Rejected |
約定確認コード
def parse_execution_report(msg: str) -> dict: """ExecutionReportを解析""" fields = {} for part in msg.split(SOH): if "=" in part: tag, value = part.split("=", 1) fields[tag] = value
exec_type = fields.get("150", "") ord_status = fields.get("39", "")
result = { "cl_ord_id": fields.get("11", ""), "exec_type": exec_type, "ord_status": ord_status, "price": fields.get("31", ""), "qty": fields.get("32", ""), "position_id": fields.get("721", ""), # ← 重要! "text": fields.get("58", ""), }
# 状態判定 if exec_type == "F" and ord_status == "2": result["status"] = "FILLED" print(f"🎉 約定成功!価格: {result['price']}, ポジションID: {result['position_id']}") elif exec_type == "8" or ord_status == "8": result["status"] = "REJECTED" print(f"❌ 拒否: {result['text']}") elif exec_type == "0": result["status"] = "NEW" print(f"📝 注文受付: {result['cl_ord_id']}")
return result❌ よくあるエラーと対処
NOT_ENOUGH_MONEY
58=NOT_ENOUGH_MONEY:Not enough funds原因:
- 本当に証拠金不足
- OrderQtyの桁違い(これが多い!)
対処: calculate_order_qty()で商品別に計算
INVALID_VOLUME
58=INVALID_VOLUME原因: Volume Stepの倍数じゃない
対処: FXなら1000の倍数、ゴールドなら1の倍数
MARKET_CLOSED
58=MARKET_CLOSED原因: 市場が閉まっている
対処: 取引時間内に送信
🔄 ポジション決済
決済の仕組み
cTrader FIX APIでは、反対売買で決済する。
BUYポジションを決済 → SELL注文を送信SELLポジションを決済 → BUY注文を送信決済に必要なもの
- ポジションID(721): ExecutionReportで取得
- 数量: 保有数量と同じ
- 反対のSide: BUY↔SELL
決済コード
def build_close_order(symbol_id: int, position_id: str, qty: int, current_side: str): """ 決済注文を組み立て
Args: symbol_id: シンボルID position_id: 決済するポジションID qty: 決済数量(OrderQty形式) current_side: 現在のポジション方向("BUY" or "SELL") """ global seq_num
# 反対売買 close_side = "2" if current_side.upper() == "BUY" else "1"
cl_ord_id = f"close_{position_id}_{int(time.time() * 1000)}"
body = ( f"35=D{SOH}" f"49={SENDER_COMP_ID}{SOH}" f"56={TARGET_COMP_ID}{SOH}" f"57=TRADE{SOH}" f"34={seq_num}{SOH}" f"52={get_utc_timestamp()}{SOH}" f"11={cl_ord_id}{SOH}" f"55={symbol_id}{SOH}" f"54={close_side}{SOH}" f"38={qty}{SOH}" f"40=1{SOH}" # 成行 f"60={get_utc_timestamp()}{SOH}" f"721={position_id}{SOH}" # ← ポジションIDを指定 )
body_length = len(body.encode('ascii')) header = f"8=FIX.4.4{SOH}9={body_length}{SOH}" msg_without_checksum = header + body checksum = calc_checksum(msg_without_checksum)
seq_num += 1 return msg_without_checksum + checksum使用例
# ゴールド0.01ロットのBUYポジション(ID=1001179)を決済close_msg = build_close_order( symbol_id=41, position_id="1001179", qty=1, # 0.01ロット = 1(ゴールド) current_side="BUY")send_message(trade_sock, close_msg)📊 ポジション照会
RequestForPositions(35=AN)
def build_position_request(): """ポジション照会リクエスト""" global seq_num
pos_req_id = str(int(time.time()))
body = ( f"35=AN{SOH}" f"49={SENDER_COMP_ID}{SOH}" f"56={TARGET_COMP_ID}{SOH}" f"57=TRADE{SOH}" f"34={seq_num}{SOH}" f"52={get_utc_timestamp()}{SOH}" f"710={pos_req_id}{SOH}" # これだけ! )
body_length = len(body.encode('ascii')) header = f"8=FIX.4.4{SOH}9={body_length}{SOH}" msg_without_checksum = header + body checksum = calc_checksum(msg_without_checksum)
seq_num += 1 return msg_without_checksum + checksumハマりポイント③: 非対応タグの使用
# ❌ 全部非対応!f"1={ACCOUNT}{SOH}" # Accountf"263=1{SOH}" # SubscriptionRequestTypef"453=1{SOH}" # NoPartyIDsf"724=0{SOH}" # PosReqType
# ✅ タグ710だけf"710={pos_req_id}{SOH}"cTrader FIX APIは標準FIXタグの多くが非対応。
🎯 注文〜決済の完全フロー
# 1. ログオンlogon_msg = build_logon_message()send_message(sock, logon_msg)response = receive_message(sock)
# 2. ログオン確認if "35=A" not in response: print("❌ ログオン失敗") return
# 3. 注文送信order_msg, order_id = build_new_order( symbol_id=41, # XAUUSD side="BUY", lot_size=0.01)send_message(sock, order_msg)
# 4. 約定確認(受信ループ)position_id = Nonewhile True: msg = receive_message(sock) if "35=8" in msg: result = parse_execution_report(msg) if result["status"] == "FILLED": position_id = result["position_id"] print(f"✅ 約定!ポジションID: {position_id}") break
# 5. 決済(必要なタイミングで)if position_id: close_msg = build_close_order( symbol_id=41, position_id=position_id, qty=1, current_side="BUY" ) send_message(sock, close_msg)📝 まとめ
注文送信のポイント
- シンボルは数値ID で指定
- OrderQtyは商品別に計算 が必須
- ExecutionReport で約定確認
- ポジションID(721) を保存
- 決済は反対売買 + ポジションID指定
OrderQty計算式
# FX通貨ペアqty = int(lot_size * 100000) # 0.01 = 1000
# ゴールドqty = int(lot_size * 100) # 0.01 = 1
# シルバーqty = int(lot_size * 5000) # 0.01 = 50
# 原油qty = int(lot_size * 1000) # 0.01 = 10🔥 次回予告
【FIX API入門④】15個のハマりポイント総まとめ
- ログオンで踏む3個
- 注文で踏む5個
- 決済で踏む2個
- その他5個
全部踏んで学んだ血の教訓集。
📚 関連記事
FIX API完全版シリーズ(2026年版・Fundora検証済み)
- FIX API完全版① 2セッション構成とパスワードの真実
- FIX API完全版② リアルタイム価格取得編
- FIX API完全版③ ポジション・オーダー照会編
- FIX API完全版④ エントリー・決済完全実装
- FIX API完全版⑤ 全機能クラスの実装
その他
0.01ロットを10ロットにされた時の絶望感。 😱
これを読んだあなたは、同じ轍を踏まないでくれ。
🎁おすすめツール・サービス
※上記リンクにはプロモーション(広告)が含まれています
量子相場謀略|ホソノP
ブログでは書けない本音のトレード戦略とEA開発の裏側を公開。 データに基づいた検証で、再現性のある手法だけをお届けします。