
リアルタイムFX取引システムにおいて、複数通貨ペアの同時購読は一見すると単なるスケーラビリティの問題に見える。しかし実運用に入ると、すぐにより本質的な問題が現れる。それが**データの乱順(Out-of-Order Data)**である。
特にEURUSD、GBPUSD、USDJPYのような複数ペアを同時に購読する高頻度環境では、同じ時間帯のデータであっても必ずしも時系列順に到達するとは限らない。ネットワーク遅延、サーバー側の並列処理、クライアント側のスケジューリングによって順序が崩れる。
一見すると単なる順序の乱れだが、取引システムにおいては以下に直接影響する:
- ローソク足生成のズレ
- インジケーター計算の歪み
- マーケットメイキングの異常な価格更新
- リスク判定の誤作動
本質的にはこれはデータの問題ではなく、ストリーミングシステムの一貫性問題である。
1. 複数通貨ペア購読の本質:1接続・複数時間軸
多くのFX APIのWebSocket設計は「単一接続・複数購読」である:
- 1本のTCP接続
- 複数シンボル同時購読
- サーバーが並列でデータ送信
例えば:
- EURUSD:更新頻度が高い
- GBPUSD:変動が大きく断続的
- USDJPY:特定時間帯に流動性集中
これらはサーバー側では並列生成されるが、クライアント側では1本のストリームに統合される。
その結果:
複数の独立した時間軸が単一キューに強制統合される。
これにより順序の保証が崩れる。
2. 乱順データはどのように発生するか?
乱順は主に4つの層で発生する:
2.1. ネットワークジッター
TCPは信頼性は保証するが順序は保証しない。
- 異なるルート
- キュー遅延
- バッチACK
結果として後のデータが先に届くことがある。
2.2. サーバー側並列処理
マーケットサーバーはマルチスレッド構造:
- symbol Aはスレッド1
- symbol Bはスレッド2
統合時に時間順には並ばない。
2.3. クライアントイベントループ
PythonやNode.jsでは:
- IOと処理は非同期
- コールバック遅延あり
そのため:
到達順 ≠ 生成順 ≠ 時刻順
2.4. 共通バッファ構造
単純実装では:
全symbol → 1つのon_message → 1キュー
これにより構造的順序は完全に崩壊する。
3. 乱順データの実務影響
3.1. ローソク足の破損
- 後のtickが先に処理される
→ OHLCが壊れる
3.2. モメンタムの歪み
- return(t)とreturn(t-1)が崩れる
→ シグナルがノイズ化
3.3. マーケットメイキング異常
- スプレッド拡大
- ミッド価格不安定
- ヘッジ誤作動
4. 本質的解決:時間とメッセージ順序を分離する
重要なのは「乱れを防ぐこと」ではなく:
クライアント側で決定的な順序を再構築すること
キーとなる要素:
- timestamp
- seq_id
- symbol
5. エンジニアリング解決:3層構造
第1層:symbolごとの分離
streams = {
"EURUSD": Queue(),
"GBPUSD": Queue(),
"USDJPY": Queue()
}
各通貨ペアごとに独立した時間軸を維持する。
第2層:時間バッファ(Reorder Buffer)
BUFFER_MS = 200
処理手順:
- tickを受信
- 即時処理しない
- バッファに格納
- timestamp順に整列して出力
第3層:単調増加シーケンス検証
APIがseq_idを提供する場合:
last_seq = {}
def check_order(symbol, tick):
seq = tick["seq"]
if symbol not in last_seq:
last_seq[symbol] = seq
return True
if seq > last_seq[symbol]:
last_seq[symbol] = seq
return True
return False
これにより遅延パケットや重複データを除外できる。
6. 実運用レベルのWebSocket構成
import websocket
import json
import uuid
import time
from collections import defaultdict, deque
API_KEY = "YOUR_API_KEY"
WS_URL = f"wss://quote.alltick.co/quote-b-ws-api?token={API_KEY}"
SYMBOLS = ["EURUSD", "GBPUSD", "USDJPY"]
buffers = defaultdict(deque)
last_seq = {}
def subscribe_msg():
return {
"cmd_id": 22004,
"seq_id": int(time.time()),
"trace": str(uuid.uuid4()),
"data": {
"symbol_list": [{"code": s} for s in SYMBOLS]
}
}
def on_open(ws):
ws.send(json.dumps(subscribe_msg()))
def process_tick(symbol, tick):
seq = tick.get("seq")
if seq is not None:
if symbol in last_seq and seq <= last_seq[symbol]:
return
last_seq[symbol] = seq
buffers[symbol].append(tick)
def on_message(ws, message):
msg = json.loads(message)
if msg.get("cmd_id") == 22998:
tick = msg["data"]
symbol = tick["code"]
process_tick(symbol, tick)
def start():
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message
)
ws.run_forever(ping_interval=10)
if __name__ == "__main__":
start()
7. FX市場がCryptoより乱順になりやすい理由
- 流動性提供者(LP)が分散
- 通貨ペアごとの更新頻度差
- セッション依存性(アジア・ロンドン・NY)
結果:
データは連続ストリームではなく、バースト型ストリームになる
8. 上級アーキテクチャ:イベント時間システムへ
より高度なシステムでは以下を採用する:
● event-time処理
arrival-timeではなくイベント発生時刻を基準にする
● watermark機構
遅延データを一定範囲まで許容する
- 遅延tickを受け入れる
- 一定時間超過で破棄
● シンボル間クロック同期
複数通貨ペアの時間基準を統一する
9. システムが安定したときに見えるもの
乱順問題が解消されると、市場データは次のように変化する:
- EURUSDの不規則な跳動が消える
- GBPUSDのスパイクが安定する
- USDJPYの流れが構造化される
結果として見えるのは単なる価格ではなく:
構造化された時間システム
になる。
その上で、AllTick API のようなインフラは、単なる価格配信ではなく、エンジニアリング可能なリアルタイム時間ストリーム基盤として機能する。


