Singapore Stocks API Integration Guide: SGX Real-Time Quotes & Historical Data Access

  1. iTick
  2. Tutorial
Singapore Stocks API Integration Guide: SGX Real-Time Quotes & Historical Data Access - iTick
Singapore Stocks API Integration Guide: SGX Real-Time Quotes & Historical Data Access

As a premier financial center in Asia, Singapore hosts the equities of many leading Southeast Asian companies. From DBS Group Holdings to Singapore Telecommunications, from Yangzijiang Shipbuilding to CapitaLand Group, Singapore Exchange (SGX) listed issuers span banking & financial services, telecommunications, real estate, marine & offshore, and other key sectors — forming a vital entry point for global investors targeting Southeast Asia.

This guide walks you through accessing the Singapore equity market using the iTick API, covering real-time Level-1 quotes, historical candlestick data, order book depth, and tick-by-tick trade records — complete with production-ready Python code examples to help you quickly build a reliable Singapore equity data & analytics pipeline.

1. Why Choose iTick for SGX Market Data?

Among available market data providers, iTick is particularly developer-friendly and well-suited for quantitative teams covering multiple Asian and global markets — including Singapore.

Comprehensive Market Coverage
One API key provides access to Singapore (SGX), United States, Hong Kong, Taiwan, Japan, India, Malaysia, Thailand, and many other exchanges.

Flexible Delivery Protocols

  • RESTful API — best for on-demand polling and batch queries
  • WebSocket — ultra-low-latency streaming
  • FIX API — institutional high-frequency connectivity

Superior Developer Experience
Free tier available immediately after registration (no credit card required); clear documentation with official code samples in Python, Java, Go, and more.

Professional-Grade Data Quality
Millisecond-level latency; full coverage of last trade (tick), Level-1 quote, and Level-2 depth snapshots — meeting requirements from discretionary analysis to high-frequency and quantitative strategies.

2. Environment Preparation

Before integration, complete these steps:

2.1 Register an iTick Account & Obtain API Token

Visit the iTick official website (or English version if available) to register in approximately 30 seconds — no credit card required. Your personal API Token is available immediately in the developer console.

2.2 Install Required Python Libraries

      pip install requests websocket-client pandas matplotlib

    

3. Real-Time SGX Quotes via REST API

For non-streaming use cases, the REST API offers the simplest method to retrieve live market data. The example below fetches real-time quotes for three flagship SGX constituents:

  • DBS Group Holdings (D05) — Southeast Asia’s largest bank by assets
  • OCBC Bank (O39) — Major Singapore financial institution
  • Singapore Telecommunications (Z74 / Singtel) — Leading regional telecommunications operator
      import requests
import datetime

# Replace with your actual token
API_TOKEN = "your_api_token_here"
BASE_URL = "https://api.itick.org"

def get_singapore_stock_quote(symbol):
    """
    Retrieve real-time Level-1 quote for an SGX-listed security
    :param symbol: SGX ticker symbol, e.g. "D05" (DBS)
    """
    url = f"{BASE_URL}/stock/quote"
    params = {
        "region": "SG",      # Singapore Exchange market code
        "code": symbol
    }
    headers = {
        "accept": "application/json",
        "token": API_TOKEN
    }

    try:
        response = requests.get(url, params=params, headers=headers, timeout=5)
        response.raise_for_status()
        result = response.json()

        if result.get("code") == 0:
            data = result.get("data", {})
            print(f"📊 Security: {data.get('n', 'N/A')}")
            print(f"Ticker: {data.get('s', 'N/A')}")
            print(f"Last Traded Price: {data.get('ld', 'N/A')} SGD")
            print(f"Open: {data.get('o', 'N/A')} SGD")
            print(f"Day High: {data.get('h', 'N/A')} SGD")
            print(f"Day Low: {data.get('l', 'N/A')} SGD")
            print(f"Volume: {data.get('v', 'N/A')} shares")
            print(f"Change: {data.get('chp', 'N/A')}%")

            # Convert millisecond timestamp
            ts = data.get('t', 0) / 1000
            if ts > 0:
                dt = datetime.datetime.fromtimestamp(ts)
                print(f"Data Timestamp: {dt.strftime('%Y-%m-%d %H:%M:%S')} (Singapore local time)")
            return data
        else:
            print(f"❌ API Error: {result.get('msg', 'Unknown error')}")
            return None

    except Exception as e:
        print(f"❌ Request exception: {str(e)}")
        return None

def get_multiple_quotes(symbols):
    """Batch fetch real-time quotes for multiple tickers"""
    for symbol in symbols:
        print(f"\n{'='*50}")
        print(f"Fetching real-time quote → {symbol}")
        get_singapore_stock_quote(symbol)

# Single ticker example
print("🔍 Fetching DBS Group (D05) real-time quote:")
get_singapore_stock_quote("D05")

# Batch example
sgx_bluechips = ["D05", "O39", "Z74"]
get_multiple_quotes(sgx_bluechips)

    

4. Historical OHLCV Data Retrieval (Backtesting Essential)

Historical bars are foundational for strategy backtesting and performance analysis. This example fetches daily bars for Singtel (Z74) and includes basic visualization.

      import requests
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

def get_singapore_stock_kline(symbol, ktype=8, limit=100):
    """
    Fetch historical candlestick (OHLCV) data for an SGX security
    :param symbol: ticker e.g. "Z74" (Singtel)
    :param ktype: bar resolution 
                 (1=1min, 2=5min, 3=15min, 4=30min, 5=60min, 
                  8=daily, 9=weekly, 10=monthly)
    :param limit: number of bars requested
    """
    url = f"{BASE_URL}/stock/kline"
    params = {
        "region": "SG",
        "code": symbol,
        "kType": ktype,
        "limit": limit
    }
    headers = {
        "accept": "application/json",
        "token": API_TOKEN
    }

    try:
        response = requests.get(url, params=params, headers=headers)
        result = response.json()

        if result.get("code") == 0:
            bars = result.get("data", [])
            if not bars:
                print(f"❌ No data returned for {symbol}")
                return None

            df = pd.DataFrame(bars)
            df['datetime'] = pd.to_datetime(df['t'], unit='ms')
            df['datetime'] = df['datetime'].dt.tz_localize('UTC').dt.tz_convert('Asia/Singapore')
            df.set_index('datetime', inplace=True)

            df.rename(columns={
                'o': 'open', 'h': 'high', 'l': 'low',
                'c': 'close', 'v': 'volume'
            }, inplace=True)

            for col in ['open', 'high', 'low', 'close', 'volume']:
                df[col] = pd.to_numeric(df[col], errors='coerce')

            print(f"✅ Retrieved {len(df)} bars")
            print(f"Range: {df.index[0].strftime('%Y-%m-%d')}{df.index[-1].strftime('%Y-%m-%d')}")

            print("\nLatest 5 bars:")
            print(df[['open', 'high', 'low', 'close', 'volume']].tail())

            return df
        else:
            print(f"❌ API Error: {result.get('msg', 'Unknown error')}")
            return None

    except Exception as e:
        print(f"❌ Request failed: {str(e)}")
        return None

def plot_stock_data(df, symbol):
    """Plot closing price and 20-day simple moving average"""
    if df is None or df.empty:
        return

    plt.figure(figsize=(14, 7))
    plt.plot(df.index, df['close'], 'b-', linewidth=1.5, label='Close')
    plt.title(f'{symbol} – Closing Price (SGX)', fontsize=16)
    plt.xlabel('Date')
    plt.ylabel('Price (SGD)')
    plt.grid(True, alpha=0.3)
    plt.legend()

    df['MA20'] = df['close'].rolling(window=20).mean()
    plt.plot(df.index, df['MA20'], 'r--', linewidth=1, label='20-day SMA')
    plt.legend()

    plt.gcf().autofmt_xdate()
    plt.tight_layout()
    plt.show()

# Example: fetch & visualize Singtel daily data
print("\n🔍 Fetching Singtel (Z74) historical daily bars...")
singtel_df = get_singapore_stock_kline("Z74", ktype=8, limit=100)

if singtel_df is not None:
    plot_stock_data(singtel_df, "Z74 (Singtel)")

    print("\n📈 Summary Statistics:")
    print(f"Latest Close:          {singtel_df['close'].iloc[-1]:.3f} SGD")
    print(f"Period High:           {singtel_df['high'].max():.3f} SGD")
    print(f"Period Low:            {singtel_df['low'].min():.3f} SGD")
    print(f"Average Daily Volume:  {singtel_df['volume'].mean():,.0f} shares")
    print(f"Period Total Return:   {((singtel_df['close'].iloc[-1] / singtel_df['close'].iloc[0] - 1) * 100):.2f}%")

    

5. Low-Latency Real-Time Streaming via WebSocket

For latency-sensitive strategies (e.g. market making, momentum, arbitrage), WebSocket provides sub-50ms push updates. The following example subscribes to quote, trade, and depth updates.

      import websocket
import json
import threading
import time

WS_URL = "wss://api.itick.org/stock"
API_TOKEN = "your_api_token_here"  # ← replace

def on_message(ws, message):
    data = json.loads(message)

    # Connection established
    if data.get("code") == 1 and data.get("msg") == "Connected Successfully":
        print("✅ WebSocket connected – awaiting authentication...")

    # Authentication result
    elif data.get("resAc") == "auth":
        if data.get("code") == 1:
            print("✅ Authentication successful – subscribing...")
            subscribe(ws)
        else:
            print("❌ Authentication failed")
            ws.close()

    # Subscription result
    elif data.get("resAc") == "subscribe":
        if data.get("code") == 1:
            print("✅ Subscription confirmed")
        else:
            print(f"❌ Subscription failed: {data.get('msg')}")

    # Market data payload
    elif data.get("data"):
        market_data = data["data"]
        data_type = market_data.get("type")
        symbol = market_data.get("s")

        if data_type == "quote":
            print(f"[{symbol}] LTP: {market_data.get('ld')} | Chg%: {market_data.get('chp')}% | Vol: {market_data.get('v')}")
        elif data_type == "tick":
            print(f"[{symbol}] Trade: {market_data.get('ld')} | {time.strftime('%H:%M:%S', time.localtime(market_data.get('t')/1000))}")
        elif data_type == "depth":
            bids = market_data.get("b", [])[:3]
            asks = market_data.get("a", [])[:3]
            print(f"[{symbol}] Bid: {bids} | Ask: {asks}")

def on_error(ws, error):
    print(f"❌ WebSocket error: {error}")

def on_close(ws, close_status_code, close_msg):
    print(f"🔌 WebSocket closed: {close_msg}")

def on_open(ws):
    print("🌐 WebSocket connection opened")

def subscribe(ws):
    subscribe_msg = {
        "ac": "subscribe",
        "params": "D05$SG,O39$SG,Z74$SG",
        "types": "tick,quote,depth"
    }
    ws.send(json.dumps(subscribe_msg))
    print(f"📤 Subscription sent: {subscribe_msg['params']}")

def send_ping(ws):
    while True:
        time.sleep(30)
        ping_msg = {
            "ac": "ping",
            "params": str(int(time.time() * 1000))
        }
        ws.send(json.dumps(ping_msg))
        print("💓 Ping sent")

if __name__ == "__main__":
    ws = websocket.WebSocketApp(
        WS_URL,
        header={"token": API_TOKEN},
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )

    ping_thread = threading.Thread(target=send_ping, args=(ws,))
    ping_thread.daemon = True
    ping_thread.start()

    print("🚀 Starting WebSocket connection...")
    ws.run_forever()

    

6. Retrieving Tick-by-Tick Trade Data

Tick-level (last trade) data is essential for microstructure analysis and execution quality monitoring.

      def get_singapore_tick_data(symbol):
    """
    Retrieve latest tick (last trade) information
    :param symbol: e.g. "D05"
    """
    url = f"{BASE_URL}/stock/tick"
    params = {
        "region": "SG",
        "code": symbol
    }
    headers = {
        "accept": "application/json",
        "token": API_TOKEN
    }

    try:
        response = requests.get(url, params=params, headers=headers)
        result = response.json()

        if result.get("code") == 0:
            tick = result.get("data", {})
            print(f"📊 Ticker: {tick.get('s', 'N/A')}")
            print(f"Last Trade Price: {tick.get('ld', 'N/A')} SGD")
            print(f"Last Trade Size: {tick.get('v', 'N/A')} shares")

            ts = tick.get('t', 0) / 1000
            if ts > 0:
                dt = datetime.datetime.fromtimestamp(ts)
                print(f"Trade Timestamp: {dt.strftime('%Y-%m-%d %H:%M:%S')} (SG time)")
            return tick
        else:
            print(f"❌ API Error: {result.get('msg', 'Unknown error')}")
            return None

    except Exception as e:
        print(f"❌ Request failed: {str(e)}")
        return None

print("\n🔍 Fetching latest tick for DBS (D05):")
get_singapore_tick_data("D05")

    

7. Singapore Market Reference Guide

7.1 Ticker Format

SGX tickers use local codes (e.g. D05, O39, Z74).

  • REST: specify region=SG
  • WebSocket: use format CODE$SG (e.g. D05$SG)

7.2 Key SGX Constituents Reference

Company NameTickerSectorBusiness Overview
DBS Group Holdings (星展银行)D05BankingSoutheast Asia’s largest bank by assets
OCBC Bank (华侨银行)O39BankingMajor Singapore-headquartered bank
United Overseas BankU11BankingLeading Singapore financial institution
Singapore Telecommunications (Singtel)Z74TelecommunicationsDominant regional telco operator
Keppel CorporationBN4ConglomerateOffshore & marine, infrastructure, real estate
Sembcorp IndustriesU96Energy / UtilitiesPower, renewables, urban solutions
CapitaLand GroupC31Real EstateLeading pan-Asian real estate investment group
Yangzijiang ShipbuildingBS6ShipbuildingMajor Chinese shipbuilder listed on SGX
Wilmar InternationalF34AgribusinessGlobal leader in palm oil & agribusiness

7.3 SGX Trading Hours (Singapore Local Time)

  • Pre-open: 08:30 – 08:59
  • Continuous trading: 09:00 – 12:00 / 13:00 – 17:00
  • Pre-close: 17:00 – 17:16
  • Close: 17:16
  • Lunch break: 12:00 – 13:00
  • Time zone: SGT (UTC+8) — same as Beijing / Hong Kong

7.4 Frequently Asked Questions

Q: What are the free tier limitations?
A: Unlimited real-time quote calls, daily/weekly/monthly historical bars, and WebSocket connections — sufficient for personal use, research, and light production workloads.

Q: How to access longer historical data?
A: Paid plans unlock 15+ years of full-history data, suitable for long-term backtesting and factor research.

Q: WebSocket connection drops frequently?
A: The example includes a 30-second heartbeat. Add automatic reconnection logic if needed.

Q: Are SGX tickers case-sensitive?
A: No — but use uppercase as shown in official listings (D05, O39, Z74, etc.).

8. Summary

This guide has covered the complete workflow for accessing Singapore Exchange (SGX) data via iTick:

  1. REST real-time quotes — simple, reliable polling for batch and scheduled tasks
  2. Historical OHLCV bars — multi-timeframe data ready for backtesting & technical analysis
  3. WebSocket streaming — sub-50ms push of trades, quotes, and depth for real-time strategies
  4. Tick-level trades — granular execution data for microstructure and slippage analysis

Key Advantages of iTick for SGX Coverage

  • Full universe of SGX-listed securities
  • Millisecond-grade latency on streaming channel
  • Clean, modern Python-friendly interface
  • Free tier suitable for prototyping and education; cost-effective paid tiers

Register today at https://itick.org and start building with Singapore market data.


Further Reading: