FIX プロトコル入門
「FIX は金融界の HTTP -- 完全に理解する必要はないが、それがあなたの注文にどう影響するかを知る必要がある。」
一、FIX プロトコルとは?
1.1 定義
FIX(Financial Information eXchange):金融情報交換プロトコル。電子取引の業界標準通信プロトコルです。
あなたの取引システム ←──FIXメッセージ──→ ブローカー/取引所
送信: "AAPL を 100 株、指値 $185 で買いたい"
受信: "注文受付完了、番号 12345"
受信: "50 株が $185.00 で約定"
受信: "残り 50 株が $185.01 で約定"
1.2 なぜ FIX が必要か?
| 問題 | FIX なし | FIX あり |
|---|
| 新しいブローカーへの接続 | ゼロから開発 | 設定変更のみ |
| 複数ブローカーでの取引 | N セットのコード | 1 セットのコード |
| 注文状態の同期 | 各社フォーマットが異なる | 標準化メッセージ |
| 障害調査 | 各社ログが異なる | 統一プロトコル分析 |
1.3 FIX プロトコルバージョン
| バージョン | リリース年 | 主な用途 |
|---|
| FIX 4.0 | 1996 | 歴史的バージョン |
| FIX 4.2 | 2000 | 現在も使用あり |
| FIX 4.4 | 2003 | 最も一般的 |
| FIX 5.0 | 2006 | 新機能追加 |
| FIXT 1.1 | 2008 | トランスポート層の分離 |
ほとんどのブローカーと取引所は FIX 4.4 をサポートしています。
二、FIX メッセージ構造
2.1 メッセージフォーマット
FIX メッセージはキーバリューペアの連続で、SOH(ASCII 01)で区切られます:
8=FIX.4.4|9=176|35=D|49=SENDER|56=TARGET|34=2|52=20240101-09:30:00.000|
11=ORD001|21=1|55=AAPL|54=1|60=20240101-09:30:00.000|38=100|40=2|44=185.00|
59=0|10=123|
人間が読めるバージョン:
8=FIX.4.4 # プロトコルバージョン
9=176 # メッセージ本文長
35=D # メッセージタイプ(D=新規注文)
49=SENDER # 送信者 ID
56=TARGET # 受信者 ID
34=2 # メッセージ連番
52=20240101-09:30:00.000 # 送信時刻
11=ORD001 # クライアント注文 ID
21=1 # 執行指示(1=自動)
55=AAPL # 銘柄コード
54=1 # 売買方向(1=買い)
60=20240101-09:30:00.000 # 取引時刻
38=100 # 注文数量
40=2 # 注文タイプ(2=指値)
44=185.00 # 指値価格
59=0 # 有効期限(0=当日有効)
10=123 # チェックサム
2.2 メッセージの階層構造
┌─────────────────────────────────────────────────────────────┐
│ FIX メッセージ構造 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Header(メッセージヘッダー) │ │
│ │ 8=バージョン 9=長さ 35=タイプ 49=送信者 56=受信者│ │
│ │ 34=連番 52=時刻 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Body(メッセージ本文) │ │
│ │ メッセージタイプ(Tag 35)により内容が異なる │ │
│ │ 注文メッセージ: 55=銘柄 54=方向 38=数量 44=価格... │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Trailer(メッセージトレーラー) │ │
│ │ 10=チェックサム │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
三、コアメッセージタイプ
3.1 セッション層メッセージ
| MsgType (35) | 名称 | 用途 |
|---|
| A | Logon | セッション確立 |
| 5 | Logout | セッション切断 |
| 0 | Heartbeat | ハートビート検出 |
| 1 | TestRequest | 接続テスト |
| 2 | ResendRequest | 再送要求 |
| 4 | SequenceReset | 連番リセット |
3.2 アプリケーション層メッセージ(注文関連)
| MsgType (35) | 名称 | 方向 | 用途 |
|---|
| D | NewOrderSingle | クライアント→ブローカー | 新規注文の送信 |
| F | OrderCancelRequest | クライアント→ブローカー | 注文の取消 |
| G | OrderCancelReplaceRequest | クライアント→ブローカー | 注文の変更 |
| 8 | ExecutionReport | ブローカー→クライアント | 注文状態/約定報告 |
| 9 | OrderCancelReject | ブローカー→クライアント | 取消拒否 |
3.3 市場データメッセージ
| MsgType (35) | 名称 | 用途 |
|---|
| V | MarketDataRequest | 相場サブスクリプション |
| W | MarketDataSnapshotFullRefresh | 相場スナップショット |
| X | MarketDataIncrementalRefresh | 相場増分更新 |
四、主要 Tag の詳細解説
4.1 注文方向 (Tag 54 - Side)
| 値 | 意味 |
|---|
| 1 | Buy(買い) |
| 2 | Sell(売り) |
| 5 | Sell Short(空売り) |
| 6 | Sell Short Exempt(免除空売り) |
4.2 注文タイプ (Tag 40 - OrdType)
| 値 | 意味 | 必須 Tag |
|---|
| 1 | Market(成行注文) | なし |
| 2 | Limit(指値注文) | 44=価格 |
| 3 | Stop(逆指値注文) | 99=トリガー価格 |
| 4 | Stop Limit(逆指値指値注文) | 44=指値, 99=トリガー価格 |
| P | Pegged(ペッグ注文) | 特定フィールド |
4.3 注文有効期限 (Tag 59 - TimeInForce)
| 値 | 意味 | 説明 |
|---|
| 0 | Day | 当日有効 |
| 1 | GTC | 取消まで有効 |
| 2 | OPG | 寄り付き |
| 3 | IOC | 即時約定または取消 |
| 4 | FOK | 全量約定または取消 |
| 6 | GTD | 指定日まで有効 |
4.4 注文状態 (Tag 39 - OrdStatus)
| 値 | 意味 |
|---|
| 0 | New(受付済み) |
| 1 | Partially Filled(一部約定) |
| 2 | Filled(全量約定) |
| 4 | Canceled(取消済み) |
| 8 | Rejected(拒否) |
| C | Expired(期限切れ) |
4.5 執行タイプ (Tag 150 - ExecType)
| 値 | 意味 |
|---|
| 0 | New(新規注文確認) |
| F | Trade(約定) |
| 4 | Canceled(取消確認) |
| 8 | Rejected(拒否) |
| C | Expired(期限切れ) |
五、典型的なメッセージフロー
5.1 通常の発注フロー
クライアント ブローカー
│ │
│ ────── NewOrderSingle (35=D) ──────→ │
│ 11=ORD001, 55=AAPL, │
│ 54=1, 38=100, 40=2, 44=185 │
│ │
│ ←── ExecutionReport (35=8) ───────── │
│ 150=0 (New), 39=0 (New) │
│ 注文受付完了 │
│ │
│ ←── ExecutionReport (35=8) ───────── │
│ 150=F (Trade), 39=1 (PartFilled) │
│ 31=185.00, 32=50 │
│ 50 株が $185.00 で約定 │
│ │
│ ←── ExecutionReport (35=8) ───────── │
│ 150=F (Trade), 39=2 (Filled) │
│ 31=185.01, 32=50 │
│ 残り 50 株が $185.01 で約定 │
│ │
5.2 取消フロー
クライアント ブローカー
│ │
│ ── OrderCancelRequest (35=F) ──────→ │
│ 11=CANCEL001, 41=ORD001 │
│ (41=元の注文 ID) │
│ │
│ ←── ExecutionReport (35=8) ───────── │
│ 150=4 (Canceled), 39=4 │
│ 取消成功 │
│ │
または取消失敗:
│ ←── OrderCancelReject (35=9) ─────── │
│ 102=1 (Unknown order) │
│ 取消失敗:注文が存在しない │
│ │
5.3 セッション確立
クライアント ブローカー
│ │
│ ────────── Logon (35=A) ───────────→ │
│ 98=0 (暗号化なし) │
│ 108=30 (ハートビート間隔 30s)│
│ │
│ ←────────── Logon (35=A) ─────────── │
│ セッション確立成功 │
│ │
│ ←───────── Heartbeat (35=0) ──────── │
│ 30 秒ごと │
│ ────────── Heartbeat (35=0) ───────→ │
│ │
六、Python 実装例
6.1 QuickFIX の使用
import quickfix as fix
import quickfix44 as fix44
class TradingApplication(fix.Application):
"""FIX 取引アプリケーション"""
def __init__(self):
super().__init__()
self.session_id = None
self.order_id = 0
def onCreate(self, session_id):
"""セッション作成"""
self.session_id = session_id
print(f"Session created: {session_id}")
def onLogon(self, session_id):
"""ログイン成功"""
print(f"Logged on: {session_id}")
def onLogout(self, session_id):
"""ログアウト"""
print(f"Logged out: {session_id}")
def toAdmin(self, message, session_id):
"""管理メッセージ送信前のコールバック"""
pass
def fromAdmin(self, message, session_id):
"""管理メッセージ受信"""
pass
def toApp(self, message, session_id):
"""アプリケーションメッセージ送信前のコールバック"""
print(f"Sending: {message}")
def fromApp(self, message, session_id):
"""アプリケーションメッセージ受信"""
msg_type = fix.MsgType()
message.getHeader().getField(msg_type)
if msg_type.getValue() == fix.MsgType_ExecutionReport:
self._handle_execution_report(message)
def _handle_execution_report(self, message):
"""執行報告の処理"""
exec_type = fix.ExecType()
message.getField(exec_type)
if exec_type.getValue() == fix.ExecType_FILL:
# 約定報告
order_id = fix.ClOrdID()
last_px = fix.LastPx()
last_qty = fix.LastQty()
message.getField(order_id)
message.getField(last_px)
message.getField(last_qty)
print(f"Fill: {order_id.getValue()} "
f"{last_qty.getValue()} @ {last_px.getValue()}")
def send_new_order(self, symbol: str, side: str,
quantity: int, price: float):
"""新規注文の送信"""
self.order_id += 1
cl_ord_id = f"ORD{self.order_id:06d}"
order = fix44.NewOrderSingle()
# 必須フィールド
order.setField(fix.ClOrdID(cl_ord_id))
order.setField(fix.Symbol(symbol))
order.setField(fix.Side(fix.Side_BUY if side == 'buy'
else fix.Side_SELL))
order.setField(fix.OrderQty(quantity))
order.setField(fix.OrdType(fix.OrdType_LIMIT))
order.setField(fix.Price(price))
order.setField(fix.TimeInForce(fix.TimeInForce_DAY))
order.setField(fix.TransactTime())
fix.Session.sendToTarget(order, self.session_id)
return cl_ord_id
def cancel_order(self, orig_cl_ord_id: str, symbol: str, side: str):
"""注文の取消"""
self.order_id += 1
cl_ord_id = f"CXL{self.order_id:06d}"
cancel = fix44.OrderCancelRequest()
cancel.setField(fix.ClOrdID(cl_ord_id))
cancel.setField(fix.OrigClOrdID(orig_cl_ord_id))
cancel.setField(fix.Symbol(symbol))
cancel.setField(fix.Side(fix.Side_BUY if side == 'buy'
else fix.Side_SELL))
cancel.setField(fix.TransactTime())
fix.Session.sendToTarget(cancel, self.session_id)
return cl_ord_id
6.2 設定ファイル
# fix_client.cfg
[DEFAULT]
ConnectionType=initiator
ReconnectInterval=5
FileStorePath=./store
FileLogPath=./log
StartTime=00:00:00
EndTime=00:00:00
UseDataDictionary=Y
DataDictionary=./FIX44.xml
ValidateUserDefinedFields=N
[SESSION]
BeginString=FIX.4.4
SenderCompID=YOUR_CLIENT_ID
TargetCompID=BROKER_ID
SocketConnectHost=fix.broker.com
SocketConnectPort=9876
HeartBtInt=30
6.3 クライアントの起動
def main():
settings = fix.SessionSettings("fix_client.cfg")
application = TradingApplication()
store_factory = fix.FileStoreFactory(settings)
log_factory = fix.FileLogFactory(settings)
initiator = fix.SocketInitiator(
application, store_factory, settings, log_factory
)
initiator.start()
try:
# ログイン待ち
import time
time.sleep(2)
# 注文送信
order_id = application.send_new_order(
symbol="AAPL",
side="buy",
quantity=100,
price=185.00
)
print(f"Order submitted: {order_id}")
# 実行を維持
while True:
time.sleep(1)
except KeyboardInterrupt:
initiator.stop()
七、よくある問題とトラブルシューティング
7.1 連番の不一致
問題: "MsgSeqNum too low, expecting 100 but received 50"
原因: クライアントとサーバーのメッセージ連番が不一致
解決策:
1. SequenceReset (35=4) を送信して連番をリセット
2. または Logon メッセージで ResetSeqNumFlag (141=Y) を設定
3. 本番環境:永続化ストレージを使用して連番を保持
7.2 ハートビートタイムアウト
問題: 接続が切断され、ログにハートビートタイムアウトが表示
原因: ネットワークレイテンシーまたはブロッキング
解決策:
1. ネットワーク接続を確認
2. HeartBtInt の値を増やす(ただし大きすぎないように)
3. アプリケーションが長時間ブロックしていないことを確認
7.3 注文の拒否
問題: ExecutionReport が OrdStatus=8 (Rejected) を表示
トラブルシューティング手順:
1. Tag 58 (Text) で拒否理由を確認
2. よくある原因:
- 資金不足 (Insufficient funds)
- 銘柄が取引不可 (Symbol not found)
- 価格が制限外 (Price out of range)
- 数量がルールに違反 (Invalid quantity)
7.4 メッセージ解析ツール
def parse_fix_message(raw_message: str) -> dict:
"""FIX メッセージを辞書に解析"""
# SOH を可視文字に置換
if '\x01' in raw_message:
raw_message = raw_message.replace('\x01', '|')
fields = {}
for pair in raw_message.split('|'):
if '=' in pair:
tag, value = pair.split('=', 1)
fields[int(tag)] = value
return fields
def format_fix_message(fields: dict) -> str:
"""FIX メッセージをフォーマットして出力"""
tag_names = {
8: 'BeginString',
9: 'BodyLength',
35: 'MsgType',
49: 'SenderCompID',
56: 'TargetCompID',
34: 'MsgSeqNum',
52: 'SendingTime',
11: 'ClOrdID',
55: 'Symbol',
54: 'Side',
38: 'OrderQty',
40: 'OrdType',
44: 'Price',
39: 'OrdStatus',
150: 'ExecType',
31: 'LastPx',
32: 'LastQty',
10: 'CheckSum',
}
lines = []
for tag, value in sorted(fields.items()):
name = tag_names.get(tag, f'Tag{tag}')
lines.append(f" {tag:>3} ({name}): {value}")
return '\n'.join(lines)
八、セキュリティに関する注意事項
8.1 本番環境の要件
| 要件 | 説明 |
|---|
| TLS/SSL | 暗号化接続が必須 |
| IP ホワイトリスト | ブローカーは通常接続 IP を制限 |
| 証明書認証 | 一部のブローカーはクライアント証明書を要求 |
| 連番の永続化 | 再起動後の連番衝突を防止 |
| メッセージログ | 全メッセージの記録が監査のために必要 |
8.2 テスト環境
1. ほとんどのブローカーは UAT(ユーザー受入テスト)環境を提供
2. まず UAT で全メッセージタイプをテスト
3. 各種異常シナリオをシミュレーション(ネットワーク切断、メッセージ順序異常等)
4. 注文ライフサイクルの完全性を検証
九、よくある誤解
誤解一:FIX プロトコルは複雑で、専門機関のみが必要
ブローカーに直接接続するクオンツトレーダーにとって、FIX の理解は必要です。ラップされた API を使用する場合でも、基盤プロトコルを理解することで問題のトラブルシューティングに役立ちます。
誤解二:全てのブローカーの FIX 実装は同じ
FIX は標準プロトコルですが、各ブローカーには以下のような違いがある場合があります:
- カスタム Tag
- 異なる必須フィールド要件
- 特定のメッセージフロー
誤解三:セッション層の管理は無視できる
セッション層(ハートビート、連番)の正しい処理は安定運用の基盤です。これらを無視すると接続の不安定性やメッセージの欠落が発生します。
十、まとめ
| ポイント | 説明 |
|---|
| 核心的な用途 | 注文送信、約定報告、取消 |
| メッセージ構造 | Header + Body + Trailer、キーバリューペア形式 |
| 主要メッセージ | D(新規注文), 8(執行報告), F(取消) |
| 実装方法 | QuickFIX が最も一般的なオープンソースライブラリ |
| 本番要件 | TLS、連番永続化、完全なログ |
関連資料