zvt: a modular quant framework where schema, provider and factor are the extension points
modular quant framework.
At a glance
- What is it?
- zvt is an MIT-licensed Python framework for stock data capture, persistence, factor computation and backtesting, with a Dash UI and a separate FastAPI server. Its own README warns that backward compatibility is not guaranteed, which is the first thing to weigh before adopting it.
- Who is it for?
- Adopt zvt if your work is research on Chinese A-share or US equity data and you want the record_data/query_data pattern plus a factor and ML layer without assembling a stack yourself. Do not adopt it if you need a stable API across upgrades, or if you are building a real-time execution system, since the README itself says the Dash UI is not applicable for real-time market data and user interaction.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 76 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem zvt addresses: a shared schema for market data, factors and targets
Most quant side projects die in the same place. You write one script that pulls prices, another that computes a moving average, a third that joins them to a symbol list, and none of them agree on what a symbol is called. zvt's answer is to make the entity the centre of the system. A stock is not a ticker string, it is an entity_id such as stock_sz_000001, and every piece of data in the framework hangs off that identifier. The README states the core concept of the system is visual, and the name of the interface corresponds to it one-to-one, so it is also uniform and extensible. That sentence is the design thesis: targets, factors, signals and performance are named objects, and the UI mirrors those names rather than inventing its own vocabulary.
The intended user is someone doing research on Chinese or US equities who wants data capture, persistence, incremental update, machine learning, prediction and display available from one import path. The README's own summary of its example is that those few lines have done all of that. It is not aimed at someone who wants a broker connection and an order router. It is aimed at the person who is tired of rewriting the same ingestion and storage layer for every idea.
How data flows: domain classes, providers and the record_data/query_data pair
The mechanism visible in the README is a class-level API on domain models. Stock.record_data(provider="em") fetches the symbol universe and writes it to the store. Stock1dHfqKdata.record_data(provider="em", entity_ids=[...], sleeping_time=1) fetches adjusted daily bars for a named set of entities. Reading back uses the same class: Stock.query_data(provider="em", index='code') returns a DataFrame indexed by code, with columns id, entity_id, timestamp, entity_type, exchange, code, name, list_date and end_date. The README prints 4136 rows for the Chinese stock universe, which tells you the framework is not lazily fetching per symbol; it materialises the list.
The provider argument is the seam. The examples use provider="em" throughout, and the US example calls Stockus.record_data() with no provider argument at all, which suggests a default provider is configured somewhere in the settings rather than being required at every call site. The sleeping_time parameter on the kdata call is a rate limit expressed in seconds, which is a candid admission that the upstream source will throttle you. Incremental update is claimed in the README's summary of the ML example, and it follows from the schema: because each row carries a timestamp and the entity_id is stable, re-running record_data can append only what is missing rather than truncating the table.
The machine learning layer sits on top of the same entities. MaStockMLMachine(entity_ids=["stock_sz_000001"], data_provider="em") is constructed with a symbol list and a provider, then train(), predict() and draw_result(entity_id=...) are called in sequence. The naming suggests a moving-average based feature set, and the README does not document the feature construction beyond that. Treat the ML part as a worked example of the pattern rather than a library of models.
Two front ends with different constraints: Dash on 8050, FastAPI on 8090
zvt ships two ways to look at your work, and the README is explicit that they are not interchangeable. The first is a Dash and Plotly application started by typing zvt on the command line and opening http://127.0.0.1:8050/. The README's own note on it is blunt: it is good for backtest and research, but it is not applicable for real-time market data and user interaction. That is a limitation stated by the project, not inferred by me.
The second is a REST API plus a separate front end. You install uvicorn, run zvt_server from the command line (or run src/zvt/zvt_server.py from source), and the API documentation is served at http://127.0.0.1:8090/docs. The front end lives in a separate repository, zvt_ui, and you point it at the server by editing its .env file, setting NEXT_PUBLIC_SERVER to the server IP, then following that repository's README to start it. The trade page is at http://127.0.0.1:3000/trade.
The REST path also requires initialisation that the Dash path does not: four scripts under src/zvt/tasks (init_tag_system.py, stock_pool_runner.py, qmt_data_runner.py and qmt_tick_runner.py) are listed as the way to init the tag system. The presence of qmt in two of those filenames points at a specific broker data interface, and the README does not describe what happens if you do not have it. If you are not on that broker's stack, expect to read those scripts before you can run them.
The compatibility declaration is the real adoption risk
The README opens with a declaration that this project does not currently guarantee any backward compatibility, and asks users to upgrade with caution. It goes further: as the author's thoughts evolve, some things that were once considered important may become less so, and thus may not be maintained. That is an unusually direct statement of maintenance risk, and it should drive how you use the library.
The release history supports reading it literally. The three most recent releases are v0.13.5, v0.13.4 and v0.13.3, and two of those are labelled with a mismatched tag in the release list (release v0.1.5 and release v0.1.4 alongside the v0.13.x names). I cannot tell from the supplied material whether that is a tagging slip or something more structural, but it is a reason to pin an exact version and read the release notes rather than tracking the latest.
The practical consequence is that zvt is a good fit for someone who treats it as a source of patterns and a data layer they control, and a poor fit for someone who wants to install it once and forget it. If your factors import domain classes directly, an upgrade that renames or removes one is a breaking change in your code, not just in theirs. The README's own framing puts the burden on you: whether the addition of some new elements will be useful to you needs to be assessed by yourself.
Where zvt is the wrong tool
The clearest boundary is real-time trading. The README states the Dash UI is not applicable for real-time market data and user interaction, and the REST path is described as more suitable for that job, but nothing in the supplied material shows an order execution or risk management layer. The tag system is described as offering a trading approach that combines AI with human intervention, which is a screening and annotation workflow, not an automated execution engine. If your requirement is sub-second reaction to market events, this framework is not built for it.
A second boundary is data sourcing. Every example in the README goes through provider="em" for Chinese data, and the tag system initialisation depends on qmt scripts. The provider seam is real, but the supplied material does not document how to write a new provider, so extending to another data source means reading the source rather than following a guide.
A third is scale of universe. The README's own example prints 4136 rows for the Chinese stock list, and the kdata call takes an explicit entity_ids list plus a sleeping_time. That is a design for targeted research on a handful of symbols, not for scanning thousands of names on every run. The MaStockMLMachine example trains on a single entity. Nothing here suggests the framework is optimised for wide cross-sectional jobs.
What to compare it against, and how the approaches differ
The obvious comparison is with a general-purpose backtesting library such as backtrader or vectorbt. The difference is where each one puts the schema. A backtesting library takes your data as input and gives you an event loop and a portfolio model. zvt starts one step earlier and takes a position on what a stock is, how it is identified, how it is stored, and how a factor is attached to it. That means you get ingestion, persistence and incremental update in the same package as the ML example, but you also inherit its domain model and its release cadence.
A second comparison is with a data vendor SDK plus pandas. That combination gives you no opinions and no upgrade risk, at the cost of writing the entity resolution and incremental update logic yourself every time. zvt's value is precisely that it has already made those decisions and named them consistently across the API and the UI. If you disagree with the decisions, the framework will feel like an obstacle rather than a foundation.
The distinguishing feature versus both is the tag system and the paired UI, since the README describes the REST path as combining the tag system with a trading approach that mixes AI and human intervention. Neither a plain backtester nor a vendor SDK offers that. It is also the part with the least documentation in the supplied material, so evaluate it by running the four init scripts rather than by reading about it.
Install, entry points and licence
Installation is a single pip command: python3 -m pip install -U zvt. Note the -U flag in the README's own instruction, which is consistent with a project that expects you to be on a recent version rather than a pinned one. For the REST server you additionally install uvicorn.
There are two command-line entry points after installation: zvt for the Dash UI on port 8050, and zvt_server for the API on port 8090. Both bind to 127.0.0.1 by default in the documented URLs, so exposing either beyond localhost is a deployment decision you make deliberately. The front end is a separate repository, zvt_ui, configured through NEXT_PUBLIC_SERVER in its .env file.
The licence is MIT, as declared in the repository metadata and shown by the PyPI licence badge in the README. MIT is permissive: it allows commercial use and modification with attribution and without a copyleft obligation on your own code. That covers the framework itself. It does not cover the market data you pull through it. Data from a provider such as em or through a broker interface carries its own terms, and the zvt licence says nothing about your right to redistribute or commercially use that data. Check the provider's terms separately; this is not legal advice and the distinction matters more than the licence choice for most users.
Maintenance cost is the version churn described earlier. The mitigation available from the material is to pin an exact version in your requirements file and read the release notes for each of v0.13.3, v0.13.4 and v0.13.5 before moving.
Editorial conclusion
Adopt zvt if your work is research on Chinese A-share or US equity data and you want the record_data/query_data pattern plus a factor and ML layer without assembling a stack yourself. Do not adopt it if you need a stable API across upgrades, or if you are building a real-time execution system, since the README itself says the Dash UI is not applicable for real-time market data and user interaction. Before committing, verify three things: that the em provider still returns the fields your domain models expect, that the tag system scripts under src/zvt/tasks still run against your data sources, and that the version you pin has not changed the domain classes your factors import.
Community notes