Self-Hosted A-Share Quant Workbench, Explained
A self-hosted A-share quant workbench runs screening and backtesting on your own machine. What it actually depends on, and the four things to check first.
A self-hosted A-share quant workbench is one web page that screens the market, watches a list of names, runs backtests and writes a post-market review, on a machine you control instead of a broker's terminal. One MIT-licensed project, tickflow-stock-panel, packages all of that behind a single port, and it had gathered roughly 2,800 GitHub stars as of August 2026. The feature list is the easy part to read. The layer underneath decides whether any of it still works next quarter.
What a self-hosted A-share quant workbench is made of
The architecture is short enough to hold in your head. Read from the code and docs at the August 2026 commit, it is:
- A web page built in React and TypeScript.
- A Python service, FastAPI, with a scheduler that refreshes data on a timer.
- A local store: Parquet files on disk, queried with DuckDB, computed with Polars.
- A backtesting engine, vectorbt, pointed at those files.
- A vendor SDK that fetches every price the panel draws.
Notice what is absent. Nothing in that stack connects to an exchange. The panel computes; the vendor supplies. "Self-hosted" describes where the code runs and says nothing about where the numbers come from. Owning the raw bars is the job of the layer below, a local A-share market data lake, and it is what makes everything above it replaceable.
What the free tier of the data source covers
One environment variable decides what the panel can see. Leave the vendor key blank and the project runs in what its docs call None mode: historical daily bars from a free endpoint, with the current session landing one to two hours after the close. Fill the key in and access opens by paid tier. That is a usable free tier for end-of-day work and a hard stop for anything intraday, on terms the vendor can restate at any renewal.
The maintainer is direct about the arrangement:
本项目仅供学习与量化研究,不构成任何投资建议。回测结果不代表未来收益。
本项目个人开源,基于 TickFlow 数据源,非 TickFlow 官方项目。
README, shy3130/tickflow-stock-panel, read 13 August 2026
In English: the project is for study and quantitative research, it "does not constitute investment advice", backtest results do not represent future returns, and it is "an open-source personal project, not an official TickFlow product". That is the durable lesson of the whole genre. Zero-ops takes the operations work off your desk. It leaves the dependency exactly where it was: you are renting data from a company that never agreed to support this panel. The README also limits use to study and research, while the licence file is MIT.
Why pin a commit SHA instead of a version number
A commit SHA is the 40-character fingerprint git assigns every saved change. It names one exact state of the code, permanently.
Read on 13 August 2026, the repository had nothing on its GitHub Releases page. It does carry 31 version tags, v0.1.31 through v0.1.88, with gaps in the numbering and no notes attached. The newest tag pointed at a commit dated 31 July 2026, while the default branch had moved on to work dated 6 August 2026. A tag that trails the branch and carries no notes cannot tell you what you are getting.
So write the SHA down. The head of the default branch on 6 August 2026 was ecfddb451e97f6fc9a7e43ac33e4ef0e69933b33. Check out that commit and record it beside any conclusion you draw from the backtests. Six weeks on, "I ran the latest" describes nothing.
The documented start is two lines: copy the example environment file to .env, then run docker compose up --build, which serves the panel on port 3018. Those are the maintainer's instructions at that commit rather than a tested path; the build pulls images and needs network access this page has not exercised. The development route (./dev.sh, or .\dev.ps1 on Windows) wants Python 3.11 or newer, Node 20 or newer, and the uv and pnpm package managers.
What the LLM strategy feature sends, and where
Strategy generation, single-stock commentary and the review write-up are optional. Leave AI_API_KEY blank and the whole surface stays dark. Fill it and you also set AI_PROVIDER (an OpenAI-compatible endpoint, or Ollama), AI_BASE_URL, AI_MODEL and AI_DAILY_TOKEN_BUDGET, which halts calls once the day's token allowance is spent.
The direction of travel matters. Your prompt, and whatever context the panel attaches to it, leaves your machine for the endpoint you configured. Point the base URL at a hosted API and the request goes to that company; point it at a runtime on your own network and it stays home. Self-hosting the panel does not self-host the model. Keys sit in a plaintext .env at the project root and can also be edited from the settings page, behind a dashboard password with a six-character minimum. Our note on LLM-generated alpha factors covers where machine-written strategies tend to fall over.
Does the backtester model trading costs?
The backtester advertises T+1 handling, commissions, slippage and stop-losses. Those switches existing is not the same as those switches being set, and a backtest that fills at the printed close with costs at zero is a claim that trading is free. It is not. A basis point is one hundredth of one percent. The panel below measures the average gap between the best bid and the best offer, in basis points, across six US listings over one midday hour on 17 June 2026.
The exact SQL behind every number
SELECT
ticker AS symbol,
round(avg(toFloat64(ask_price) - toFloat64(bid_price))
/ avg((toFloat64(ask_price) + toFloat64(bid_price)) / 2) * 10000, 2) AS spread_bps
FROM global_markets.cache_stocks_quotes
WHERE ticker IN ('SPY', 'AAPL', 'MSFT', 'NVDA', 'KO', 'F')
AND sip_timestamp >= toDateTime('2026-06-17 15:00:00', 'UTC')
AND sip_timestamp < toDateTime('2026-06-17 16:00:00', 'UTC')
AND bid_price > 0
AND ask_price > bid_price
GROUP BY ticker
ORDER BY spread_bpsThe tightest name in the panel, SPY, quoted an average 0.29 bps over that hour. The widest, F, quoted 6.98 bps. Cross that gap twice, once to get in and once to get out, and the round trip costs about double the figure shown, before commissions.
Timing costs as much as naming. The next panel takes one of those listings and measures how far its price traveled inside a single minute, averaged into half-hour buckets across the same day.
The exact SQL behind every number
SELECT
formatDateTime(
toStartOfInterval(toTimeZone(window_start, 'America/New_York'), INTERVAL 30 MINUTE),
'%H:%i') AS et_time,
round(avg((toFloat64(high) - toFloat64(low)) / toFloat64(close)) * 10000, 1) AS range_bps
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'KO'
AND window_start >= toDateTime('2026-06-17 12:00:00', 'UTC')
AND window_start < toDateTime('2026-06-17 21:00:00', 'UTC')
AND volume > 0
AND close > 0
GROUP BY et_time
ORDER BY et_timeThe panel opens at 08:00, before the regular session, where the average minute bar measured 0 bps of travel: the minutes that traded at all sat on a single price. The last bucket, 16:30, sits after the close and averaged 4.2 bps. A backtest picks one point out of each of those ranges and calls it the fill. Read the curve rather than the endpoints: the same assumption is generous in some hours and punitive in others. A-share rules add their own weight, since T+1 keeps shares bought today from being sold until the next session, and daily price limits can remove a modeled exit entirely. Our walkthrough of a reproducible backtest shows what a defensible setup records, and look-ahead bias covers the other way a clean-looking curve goes wrong.
Adjusted or unadjusted: the provenance question
Corporate actions are where a screener quietly misleads. A stock that splits ten for one prints a 90% fall on the day with nobody losing a cent. The panel below lists the twelve biggest forward splits on US listings between 1 January and 13 August 2026.
The exact SQL behind every number
SELECT
ticker AS symbol,
formatDateTime(execution_date, '%b %e, %Y') AS effective_on,
round(any(toFloat64(split_to)) / any(toFloat64(split_from)), 2) AS shares_after_per_share
FROM global_markets.stocks_splits
WHERE execution_date >= toDate('2026-01-01')
AND execution_date <= toDate('2026-08-13')
AND split_from > 0
AND split_to > split_from
AND ticker NOT IN ('SPCX')
GROUP BY ticker, execution_date
ORDER BY shares_after_per_share DESC, execution_date DESC
LIMIT 12The largest multiplies one share into 10000, effective Feb 10, 2026. Ratios that size are not the household four-for-one; a corporate-action feed carries the whole range in one column. The smallest of the twelve still turns one share into 20, enough to make an unadjusted chart look like a collapse. A momentum screen reading raw prices flags every one of them as a crash. Chinese feeds carry the same problem in their own vocabulary, shipping three variants of every series: unadjusted, forward-adjusted and back-adjusted. Know which one your panel loaded. Split-adjusted price history walks through the arithmetic.
What to check before you self-host anything like this
- Data provenance. Which vendor, under which licence, and whether the free tier covers what you intend to do with it.
- Key handling. Where the API keys land on disk, who can reach the settings page, and whether the machine is exposed past your own network.
- Cost modelling. Whether the backtester carries commissions, slippage and the market's own rules, and what those fields hold when you leave them alone.
- The update path. The exact commit you deployed, written beside your results, reproducible months later.
FAQ
Is a self-hosted A-share quant workbench free to run?
The code is MIT-licensed and free. The data is a separate question: with the vendor key left blank, this project falls back to free historical daily bars, the current session arriving one to two hours after the close. Anything faster or deeper runs through the vendor's paid tiers.
What does self-hosted actually mean here?
The web page, the scheduler, the stored files and the backtester all run on your own machine or server, with no account on someone else's platform. It does not mean the data is yours, and the optional AI features stay remote unless you point them at a model on your own network.
Does the LLM feature send my data to a third party?
When it is switched on, yes, to whichever OpenAI-compatible endpoint you configure. The feature ships disabled: with the AI key blank, nothing leaves the machine.
Why pin a commit SHA instead of a version tag?
As of 13 August 2026 this repository had no published releases and no release notes, and its newest tag pointed at older code than the default branch. "Latest" then means whatever the branch held the day you cloned it, while a commit SHA is exact. The same discipline runs through open-source quant trading material generally: pin what you ran.
Verification notes
Every architecture and configuration detail above was read from the public repository on 13 August 2026, at commit ecfddb451e97f6fc9a7e43ac33e4ef0e69933b33, the head of the default branch, dated 6 August 2026. The Releases page listed nothing; the tag list held 31 version tags from v0.1.31 to v0.1.88, the newest pointing at a commit dated 31 July 2026. The quoted lines are the maintainer's own, copied from the README rather than paraphrased. The container start command is quoted from that README and was not executed here.
The market panels are US listings, pinned to fixed past dates so the figures stay put. They are here to demonstrate the two checks that travel to any market, execution cost and corporate-action adjustment, rather than to describe the Chinese market.
Every panel here ships with the SQL that produced it, expandable underneath. To run the same spread check across your own list of names before wiring one into a screener, ask for it in plain English on the Strasmore terminal.