⚠️【非推奨】FIX API完全版⑤|全機能クラス→ 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をどうしても使いたい方のみお読みください。
🎉 シリーズ最終回:すべてを統合する
FIX API完全版シリーズ、ついに最終回です!
これまで学んできた:
- ①セッション構成とパスワード
- ②リアルタイム価格取得
- ③ポジション・オーダー照会
- ④エントリー・決済
これらすべてを1つのクラスに統合します。
この記事のコードをコピペすれば、今日からcTrader FIX APIで自動売買ができます!
完成形のイメージ
# こんな感じで使えるfrom ctrader_fix_client import CTraderFIXClient, Side
client = CTraderFIXClient( host="live-uk-eqx-01.p.c-trader.com", trade_port=5212, quote_port=5211, sender_comp_id="live.fundoratrading.5006639", password="YourFIXPassword", trader_login="5006639")
# 接続client.connect()
# 価格取得client.subscribe_market_data(41) # XAUUSDquote = client.get_quote(41)print(f"Bid: {quote.bid}, Ask: {quote.ask}")
# 買い注文client.buy(symbol_id=41, lot_size=0.01, contract_size=100)
# ポジション確認positions = client.request_positions()for pos in positions: print(f"{pos.side} {pos.volume}@{pos.entry_price}")
# 全決済client.close_all_positions(contract_size=100)
# 切断client.disconnect()クラス設計
全体構成
CTraderFIXClient├── 接続管理│ ├── connect()│ ├── disconnect()│ └── _connect_session()├── メッセージ構築│ ├── _build_message()│ └── _get_utc_timestamp()├── セッション管理│ ├── _send_logon()│ ├── _send_logout()│ └── _send_heartbeat()├── 価格取得 (QUOTE)│ ├── subscribe_market_data()│ ├── unsubscribe_market_data()│ ├── get_quote()│ ├── get_bid()│ ├── get_ask()│ └── get_spread()├── ポジション/オーダー照会 (TRADE)│ ├── request_positions()│ ├── request_orders()│ └── request_symbol_info()├── 注文送信 (TRADE)│ ├── new_order()│ ├── buy() / sell()│ ├── buy_limit() / sell_limit()│ ├── buy_stop() / sell_stop()│ └── cancel_order()├── 決済│ ├── close_position()│ └── close_all_positions()├── ユーティリティ│ ├── lot_to_volume()│ └── volume_to_lot()└── コールバック ├── set_on_quote() ├── set_on_execution() └── set_on_position()データクラス定義
from dataclasses import dataclassfrom datetime import datetimefrom typing import Optionalfrom enum import Enum
class Side(Enum): """売買方向""" BUY = "1" SELL = "2"
class OrdType(Enum): """注文タイプ""" MARKET = "1" LIMIT = "2" STOP = "3" STOP_LIMIT = "4"
class TimeInForce(Enum): """有効期限""" GTC = "1" # Good Till Cancel IOC = "3" # Immediate Or Cancel GTD = "6" # Good Till Date
class ExecType(Enum): """約定タイプ""" NEW = "0" PARTIAL_FILL = "1" FILL = "2" CANCELED = "4" REJECTED = "8" TRADE = "F" EXPIRED = "C"
@dataclassclass 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
@dataclassclass Position: """ポジション情報""" position_id: str symbol_id: int side: str # "BUY" or "SELL" volume: float entry_price: float symbol_name: Optional[str] = None
@dataclassclass Order: """注文情報""" order_id: str cl_ord_id: str symbol_id: int side: str ord_type: str volume: float price: Optional[float] = None stop_price: Optional[float] = None status: str = ""
@dataclassclass SymbolInfo: """通貨ペア情報""" symbol_id: int symbol_name: str digits: int = 5 contract_size: float = 100000メインクラス実装
import socketimport sslimport timeimport threadingimport loggingfrom datetime import datetime, timezonefrom typing import Optional, Dict, List, Callable
log = logging.getLogger("CTraderFIX")
# =========================================================================# 重要: ロガー設定の注意点(2026-01-08追加)# =========================================================================## このクライアントを別ファイルからインポートして使う場合、# ロガーが出力されない問題が発生することがあります。## 原因: Pythonのロガーは名前ごとに独立しているため、# "CTraderFIX"ロガーにハンドラを設定しないと出力されません。## 解決方法: メインファイルで以下のように設定## def setup_logger():# formatter = logging.Formatter(# "%(asctime)s [%(levelname)s] %(message)s",# datefmt="%Y-%m-%d %H:%M:%S"# )# console_handler = logging.StreamHandler(sys.stdout)# console_handler.setFormatter(formatter)## # メインロガー設定# main_logger = logging.getLogger("YourApp")# main_logger.setLevel(logging.INFO)# main_logger.addHandler(console_handler)## # CTraderFIXロガーも設定(これが重要!)# fix_logger = logging.getLogger("CTraderFIX")# fix_logger.setLevel(logging.INFO)# fix_logger.addHandler(console_handler)## return main_logger# =========================================================================
class CTraderFIXClient: """ cTrader FIX API クライアント
Fundoraライブ口座で動作確認済み。
使用例: client = CTraderFIXClient( host="live-uk-eqx-01.p.c-trader.com", trade_port=5212, quote_port=5211, sender_comp_id="live.broker.12345", password="your_fix_password", trader_login="12345" ) client.connect() quote = client.get_quote(41) client.disconnect() """
SOH = "\x01"
# 契約サイズ定数 CONTRACT_SIZES = { "FX": 100000, "XAUUSD": 100, "XAGUSD": 5000, "XPTUSD": 100, "XPDUSD": 100, }
def __init__( self, host: str, trade_port: int, quote_port: int, sender_comp_id: str, password: str, trader_login: str, target_comp_id: str = "cServer", heartbeat_interval: int = 30, ): self.host = host self.trade_port = trade_port self.quote_port = quote_port self.sender_comp_id = sender_comp_id self.password = password self.trader_login = trader_login self.target_comp_id = target_comp_id self.heartbeat_interval = heartbeat_interval
# ソケット self._trade_sock: Optional[ssl.SSLSocket] = None self._quote_sock: Optional[ssl.SSLSocket] = None
# シーケンス番号 self._trade_seq = 1 self._quote_seq = 1
# 接続状態 self._trade_logged_in = False self._quote_logged_in = False self._running = False
# データストア self._quotes: Dict[int, Quote] = {} self._positions: Dict[str, Position] = {} self._orders: Dict[str, Order] = {} self._symbols: Dict[int, SymbolInfo] = {}
# コールバック self._on_quote: Optional[Callable[[Quote], None]] = None self._on_execution: Optional[Callable[[Dict], None]] = None self._on_position: Optional[Callable[[Position], None]] = None
# スレッド self._trade_thread: Optional[threading.Thread] = None self._quote_thread: Optional[threading.Thread] = None
# ロック self._lock = threading.Lock()
# 待機用イベント self._position_received = threading.Event() self._order_received = threading.Event() self._symbol_received = threading.Event()
# ========================================================================= # 接続管理 # =========================================================================
def connect(self) -> bool: """TRADEとQUOTEセッションに接続""" try: # TRADE接続 self._trade_sock = self._connect_session(self.trade_port, "TRADE") self._send_logon("TRADE")
# 受信スレッド開始 self._running = True self._trade_thread = threading.Thread( target=self._receiver_loop, args=(self._trade_sock, "TRADE"), daemon=True ) self._trade_thread.start()
# ログイン待ち for _ in range(10): if self._trade_logged_in: break time.sleep(1)
if not self._trade_logged_in: log.error("TRADEセッション ログインタイムアウト") return False
# QUOTE接続 self._quote_sock = self._connect_session(self.quote_port, "QUOTE") self._send_logon("QUOTE")
self._quote_thread = threading.Thread( target=self._receiver_loop, args=(self._quote_sock, "QUOTE"), daemon=True ) self._quote_thread.start()
for _ in range(10): if self._quote_logged_in: break time.sleep(1)
log.info("FIX接続完了") return True
except Exception as e: log.error(f"接続エラー: {e}") return False
def disconnect(self): """セッション切断""" self._running = False
try: if self._trade_logged_in: self._send_logout("TRADE") if self._quote_logged_in: self._send_logout("QUOTE") time.sleep(1) except Exception: pass
for sock in (self._trade_sock, self._quote_sock): if sock: try: sock.close() except Exception: pass
log.info("FIX切断完了")
def _connect_session(self, port: int, session: str) -> ssl.SSLSocket: """SSL接続""" raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) raw_sock.settimeout(10)
context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE
wrapped_sock = context.wrap_socket(raw_sock, server_hostname=self.host) wrapped_sock.connect((self.host, port)) wrapped_sock.settimeout(1)
log.info(f"{session}セッション SSL接続完了") return wrapped_sock
# ========================================================================= # FIXメッセージ構築 # =========================================================================
def _get_utc_timestamp(self) -> str: return datetime.now(timezone.utc).strftime("%Y%m%d-%H:%M:%S.%f")[:-3]
def _build_message(self, msg_type: str, fields: List[str], session: str) -> bytes: with self._lock: if session == "TRADE": seq = self._trade_seq self._trade_seq += 1 else: seq = self._quote_seq self._quote_seq += 1
header = [ f"35={msg_type}", f"49={self.sender_comp_id}", f"56={self.target_comp_id}", f"57={session}", f"50={session}", f"34={seq}", f"52={self._get_utc_timestamp()}", ]
body = header + fields body_str = self.SOH.join(body) + self.SOH
begin = "8=FIX.4.4" + self.SOH body_length = f"9={len(body_str)}" + self.SOH
msg_without_checksum = begin + body_length + body_str checksum = sum(ord(c) for c in msg_without_checksum) % 256 checksum_str = f"10={checksum:03d}" + self.SOH
return (msg_without_checksum + checksum_str).encode("utf-8")
def _send_trade(self, msg: bytes): if self._trade_sock: self._trade_sock.send(msg)
def _send_quote(self, msg: bytes): if self._quote_sock: self._quote_sock.send(msg)
# ========================================================================= # セッション管理 # =========================================================================
def _send_logon(self, session: str): fields = [ "98=0", f"108={self.heartbeat_interval}", "141=Y", f"553={self.trader_login}", f"554={self.password}", ] msg = self._build_message("A", fields, session) if session == "TRADE": self._send_trade(msg) else: self._send_quote(msg)
def _send_logout(self, session: str): msg = self._build_message("5", [], session) if session == "TRADE": self._send_trade(msg) else: self._send_quote(msg)
def _send_heartbeat(self, session: str, test_req_id: str = ""): fields = [f"112={test_req_id}"] if test_req_id else [] msg = self._build_message("0", fields, session) try: if session == "TRADE": self._send_trade(msg) else: self._send_quote(msg) except Exception: pass
# ========================================================================= # 価格取得 # =========================================================================
def subscribe_market_data(self, symbol_id: int): """マーケットデータ購読""" if not self._quote_logged_in: log.warning("QUOTEセッション未接続") return
md_req_id = f"MD{int(time.time())}" fields = [ f"262={md_req_id}", "263=1", "264=1", "265=1", "267=2", "269=0", "269=1", "146=1", f"55={symbol_id}", ] msg = self._build_message("V", fields, "QUOTE") self._send_quote(msg) log.info(f"マーケットデータ購読: Symbol ID={symbol_id}")
def unsubscribe_market_data(self, symbol_id: int): """マーケットデータ購読解除""" if not self._quote_logged_in: return md_req_id = f"MDU{int(time.time())}" fields = [ f"262={md_req_id}", "263=2", "264=1", "265=1", "267=2", "269=0", "269=1", "146=1", f"55={symbol_id}", ] msg = self._build_message("V", fields, "QUOTE") self._send_quote(msg)
def get_quote(self, symbol_id: int) -> Optional[Quote]: return self._quotes.get(symbol_id)
def get_bid(self, symbol_id: int) -> Optional[float]: q = self._quotes.get(symbol_id) return q.bid if q else None
def get_ask(self, symbol_id: int) -> Optional[float]: q = self._quotes.get(symbol_id) return q.ask if q else None
def get_spread(self, symbol_id: int) -> Optional[float]: q = self._quotes.get(symbol_id) return q.spread if q else None
# ========================================================================= # ポジション/オーダー照会 # =========================================================================
def request_positions(self, timeout: float = 5.0) -> List[Position]: """全ポジション取得""" self._positions.clear() self._position_received.clear()
fields = [f"710={int(time.time())}"] msg = self._build_message("AN", fields, "TRADE") self._send_trade(msg)
self._position_received.wait(timeout) return list(self._positions.values())
def request_orders(self, timeout: float = 5.0) -> List[Order]: """全オーダー取得""" self._orders.clear() self._order_received.clear()
fields = [f"584={int(time.time())}", "585=7"] msg = self._build_message("AF", fields, "TRADE") self._send_trade(msg)
self._order_received.wait(timeout) return list(self._orders.values())
# ========================================================================= # 注文送信 # =========================================================================
@staticmethod def lot_to_volume(lot_size: float, contract_size: float) -> int: return int(lot_size * contract_size)
@staticmethod def volume_to_lot(volume: int, contract_size: float) -> float: return volume / contract_size
def new_order( self, symbol_id: int, side: Side, lot_size: float, contract_size: float, ord_type: OrdType = OrdType.MARKET, price: float = 0, stop_price: float = 0, time_in_force: TimeInForce = TimeInForce.IOC, label: str = "", ) -> Optional[str]: """新規注文""" if not self._trade_logged_in: log.error("TRADEセッション未接続") return None
volume = self.lot_to_volume(lot_size, contract_size) if volume <= 0: log.error(f"無効なVolume: {volume}") return None
cl_ord_id = f"ORD_{int(time.time() * 1000)}"
fields = [ f"11={cl_ord_id}", f"55={symbol_id}", f"54={side.value}", f"38={volume}", f"40={ord_type.value}", f"60={self._get_utc_timestamp()}", f"59={time_in_force.value}", ]
if ord_type in (OrdType.LIMIT, OrdType.STOP_LIMIT) and price > 0: fields.append(f"44={price}") if ord_type in (OrdType.STOP, OrdType.STOP_LIMIT) and stop_price > 0: fields.append(f"99={stop_price}") if label: fields.append(f"494={label}")
msg = self._build_message("D", fields, "TRADE") self._send_trade(msg) log.info(f"新規注文: {side.name} {lot_size}ロット Volume={volume}") return cl_ord_id
def buy(self, symbol_id: int, lot_size: float, contract_size: float, label: str = "") -> Optional[str]: return self.new_order(symbol_id, Side.BUY, lot_size, contract_size, label=label)
def sell(self, symbol_id: int, lot_size: float, contract_size: float, label: str = "") -> Optional[str]: return self.new_order(symbol_id, Side.SELL, lot_size, contract_size, label=label)
def buy_limit(self, symbol_id: int, lot_size: float, contract_size: float, price: float, label: str = "") -> Optional[str]: return self.new_order(symbol_id, Side.BUY, lot_size, contract_size, OrdType.LIMIT, price=price, time_in_force=TimeInForce.GTC, label=label)
def sell_limit(self, symbol_id: int, lot_size: float, contract_size: float, price: float, label: str = "") -> Optional[str]: return self.new_order(symbol_id, Side.SELL, lot_size, contract_size, OrdType.LIMIT, price=price, time_in_force=TimeInForce.GTC, label=label)
def buy_stop(self, symbol_id: int, lot_size: float, contract_size: float, stop_price: float, label: str = "") -> Optional[str]: return self.new_order(symbol_id, Side.BUY, lot_size, contract_size, OrdType.STOP, stop_price=stop_price, time_in_force=TimeInForce.GTC, label=label)
def sell_stop(self, symbol_id: int, lot_size: float, contract_size: float, stop_price: float, label: str = "") -> Optional[str]: return self.new_order(symbol_id, Side.SELL, lot_size, contract_size, OrdType.STOP, stop_price=stop_price, time_in_force=TimeInForce.GTC, label=label)
# ========================================================================= # 決済 # =========================================================================
def close_position(self, position: Position, contract_size: float) -> Optional[str]: """ポジション決済""" close_side = Side.SELL if position.side == "BUY" else Side.BUY lot_size = self.volume_to_lot(position.volume, contract_size) return self.new_order(position.symbol_id, close_side, lot_size, contract_size, label=f"CLOSE_{position.position_id}")
def close_all_positions(self, contract_size: float, symbol_id: int = 0) -> List[str]: """全ポジション決済""" positions = self.request_positions() cl_ord_ids = [] for pos in positions: if symbol_id > 0 and pos.symbol_id != symbol_id: continue cl_ord_id = self.close_position(pos, contract_size) if cl_ord_id: cl_ord_ids.append(cl_ord_id) return cl_ord_ids
def cancel_order(self, original_cl_ord_id: str, symbol_id: int, side: str) -> str: """注文キャンセル""" new_cl_ord_id = f"CXL_{int(time.time() * 1000)}" fields = [ f"11={new_cl_ord_id}", f"41={original_cl_ord_id}", f"55={symbol_id}", f"54={side}", f"60={self._get_utc_timestamp()}", ] msg = self._build_message("F", fields, "TRADE") self._send_trade(msg) return new_cl_ord_id
# ========================================================================= # 受信処理 # =========================================================================
def _receiver_loop(self, sock: ssl.SSLSocket, session: str): """受信ループ""" buffer = "" while self._running: try: data = sock.recv(4096) if not data: break buffer += data.decode("utf-8")
while self.SOH + "10=" in buffer: end = buffer.find(self.SOH, buffer.find(self.SOH + "10=") + 1) + 1 if end > 0: msg = buffer[:end] buffer = buffer[end:] self._handle_message(msg, session) except socket.timeout: continue except Exception as e: if self._running: log.error(f"{session}受信エラー: {e}") break
def _parse_message(self, msg: str) -> Dict[str, str]: fields = {} for field in msg.split(self.SOH): if "=" in field: tag, value = field.split("=", 1) fields[tag] = value return fields
def _handle_message(self, msg: str, session: str): fields = self._parse_message(msg) msg_type = fields.get("35", "")
if msg_type == "A": if session == "TRADE": self._trade_logged_in = True else: self._quote_logged_in = True log.info(f"{session}セッション ログオン成功")
elif msg_type == "5": if session == "TRADE": self._trade_logged_in = False else: self._quote_logged_in = False
elif msg_type == "1": self._send_heartbeat(session, fields.get("112", ""))
elif msg_type == "8": self._handle_execution_report(fields)
elif msg_type == "AP": self._handle_position_report(fields)
elif msg_type == "AO": if int(fields.get("727", "0")) == 0: self._position_received.set()
elif msg_type in ("W", "X"): self._handle_market_data(fields, msg)
elif msg_type == "j": self._position_received.set() self._order_received.set()
def _handle_execution_report(self, fields: Dict[str, str]): exec_type = fields.get("150", "") cl_ord_id = fields.get("11", "")
if exec_type == "F": price = fields.get("31", fields.get("6", "")) qty = fields.get("32", "") pos_id = fields.get("721", "") log.info(f"約定: {qty}@{price} PosID={pos_id}") if self._on_execution: self._on_execution(fields)
elif exec_type == "0": log.info(f"注文受付: ClOrdID={cl_ord_id}") self._order_received.set()
elif exec_type == "8": log.warning(f"注文拒否: {fields.get('58', 'Unknown')}")
def _handle_position_report(self, fields: Dict[str, str]): if fields.get("728") == "2": self._position_received.set() return
pos_id = fields.get("721", "") symbol_id = int(fields.get("55", "0")) long_qty = float(fields.get("704", "0")) short_qty = float(fields.get("705", "0")) settle_price = float(fields.get("730", "0"))
if long_qty > 0: side, volume = "BUY", long_qty elif short_qty > 0: side, volume = "SELL", short_qty else: self._position_received.set() return
pos = Position(pos_id, symbol_id, side, volume, settle_price) self._positions[pos_id] = pos
if len(self._positions) >= int(fields.get("727", "1")): self._position_received.set()
def _handle_market_data(self, fields: Dict[str, str], raw_msg: str): symbol_id = int(fields.get("55", "0")) if symbol_id <= 0: return
quote = self._quotes.get(symbol_id, Quote(symbol_id=symbol_id))
for entry in raw_msg.split("269=")[1:]: px_start = entry.find("270=") if px_start != -1: px_end = entry.find(self.SOH, px_start) if px_end != -1: try: px = float(entry[px_start+4:px_end]) if entry.startswith("0"): quote.bid = px elif entry.startswith("1"): quote.ask = px except ValueError: pass
quote.timestamp = datetime.now(timezone.utc) self._quotes[symbol_id] = quote
if self._on_quote: self._on_quote(quote)
# ========================================================================= # コールバック # =========================================================================
def set_on_quote(self, callback: Callable[[Quote], None]): self._on_quote = callback
def set_on_execution(self, callback: Callable[[Dict], None]): self._on_execution = callback
# ========================================================================= # プロパティ # =========================================================================
@property def is_connected(self) -> bool: return self._trade_logged_in使用例:自動売買Bot
import timeimport logging
logging.basicConfig(level=logging.INFO)
def simple_trading_bot(): """シンプルな自動売買Bot"""
client = CTraderFIXClient( host="live-uk-eqx-01.p.c-trader.com", trade_port=5212, quote_port=5211, sender_comp_id="live.fundoratrading.5006639", password="YourFIXPassword", trader_login="5006639" )
SYMBOL_ID = 41 # XAUUSD CONTRACT_SIZE = 100 # ゴールド LOT_SIZE = 0.01
# 約定コールバック def on_fill(fields): price = fields.get("31", "") print(f"🎉 約定! @ {price}")
client.set_on_execution(on_fill)
# 接続 if not client.connect(): print("❌ 接続失敗") return
# 価格購読 client.subscribe_market_data(SYMBOL_ID) time.sleep(2)
# 現在価格 quote = client.get_quote(SYMBOL_ID) if quote: print(f"\n💰 現在価格: Bid={quote.bid:.2f} Ask={quote.ask:.2f} Spread={quote.spread:.1f}pt")
# 買い注文 print("\n📊 成行買い...") client.buy(SYMBOL_ID, LOT_SIZE, CONTRACT_SIZE) time.sleep(3)
# ポジション確認 positions = client.request_positions() print(f"\n📋 保有ポジション: {len(positions)}件") for pos in positions: lot = client.volume_to_lot(pos.volume, CONTRACT_SIZE) print(f" {pos.side} {lot:.2f}ロット @ {pos.entry_price}")
# 決済 print("\n🔄 全決済...") client.close_all_positions(CONTRACT_SIZE) time.sleep(3)
# 確認 positions = client.request_positions() print(f"\n✅ 決済後ポジション: {len(positions)}件")
# 切断 client.disconnect()
if __name__ == "__main__": simple_trading_bot()実行結果
INFO:CTraderFIX:TRADEセッション SSL接続完了INFO:CTraderFIX:TRADEセッション ログオン成功INFO:CTraderFIX:QUOTEセッション SSL接続完了INFO:CTraderFIX:QUOTEセッション ログオン成功INFO:CTraderFIX:FIX接続完了INFO:CTraderFIX:マーケットデータ購読: Symbol ID=41
💰 現在価格: Bid=2648.31 Ask=2648.61 Spread=30.0pt
📊 成行買い...INFO:CTraderFIX:新規注文: BUY 0.01ロット Volume=1INFO:CTraderFIX:注文受付: ClOrdID=ORD_1736222712345🎉 約定! @ 2648.61INFO:CTraderFIX:約定: 1@2648.61 PosID=987654321
📋 保有ポジション: 1件 BUY 0.01ロット @ 2648.61
🔄 全決済...INFO:CTraderFIX:新規注文: SELL 0.01ロット Volume=1🎉 約定! @ 2648.31
✅ 決済後ポジション: 0件
INFO:CTraderFIX:FIX切断完了期待されるログ出力(デバッグ用)
正常に動作している場合のログ出力パターンです。問題が発生した場合はこれと比較してください。
接続〜注文〜約定の正常フロー
[TRADE] 受信ループ開始[TRADE] 受信 MsgType=ATRADEセッション ログオン成功[QUOTE] 受信ループ開始[QUOTE] 受信 MsgType=AQUOTEセッション ログオン成功マーケットデータ購読: Symbol ID=41
新規注文送信: BUY 0.01ロット (Volume=1) Symbol=41 ClOrdID=ORD_xxx[TRADE] 受信 MsgType=8注文受付: ClOrdID=ORD_xxx[TRADE] 受信 MsgType=8約定: ClOrdID=ORD_xxx 1@4453.24 PosID=1010176ポジション照会の正常フロー
ポジション照会送信[TRADE] 受信 MsgType=AOポジション照会応答: TotalNumPosReports=1[TRADE] 受信 MsgType=APPositionReport受信: 728=0 721=1010176PositionReport詳細: SymbolID=41 LongQty=1.0 ShortQty=0.0ポジション登録: ID=1010176 Symbol=41 BUY Vol=1.0 Price=4453.24ポジション受信状況: 1/1ポジション照会結果: 1件ログが出ない場合のチェックリスト
□ CTraderFIXロガーにハンドラを設定したか?□ ログレベルはINFO以下か?□ 受信スレッドが開始されているか?□ ソケットがタイムアウトしていないか?ファイル構成
完成形のファイル構成:
your_project/├── ctrader_fix_client.py # メインクライアント├── trading_bot.py # 自動売買ロジック└── config.py # 設定ファイルconfig.py例
# FIX API設定FIX_HOST = "live-uk-eqx-01.p.c-trader.com"TRADE_PORT = 5212QUOTE_PORT = 5211SENDER_COMP_ID = "live.fundoratrading.5006639"FIX_PASSWORD = "YourFIXPassword"TRADER_LOGIN = "5006639"
# シンボル設定SYMBOLS = { "XAUUSD": {"id": 41, "contract_size": 100}, "EURUSD": {"id": 1, "contract_size": 100000}, # ...}まとめ
シリーズで学んだこと
- ①セッション構成: TRADE(5212) + QUOTE(5211) の2セッション
- ②価格取得: QUOTEセッションでMarketDataRequest
- ③照会: RequestForPositionsはタグ710のみ
- ④注文: OrderQty = ロット × 契約サイズ
- ⑤統合: 全機能を1クラスに
このクライアントでできること
- ✅ リアルタイム価格取得
- ✅ 成行/指値/逆指値注文
- ✅ ポジション照会・決済
- ✅ オーダー照会・キャンセル
- ✅ スレッドセーフ設計
- ✅ コールバック対応
お疲れさまでした!
FIX API完全版シリーズ、これで完結です。
このコードはFundoraのライブ口座で実際に動作確認済みです。ぜひ自動売買Botの開発に活用してください!
質問があればお気軽にどうぞ 🎉
関連記事
🎁おすすめツール・サービス
※上記リンクにはプロモーション(広告)が含まれています
量子相場謀略|ホソノP
ブログでは書けない本音のトレード戦略とEA開発の裏側を公開。 データに基づいた検証で、再現性のある手法だけをお届けします。