LabHub

ブログ

証券会社MTS/HTS開発者:取引所プロトコルからマイクロ秒レイテンシまで

한국어English日本語

対象者

証券会社IT、MTS(Mobile Trading System)、HTS(Home Trading System)、OMS(Order Management System)、取引所ゲートウェイ開発職に進む新卒・ジュニア、そしてfintechや取引所隣接ドメインから証券会社へ転職を考える5-10年目のエンジニアが対象です。求人票の「大量トラフィック処理経験」「リアルタイム市場データ処理」といった抽象的な表現が、実際にはどのプロトコル・どのレイテンシ数字・どの障害パターンを意味するかを噛み砕いて説明します。会社名や職種名を覚えるだけでなく、取引所仕様書を1ページ開いた時にどの単語を検索すべきかという感覚を身につけましょう。

なぜMTS/HTS開発が「扱う仕事」なのか

証券会社ITエンジニアは単に画面を作る人ではありません。実際に扱う対象は3層あります。

第一に、市場です。毎日09:00にKRX(韓国取引所)・東証の前場が開く瞬間、数万件の注文が同時に殺到します。09:00:00.000から09:00:00.500までの500ms以内に、OMSは板残量、価格優先順位、寄付板寄せのマッチングロジックを経て取引所へ注文を送らなければなりません。

第二に、プロトコルです。KRX EXTURE+システムは独自バイナリプロトコルKRX UMSを使い、グローバル市場はFIX 4.4/5.0が標準です。NasdaqはITCH(市場データ)/OUCH(注文)、東証はArrowheadの独自バイナリフレームを使います。これらすべてのメッセージフレームレイアウト、シーケンス番号処理、ギャップフィルロジックを扱います。

第三に、システムです。フロント(MTS/HTSアプリ)→APIゲートウェイ→Order Router→OMS→取引所ゲートウェイまで6-7ホップを通る間、レイテンシを50ms以内に保たないとリテール顧客の体感速度は出ません。HFTならtick-to-trade <100μsまで落とす必要があります。

「扱う」とは、この3層間のトレードオフを毎日決定することを意味します。

システムアーキテクチャ概要

典型的な韓国証券会社トレーディングシステムは5階層アーキテクチャです。

  1. クライアント層: MTS(iOS/Android、React Nativeまたはネイティブ)、HTS(Windows C++/.NETネイティブ)、Webトレーディング(React/Vue)
  2. ゲートウェイ層: APIゲートウェイ(Nginx/Envoy + 独自認証)、WebSocketゲートウェイ(市場データプッシュ)、MQTTブローカー(モバイルプッシュ)
  3. ミドルウェア層: Order Router、Risk Engine、Position Manager、Account Service - Java Spring BootまたはC++低レイテンシ
  4. バックエンド層: OMS、EMS(Execution Management System)、市場データ配信エンジン、清算/決済システム
  5. 取引所ゲートウェイ層: KRXゲートウェイ、KOSCOMバックボーン、ATS接続、海外取引所FIXゲートウェイ

各層の間にはKafka、Solace、Tibco RV、Aeronなどのメッセージングミドルウェアが入ります。韓国証券会社はKafka + Redis組合せが多く、グローバルIBはSolaceまたはTibcoを使います。

コアプロトコル: FIX 4.4/5.0、FAST、ITCH/OUCH、Arrowhead、KRX UMS、ISO 20022

証券IT面接で「FIXプロトコル触ったことある?」は本気の質問です。標準プロトコルをまとめると次の通りです。

FIXは人間が読めるのが強みでデバッグが楽です。ITCH/OUCHはバイナリなのでwire format仕様書を持ち、Wiresharkダンプを開く必要があります。

実際の一日

証券会社IT運用チームの一日は市場時間に合わせて流れます。

立会時間中は絶対にリリースしません。変更凍結(Change Freeze)ポリシーが厳格です。

Smart Order Router構造

Smart Order Router(SOR)は1つの注文を複数市場へ分割送信するエンジンです。韓国は長らくKRX単独市場でSOR需要が低かったのですが、2025年Nextrade ATS出帆以降本格化しました。日本ではすでにPTS存在で本格運用されています。

SORが判断する基準は以下の通りです。

SORは通常C++またはJava Aeronで実装されます。意思決定ロジックはマイクロ秒単位で動作するため、GCポーズを許しません。

韓国取引所接続: KRX KOSPI/KOSDAQ、ATS、Nextrade

韓国市場はKRX(韓国取引所)がKOSPI、KOSDAQ、KONEXを運営する単一取引所体制でした。2025年Nextrade ATS出帆で変化が始まりました。

証券会社ゲートウェイはKOSCOM回線を通じてKRXに接続します。回線は通常二重化し、KOSCOM釜山バックアップセンターまで回線が引かれています。

日本取引所: TSE Arrowhead、JPX、PTS

日本市場は東京証券取引所(TSE)と大阪証券取引所(OSE)を統合したJPX(Japan Exchange Group)傘下体制です。

日本の証券会社は東証FLEXプロトコル(独自バイナリ)を扱い、グローバルIBはFIX 4.4 + FIX FAST組合せを使います。

レイテンシ要件: tick-to-trade <100μs HFT vs <10ms retail

証券ITのレイテンシはサービスセグメントごとに異なります。

HFTはGCポーズ(Java)5msも許容しません。Zing JVM、Azul Platform Primeのようなpauseless GCまたはC++/Rustで書きます。

テックスタック韓国: Java/Spring Boot、Kafka、Redis、Cassandra、kdb+、C++

韓国証券会社ITのテックスタックは次の組合せが多いです。

未来アセット、韓国投資、サムスン、KBはJava/Spring中心。C++低レイテンシコアは一部OMS/EMSのみ使用します。

テックスタック日本/グローバル: C++/Java、Aeron、Chronicle、Solace、Tibco RV

グローバルIB(Goldman、JPM、MS、Citi)と日本大手証券(野村、大和、SBI)のテックスタックは韓国と差があります。

日本大手証券もグローバルIBと類似スタックを使います。ただし社内標準が保守的でJavaが主力、C++は一部トレーディングデスクのみで使用します。

セキュリティ: 公的認証書/金融認証書、PKI、生体認証、OTP、2FA、FIDO2、網分離、ISMS-P、FISC

証券ITセキュリティはユーザー認証と網セキュリティ二軸です。

網分離のため韓国証券会社開発者はGitHub.com直接アクセスが困難です。内部GitLab/Bitbucketを使い、外部OSSダウンロードはnexusのようなプロキシを経由します。

韓国証券会社IT: 未来アセット、韓投、サムスン、KB、新韓投資

韓国証券会社IT組織はin-house vs SI外注モデルに分かれます。

新卒採用は未来アセット、韓投、サムスン、KBが毎年2桁規模で採用。外注SIはLG CNS、サムスンSDS、KOSCOM、韓投ICT、新韓DSが主力。

日本証券会社IT: SBI、楽天、松井、野村、大和 + Toss STARTRADE、Kakao Pay Securities KakaoStock

日本証券会社ITは老舗と新興の二元化構造です。

韓国新興はToss証券(STARTRADEシステム)、Kakao Pay証券(KakaoStockアプリ)が代表的です。Toss証券は自社開発OMSを運営し、Kakao Pay証券は韓投ICTバックエンドの上に自社フロントを載せています。

AI/ML適用: 不正取引検知、KYC OCR、チャットボット、推奨銘柄

証券会社AI/ML活用領域は大きく5つです。

  1. 不正取引検知(FDS、Fraud Detection System): 借名口座、自己取引、相場操縦、pump-and-dump検知。Graph Neural Network、anomaly detection
  2. KYC(Know Your Customer): 身分証OCR、顔認識、ライブネスチェック。AWS Textract、GCP Vision API
  3. チャットボット: 残高照会、注文履歴、市場情報案内。LLMベース(GPT-4o、Claude、HyperCLOVA X)
  4. 推奨銘柄: 顧客ポートフォリオ分析、類似顧客買付銘柄推奨。Collaborative Filtering、Matrix Factorization
  5. アルゴリズム取引シグナル: 価格パターン、出来高、ファンダメンタル結合。XGBoost、LSTM、Transformer

AI領域は別チームに分離されたところが多いです(未来アセットデジタル革新本部傘下AI Lab、韓投AI推進TF)。トレーディングシステム開発者が直接モデルを作るより、AIチーム成果物をシステムに統合する仕事が多いです。

失敗事例: サムスン証券2018社員株配当事故、Knight Capital 2012、KOSCOM 2008

証券ITの失敗事例はシステム開発者が必ず暗記すべきです。

これら事例の共通点は入力検証、限度チェック、デプロイプロセス、バックアップ手順のどこかが崩壊したことです。

面接質問20個

証券IT面接でよく出る質問20個を領域別にまとめました。

  1. FIX 4.4のMsgType=D(NewOrderSingle)とMsgType=8(ExecutionReport)の違いは?
  2. OMSとEMSの責任分離を説明せよ
  3. Sequence number gap発生時の復旧手順は?
  4. 板情報(Order Book)をメモリにどう表現するか?データ構造と時間計算量
  5. 注文重複防止のためのidempotency key設計
  6. Java GCポーズ<5msを保証するならどのGCと設定を使うか?
  7. KafkaパーティショニングキーをティッカーコードにしたときのHot Partition問題解決法
  8. Redisで板キャッシュを作るときのメモリ使用量推定とeviction policy
  9. 低レイテンシ環境でlock-freeキュー実装方法(SPSC、MPMC)
  10. 網分離環境で外部OSSライブラリはどう導入するか?
  11. 寄付09:00:00の板寄せマッチングの価格優先/時間優先原則
  12. 信用取引融資/貸株差とITシステムで追跡すべきデータ
  13. 追証発生時の強制決済アルゴリズム
  14. 米国株24時間取引(プレ、アフター)の為替処理
  15. WebSocketで市場データプッシュ時のバックプレッシャー処理方法
  16. 権利落ち処理: 株式分割、無償増資、配当発生時の残高自動調整
  17. 成行(Market Order)vs指値(Limit Order)注文の取引所メッセージ差
  18. オプション満期日の株価指数SQ(Special Quotation)算出方式
  19. ETF市場メイカー(LP)気配義務システム実装
  20. アルゴリズム取引のTWAP、VWAP、IS、POV戦略差

面接官は答えに詰まったときどう思考を展開するかを見ています。分からないと終わらず、「この部分は分かりませんが、Xという原則で推論すると...」のように説明しましょう。

年収

おおよその年収水準(2025年基準、ベース + インセンティブ)です。

HFT/アルゴ取引quant devは別市場です。米国Citadel/Two Sigma/Jane Street新卒パッケージは$400K+、シニアは$1M+も出ます。

キャリアパス: Junior Dev→Senior→Tech Lead→Architect→CTO

典型的な証券会社ITキャリアパスです。

水平移動も頻繁です。証券会社→fintech(Toss、Kakao Pay)、証券会社→取引所(KRX/KOSCOM)、証券会社→グローバルIB(GS、JPM、MS韓国支店)に転職。

References

本文で引用された資料です。実際の学習はこれらのURLをブックマークし、毎週新しい発表/スペックを追う形が良いです。

# FIX 4.4 ExecutionReport handler例
# 実production環境ではQuickFIX/Jやonixsなど検証済みライブラリを使用

import quickfix as fix
import quickfix44 as fix44

class TradingApp(fix.Application):
    def onMessage(self, message, sessionID):
        msg_type = fix.MsgType()
        message.getHeader().getField(msg_type)

        if msg_type.getValue() == fix.MsgType_ExecutionReport:
            self.handle_execution_report(message, sessionID)

    def handle_execution_report(self, message, sessionID):
        exec_type = fix.ExecType()
        ord_status = fix.OrdStatus()
        order_id = fix.OrderID()
        cl_ord_id = fix.ClOrdID()
        symbol = fix.Symbol()
        side = fix.Side()
        last_qty = fix.LastQty()
        last_px = fix.LastPx()
        cum_qty = fix.CumQty()
        avg_px = fix.AvgPx()

        message.getField(exec_type)
        message.getField(ord_status)
        message.getField(order_id)
        message.getField(cl_ord_id)
        message.getField(symbol)
        message.getField(side)

        # ExecType=F(Trade) -> 約定
        if exec_type.getValue() == fix.ExecType_TRADE:
            message.getField(last_qty)
            message.getField(last_px)
            message.getField(cum_qty)
            message.getField(avg_px)

            print(f"[FILL] Order={order_id.getValue()} "
                  f"ClOrdID={cl_ord_id.getValue()} "
                  f"Symbol={symbol.getValue()} "
                  f"Side={side.getValue()} "
                  f"LastQty={last_qty.getValue()} "
                  f"LastPx={last_px.getValue()} "
                  f"CumQty={cum_qty.getValue()} "
                  f"AvgPx={avg_px.getValue()}")

            # 約定データをOMS、Position Manager、Risk Engineへ伝播
            self.publish_to_kafka("fills.equity.kospi", {
                "order_id": order_id.getValue(),
                "symbol": symbol.getValue(),
                "qty": last_qty.getValue(),
                "px": last_px.getValue(),
            })

        # OrdStatus=8(Rejected) -> 取引所拒否
        elif ord_status.getValue() == fix.OrdStatus_REJECTED:
            ord_rej_reason = fix.OrdRejReason()
            message.getField(ord_rej_reason)
            print(f"[REJECT] Order={order_id.getValue()} "
                  f"Reason={ord_rej_reason.getValue()}")
            self.alert_oms_reject(order_id.getValue(), ord_rej_reason.getValue())
# Smart Order Router意思決定ロジック(簡略化)
# 実SORはマイクロ秒単位レイテンシ要求でC++またはJava Aeron使用

from dataclasses import dataclass
from typing import List

@dataclass
class Venue:
    name: str          # KRX、Nextrade、NYSEなど
    best_bid: float
    best_ask: float
    bid_qty: int
    ask_qty: int
    latency_us: int    # マイクロ秒
    fee_bps: float     # basis points

@dataclass
class Order:
    symbol: str
    side: str          # BUYまたはSELL
    qty: int
    limit_price: float

def route_order(order: Order, venues: List[Venue]) -> List[dict]:
    """
    注文を複数venueへ分割ルーティング
    優先順位: 価格 > 手数料込みnet price > レイテンシ
    """
    routes = []
    remaining_qty = order.qty

    # BUYはask基準、SELLはbid基準
    if order.side == "BUY":
        candidates = [v for v in venues if v.best_ask <= order.limit_price and v.ask_qty > 0]
        # net price = 気配 + 手数料
        candidates.sort(key=lambda v: (
            v.best_ask * (1 + v.fee_bps / 10000),
            v.latency_us,
        ))

        for venue in candidates:
            if remaining_qty <= 0:
                break
            fill_qty = min(remaining_qty, venue.ask_qty)
            routes.append({
                "venue": venue.name,
                "qty": fill_qty,
                "price": venue.best_ask,
            })
            remaining_qty -= fill_qty
    else:  # SELL
        candidates = [v for v in venues if v.best_bid >= order.limit_price and v.bid_qty > 0]
        candidates.sort(key=lambda v: (
            -v.best_bid * (1 - v.fee_bps / 10000),
            v.latency_us,
        ))
        for venue in candidates:
            if remaining_qty <= 0:
                break
            fill_qty = min(remaining_qty, venue.bid_qty)
            routes.append({
                "venue": venue.name,
                "qty": fill_qty,
                "price": venue.best_bid,
            })
            remaining_qty -= fill_qty

    if remaining_qty > 0:
        # 残量はlimit orderで最も有利なvenueに待機
        routes.append({
            "venue": candidates[0].name if candidates else "KRX",
            "qty": remaining_qty,
            "price": order.limit_price,
            "type": "LIMIT_RESTING",
        })

    return routes
# KRXゲートウェイ接続設定例
# 実production環境ではパスワードvault、証明書PKCS#12に分離

krx_gateway:
  environment: production
  member_code: "001"          # 証券会社会員番号
  participant_id: "PROD001"

  primary:
    host: ums-primary.koscom.co.kr
    port: 30443
    ssl: true
    cert_path: /opt/secrets/krx-prod.p12
  backup:
    host: ums-backup.koscom.co.kr
    port: 30443
    ssl: true
    cert_path: /opt/secrets/krx-prod.p12

  session:
    heartbeat_interval_sec: 30
    logon_timeout_sec: 10
    sequence_reset_on_logon: false
    max_messages_per_sec: 5000
    reconnect_attempts: 5
    reconnect_delay_sec: 2

  order_limits:
    max_order_qty: 1000000
    max_order_value_krw: 10000000000  # 100億ウォン
    max_orders_per_second: 1000

  market:
    kospi_open: "09:00:00"
    kospi_close: "15:30:00"
    pre_market_open: "08:30:00"      # 寄付板寄せ
    after_market_close: "16:00:00"   # 立会外単一価格
    timezone: Asia/Seoul

  monitoring:
    metrics_endpoint: http://prometheus.internal:9090
    alert_channel: "#trading-prod-alerts"
    latency_alert_threshold_ms: 50
    rejected_orders_threshold_per_min: 100

コメント

まだコメントはありません。

ログインするとコメントできます