finance-dashboard is a private dashboard: one person's view of their own investment analysis, on their own machine or behind their own access control. It renders CSV files and chart images produced by an external data pipeline, and holds no database of its own.
It is not a public web service, and it is not a way of publishing market data. See Purpose and Scope before deploying it anywhere a stranger can reach.
The application is written in Python with FastAPI and Jinja2. Pages are rendered on the server, and the only client side dependency is a small table component, so no Node.js toolchain or build step is required.
It only reads. The data pipeline fetches the prices, computes the indicators, trains the models, draws the charts and writes a directory of files; this application parses that directory and displays it. The two components share no code and no process — they share a directory, and the format of those files is the whole of the interface between them.
J-Quants API (Free plan)
|
v
data pipeline (batch, cron, 18:10 on weekdays)
|
v
<data directory>/*.csv *.txt *.png
|
v
finance-dashboard (FastAPI, read only)
The arrow points one way. This repository never calls the J-Quants API, is never given its API key, and makes no outbound request of its own during a page view. Fetching market data is the pipeline's responsibility and stays there; a test asserts that no module here names the endpoint, the credential or an HTTP client.
The application starts and serves without the pipeline installed; pointed at an empty directory it renders empty tables. See Data Files and doc/DATA_CONTRACT.md.
- A private view of your own analysis, not a service for anyone else
- A provenance and data-age notice on every page, with the last trading day shown
- Server side rendering with FastAPI and Jinja2
- No build step and no Node.js dependency
- Sortable and searchable screening table
- Chart, short term, long term, and time series views per stock
- Recently viewed codes kept in a signed session cookie
- Optional Basic authentication with a plain or hashed password
- File cache invalidated by modification time, so regenerated data appears without a restart
- Python 3.9 or later
- Linux (Debian, Ubuntu) with systemd for production use
- Apache or any reverse proxy in front of Uvicorn
- Purpose and Scope
- Data Source and Delay
- Data Files
- Installation
- Configuration
- Access Control
- Running
- Deployment
- Testing
- Directory Structure
- Documents
- Contribution
- Primary Branch
- License
This is software for looking at your own investment analysis. It is written for one reader — the person who runs the pipeline that fills its data directory — and everything about it assumes that reader.
These are design premises, not preferences, and the code and the tests are arranged around them:
- Private use. The dashboard exists so that its operator can read their own analysis. It is not a product, a service, or a publication.
- No redistribution of market data. The figures on these pages come from a market data provider under terms that permit personal analysis and forbid redistribution. Serving them to a third party — as a page, as a download, as an API — is redistribution.
- No continuous analysis service for others. Providing an ongoing feed of analysis derived from that data to anyone else is equally out of scope, whether it is paid for or not.
- Not a public web service. It is not intended to be published on the open internet without authentication. Run it on localhost, or behind a reverse proxy with Basic authentication, a VPN, or an IP restriction. See Access Control.
public/datais a path, not a permission. The directory is named that because the previous Sinatra application served its static files frompublic/. It does not mean the data in it may be made public, and renaming it changes nothing about the terms the data came under. It is kept because the deployment, the symbolic link and the/dataroute are all built on it, and a rename would break existing installations for no gain.- Open source code, licensed data. This repository is published under the GPL or the LGPL. That covers the source code in it and nothing else. It says nothing whatever about the market data the dashboard displays, which is governed by the provider's terms. See License.
- No credentials, no market data in the repository. No API key, no fetched price, no real portfolio is committed here. Every fixture in
test/is invented.
Everything shown here originates from the J-Quants API, the market data service JPX Market Innovation & Research operates for individual investors, on its Free plan. The pipeline fetches it; this repository only reads what the pipeline wrote.
The figures are not live. The pipeline may operate under a subscription whose published data lags the present. The dashboard does not encode that lag as a fixed number; it displays the last trading day recorded by the pipeline.
Because a delayed figure that looks current is worse than no figure, every page carries a notice naming the source and the last trading day the data covers, beside the day the pipeline generated it. Those come from data_source.txt, which the data pipeline writes; when it is absent the notice says the age is unknown rather than assuming it is today.
The exact length of the delay is deliberately not repeated through this repository. It is a published property of a subscription plan that can change, and the pipeline configures it in one place. What is shown here is the last trading day itself, which is the fact a reader needs and cannot go stale.
The dashboard only reads data. Files are generated outside this repository and placed in a single directory, which defaults to public/data.
| File | Format | Content |
|---|---|---|
stocks.txt |
code,name |
Full stock listing shown on the index page |
data_source.txt |
Tab separated key and value | Where the data came from and its last trading day |
topix_core30.csv |
Tab separated with a Code header |
TOPIX Core30 summary |
portfolio.csv |
Tab separated with a Code header |
Portfolio summary |
screening_rsi14.csv |
Tab separated with a Code header |
RSI14 screening result |
ti_CODE.csv |
Comma separated with a named header | Technical indicators of one stock |
stock_CODE.csv |
Comma separated | Prices, linked from the stock pages |
chart_CODE.png, short_CODE.png, long_CODE.png |
PNG | Pre generated charts |
Link the generated directory into the repository, or point the application at it with FINANCE_DASHBOARD_DATA_DIR.
ln -s /var/stock/data public/dataThree details are load bearing and easy to lose:
- The summary files are tab separated and are read positionally. A column inserted or reordered on the producing side does not fail here; it shifts every later value into the wrong name.
ti_CODE.csvis the only file read by header name. - A missing file is a warning and an empty table, never an error. The pipeline and the dashboard are deployed and restarted independently, and a file that has not been generated yet is an ordinary state.
- Missing stock-level data and provenance are not invented to fill a gap. A stock with no indicator data renders the placeholder view, and an unrecorded last trading day renders as unknown. Numeric field parsing follows the tolerance rules in
doc/DATA_CONTRACT.mdrather than this no-data rule. No page ever shows a stale figure as though it were current, or today's date for data that does not reach it.
ref_index.csv was linked from the index page by earlier versions and was never produced by the data pipeline. The link is gone; the reference indices behind it came from a data source this project no longer uses, and no replacement source has been adopted. See doc/DATA_CONTRACT.md for the withdrawn-file contract.
doc/DATA_CONTRACT.md is the full description of what is read and the rules it is read by.
git clone https://github.com/id774/finance-dashboard.git
cd finance-dashboard
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"Install without [dev] for production, which omits the test and lint tools.
Settings are read from environment variables first, then from config.yml in the repository root. Copy the sample to start.
cp config.yml.sample config.yml| Environment variable | Configuration key | Default | Description |
|---|---|---|---|
FINANCE_DASHBOARD_CONFIG |
- | config.yml |
Path of the configuration file |
FINANCE_DASHBOARD_DATA_DIR |
data.directory |
public/data |
Directory holding the generated files |
FINANCE_DASHBOARD_USERNAME |
auth.username |
- | Basic authentication user name |
FINANCE_DASHBOARD_PASSWORD |
auth.password |
- | Basic authentication password |
FINANCE_DASHBOARD_PASSWORD_SHA256 |
auth.password_sha256 |
- | SHA-256 digest used instead of a plain password |
FINANCE_DASHBOARD_SECRET_KEY |
session.secret_key |
generated | Key signing the session cookie |
FINANCE_DASHBOARD_SESSION_MAX_AGE |
- | 1209600 |
Lifetime of the session cookie in seconds |
FINANCE_DASHBOARD_ROOT_PATH |
- | empty | Path prefix when served under a sub directory |
No J-Quants API key appears in this table, and none is accepted. This application does not fetch market data and has no use for the credential that does; it belongs to the pipeline and stays on the host that runs it.
Generate a digest for password_sha256 as follows.
printf '%s' 'your-password' | sha256sumAuthentication is enabled only when a user name and either a password or its SHA-256 digest are configured. Without them the dashboard is served to anyone who can reach it. That is the behaviour the previous Sinatra version had without a configuration file, and it is safe only because of where you run it.
Pick whichever of these suits the host. Any one of them is enough; none of them is optional if the port is reachable from outside:
| Approach | When it fits |
|---|---|
Bind to 127.0.0.1 |
A single machine. The default host is already localhost |
| Basic authentication | Reachable on a LAN, or proxied. Configure auth.username and auth.password_sha256 |
| Reverse proxy with its own authentication | Apache or nginx already terminates TLS and holds credentials |
| VPN or IP restriction | The dashboard is reached from a fixed set of addresses |
When Basic authentication is enabled it wraps the whole site, including /data. The generated files are the same information the pages show, and protecting only the HTML would leave the CSVs and charts open. Serving those files to someone else is redistributing market data obtained for personal use; see Purpose and Scope.
Set FINANCE_DASHBOARD_SECRET_KEY in any deployment that outlives one process, or every restart empties each reader's recently viewed list.
.venv/bin/python -m finance_dashboard.main --port 3000 --reloadThe same server can be started through the Procfile with foreman start, or directly with Uvicorn.
The Procfile binds to 0.0.0.0. Use it only where access is controlled by one of the approaches described in Access Control.
.venv/bin/uvicorn finance_dashboard.main:app --host 127.0.0.1 --port 3000| Path | View |
|---|---|
/ |
Index with screening, portfolio, and TOPIX Core30 tables |
/stock/CODE |
Standard chart |
/stock/CODE/short |
Short term chart |
/stock/CODE/long |
Long term chart |
/stock/CODE/detail |
Full time series |
/stock/CODE/none |
Placeholder for a stock without data |
/clear_recent |
Clear the recently viewed list |
/data/... |
Generated data files |
Install the systemd unit and let Apache proxy to Uvicorn. The unit sample assumes the application is deployed in /var/www/finance-dashboard.
sudo cp etc/finance-dashboard.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now finance-dashboardApache configuration for serving the dashboard under /finance-dashboard/:
ProxyPreserveHost On
ProxyPass /finance-dashboard http://127.0.0.1:3000
ProxyPassReverse /finance-dashboard http://127.0.0.1:3000Update an existing deployment with deploy.sh, which pulls the latest revision,
refreshes the virtual environment, grants the runtime service group read and
execute access without changing the deployment owner's file ownership, and
restarts the service. Any step that fails stops the script with a non-zero
status.
./deploy.shDeployment-sensitive settings and their required relationships are
documented in doc/DEPLOYMENT.md. FINANCE_DASHBOARD_SECRET_KEY must be set, or every restart empties each reader's recently viewed list. FINANCE_DASHBOARD_ROOT_PATH must match both the --root-path on the Uvicorn command line and the path Apache proxies, or generated links lose the prefix.
Authentication is off unless credentials are configured, so an unconfigured dashboard is served to anyone who can reach it. Decide how it is protected before it listens on anything but localhost; Access Control has the options.
The dashboard needs read access to the generated directory and nothing else. It is not installed on the pipeline host's behalf and is not given the J-Quants API key.
The whole procedure, the routine operations and what to check when something fails are in doc/DEPLOYMENT.md.
.venv/bin/pytest
.venv/bin/ruff check .The test suite covers the routes, the loaders and the indicator tables. Each builds its own data directory under a temporary path, so the real data files are never touched, config.yml is never read, and the suite runs on a host where the pipeline has never run. No test reaches the network. The suite also asserts that application modules do not name a market-data API endpoint, an API key, or an HTTP client.
The fixtures in test/conftest.py mirror the schemas and positional ordering
defined by doc/DATA_CONTRACT.md, including the
summary files, ti_CODE.csv, and data_source.txt. A generated-format change
updates the data contract and its matching fixtures in the same change.
All test data is invented. No real holding, price or portfolio appears in this repository, and nothing obtained from the J-Quants API may be added as a fixture. The stock codes and names are well known issuers used as labels; every figure beside them was made up.
.
├── finance_dashboard/
│ ├── main.py # FastAPI application and development launcher
│ ├── config.py # Settings from the environment and config.yml
│ ├── data.py # CSV loaders, provenance, and file cache
│ ├── formatting.py # Value conversion and display helpers
│ ├── indicators.py # Indicator table definitions
│ ├── links.py # External link definitions (hrefs only, never fetched)
│ ├── templates/ # Jinja2 templates
│ └── static/ # Stylesheets, scripts, favicon
├── test/ # pytest test suite and its fixtures
├── etc/ # systemd unit sample
├── doc/ # architecture, contract, deployment, policy, license
├── public/data # Symbolic link to the generated data
├── deploy.sh # Deployment script
├── config.yml.sample # Sample configuration
└── pyproject.toml # Project metadata and dependencies
Dependency points one way, and a module never imports one above it. main.py is the only module that knows about HTTP, config.py the only one that reads the environment, and data.py the only one that opens a generated file. doc/ARCHITECTURE.md has the composition and what happens during a request.
Third party assets bundled under finance_dashboard/static are Pico.css and Grid.js, both distributed under the MIT license. They are vendored rather than fetched from a CDN so that the dashboard works on a host with no outbound access.
| Document | Contents |
|---|---|
doc/ARCHITECTURE.md |
Module composition, one request end to end, the views, state |
doc/DATA_CONTRACT.md |
What is read, how it is parsed, and what is tolerated |
doc/DEPLOYMENT.md |
Installing, configuring, operating, and diagnosing |
doc/POLICY.md |
Implementation rules a change is judged against |
doc/VERSIONS |
Release history of the repository |
doc/LICENSE.md |
The license, with the full texts beside it |
This README is the entrance and doc/ holds the detail. This repository's
doc/DATA_CONTRACT.md is the normative description of
the files the application reads.
Contributions are welcome. You can help by:
- Reporting bugs or feature requests
- Improving the views or the deployment scripts
- Adding tests for uncovered behavior
Please follow the style used in this repository: English comments and documents, the module header each file carries, and documentation updated together with the code. doc/POLICY.md states the rules a change is judged by.
This repository uses master as its primary branch name.
The name is used solely as a technical identifier, following the long-standing convention historically used by Git. It does not express or imply any association with racism, slavery, discrimination, or any political or social ideology.
The source code in this repository is dual licensed under the GPL version 3 or the LGPL version 3, at your option.
For full details, please refer to doc/LICENSE.md. See also doc/COPYING and doc/COPYING.LESSER for the complete license texts.
The market data this dashboard displays is not covered by that license and never becomes free to use because of it. The prices, the indicators derived from them and the charts drawn from them originate from the J-Quants API and remain governed by that provider's terms of service, which permit personal analysis and prohibit redistributing the data or providing a continuing analysis service to third parties. Two separate questions: what you may do with this code, and what you may do with the data you put through it. The first is answered here; the second is answered by the provider.
The bundled third party assets keep their own MIT licenses, as noted above.
Thank you for using and contributing to this repository!