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


🎉 シリーズ最終回:すべてを統合する

FIX API完全版シリーズ、ついに最終回です!

これまで学んできた:

  • ①セッション構成とパスワード
  • ②リアルタイム価格取得
  • ③ポジション・オーダー照会
  • ④エントリー・決済

これらすべてを1つのクラスに統合します。

この記事のコードをコピペすれば、今日からcTrader FIX APIで自動売買ができます!


完成形のイメージ

Python
# こんな感じで使える
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) # XAUUSD
quote = 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()

クラス設計

全体構成

Code
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()

データクラス定義

Python
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from 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"
@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
@dataclass
class Position:
"""ポジション情報"""
position_id: str
symbol_id: int
side: str # "BUY" or "SELL"
volume: float
entry_price: float
symbol_name: Optional[str] = None
@dataclass
class 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 = ""
@dataclass
class SymbolInfo:
"""通貨ペア情報"""
symbol_id: int
symbol_name: str
digits: int = 5
contract_size: float = 100000

メインクラス実装

Python
import socket
import ssl
import time
import threading
import logging
from datetime import datetime, timezone
from 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

Python
import time
import 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()

実行結果

Code
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=1
INFO:CTraderFIX:注文受付: ClOrdID=ORD_1736222712345
🎉 約定! @ 2648.61
INFO: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切断完了

期待されるログ出力(デバッグ用)

正常に動作している場合のログ出力パターンです。問題が発生した場合はこれと比較してください。

接続〜注文〜約定の正常フロー

Code
[TRADE] 受信ループ開始
[TRADE] 受信 MsgType=A
TRADEセッション ログオン成功
[QUOTE] 受信ループ開始
[QUOTE] 受信 MsgType=A
QUOTEセッション ログオン成功
マーケットデータ購読: 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

ポジション照会の正常フロー

Code
ポジション照会送信
[TRADE] 受信 MsgType=AO
ポジション照会応答: TotalNumPosReports=1
[TRADE] 受信 MsgType=AP
PositionReport受信: 728=0 721=1010176
PositionReport詳細: SymbolID=41 LongQty=1.0 ShortQty=0.0
ポジション登録: ID=1010176 Symbol=41 BUY Vol=1.0 Price=4453.24
ポジション受信状況: 1/1
ポジション照会結果: 1件

ログが出ない場合のチェックリスト

Code
□ CTraderFIXロガーにハンドラを設定したか?
□ ログレベルはINFO以下か?
□ 受信スレッドが開始されているか?
□ ソケットがタイムアウトしていないか?

ファイル構成

完成形のファイル構成:

Code
your_project/
├── ctrader_fix_client.py # メインクライアント
├── trading_bot.py # 自動売買ロジック
└── config.py # 設定ファイル

config.py例

Python
# FIX API設定
FIX_HOST = "live-uk-eqx-01.p.c-trader.com"
TRADE_PORT = 5212
QUOTE_PORT = 5211
SENDER_COMP_ID = "live.fundoratrading.5006639"
FIX_PASSWORD = "YourFIXPassword"
TRADER_LOGIN = "5006639"
# シンボル設定
SYMBOLS = {
"XAUUSD": {"id": 41, "contract_size": 100},
"EURUSD": {"id": 1, "contract_size": 100000},
# ...
}

まとめ

シリーズで学んだこと

  1. ①セッション構成: TRADE(5212) + QUOTE(5211) の2セッション
  2. ②価格取得: QUOTEセッションでMarketDataRequest
  3. ③照会: RequestForPositionsはタグ710のみ
  4. ④注文: OrderQty = ロット × 契約サイズ
  5. ⑤統合: 全機能を1クラスに

このクライアントでできること

  • ✅ リアルタイム価格取得
  • ✅ 成行/指値/逆指値注文
  • ✅ ポジション照会・決済
  • ✅ オーダー照会・キャンセル
  • ✅ スレッドセーフ設計
  • ✅ コールバック対応

お疲れさまでした!

FIX API完全版シリーズ、これで完結です。

このコードはFundoraのライブ口座で実際に動作確認済みです。ぜひ自動売買Botの開発に活用してください!

質問があればお気軽にどうぞ 🎉


関連記事

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

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