Introduction: The “Fuel” Problem in Quantitative Trading
The foundation of quantitative trading is never the strategy model itself — it is the data. Statistics show that over 85% of quantitative strategies fail, with one of the core reasons being delayed market data or unstable interfaces. For individual developers and small teams, professional data providers such as Bloomberg Terminal can cost tens of thousands of dollars per year, while free channels often suffer from messy data formats, significant latency, or highly unreliable connectivity.
So, what reliable and truly free APIs are available to individual quantitative traders in 2026? How can one build a stable and scalable data acquisition system without violating the terms of service? This article provides a complete technical solution across three dimensions: data source selection, practical integration with the iTick API, and performance optimization and pitfall avoidance.
I. Free Data Source Selection: Clarify Your Requirements First
Before writing any code, ask yourself three key questions:
- What types of assets do you need? A-shares, U.S. stocks, cryptocurrencies, forex, or futures? Different data sources have different strengths.
- How high are your real-time requirements? High-frequency strategies require tick-level real-time pushes (WebSocket), while backtesting and research can tolerate 15-minute delays.
- How much technical effort are you willing to invest? Some APIs are plug-and-play (no registration required), while others demand token application, documentation review, and handling various format pitfalls.
Mainstream free quantitative data sources in the market can be roughly divided into three categories:
- Open-source Python libraries: Such as AKShare, yfinance, and Baostock. Advantages: completely free and require no API keys — install and use immediately. Disadvantages: data depends on third-party websites, stability is uncontrollable, and some interfaces have latency.
- Commercial API free tiers: Such as iTick, Tushare Pro, and Alpha Vantage. Advantages: standardized interfaces, low latency, official documentation, and technical support. Disadvantages: request rate limits and, in some cases, registration required.
- Direct exchange connections: Such as Binance WebSocket and Coinbase API. Advantages: best real-time performance and authoritative data source. Disadvantages: limited to cryptocurrencies and requires handling raw protocols.
For most individual developers, a hybrid approach is the optimal strategy: use open-source libraries for historical data in backtesting, and commercial free APIs or direct exchange connections for real-time market monitoring. The remainder of this article focuses on integrating the iTick API, as it provides both REST and WebSocket protocols, covers multiple markets including A-shares, Hong Kong stocks, U.S. stocks, forex, and cryptocurrencies, and offers a free tier that is sufficiently generous for individual developers.
II. Cross-Market Real-Time Market Data Solution
1. REST API in Practice: Retrieving Real-Time and Historical Data
The following code examples assume you have already installed the requests and pandas libraries.
1.1 Basic Configuration
import requests
import pandas as pd
API_TOKEN = "YOUR_API_TOKEN"
BASE_URL = "https://api.itick.org"
HEADERS = {"accept": "application/json", "token": API_TOKEN}
1.2 Retrieve Real-Time Quote for a Single Stock
def get_quote(region, code):
url = f"{BASE_URL}/stock/quote?region={region}&code={code}"
resp = requests.get(url, headers=HEADERS)
if resp.status_code == 200:
data = resp.json()
if data.get("code") == 0:
return data.get("data")
return None
# Example: Get real-time quote for Apple Inc. (AAPL)
quote = get_quote("US", "AAPL")
if quote:
print(f"Latest Price: {quote['ld']}, Change: {quote['chp']}%, Volume: {quote['v']}")
Parameter Description: region supports values such as US (U.S. stocks), SH (Shanghai Stock Exchange), SZ (Shenzhen Stock Exchange), HK (Hong Kong stocks), etc. Key return fields include ld (latest price), chp (percentage change), and v (volume).
1.3 Batch Retrieval of Real-Time Quotes for Multiple Stocks
def get_batch_quotes(region, codes):
# codes: comma-separated string, e.g., "AAPL,MSFT,GOOG"
url = f"{BASE_URL}/stock/quotes?region={region}&codes={codes}"
resp = requests.get(url, headers=HEADERS)
if resp.status_code == 200:
data = resp.json()
if data.get("code") == 0:
return data.get("data")
return None
quotes = get_batch_quotes("US", "AAPL,MSFT,GOOG")
if quotes:
df = pd.DataFrame(quotes).T
print(df[["ld", "chp"]].head())
1.4 Retrieve Historical K-Line (Candlestick) Data for Backtesting
def get_kline(region, code, ktype=6, limit=100):
"""
ktype: 1=1-minute, 2=5-minute, 3=15-minute, 4=30-minute,
5=60-minute, 6=daily, 7=weekly, 8=monthly
"""
url = f"{BASE_URL}/stock/kline?region={region}&code={code}&kType={ktype}&limit={limit}"
resp = requests.get(url, headers=HEADERS)
if resp.status_code == 200:
data = resp.json()
if data.get("code") == 0:
klines = data.get("data", [])
df = pd.DataFrame(klines, columns=["timestamp", "open", "high", "low", "close", "volume"])
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
df.set_index("timestamp", inplace=True)
return df
return None
# Get recent 100 trading days of daily data for Kweichow Moutai (600519.SH)
df = get_kline("SH", "600519", ktype=6, limit=100)
print(df.tail())
iTick supports up to 15 years of daily historical data. Simply increase the limit parameter to retrieve more records.
1.5 Retrieve Forex Data
def get_forex(region="GB", code="EURUSD"):
url = f"{BASE_URL}/forex/quote?region={region}&code={code}"
resp = requests.get(url, headers=HEADERS)
if resp.status_code == 200:
data = resp.json()
if data.get("code") == 0:
return data.get("data")
return None
eurusd = get_forex("GB", "EURUSD")
print(f"EURUSD Latest Price: {eurusd['ld']}")
1.6 Retrieve Cryptocurrency Data
def get_crypto(code="BTC-USD"):
url = f"{BASE_URL}/crypto/quote?code={code}"
resp = requests.get(url, headers=HEADERS)
if resp.status_code == 200:
data = resp.json()
if data.get("code") == 0:
return data.get("data")
return None
btc = get_crypto("BTC-USD")
print(f"Bitcoin: ${btc['ld']}")
2. WebSocket Real-Time Push: Ideal for Low-Latency Strategies
For strategies requiring real-time monitoring of multiple instruments, polling via REST API is inefficient. WebSocket uses a persistent connection with push mechanism, delivering data in real time with latency controllable within 50ms.
2.1 Install Dependencies
pip install websocket-client
2.2 Complete WebSocket Client Example
import websocket
import json
import threading
import time
API_TOKEN = "YOUR_API_TOKEN"
WS_URL = "wss://api-free.itick.org/stock" # Free tier WebSocket endpoint
def on_message(ws, message):
data = json.loads(message)
# Handle authentication result
if data.get("resAc") == "auth":
if data.get("code") == 1:
print("Authentication successful, subscribing...")
subscribe(ws)
else:
print("Authentication failed")
ws.close()
# Handle subscription result
elif data.get("resAc") == "subscribe":
if data.get("code") == 1:
print("Subscription successful")
else:
print(f"Subscription failed: {data.get('msg')}")
# Handle market data
elif data.get("data"):
for symbol, tick in data["data"].items():
print(f"{symbol} Latest Price: {tick.get('ld')}, Volume: {tick.get('v', 0)}")
def on_error(ws, error):
print(f"WebSocket Error: {error}")
def on_close(ws, close_status_code, close_msg):
print("WebSocket connection closed")
def on_open(ws):
# Send authentication request
auth_msg = {"ac": "auth", "token": API_TOKEN}
ws.send(json.dumps(auth_msg))
def subscribe(ws):
# Subscribe to tick data. Multiple symbols separated by commas, format: code$region
sub_msg = {
"ac": "subscribe",
"params": "AAPL$US,MSFT$US,600519$SH",
"types": "tick"
}
ws.send(json.dumps(sub_msg))
def send_heartbeat(ws):
"""Send heartbeat every 30 seconds to maintain connection"""
while True:
time.sleep(30)
try:
ws.send(json.dumps({"ac": "ping"}))
except:
break
if __name__ == "__main__":
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
# Start heartbeat thread
threading.Thread(target=send_heartbeat, args=(ws,), daemon=True).start()
ws.run_forever()
WebSocket supports data types including tick (tick-by-tick trades), quote (quotes), and depth (order book depth), which can be freely combined via the types parameter.
III. Practical Case: Dual Moving Average Strategy Backtest
With the data interface ready, let’s build a simple quantitative strategy — the Dual Moving Average Crossover — and backtest it using historical data from iTick.
Strategy Logic: Go long (full position) when the 20-day MA crosses above the 60-day MA; close the position when the 20-day MA crosses below the 60-day MA.
import numpy as np
def fetch_kline_for_backtest(symbol, region, limit=200):
"""Fetch daily data for backtesting"""
return get_kline(region, symbol, ktype=6, limit=limit)
def backtest_double_ma(df, short=20, long=60, initial_capital=100000):
df = df.copy()
df["MA_short"] = df["close"].rolling(short).mean()
df["MA_long"] = df["close"].rolling(long).mean()
# Generate signals: 1 = in position, 0 = flat
df["signal"] = 0
df.loc[df["MA_short"] > df["MA_long"], "signal"] = 1
df["position_change"] = df["signal"].diff()
capital = initial_capital
position = 0
trades = []
for idx, row in df.iterrows():
if row["position_change"] == 1 and position == 0: # Buy
position = capital / row["close"]
capital = 0
trades.append(("BUY", idx, row["close"]))
elif row["position_change"] == -1 and position > 0: # Sell
capital = position * row["close"]
position = 0
trades.append(("SELL", idx, row["close"]))
# Final portfolio value (close any open position at the last closing price)
final_value = capital + (position * df.iloc[-1]["close"] if position > 0 else 0)
total_return = (final_value - initial_capital) / initial_capital * 100
return trades, total_return, final_value
# Fetch historical data for Kweichow Moutai
df = fetch_kline_for_backtest("600519", "SH", limit=300)
if df is not None:
trades, ret, final = backtest_double_ma(df)
print(f"Total Return: {ret:.2f}%")
print(f"Final Portfolio Value: ¥{final:.2f}")
print(f"Number of Trades: {len(trades)}")
for t in trades[:5]:
print(f"{t[0]} @ {t[2]:.2f} on {t[1].strftime('%Y-%m-%d')}")
Backtesting results allow you to quickly validate strategy effectiveness without risking real capital.
IV. Best Practices and Pitfall Avoidance Guide
1. Strictly Observe Rate Limits
Free APIs have clear request frequency limits. Implement proactive rate limiting in your code:
import time
def rate_limited_call(func, min_interval=1.0):
time.sleep(min_interval)
return func()
For more elegant solutions, use the ratelimit or tenacity libraries.
2. Local Caching of Historical Data
Repeatedly pulling historical data from the API for every backtest is both slow and likely to trigger rate limits. It is recommended to store data in a local database or files:
import sqlite3
def cache_to_sqlite(df, table_name):
conn = sqlite3.connect("market_cache.db")
df.to_sql(table_name, conn, if_exists="replace")
conn.close()
def load_from_cache(table_name):
conn = sqlite3.connect("market_cache.db")
df = pd.read_sql(f"SELECT * FROM {table_name}", conn, parse_dates=["timestamp"])
conn.close()
return df
Perform incremental daily updates.
3. WebSocket Automatic Reconnection Mechanism
Network fluctuations may cause WebSocket disconnections. Implement exponential backoff reconnection in on_close:
def on_close(ws, *args):
print("Connection closed. Reconnecting in 5 seconds...")
time.sleep(5)
ws.run_forever()
4. Cross-Validation of Data Quality
Free data sources may occasionally contain anomalies. Before making live trading decisions, cross-validate critical prices and volumes using two independent sources (e.g., iTick and AKShare or yfinance). If significant discrepancies are found, pause the strategy and perform manual verification.
5. Monitor Field Changes and API Upgrades
API response fields may change with version updates. Add existence checks for critical fields in your code and subscribe to official update notifications.
V. Conclusion
Free data APIs enable individual quantitative traders to build professional-grade data acquisition systems at zero cost. This article has detailed both REST and WebSocket integration methods, covering multiple asset classes including equities, forex, and cryptocurrencies, and provided complete code for a dual moving average strategy backtest.
Finally, a reminder: data is only the starting point of quantitative trading, not the end. Even the best data source cannot generate sustainable returns without sound strategy design, robust risk management, and rigorous backtesting validation. We hope the tools and practices presented here will help you take a solid first step.
References:
https://blog.itick.org/stock-api/itick-chanlun-strategy-backtesting-tutorial
GitHub: https://github.com/itick-org/
Top comments (0)