Self-hosted service
pythonstock/stock avatar
pythonstock/stock

pythonstock: one daily batch computes 17 indicators into MySQL

stock: a stock market system developed in Python.

7,891 stars2,353 forksPythonApache-2.0

At a glance

What is it?
pythonstock is a Python stock analysis system that scrapes Chinese market data with akshare, computes a fixed set of technical indicators into MySQL once a day at 18:00, and serves charts through nginx and tornado. It is a learning and analysis codebase, and its own disclaimer says losing money on its output is not a bug.
Who is it for?
Adopt pythonstock if you want a readable, self-hosted pipeline that turns akshare data into seventeen technical indicators on a fixed evening schedule, and you are comfortable patching scraper calls when akshare renames a function. Do not adopt it for intraday data, for anything other than A-share codes, or as a signal you can trade without independent verification.
Can I use it commercially?
Yes. Apache-2.0 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?
Activity is slowing. The repository last received commits 6 months 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 November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • pythonstock/stock7,891 ★ · +12
Star history
Repository2026-09-222026-11-14
pythonstock/stock7.9k7.9k

12 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Seventeen indicator rows land in one daily table

The quantitative work ends up in a single MySQL table called guess_indicators_daily, and the indicator list reads like a textbook index: volume delta, an n-day difference, an n-day percent change, CR, a rolling max and min, KDJ, SMA, MACD, BOLL, RSI, W%R, TR and ATR, DMA, the DMI family with +DI, -DI, DX, ADX and ADXR, TRIX and MATRIX, and VR with MAVR. Most entries link out to the mbalib wiki, so the definitions live outside the repository and the code only implements them.

Two entries are written as pandas expressions, which is the clearest hint about how the table is built. A window maximum uses stock["volume_-3,2,-1_max"] and a window minimum uses stock["volume_-3~1_min"], with a note that switching to -2~2 gives a five-day max and min. DMA is the difference of the 10 and 50 moving averages, exposed as stock['dma'].

The index numbering is also inconsistent. The numbering runs 1 through 11, then jumps to 14 twice, and ends at 17, so 12 and 13 never appear. Anyone adding an indicator to guess_indicators_daily has to read the formula links rather than trust the numbering, and the display layer is built so you can add your own once the dictionary template is filled in.

Buy and sell guesses get their own tables, and losses are not a bug

The project goes past raw indicators and writes two derived tables, guess_indicators_lite_buy_daily and guess_indicators_lite_sell_daily, from threshold rules stated in the version 3.0 notes. KDJ counts as overbought when K is above 80, D is above 70 and J is above 90, and oversold when K is below 20 and D is below 30. RSI treats 80 as overbought, 90 as a severe warning, 20 as oversold and 10 as severe oversold, and adds that readings above 50 mean a strong market and below 50 a weak one.

Those thresholds are the entire strategy. The 2025-01-10 changelog records only that the scripts producing the buy and sell predictions were fixed, not how they choose a side.

Read the disclaimer at the top of the file before wiring those tables into anything. It says the project is for Python code learning and stock analysis, that markets are risky, and that losing money is not counted as a bug. That sentence is the project's own statement of what the output is worth, and neither table carries any guarantee beyond it.

The 18:00 cron job is the entire update story

There is no streaming layer here. cron drives a daily batch, and the schedule is documented as starting at 18:00 because that is roughly when the current day's data appears upstream. The batch computes over 300 days of history and takes about fifteen minutes. On first start the container calls run_init.sh to initialise data and then runs the same job in the background.

Every operational consequence follows from that one schedule. The system shows nothing for today until the evening run finishes, so an intraday workflow gets yesterday's close. If the host is down at 18:00, that day's batch does not run unless you trigger run_daily by hand. The 2025-02-28 entry records that the scheduled task only stopped failing after permissions were set.

Want data during trading hours? This design does not give it to you. You would run the akshare fetch yourself on your own schedule and write into the same tables.

Bringing the stack up with docker-compose

The install path is Docker, and the reason is dependency resolution rather than convenience. The system is built on pandas, akshare, bokeh, tornado, stockstats and ta-lib, and the stated purpose of the container was to solve Python library installation problems. Install Docker and the compose binary first, and note that the compose binary comes through a ghproxy.cn mirror rather than straight from GitHub:

bash

# 下载 docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

#
curl -L "https://www.ghproxy.cn/https://github.com/docker/compose/releases/download/v2.23.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Then start the stack. Two compose files exist and the difference is how the frontend gets built:

bash

# 生产环境,编译前端部署:
docker-compose up -d

# 开发环境,node dev 方式部署:
docker-compose -f dev-docker-compose.yml up -d

The frontend answers on port 8080 and the backend on port 9090. To force the daily job instead of waiting for cron:

bash
docker exec -it stock bash
sh /data/stock/jobs/cron.daily/run_daily

Before that produces anything, MySQL has to be running and the schema created. The deployment note puts the project in /data/stock and gives one statement:

sql
CREATE DATABASE IF NOT EXISTS `stock_data` CHARACTER SET utf8 COLLATE utf8_general_ci;

Column types matter here too. The 2025-02-28 entry moved numeric columns to double because decimal conversions were causing problems, so a database created from an older schema will not match a fresh one unless you apply the change yourself.

Three days of gzip pickles stand between you and a blocked endpoint

Data comes from akshare, which reads public endpoints from sources including eastmoney and sina. Those endpoints throttle, so the project caches by day: read_pickle and to_pickle with gzip compression, keeping the three most recent days on disk and clearing them on a schedule. The stated reason is preventing the interface from blocking the request.

That cache is doing more work than a cache normally would. It is the only buffer between a job that runs once a day and upstream endpoints that can start refusing requests, and the project does not say what happens to a pickle written by an older akshare release or a different Python version. If a decode fails after an upgrade, clearing those cached files is the first thing to try.

The 2.1 release shows how brittle the dependency is. That entry records the akshare function stock_sina_lhb_ggtj being replaced by stock_lhb_ggtj_sina at akshare 1.7.99, an upstream rename that broke the dragon-tiger list crawler until the project patched its own call. Pinning akshare is therefore not optional, and the same entry raises the floor to Python 3.8 to reach newer akshare features.

Four folders in the architecture note, three directories at the root

The architecture section says everything is Python and splits the system into four folders: jobs for fetching and storing, libs for shared utilities, web for the frontend, and supervisor for process management. The reason given is that calling between Python libraries is convenient. That description does not match the current root layout.

The repository root now holds backend/, docker-compose/ and frontend/, which matches the 2025-01-10 changelog entry describing a frontend split, images built from a dockerfile, a Vue UI and a developer mode. The daily command still runs from /data/stock/jobs/cron.daily/run_daily, so jobs/ lives inside the container image even though it is no longer a root directory.

For a reader this narrows to something practical. Production no longer serves pages from tornado directly: the 2025-02-28 entry moved deployment to nginx, with the compiled frontend copied into ./data/html and nginx mapping its html there. So port 8080 is a compiled bundle and 9090 is the API, which means an empty page is a question about nginx and the copy step, not about a Python route.

tushare was dropped because its pro tier meters access

The clearest decision in this project's history is which data source it stopped using. The 2.0 entry says tushare data could no longer be fetched, that registering a pro account was required, and that pro imposes credit limits, so the project moved to akshare and rewrote the affected code. That is a real difference in approach between two sources for the same market: one metered through account credits, the other read from public pages.

The same entry shows what chasing a free source costs. New akshare function names had to be located and the callers rewritten, and unused libraries were removed to shrink the image. Docker plus a pinned dependency set is the answer to that churn, which is why the install path stayed stable while the scrapers changed underneath it.

Two consequences follow for anyone considering the system. Scraping public endpoints means upstream page structure decides when your data breaks, and this project's own response has been a fast pin-and-patch cycle recorded in the changelog. And the system is bound to one market: every source named is eastmoney or sina, so the tables describe Chinese exchange codes, and the changelog explains prefixes such as 600 (Shanghai A shares), 900 (Shanghai B shares) and 000.

Image size, dependency pins and two mirrors

Docker is the deployment contract and its size is a tracked number. The 2.1 entry records the image shrinking from 852MB to 597MB after repackaging, and the project states the compressed image is 200MB with about 500MB of local disk needed. The pins move with each release: numpy from 1.21.5 to 1.21.6, akshare from 1.3.50 to 1.10.5, bokeh from 2.4.2 to 2.4.3, with an earlier entry upgrading bokeh to 2.4.0 by running a script against /usr/local/lib/python3.7/site-packages. The 2.0 line was built on pythonstock/pythonstock:base-2021-09.

Dates tell you how fresh to expect this. Version 3.0 was released on 2025-03-01 after the January frontend split, and the last push to master was on 2026-05-07, against a project created on 2017-07-17. The README itself is headed as updated 2025.02.28, and the project says updates happen irregularly rather than on a schedule.

Two mirrors are published, github.com/pythonstock/stock and gitee.com/pythonstock/stock, next to a bilibili video series and a CSDN blog category. That is where you look when the changelog goes quiet, since the commit history on those mirrors is the real record of what changed. The licence is Apache-2.0, so reading and reusing the indicator code carries no restriction beyond keeping the notice.

Editorial conclusion

Adopt pythonstock if you want a readable, self-hosted pipeline that turns akshare data into seventeen technical indicators on a fixed evening schedule, and you are comfortable patching scraper calls when akshare renames a function. Do not adopt it for intraday data, for anything other than A-share codes, or as a signal you can trade without independent verification. Check three things first: that your akshare version matches the one the pinned image carries, that your MySQL instance accepts the double column types the current schema uses, and that the 18:00 cron job actually completes on your host, since every table in the database depends on that one run.

Frequently asked questions

How do I run pythonstock on my own machine?

Through Docker. The production path is docker-compose up -d with a compiled frontend, the development path is docker-compose -f dev-docker-compose.yml up -d, and the frontend lands on port 8080 with the backend on 9090. A running MySQL and a stock_data database are required, and the project expects to sit in /data/stock.

Does pythonstock still use tushare for market data?

No. The 2.0 changelog says tushare data could no longer be fetched without registering a pro account, and that pro has credit limits, so the project switched to akshare and rewrote the data code. Since then the crawlers have followed akshare renames, such as stock_sina_lhb_ggtj becoming stock_lhb_ggtj_sina.

When does pythonstock update its data?

A cron job starts at 18:00, because current day data only becomes available upstream around then. It computes over 300 days of history and takes roughly fifteen minutes, so anything earlier in the day shows you the previous close rather than live prices.

What does pythonstock store in guess_indicators_daily?

Seventeen daily indicator values per stock, covering volume delta, n-day differences and percentage change, CR, rolling max and min, KDJ, SMA, MACD, BOLL, RSI, W%R, TR and ATR, DMA, the DMI and ADX family, TRIX and MATRIX, and VR with MAVR. Buy and sell conditions derived from thresholds are written separately to guess_indicators_lite_buy_daily and guess_indicators_lite_sell_daily.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. pythonstock/stock on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pythonstock-stock.svg)](https://hysenlabs.com/projects/pythonstock-stock)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/pythonstock-stock.svg)](https://hysenlabs.com/projects/pythonstock-stock)

Was this useful?