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.01996歴史的バージョン
FIX 4.22000現在も使用あり
FIX 4.42003最も一般的
FIX 5.02006新機能追加
FIXT 1.12008トランスポート層の分離

ほとんどのブローカーと取引所は 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)名称用途
ALogonセッション確立
5Logoutセッション切断
0Heartbeatハートビート検出
1TestRequest接続テスト
2ResendRequest再送要求
4SequenceReset連番リセット

3.2 アプリケーション層メッセージ(注文関連)

MsgType (35)名称方向用途
DNewOrderSingleクライアント→ブローカー新規注文の送信
FOrderCancelRequestクライアント→ブローカー注文の取消
GOrderCancelReplaceRequestクライアント→ブローカー注文の変更
8ExecutionReportブローカー→クライアント注文状態/約定報告
9OrderCancelRejectブローカー→クライアント取消拒否

3.3 市場データメッセージ

MsgType (35)名称用途
VMarketDataRequest相場サブスクリプション
WMarketDataSnapshotFullRefresh相場スナップショット
XMarketDataIncrementalRefresh相場増分更新

四、主要 Tag の詳細解説

4.1 注文方向 (Tag 54 - Side)

意味
1Buy(買い)
2Sell(売り)
5Sell Short(空売り)
6Sell Short Exempt(免除空売り)

4.2 注文タイプ (Tag 40 - OrdType)

意味必須 Tag
1Market(成行注文)なし
2Limit(指値注文)44=価格
3Stop(逆指値注文)99=トリガー価格
4Stop Limit(逆指値指値注文)44=指値, 99=トリガー価格
PPegged(ペッグ注文)特定フィールド

4.3 注文有効期限 (Tag 59 - TimeInForce)

意味説明
0Day当日有効
1GTC取消まで有効
2OPG寄り付き
3IOC即時約定または取消
4FOK全量約定または取消
6GTD指定日まで有効

4.4 注文状態 (Tag 39 - OrdStatus)

意味
0New(受付済み)
1Partially Filled(一部約定)
2Filled(全量約定)
4Canceled(取消済み)
8Rejected(拒否)
CExpired(期限切れ)

4.5 執行タイプ (Tag 150 - ExecType)

意味
0New(新規注文確認)
FTrade(約定)
4Canceled(取消確認)
8Rejected(拒否)
CExpired(期限切れ)

五、典型的なメッセージフロー

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、連番永続化、完全なログ

関連資料

この章を引用する
Zhang, Wayland (2026). FIX プロトコル入門. In AIクオンツ取引:ゼロからイチへ. https://waylandz.com/quant-book-ja/resources-fix-protocol-introduction
@incollection{zhang2026quant_resources_fix_protocol_introduction,
  author = {Zhang, Wayland},
  title = {FIX プロトコル入門},
  booktitle = {AIクオンツ取引:ゼロからイチへ},
  year = {2026},
  url = {https://waylandz.com/quant-book-ja/resources-fix-protocol-introduction}
}