LabHub

Blog

Securities MTS/HTS Developer: Exchange Protocols to Microsecond Latency

한국어English日本語

Who's this for

This piece targets junior developers entering securities IT roles (MTS, HTS, OMS, exchange gateways) and 5-10 year experienced engineers thinking of moving from fintech or adjacent domains into a brokerage. JD lines like "experience with high-throughput systems" and "real-time market data" are abstract until you map them to specific protocols, latency numbers, and failure patterns - that's what this article unpacks. Don't stop at memorizing company names; build the muscle to know which keyword to search when you open a one-page exchange spec.

Why MTS/HTS development is "handling things"

A securities IT engineer is not just building screens. The actual things being handled are three layers.

First, the market. The moment KRX regular session opens at 09:00, tens of thousands of orders rush in simultaneously. Between 09:00:00.000 and 09:00:00.500 (500ms), the OMS has to traverse order-book residuals, price-time priority, and call-auction matching logic before forwarding to the exchange.

Second, protocols. KRX EXTURE+ uses its proprietary binary protocol KRX UMS; the global standard is FIX 4.4/5.0. Nasdaq uses ITCH (market data) / OUCH (orders); TSE uses Arrowhead's proprietary binary frame. You deal with message frame layouts, sequence number processing, and gap-fill logic for all of these.

Third, systems. From the front (MTS/HTS app) through API Gateway to Order Router to OMS to Exchange Gateway is 6-7 hops, and latency must stay under 50ms for retail to feel snappy. HFT pushes tick-to-trade down to <100μs.

"Handling" really means making trade-offs across these three layers, every single day.

System architecture overview

A typical Korean brokerage trading system is a 5-tier architecture.

  1. Client tier: MTS (iOS/Android, React Native or native), HTS (Windows C++/.NET native), Web Trading (React/Vue)
  2. Gateway tier: API Gateway (Nginx/Envoy + custom auth), WebSocket Gateway (market data push), MQTT broker (mobile push)
  3. Middleware tier: Order Router, Risk Engine, Position Manager, Account Service - Java Spring Boot or C++ low-latency
  4. Backend tier: OMS, EMS, market data distribution engine, settlement system
  5. Exchange Gateway tier: KRX gateway, KOSCOM backbone connection, ATS link, FIX gateways to overseas exchanges

Between each tier sits a messaging middleware: Kafka, Solace, Tibco RV, Aeron. Korean brokerages tend toward Kafka + Redis; global IBs lean Solace or Tibco.

Core protocols: FIX 4.4/5.0, FAST, ITCH/OUCH, Arrowhead, KRX UMS, ISO 20022

"Have you worked with FIX?" in a securities IT interview is a serious question. The standard protocols:

FIX is human-readable, easy to debug. ITCH/OUCH is binary so you need the wire-format spec and Wireshark dumps.

A real day

Operations runs on market hours.

No releases during market hours. Change Freeze policy is strict.

Smart Order Router

A Smart Order Router (SOR) splits a single order across multiple venues. Korea historically had a single-venue market with KRX, so SOR demand was low, but with the launch of the Nextrade ATS in 2025 it's becoming serious.

What an SOR weighs:

SOR is typically built in C++ or Java Aeron. Decision logic runs in microseconds; GC pauses are not allowed.

Korean exchanges: KRX KOSPI/KOSDAQ, ATS, Nextrade

Korea historically operated under a single-exchange regime with KRX running KOSPI, KOSDAQ, and KONEX. That changed with the 2025 launch of Nextrade ATS.

Brokerage gateways connect to KRX via KOSCOM circuits. Circuits are dual-routed and reach KOSCOM's Busan backup data center.

Japanese exchanges: TSE Arrowhead, JPX, PTS

The Japanese market is unified under JPX (Japan Exchange Group), which consolidated TSE (Tokyo Stock Exchange) and OSE (Osaka Securities Exchange).

Japanese brokers handle the TSE FLEX protocol (proprietary binary); global IBs in Japan use FIX 4.4 + FIX FAST.

Latency budgets: tick-to-trade <100μs HFT vs <10ms retail

Latency targets vary by segment.

HFT doesn't tolerate even a 5ms GC pause in Java. People use pauseless GC like Zing JVM, Azul Platform Prime, or rewrite in C++/Rust.

Korean tech stack: Java/Spring Boot, Kafka, Redis, Cassandra, kdb+, C++

Korean brokerage IT stacks tend to look like this.

Mirae Asset, Korea Investment, Samsung, KB lean Java/Spring. C++ low-latency cores show up only in select OMS/EMS components.

Global / Japan tech stack: C++/Java, Aeron, Chronicle, Solace, Tibco RV

Global IBs (Goldman, JPM, MS, Citi) and large Japanese brokers (Nomura, Daiwa, SBI) differ from the Korean stack.

Big Japanese brokers run similar stacks. Their in-house standards skew conservative so Java is dominant; C++ shows up only on select trading desks.

Security: PKI, biometrics, OTP, 2FA, FIDO2, network segregation, ISMS-P, FISC

Two axes: user authentication and network security.

Because of segregation, Korean brokerage devs can't access GitHub.com directly. They use internal GitLab/Bitbucket and pull OSS through a proxy like Nexus.

Korean brokerage IT: Mirae Asset, KIS, Samsung, KB, Shinhan

Korean brokerage IT splits into in-house vs SI-outsourced models.

Fresh-grad hiring at Mirae, KIS, Samsung, KB runs in the double-digits each year. SI outsourcing names: LG CNS, Samsung SDS, KOSCOM, KIS ICT, Shinhan DS.

Japanese brokerage IT: SBI, Rakuten, Matsui, Nomura, Daiwa + Toss STARTRADE, Kakao Pay Securities KakaoStock

Japan has a long-established / newcomer split.

Korean newcomers: Toss Securities (STARTRADE system) and Kakao Pay Securities (KakaoStock app). Toss Securities operates its own OMS in-house; Kakao Pay Securities runs a custom front on KIS ICT's backend.

AI/ML applications: fraud detection, KYC OCR, chatbots, stock recommendations

Five common AI/ML applications in brokerages.

  1. Fraud Detection System (FDS): Detection of nominee accounts, wash trades, market manipulation, pump-and-dump. Graph Neural Networks, anomaly detection
  2. KYC: ID document OCR, face recognition, liveness checks. AWS Textract, GCP Vision API
  3. Chatbots: Balance lookup, order history, market info. LLM-based (GPT-4o, Claude, HyperCLOVA X)
  4. Stock recommendations: Portfolio analysis, similar-customer purchase recommendations. Collaborative Filtering, Matrix Factorization
  5. Algo trading signals: Price patterns + volume + fundamentals. XGBoost, LSTM, Transformer

AI is often a separate org (Mirae's AI Lab inside Digital Innovation HQ, KIS's AI Task Force). Trading system developers more often integrate AI team outputs than build models themselves.

Failure cases: Samsung Securities 2018 employee stock incident, Knight Capital 2012, KOSCOM 2008

Failure cases every securities IT engineer should memorize.

The common thread: input validation, limit checks, deployment process, or backup procedures broke somewhere.

20 interview questions

Twenty common questions for securities IT interviews.

  1. Difference between FIX 4.4 MsgType=D (NewOrderSingle) and MsgType=8 (ExecutionReport)?
  2. Explain the responsibility split between OMS and EMS
  3. Recovery procedure when a sequence number gap occurs?
  4. How would you represent the order book in memory? Data structure and time complexity
  5. Idempotency key design to prevent duplicate orders
  6. Which Java GC and config to guarantee a <5ms pause?
  7. Solving hot partition when Kafka partition key is the ticker
  8. Memory estimation and eviction policy for a Redis order-book cache
  9. Lock-free queue implementations in low-latency settings (SPSC, MPMC)
  10. How do you bring in external OSS in a network-segregated environment?
  11. Price-time priority of the 09:00:00 call auction
  12. Margin loan vs stock loan differences and what data the IT system tracks
  13. Forced liquidation algorithm when a margin call goes unmet
  14. FX handling for 24-hour US equity trading (pre-market, after-market)
  15. Backpressure handling when pushing market data over WebSocket
  16. Corporate-action handling: stock splits, bonus issues, dividends and auto-adjustment of positions
  17. Difference in exchange messages for Market Order vs Limit Order
  18. How options-expiry index SQ (Special Quotation) is calculated
  19. Implementation of ETF Liquidity Provider (LP) quoting obligations
  20. Differences between TWAP, VWAP, IS, and POV algo strategies

The interviewer is watching how you reason when you don't know. Don't just say "I don't know"; instead say "I'm not sure about this, but if I reason from principle X..."

Compensation

Approximate ranges (2025, base + incentive).

HFT / algo trading quant dev is a separate market. US Citadel / Two Sigma / Jane Street fresh-grad packages are $400K+; seniors can clear $1M+.

Career path: Junior Dev to Senior to Tech Lead to Architect to CTO

A typical brokerage IT career.

Lateral moves are common. Brokerage to fintech (Toss, Kakao Pay), brokerage to exchange (KRX/KOSCOM), brokerage to global IB (GS, JPM, MS Korea office).

References

Cited references. Better than re-reading these once: bookmark them and skim every new spec release weekly.

# FIX 4.4 ExecutionReport handler example
# In production use a vetted library like QuickFIX/J or 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) -> fill
        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()}")

            # Propagate fill to 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) -> exchange rejection
        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 decision logic (simplified)
# Real SORs need microsecond latency and are written in C++ or Java Aeron

from dataclasses import dataclass
from typing import List

@dataclass
class Venue:
    name: str          # KRX, Nextrade, NYSE, etc.
    best_bid: float
    best_ask: float
    bid_qty: int
    ask_qty: int
    latency_us: int    # microseconds
    fee_bps: float     # basis points

@dataclass
class Order:
    symbol: str
    side: str          # BUY or SELL
    qty: int
    limit_price: float

def route_order(order: Order, venues: List[Venue]) -> List[dict]:
    """
    Split a single order across venues.
    Priority: price > fee-adjusted net price > latency
    """
    routes = []
    remaining_qty = order.qty

    # BUY against ask, SELL against 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 = quote + fee
        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:
        # Park remainder as resting limit on the best venue
        routes.append({
            "venue": candidates[0].name if candidates else "KRX",
            "qty": remaining_qty,
            "price": order.limit_price,
            "type": "LIMIT_RESTING",
        })

    return routes
# KRX gateway connection configuration example
# In production separate secrets via vault and PKCS#12 certificates

krx_gateway:
  environment: production
  member_code: "001"          # broker member number
  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  # 10 billion KRW
    max_orders_per_second: 1000

  market:
    kospi_open: "09:00:00"
    kospi_close: "15:30:00"
    pre_market_open: "08:30:00"      # call auction
    after_market_close: "16:00:00"   # after-market single price
    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

Comments

No comments yet.

Sign in to leave a comment