Web Lukes
📄

⚠️【非推奨】FIX API完全版②|リアルタイム価格取得→ Python cBotを使おう

公開日: 2026/1/8

🚨 重要: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をどうしても使いたい方のみお読みください。


😤「Bid/Askが取れない!」3時間の格闘

FIX APIで接続成功した!ログオンも通った!

...でも価格が取れない。

「MarketDataRequest送ったのにレスポンスがない」 「Rejectが返ってくるけど理由がわからない」 「QUOTEセッションって何?」

私も最初は意味不明でした。TRADEセッションでMarketDataRequestを送って「なんで動かんのや!」と叫んでましたから 😭

実は、cTrader FIX APIで価格データを取得するにはQUOTEセッションという別のセッションを使う必要があります。この記事ではその全てを解説します!


この記事で学べること

  1. QUOTEセッションとは何か(TRADEセッションとの違い)
  2. MarketDataRequestの正しい送り方
  3. レスポンスのパース方法(Bid/Ask抽出)
  4. スプレッド計算の実装
  5. 完全動作するPythonコード

前回の記事「FIX API完全版①」で2セッション構成を理解している前提で進めます。


QUOTEセッションの基礎知識

2セッションおさらい

cTrader FIX APIには2つのセッションがあります:

セッションポート用途
TRADE5212注文・ポジション管理
QUOTE5211価格データ(Bid/Ask)

超重要: 価格データはQUOTEセッションでしか取得できません!

TRADEセッションでMarketDataRequestを送っても無視されるか、リジェクトされます。

なぜ分離されているのか

Code
理由:
├── 負荷分散: 価格データは高頻度(秒間数十回更新)
├── セキュリティ: 価格は読み取り専用、注文は書き込み
└── 信頼性: 価格系が落ちても注文は継続可能

プロップファームやブローカーにとって、これは理にかなった設計です。


QUOTEセッションへの接続

ログオンメッセージ

TRADEセッションとほぼ同じですが、TargetSubIDをQUOTEに変更:

Python
def send_quote_logon():
"""QUOTEセッションにログオン"""
fields = [
"98=0", # EncryptMethod
"108=30", # HeartBtInt
"141=Y", # ResetSeqNumFlag
"553=5006639", # Username (口座番号)
"554=YourFIXPassword", # Password (FIX専用)
]
# ヘッダーのSubIDをQUOTEに!
header = {
"49": "live.fundoratrading.5006639", # SenderCompID
"56": "cServer", # TargetCompID
"57": "QUOTE", # TargetSubID ← これ重要!
"50": "QUOTE", # SenderSubID
}
msg = build_fix_message("A", fields, header)
quote_socket.send(msg)

接続シーケンス

Code
推奨接続順序:
1. TRADEセッション接続
2. TRADEログオン成功確認
3. QUOTEセッション接続
4. QUOTEログオン成功確認
5. マーケットデータ購読開始

TRADE→QUOTEの順番を守ると安定します。


MarketDataRequest(35=V)の送信

必須フィールド

QUOTEセッションにログオン成功したら、価格を取得したいシンボルを「購読」します:

Code
┌─────┬──────────────────────────┬───────────────────────────┐
│ Tag │ 名称 │ 値・説明 │
├─────┼──────────────────────────┼───────────────────────────┤
│ 262 │ MDReqID │ 一意のリクエストID │
│ 263 │ SubscriptionRequestType │ 1=購読, 2=購読解除 │
│ 264 │ MarketDepth │ 1 (Top of Book) │
│ 265 │ MDUpdateType │ 1 (Incremental Refresh) │
│ 267 │ NoMDEntryTypes │ 2 (Bid + Ask) │
│ 269 │ MDEntryType │ 0 (Bid) │
│ 269 │ MDEntryType │ 1 (Offer/Ask) │
│ 146 │ NoRelatedSym │ 1 │
│ 55 │ Symbol │ シンボルID(数値!) │
└─────┴──────────────────────────┴───────────────────────────┘

ハマりポイント:シンボルIDは数値

Python
# ❌ これは動かない
"55=XAUUSD"
# ✅ 正しい(数値IDを指定)
"55=41"

シンボルIDの調べ方は前回記事を参照してください。

Python実装

Python
def subscribe_market_data(symbol_id: int) -> str:
"""
マーケットデータを購読
Args:
symbol_id: シンボルID(数値)例: 41=XAUUSD
Returns:
MDReqID(レスポンス照合用)
"""
# 一意のリクエストID
md_req_id = f"MD{int(time.time())}"
fields = [
f"262={md_req_id}", # MDReqID
"263=1", # Subscribe (購読開始)
"264=1", # MarketDepth: Top of book
"265=1", # MDUpdateType: Incremental
"267=2", # NoMDEntryTypes: 2種類
"269=0", # MDEntryType: Bid
"269=1", # MDEntryType: Ask/Offer
"146=1", # NoRelatedSym: 1シンボル
f"55={symbol_id}", # Symbol: 数値ID
]
msg = build_fix_message("V", fields, "QUOTE")
send_to_quote_session(msg)
print(f"✅ マーケットデータ購読: Symbol ID={symbol_id}")
return md_req_id

購読解除

Python
def unsubscribe_market_data(symbol_id: int):
"""マーケットデータ購読解除"""
md_req_id = f"MDU{int(time.time())}"
fields = [
f"262={md_req_id}",
"263=2", # ← 2 = 購読解除
"264=1",
"265=1",
"267=2",
"269=0",
"269=1",
"146=1",
f"55={symbol_id}",
]
msg = build_fix_message("V", fields, "QUOTE")
send_to_quote_session(msg)

レスポンスのパース

2種類のレスポンス

購読後、サーバーから価格データが送られてきます:

MsgType名称説明
WMarketDataSnapshotFullRefresh最初の完全スナップショット
XMarketDataIncrementalRefresh以降の差分更新

どちらも同じ形式でBid/Ask価格が含まれています。

レスポンス構造

Code
35=W (または X)
├── 55 = シンボルID
├── 268 = NoMDEntries (エントリー数)
├── 269 = MDEntryType (0=Bid, 1=Ask)
├── 270 = MDEntryPx (価格)
├── 269 = MDEntryType (次のエントリー)
└── 270 = MDEntryPx (価格)

実際のレスポンス例

Code
8=FIX.4.4|9=143|35=W|49=cServer|56=live.fundoratrading.5006639|
34=5|52=20260107-03:45:12.123|55=41|262=MD1736222712|
268=2|269=0|270=2648.31|269=1|270=2648.61|10=187|

解読:

  • シンボルID: 41 (XAUUSD)
  • Bid: 2648.31
  • Ask: 2648.61
  • スプレッド: 30ポイント

Pythonパース実装

Python
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Optional
@dataclass
class Quote:
"""価格情報"""
symbol_id: int
bid: Optional[float] = None
ask: Optional[float] = None
timestamp: Optional[datetime] = None
@property
def spread(self) -> Optional[float]:
"""スプレッド(ポイント)"""
if self.bid is None or self.ask is None:
return None
return (self.ask - self.bid) * 100 # ポイント換算
def parse_market_data(raw_msg: str, quotes_store: dict) -> Optional[Quote]:
"""
MarketDataメッセージをパース
Args:
raw_msg: 生のFIXメッセージ
quotes_store: 価格保存用辞書 {symbol_id: Quote}
Returns:
更新されたQuoteオブジェクト
"""
SOH = "\x01"
# フィールドをパース
fields = {}
for field in raw_msg.split(SOH):
if "=" in field:
tag, value = field.split("=", 1)
fields[tag] = value
# メッセージタイプ確認
msg_type = fields.get("35", "")
if msg_type not in ("W", "X"):
return None
# シンボルID取得
symbol_id = int(fields.get("55", "0"))
if symbol_id <= 0:
return None
# 既存のQuoteを取得または新規作成
quote = quotes_store.get(symbol_id, Quote(symbol_id=symbol_id))
# 269タグ(MDEntryType)を解析
# 複数のエントリーがあるため、rawメッセージを分割
entries = raw_msg.split("269=")
for entry in entries[1:]: # 最初の要素はヘッダー
if entry.startswith("0"): # Bid
# 270=xxxを抽出
px_start = entry.find("270=")
if px_start != -1:
px_end = entry.find(SOH, px_start)
if px_end != -1:
try:
quote.bid = float(entry[px_start+4:px_end])
except ValueError:
pass
elif entry.startswith("1"): # Ask
px_start = entry.find("270=")
if px_start != -1:
px_end = entry.find(SOH, px_start)
if px_end != -1:
try:
quote.ask = float(entry[px_start+4:px_end])
except ValueError:
pass
# タイムスタンプ更新
quote.timestamp = datetime.now(timezone.utc)
# ストアに保存
quotes_store[symbol_id] = quote
return quote

リアルタイム価格取得の完全実装

Quoteクラスの活用

Python
# グローバルな価格ストア
quotes: dict = {}
def on_quote_message(raw_msg: str):
"""QUOTEセッションからのメッセージ処理"""
fields = parse_fields(raw_msg)
msg_type = fields.get("35", "")
if msg_type in ("W", "X"): # MarketData
quote = parse_market_data(raw_msg, quotes)
if quote:
print(f"💰 {quote.symbol_id}: Bid={quote.bid} Ask={quote.ask} Spread={quote.spread:.1f}pt")
# コールバック実行
if on_quote_callback:
on_quote_callback(quote)
elif msg_type == "Y": # MarketDataRequestReject
reason = fields.get("58", "Unknown")
print(f"❌ マーケットデータ拒否: {reason}")
# 価格取得ヘルパー関数
def get_bid(symbol_id: int) -> Optional[float]:
"""Bid価格を取得"""
quote = quotes.get(symbol_id)
return quote.bid if quote else None
def get_ask(symbol_id: int) -> Optional[float]:
"""Ask価格を取得"""
quote = quotes.get(symbol_id)
return quote.ask if quote else None
def get_spread(symbol_id: int) -> Optional[float]:
"""スプレッド(ポイント)を取得"""
quote = quotes.get(symbol_id)
return quote.spread if quote else None

動作確認コード

Python
import time
def main():
# 1. TRADEセッション接続
trade_socket = connect_session("TRADE", 5212)
send_logon(trade_socket, "TRADE")
wait_for_logon("TRADE")
# 2. QUOTEセッション接続
quote_socket = connect_session("QUOTE", 5211)
send_logon(quote_socket, "QUOTE")
wait_for_logon("QUOTE")
# 3. 受信スレッド開始
start_receiver_thread(quote_socket, "QUOTE")
# 4. マーケットデータ購読
subscribe_market_data(41) # XAUUSD
# 5. 価格表示ループ
print("\n📊 リアルタイム価格モニタリング開始...")
for i in range(30):
quote = quotes.get(41)
if quote and quote.bid and quote.ask:
print(f"XAUUSD: Bid={quote.bid:.2f} Ask={quote.ask:.2f} Spread={quote.spread:.1f}pt")
time.sleep(1)
# 6. 購読解除・切断
unsubscribe_market_data(41)
disconnect()
if __name__ == "__main__":
main()

実行結果(Fundoraライブ口座で検証)

Code
✅ TRADEセッション ログオン成功
✅ QUOTEセッション ログオン成功
✅ マーケットデータ購読: Symbol ID=41
📊 リアルタイム価格モニタリング開始...
💰 41: Bid=2648.31 Ask=2648.61 Spread=30.0pt
💰 41: Bid=2648.33 Ask=2648.63 Spread=30.0pt
💰 41: Bid=2648.35 Ask=2648.65 Spread=30.0pt
...

よくあるエラーと対処法

1. MarketDataRequestReject (35=Y)

Code
原因:
├── シンボルIDが文字列("XAUUSD")になっている
├── シンボルIDが存在しない
├── QUOTEセッションにログインしていない
└── TRADEセッションに送信している

対処: シンボルIDを数値で指定し、QUOTEセッションに送信

2. レスポンスが来ない

Python
# 確認事項:
# 1. QUOTEセッションにログオンしているか?
if not quote_logged_in:
print("QUOTEセッション未接続!")
# 2. 受信スレッドが動いているか?
# 3. ソケットがブロッキングモードになっていないか?

3. Bid/Askが片方だけNone

Code
原因:
├── パースロジックのバグ
├── サーバーがまだ完全なスナップショットを送っていない
└── 市場が閉まっている(週末など)

対処: 数秒待ってから再度確認、週末は価格更新がないことを考慮


スプレッド監視Bot例

実用的な例として、スプレッド監視Botを作ってみましょう:

Python
class SpreadMonitor:
"""スプレッド監視クラス"""
def __init__(self, client, symbol_id: int, alert_threshold: float = 50.0):
self.client = client
self.symbol_id = symbol_id
self.alert_threshold = alert_threshold # ポイント
self.max_spread = 0.0
self.min_spread = float('inf')
def on_quote(self, quote: Quote):
"""価格更新時のコールバック"""
if quote.symbol_id != self.symbol_id:
return
spread = quote.spread
if spread is None:
return
# 最大・最小更新
self.max_spread = max(self.max_spread, spread)
self.min_spread = min(self.min_spread, spread)
# アラート
if spread > self.alert_threshold:
print(f"⚠️ スプレッド拡大警告! {spread:.1f}pt")
def report(self):
"""レポート出力"""
print(f"\n📊 スプレッドレポート:")
print(f" 最小: {self.min_spread:.1f}pt")
print(f" 最大: {self.max_spread:.1f}pt")
# 使用例
monitor = SpreadMonitor(client, symbol_id=41, alert_threshold=40.0)
client.set_on_quote(monitor.on_quote)
# 1時間監視後
monitor.report()

まとめ

今日学んだこと

  1. QUOTEセッションで価格データを取得
  2. **MarketDataRequest (35=V)**で購読開始
  3. シンボルIDは数値で指定
  4. W/XメッセージでBid/Askを受信
  5. スプレッド計算: (Ask - Bid) × 100

次回予告

次回「FIX API完全版③ ポジション・オーダー照会編」では:

  • RequestForPositions(ポジション照会)
  • OrderMassStatusRequest(オーダー照会)
  • cTrader独自の制限と対処法

を解説します!


関連記事

🎁おすすめツール・サービス

FX自動売買VPSなら50%OFF

キャンペーンで月1,790円〜。MT4/MT5プリインストール済み、自動復旧機能付き。14日間無料お試し。

シンクラウドデスクトップ for FX
ゴトー日EAを探しているならソースコード付

4年で資金4.7倍のバックテスト実績。ソースコード付きで自由にカスタマイズ可能。

ゴトー日EA Ultimate版(3,980円)

※上記リンクにはプロモーション(広告)が含まれています

🔮
MEMBERSHIP

量子相場謀略|ホソノP

ブログでは書けない本音のトレード戦略とEA開発の裏側を公開。 データに基づいた検証で、再現性のある手法だけをお届けします。

✓EAソースコード公開
✓バックテスト生データ共有
✓リアルタイム相場分析
✓メンバー限定記事
メンバーシップに参加する※ noteアカウントで簡単登録

📚あわせて読みたい

📄

⚠️【非推奨】FIX API完全版⑤|全機能クラス→ Python cBotを使おう

【非推奨】FIX APIは17個のハマりポイントがあり初心者には推奨しません。代わりにPython cBotを使いましょう。この記事は参考資料として残しています。

ctrader2026/1/8
📄

⚠️【非推奨】FIX API完全版④|エントリー・決済→ Python cBotを使おう

【非推奨】FIX APIは17個のハマりポイントがあり初心者には推奨しません。代わりにPython cBotを使いましょう。この記事は参考資料として残しています。

ctrader2026/1/8
📄

⚠️【非推奨】FIX API完全版③|ポジション照会→ Python cBotを使おう

【非推奨】FIX APIは17個のハマりポイントがあり初心者には推奨しません。代わりにPython cBotを使いましょう。この記事は参考資料として残しています。

ctrader2026/1/8
📄

🚀【神機能】cTrader Open APIはプラットフォーム不要!MT5との決定的な違いとおすすめブローカー・プロップファーム

cTrader Open APIはMT5と違ってデスクトップアプリ不要!Pythonだけで直接サーバーに接続できる革命的なAPI。VPSでヘッドレス運用したいトレーダー必見。日本人におすすめのcTraderブローカーとプロップファームも徹底解説。

ctrader2026/1/6
📄

⚠️【非推奨】FIX API入門②|PythonでLogon→ Python cBotを使おう

【非推奨】FIX APIは17個のハマりポイントがあり初心者には推奨しません。代わりにPython cBotを使いましょう。この記事は参考資料として残しています。

fx2026/1/8
📄

🚨【結論】FIX APIは非推奨!Python cBotを使いましょう - 17個の地獄を経験した結論

FIX APIは17個のハマりポイントがあり非推奨です。cTrader 5.4以降のPython cBotなら1行で注文可能。FIX APIで813行書いた経験者が断言します。

fx2026/1/8