尚未检测到更新。GitHub 发布版本后会显示在这里。
制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗python3 fetch_dividends.py # refresh dividends.json + the embedded seed
python3 fetch_dividends.py --dry-run # report what would change, write nothing
python3 fetch_dividends.py --only AVGO,D05
Needs yfinance (pip3 install yfinance). The Moomoo path is used automatically
when the OpenD gateway is running on 127.0.0.1:11111; otherwise the script
falls back to Yahoo Finance, which covers KLSE / SGX / HKEX / SWX / US.
The script only rewrites each counter's auto block — the counter list, market
and type classification, manual entries, links and estimate fallbacks are
curated by hand and preserved. Serve the folder (python3 -m http.server) so the
dashboard reads dividends.json; opening index.html directly falls back to the
embedded seed.
The dashboard's Counters tab adds and removes counters without editing JSON
by hand. Pick the portfolio (or name a new one), the market, and the type; the
Yahoo symbol is derived from the ticker per market (5285 on KLSE becomes
5285.KL, 00700 on HKEX becomes 0700.HK) and can be overridden.
Those edits live in the browser's localStorage, so they show on the board at once but the counter has no dividend data yet. To make them part of the project:
python3 fetch_dividends.pyThe fetcher folds the roster into dividends.json, fetches the new counters,
and the additions become permanent for everyone. Delete roster.json afterwards
so the same edits are not replayed on the next run.
Two scheduled jobs, split because they need different things:
.github/workflows/refresh.yml) — weekdays 01:03 UTC.
Runs the fetcher and commits dividends.json and index.html to main.
This is where the fetching happens: GitHub's runners have unrestricted
outbound network.Both refuse to write when the data looks wrong: the fetcher's --min-ok 40
aborts if fewer than 40 counters come back clean, and the routine re-checks the
same thing before publishing.
Every run of fetch_dividends.py also writes dividend-tracker.html — one
self-contained file with the data baked in. Open it straight from the Finder,
AirDrop it to a phone, or keep it in iCloud Drive; it needs no server and no
network. Ticks and Counters-tab edits are saved in whichever browser opens it.
Its data is frozen at the moment it was built, so rebuild it (python3 fetch_dividends.py) whenever you want current figures.
The dashboard runs in its own container, published to the tailnet by
tailscale serve. The container binds to loopback only, so Serve is the one
route in — reachable on the tailnet, not on the local network, never public.
https://winnies-imac.tail823d40.ts.net
Docker here is Colima, installed as plain binaries under the home directory, so it needed no admin password:
~/.local/bin/docker docker CLI 29.9.2
~/.docker/cli-plugins/docker-compose compose 5.6.1
~/.local/lima/bin/limactl lima 2.2.1
~/.local/bin/colima colima 0.10.3
It runs on Apple's Virtualization.framework (no QEMU). ~/.zshrc puts these on
PATH, and a launch agent (~/Library/LaunchAgents/com.winnie.colima.plist)
starts the VM at login, so the dashboard comes back by itself after a reboot.
docker compose -f deploy/docker-compose.yml logs -f # watch requests
docker compose -f deploy/docker-compose.yml restart # reload
docker compose -f deploy/docker-compose.yml down # stop
colima status # is the VM up?
tailscale serve status # is it published?
index.html and dividends.json are bind-mounted read-only, so a refresh
updates the live site with no rebuild, and nginx sends no-store on both so the
page's 60-second poll sees it.
"Added to calendar" is a fact about the dividend, not about the browser that
recorded it, so the ticks are held server-side and everyone who can open the
dashboard sees the same ones. A second container (dividend-checks,
deploy/checks-api.py) keeps them in a Docker volume:
GET /api/checks the whole map, plus a revision number
PUT /api/checks/<counterId> merge one counter's tick
Each tick is its own request, so two people ticking different rows cannot overwrite each other, and a tick reaches other open pages over an SSE stream in well under a second. The header says ● Ticks shared when a store answered.
The counter roster is shared the same way:
GET /api/roster the added/removed overlay, plus a revision number
PUT /api/roster replace it
So adding or removing a counter in the Counters tab changes the board for everyone, not just the browser that did it, and a removal is reversible from the Removed section on any device.
Those edits become permanent on the next refresh. The agent pulls the
overlay, lets the fetcher fold it into dividends.json, and only then clears
it — so a throttled run postpones the edit rather than losing it. In practice a
counter removed in the dashboard is gone from the project file within 15
minutes, and the Removed section empties once it is.
Where none does — the standalone file, the hosted claude.ai copy, or the API being down — the page falls back to this-browser-only ticks and says so.
以上为 README 的开头部分。其余内容请阅读原文。 查看完整 README ↗