How to Choose an A-Share Data Source: From Historical Downloads to Real-Time Market APIs, Match by Use Case
TickDB Research ·
How to Choose an A-Share Data Source: From Historical Downloads to Real-Time Market APIs, Match by Use Case
Abstract: When searching for A-share data sources, historical backtesting and real-time market feed integration require different considerations. This article addresses three types of needs—historical data downloads, daily and minute-level data, and real-time market APIs—explaining how to evaluate data range, update frequency, and integration costs, with trial connection and validation steps to help you select the right data source for your current task.

Choosing an A-share data source starts with clearly defining the task. Downloading several years of daily bars for backtesting, analyzing minute-level movements on a specific date, or continuously updating the latest prices on a dashboard are all "fetching market data," but the validation criteria differ.
An API that returns the latest price may not support downloading minute-level history for a specified year. A saved daily bar file cannot replace a live data feed that updates continuously during trading hours. Defining the use case first, then comparing candidate sources, is usually more effective than starting with a vendor ranking list.
First, Clarify the Task: Historical Download or Continuous Real-Time Feed
Start by writing out the requirement: "Which stocks, what date range, what granularity, how often to update, and what final function will this serve?" For example, "Download daily bars for 10 stocks over the past three years, backfill daily after market close, for price strategy backtesting" already defines most of the selection criteria.
| Current Task | Priority Validation | Common Mistakes |
|---|---|---|
| Historical daily bar download and backtesting | Start/end dates, adjustment method, gaps and revisions | Treating the latest records as complete history |
| Historical minute analysis | Whether specified dates are available, period boundaries, trading sessions | Treating the recent minute endpoint as a multi-year minute archive |
| Real-time dashboard or alerts | Snapshot or push, quote timestamps, limits and recovery | Assuming successful requests mean data is continuously updating |

Figure 1: Three task types correspond to different validation focuses; the diagram is not a vendor ranking.
"Historical" and "real-time" don't necessarily come from the same source. Research libraries can use bulk historical data while online applications use a separate real-time endpoint. However, when combining both, you need to unify securities identifiers, timestamps, adjustment methods, and units—don't simply join two response tables together.
Historical Data Download: Verify Range First, Then Record Count
First, check actual coverage. Documentation stating "historical data supported" isn't enough to answer your question. At minimum, confirm the earliest queryable date, single-request return limit, pagination or time-segmentation method, and whether current permissions allow specifying start and end times. Requesting three years and getting 1,000 records back doesn't mean the full three years downloaded. When the return count hits the limit, you should definitely check for truncation.
Daily and minute bars require separate validation. According to AKShare documentation, Eastmoney's stock_zh_a_hist provides historical market data with specified cycles and date ranges. Another endpoint, stock_zh_a_hist_min_em, returns 1-minute data limited to the last 5 trading days without adjustment. This limitation applies to that specific endpoint—it cannot be extrapolated as a universal capability of the entire library, nor can setting an earlier start date guarantee retrieval of older data.
Next, unify field definitions. Before backtesting, record at minimum: securities code, date or timestamp, open/high/low/close prices, volume, turnover amount, and adjustment method. Don't assume units based on column names: the same "volume" field may use different units, and turnover amounts don't necessarily share the same magnitude. Convert according to the endpoint documentation first, then compare results.
Adjustment method selection should serve the research methodology. Unadjusted prices, forward-adjusted prices, and backward-adjusted prices cannot be mixed. After corporate actions or upstream revisions, re-downloading historical prices may produce different values. Save the query date, adjustment parameters, and original version to avoid mistaking data version changes for strategy changes.
Finally, explain gaps. A missing record may result from market holidays, trading halts, stocks not yet listed, endpoints omitting zero-volume periods, or collection failures. First classify gaps using the trading calendar, securities status, and endpoint rules, then decide whether to fill them. Don't fill missing prices with 0, and don't assume every stock has the same number of minute records per day.
An executable approach for historical trial connection: select three stocks and a clear date range, download, and save the raw results. Then count the earliest, latest times, total rows, and duplicate keys per stock. List gaps by date and explain the reasons. For minute data, add checks for midday boundaries, near open/close periods, and the last K-line. K-lines still forming cannot be directly included in backtesting as completed periods.
Real-Time Market APIs: Compare Update Methods and Exception Behavior
Real-time endpoints first distinguish between two consumption patterns: REST snapshot means you send one request and get one current state. WebSocket means you establish a connection, subscribe, and receive continuous messages. The former is convenient for prototyping with a small watchlist; the latter suits message-driven updates. Neither protocol inherently proves low latency or guarantees each trade appears in messages.
For quote timestamps, preserve at least two moments: the quote timestamp as recorded by the upstream source, and the time the client received the response. The difference between them includes upstream data age, transmission and processing time, and is affected by local clock accuracy—it shouldn't be directly labeled "network latency." When consecutive successful requests show unchanged quote timestamps, evaluate this against the trading session and instrument status.
Translate rate limits to your scale. Suppose one query covers 50 instruments, your application polls 500 instruments, and each round completes in 5 seconds. Ideally, each round needs 10 requests, approximately 120 per minute. This excludes retries, historical backfill, and other users. This is a capacity estimation example, not a commitment to any plan.
Exception behavior should also be part of trial connection: How does an invalid key return? Can timeouts be identified? How are partially missing instruments displayed? How does backoff work after hitting limits? Does reconnection require re-subscription? Applications may retain the last valid value but must simultaneously display stale data status. Empty responses and failures should not silently become "latest price = 0."
Free Tools vs. Commercial APIs: Compare Total Task Cost
Free tools suit validating research ideas with small samples, but account for endpoint changes, data filling, cleaning, and maintenance time as costs. For commercial APIs, verify what data, permissions, and support are included; paid access doesn't substitute for data validation.
AKShare is suitable as an entry point to evaluate libraries—during selection, narrow down to specific functions and upstream sources. Tushare requires permission verification per endpoint: the official permission table separates regular credit-point endpoints from independent permission endpoints; minute data is not included in regular credit-point coverage. Obtaining daily bar permissions doesn't automatically grant minute-level history or real-time data access.
| Task and Conditions | Which Entry to Try First | Conditions for Continued Use |
|---|---|---|
| Learning data collection, small-scale daily bar research | Public data tools or authorized data endpoints | Covers target range, can rerun next day, maintenance effort acceptable |
| Multi-year minute backtesting | Data services explicitly offering target minute archives | Range and granularity validated, permissions and bulk backfill feasible |
| Long-running dashboards or alerts | Real-time endpoints with clear update and rate-limit rules | Latency, failure display, recovery, and capacity meet requirements |
This table provides a trial sequence, not a quality ranking. When comparing total cost, itemize data permissions, initial historical backfill, daily calls, storage, exception handling, and migration work. For externally displayed applications, also include the corresponding usage license as a procurement checklist item. Low endpoint costs don't necessarily mean low total investment, and higher plan prices don't automatically indicate better fit for your use case.
What Is TickDB Suitable For?
If your task involves integrating the latest prices of several A-shares into a web dashboard, watchlist page, or providing market queries for an application, TickDB can be included in the trial evaluation for real-time market APIs. The examples later in this article use its market snapshot endpoint, checking whether latest price, quote timestamp, and instrument completeness are returned—suitable for validating the integration path for such needs.
If your primary goal is downloading multi-year daily bars for backtesting or building a continuous minute-level history database, you need to separately confirm the historical endpoint's coverage range, data granularity, adjustment definition, and account permissions. The market snapshot endpoint's response cannot prove that historical data meets these requirements.
For continuously running market applications, also verify accessible markets and instruments, request frequency limits, and recovery procedures after disconnection or request failures against your actual account. Start with a small instrument set for trial connection before deciding whether to expand to full business scale.
An Executable Example to Check Fields and Data Timestamps
Below is a demonstration of a minimal trial connection using a REST snapshot endpoint with verified field definitions. It only validates the response structure, prices, and timestamps for two stocks—it's not responsible for historical downloads, and one successful request cannot rate the data source's quality. When switching to other sources, modify the request and field mapping first; don't assume identical response structures.
The code uses only Python 3.10+ standard library, no third-party dependencies required. Save it as probe.py. Online mode requires configuring the environment variable MARKET_API_KEY locally, ensuring the key has the target market permissions. Keys should not be hardcoded in the code, output reports, or articles.

Figure 2: Each trial connection preserves query conditions and raw data. Field validation passing only indicates this check passed—not data completeness or continuous real-time capability.
import json
import os
import sys
import time
from datetime import datetime, timedelta, timezone
from decimal import Decimal, InvalidOperation
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
SYMBOLS = ("600519.SH", "000858.SZ")
URL = "https://api.tickdb.ai/v1/market/ticker"
TZ = timezone(timedelta(hours=8))
def inspect(payload, received_ms):
if not isinstance(payload, dict) or str(payload.get("code")) != "0":
raise ValueError("business response failed")
data = payload.get("data")
if not isinstance(data, list):
raise ValueError("data must be a list")
report = []
for symbol in SYMBOLS:
matches = [q for q in data if isinstance(q, dict)
and q.get("symbol") == symbol]
if len(matches) != 1:
report.append({"symbol": symbol, "state": "missing or duplicate"})
continue
q = matches[0]
try:
price = Decimal(str(q["last_price"]))
stamp = q["timestamp"]
if not price.is_finite() or price < 0:
raise ValueError()
if type(stamp) is not int or not 946684800000 <= stamp < 4102444800000:
raise ValueError()
except (KeyError, ValueError, InvalidOperation):
report.append({"symbol": symbol, "state": "invalid fields"})
continue
quote_time = datetime.fromtimestamp(stamp / 1000, TZ)
age = (received_ms - stamp) / 1000
report.append({
"symbol": symbol, "name": q.get("name") or symbol,
"last_price": str(price), "quote_time": quote_time.isoformat(),
"age_seconds": round(age, 3),
"state": "future timestamp" if age < -5 else "fields valid",
})
return report
def main():
offline = len(sys.argv) > 1
if offline:
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
else:
key = os.environ.get("MARKET_API_KEY")
if not key:
raise ValueError("set MARKET_API_KEY first")
query = "?symbols=" + ",".join(SYMBOLS) + "&type=stock"
req = Request(URL + query, headers={"X-API-Key": key})
with urlopen(req, timeout=10) as response:
payload = json.load(response)
received_ms = time.time_ns() // 1_000_000
report = inspect(payload, received_ms)
record = {"mode": "offline" if offline else "online",
"checked_at_ms": received_ms, "requested": list(SYMBOLS),
"payload": payload, "report": report}
filename = f"probe-{time.time_ns()}.json"
Path(filename).write_text(json.dumps(record, ensure_ascii=False, indent=2),
encoding="utf-8")
print(json.dumps(report, ensure_ascii=False, indent=2))
print("mode:", record["mode"], "saved:", filename)
if __name__ == "__main__":
try:
main()
except HTTPError as exc:
raise SystemExit(f"HTTP error: {exc.code}")
except (URLError, TimeoutError, OSError, ValueError) as exc:
raise SystemExit(f"Probe failed: {type(exc).__name__}")
After configuring the environment variable, run python probe.py. Each successful business response parsing saves a JSON file with a unique filename containing the request code, check timestamp, raw response, and results. fields valid indicates this example's field checks passed—it cannot be translated as "market data is real-time." Missing instruments or duplicates are flagged individually; optional names fall back to the symbol if absent.
This example interprets timestamp as milliseconds according to the endpoint documentation. The 2000–2100 year range in the code is a reasonableness check for demonstration purposes; it cannot replace endpoint contracts. Obviously future timestamps receive a separate warning. Data age retains positive and negative values rather than truncating to 0 to hide clock issues.
Without a key, first save the following synthetic data as sample.json and run python probe.py sample.json:
{"code":0,"data":[
{"symbol":"600519.SH","last_price":"10.20","timestamp":1792114200000},
{"symbol":"000858.SZ","last_price":"20.30","timestamp":1792114200000}
]}
The sample timestamp corresponds to 2026-10-16 09:30:00+08:00; prices are synthetic. In offline mode, the check timestamp is the file read time and cannot be treated as the original network receive time. The report explicitly records offline. Fixed sample timestamps may be in the past or still in the future. A future timestamp is an expected result of the time check, not an indication of field format errors.
Recommended sequence for four validation steps: keep as-is, confirm both rows pass field checks; delete one stock, confirm it's marked as missing; change last_price to null, confirm it's not treated as 0; change code to non-zero, confirm the program exits on business failure. After that, use a valid key for trial connection. During trading hours, save multiple results at allowed frequency and compare whether quote timestamps advance—don't just compare whether prices changed.
This example has verified the request URL, authentication, and fields for the provided sample, completing offline validation with synthetic input. It has not completed local key direct connection, intraday continuous sampling, or multi-source identical-condition performance testing. Therefore, this code is a trial connection starting point and does not constitute a latency, coverage, or stability rating.
Document the Selection as a Verifiable Conclusion
The final conclusion should trace back to the original task: for historical daily bar research, confirm the specified range, field definitions, and gap explanations. For minute backtesting, confirm the target dates and period boundaries—not just that "a minute endpoint exists." For real-time applications, confirm updates, capacity, and exception recovery.
First complete "fetch, save, check, rerun" with a small-scale dataset, then decide whether to expand scope or purchase permissions. The key to choosing an A-share data source is demonstrating it can complete the current task—and understanding which conditions remain unvalidated.