Local A-Share Data Lake for AI Agents
ashare-lake builds a local A-share data lake on your own disk: 39 datasets, delisting records, point-in-time queries, and an MCP server for agents.
A local A-share data lake is the Chinese mainland equity market's history sitting on your own disk as columnar Parquet files, readable by DuckDB or Polars, instead of a vendor endpoint you call one page at a time. ashare-lake is an open-source project that builds one, plus the daily job that keeps it current and a Model Context Protocol server that lets an AI agent query it. Two design choices earn it a post here: delisted names are kept, and fundamentals can be read as of a past date.
Why an AI agent needs a local A-share data lake
An agent researching Chinese equities without a local copy has two paths. It scrapes finance pages, spending its context window on HTML and producing numbers nobody can reproduce next month. Or it calls a registration-gated vendor, which caps rows and ties every result to an account.
Scale is the part that gets underestimated. Our own warehouse carries the US tape at minute resolution, and one ordinary week of it looks like this:
The exact SQL behind every number
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session,
formatDateTime(toDate(toTimeZone(window_start, 'America/New_York')), '%b %e') AS session_label,
uniqExact(ticker) AS tickers_count,
round(count() / 1000000, 2) AS minute_bars_millions
FROM global_markets.delayed_stocks_minute_aggs
WHERE toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2026-07-20')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-24')
GROUP BY session, session_label
ORDER BY sessionOn Jul 20 the tape produced 1.79 million minute bars across 11737 symbols, and the four other sessions in the panel repeat it. One national market, one week, one resolution. A decade of daily bars, fundamentals, index membership, and money-flow records for another market has the same shape, and paging it over HTTP burns an agent's run.
A local lake changes two things at once. Reads become a file scan rather than a quota, and a query written today returns the same rows in six months, which is what a backtest needs to be checkable. Our notes on market data skills for AI agents and on a SQL API over market data make the same argument for US data.
Install it, pinned to one version
Python 3.10 or newer. Pin the version: six releases landed between July 27 and August 2, 2026, and an unpinned install inside an agent's setup script is a moving target. The code sits at rootSunc/ashare-lake under Apache 2.0.
pip install ashare-lake==0.5.0installs the current release as of early August 2026.asl --versionprints the installed build.asl config init --data-root /path/to/ashare-lakewrites the packaged example TOML with your data root filled in, no repository checkout required.--configsets the output path,--forceoverwrites.asl doctorruns its checks offline, before any data moves.asl servers testprobes the upstream quote hosts.asl sourcesprobes each source, with a--vantageofcn,overseas, orlocal.
Stop there. The next command is the backfill, and it is not a thing to fire off while reading.
What the backfill does
asl init creates the directory layout and fills in history. The project's docs put a full run at hours of wall time and several GB on disk, and it needs a live connection to an upstream Tongdaxin quote host, which is what asl servers test verifies. asl init --profile quick covers the recent three years instead, in minutes, and still keeps every name that traded inside that window, including the ones that have since left the market. asl run daily is the incremental job afterwards, asl status --datasets reports coverage and freshness per dataset, and asl serve puts a read-only dashboard on 127.0.0.1:8787.
The repository ships no data at all. Every Parquet file is built on your machine, with row-level lineage on each row recording which source produced it and when it was fetched.
What are the 39 datasets?
They are layered, from reference data outward: 36 curated tables and 3 derived ones.
- Reference: instruments, a trading calendar covering 2016 to 2027, trading status.
- Market data: daily bars, index bars, 1-minute and 5-minute bars, trade ticks, commodity bars, adjustment factors, delisting events.
- Corporate events: corporate actions, an announcement index, the earnings disclosure schedule.
- Fundamentals and valuation: financial statement items, valuation metrics, analyst consensus.
- Capital flow: fund flow, margin trading, northbound flows and holdings, the dragon-tiger board of large-order disclosures, block trades, institutional holdings.
- Structure and industry: sector members, index constituents, industry members, industry index.
- Macro: macro indicators, market breadth, an economic calendar.
- Sentiment and rotation: sentiment scores, hot rank, sector bars, sector fund flow, news headlines, a flash news wire.
- Risk and compliance: the share unlock schedule, regulatory events.
Where a dataset sits tells you what the author cares about. Adjustment factors and delisting events are in the market-data layer beside daily bars, not parked in an appendix, and that placement is the half of the project worth the most to anyone who tests ideas on history.
How a local lake handles survivorship bias
Survivorship bias is what you get when the universe of a study is drawn from the names that are still listed today. Every company that merged, went private, or was delisted is silently absent, and the names that vanish are rarely the winners.
Our warehouse can size that hole for the US market, the same arithmetic in a different alphabet. For each year, take every symbol that printed a minute bar in the second week of March, then check which of them were still printing in the last two weeks of July 2026:
The exact SQL behind every number
WITH on_tape_now AS (
SELECT ticker
FROM global_markets.delayed_stocks_minute_aggs
WHERE toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2026-07-20')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
GROUP BY ticker
),
cohort AS (
SELECT toYear(toTimeZone(window_start, 'America/New_York')) AS cohort_year,
ticker
FROM global_markets.delayed_stocks_minute_aggs
WHERE toYear(toTimeZone(window_start, 'America/New_York')) BETWEEN 2016 AND 2025
AND toMonth(toTimeZone(window_start, 'America/New_York')) = 3
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) BETWEEN 10 AND 14
GROUP BY cohort_year, ticker
)
SELECT c.cohort_year AS year,
count() AS names_on_tape_count,
countIf(n.ticker != '') AS still_trading_count,
count() - countIf(n.ticker != '') AS gone_count,
round(100 * countIf(n.ticker != '') / count(), 1) AS still_trading_pct
FROM cohort AS c
LEFT JOIN on_tape_now AS n ON c.ticker = n.ticker
GROUP BY year
ORDER BY yearOf the 8101 symbols trading that week in 2016, 50.1% were still on the tape in late July 2026, and 4040 were not. The 2025 cohort reads 86.7%. Run a screen built from today's listings backwards across those 10 years and it drops a widening share of the market as it goes.
The comfortable assumption is that the missing names were all penny stocks. Sorting the March 2021 cohort into tiers by its average daily dollar volume that month says otherwise:
The exact SQL behind every number
WITH on_tape_now AS (
SELECT ticker
FROM global_markets.delayed_stocks_minute_aggs
WHERE toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2026-07-20')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
GROUP BY ticker
),
march_2021 AS (
SELECT ticker,
sum(toFloat64(close) * toFloat64(volume))
/ uniqExact(toDate(toTimeZone(window_start, 'America/New_York'))) AS avg_daily_dollar_volume
FROM global_markets.delayed_stocks_minute_aggs
WHERE toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2021-03-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2021-03-31')
GROUP BY ticker
)
SELECT multiIf(d.avg_daily_dollar_volume >= 1000000000, '$1B or more',
d.avg_daily_dollar_volume >= 100000000, '$100M to $1B',
d.avg_daily_dollar_volume >= 10000000, '$10M to $100M',
d.avg_daily_dollar_volume >= 1000000, '$1M to $10M',
'under $1M') AS liquidity_bucket,
count() AS names_count,
countIf(n.ticker = '') AS gone_count,
round(100 * countIf(n.ticker = '') / count(), 1) AS gone_pct
FROM march_2021 AS d
LEFT JOIN on_tape_now AS n ON d.ticker = n.ticker
GROUP BY liquidity_bucket
ORDER BY min(d.avg_daily_dollar_volume)The under $1M tier lost the largest share, 57.5% of 3968 names. The busiest tier was not exempt: 3.4% of the 89 symbols trading $1B or more a day in March 2021 had left by late July 2026. Mergers, take-privates, bankruptcies, and index exits all end the same way in a price table: the rows stop.
ashare-lake treats that as a first-class problem. The instruments dataset retains delisted symbols rather than filtering down to live ones, a delisting_events table records how each departed name ended, and universe="all_a" in the Python API resolves a historical snapshot that includes them. The project's own docs report roughly a two-fold gap between a survivor-only backtest and a delisting-inclusive one over 2016 to 2021. That is the mirror image of the trap in our look-ahead bias in backtesting notes: a universe that quietly knows the future.
Point-in-time reads
load("financial_statement_items", as_of="2018-04-30") returns the latest version of each line item announced on or before that date, not the number as later restated. Bars carry an adjust argument: hfq for backward adjustment, qfq normalized inside the query window, or raw prices. A factor fitted on numbers the market had not published yet measures nothing, which is the first thing to check in any LLM generated alpha factor pipeline.
Registering it as an MCP server
The Model Context Protocol is how an agent picks up tools. asl mcp speaks it over stdio, and the client launches the process:
claude mcp add ashare-lake -- asl mcp --config /abs/path/to/ashare-lake.toml- Any other MCP client takes the same two pieces:
aslas the command, andmcp --configplus an absolute path as the arguments. The path has to be absolute, since the client may start the process from any directory.
Six tools come up, organized by question rather than by dataset: describe_lake for what exists and how to read it, resolve_symbol for a name to a code (delisted names included), query_bars, query_fundamentals with its as_of argument, query_dataset for everything else, and run_sql for a single read-only DuckDB SELECT across datasets. A --live flag lets the server answer symbol lookups and unadjusted daily bars from upstream without writing to the lake.
Limits worth knowing first
- A-shares only. No Hong Kong, no US listings, nothing outside mainland China.
- A personal project. Issues and pull requests get best-effort attention, and the docs state plainly that there is no availability guarantee: upstream sites change, and an IP can get blocked, which stops ingest until someone patches it.
- Apache 2.0 covers the code, not the data. Each upstream source keeps its own terms, and the maintainer grants no right to redistribute or resell the Parquet files you build. Read the source terms before any commercial use.
- No account or token is needed for the sources it reads, which is the appeal and also the fragility.
- Windows support arrived in 0.3.0, and the Python floor moved to 3.10 in 0.3.1.
Where these project facts come from
Version 0.5.0, the six release dates, and the Python floor come from the project's PyPI release history and CHANGELOG, read on August 4, 2026. The dataset catalog, the delisting and point-in-time behaviour, the CLI flags, the MCP tool list, and the licensing and support wording come from the repository docs: docs/datasets/catalog.md, docs/reference/cli.md, docs/reference/mcp.md, and docs/legal-and-data-sources.md. Software moves faster than posts do, so check the docs for the version you install. The two survivorship panels measure US symbols in our own warehouse, not Chinese listings, and stand as an illustration of the mechanic rather than a measurement of the A-share market.
FAQ
Do you need an API key to build a local A-share data lake?
Not with this one. The upstream sources it reads need no registration or token, and the lake itself lives on your machine. Each source keeps its own terms of use, which matter before any commercial work.
How long does the ashare-lake backfill take?
The docs put a full history backfill at hours of wall time and several GB of disk, with a working connection to an upstream quote host required throughout. asl init --profile quick covers the recent three years in minutes and still includes names that have since delisted.
Does a local A-share data lake include delisted stocks?
This one does. Delisted symbols stay in the instruments dataset, a delisting_events table records how each name ended, and universe="all_a" builds a historical snapshot that includes them. Our own US measurement above shows the size of what a survivor-only universe leaves out.
Can an AI agent query ashare-lake directly?
Yes. asl mcp exposes six tools over the Model Context Protocol, one of them a read-only SQL tool, so the agent queries local Parquet instead of scraping. Register it with claude mcp add ashare-lake -- asl mcp --config /abs/path/to/ashare-lake.toml.
Is ashare-lake a replacement for AkShare or Baostock?
No. It sits above them. Those libraries fetch from upstream; this project stores, versions, and reconciles what they fetch into curated Parquet with row-level lineage and one contract per dataset.
Every figure above is a stored, versioned query over real market data, expand any panel to read the SQL, or run the same survivorship check on your own universe on the Strasmore terminal.