dsh-market-watch
English · 简体中文
A financial market monitor bundle for DeepSeek Harness (dsh): real-time quotes, a local watchlist, threshold alerts, periodic polling, and in-chat ASCII/mermaid charts for A-share stocks/indices and cryptocurrencies — using free public data sources only. This is the first market/finance data plugin in the dsh ecosystem.
- Quote — latest quotes for stocks, indices, and crypto via free sources
(Tencent
qt.gtimg.cnfor CN securities, CoinGecko for crypto). - Watchlist — locally persisted watch items (code / name / market / kind), shared by the plugin and the CLI.
- Alerts — threshold rules (
changePercent/price,gt/gte/lt/lte) evaluated on every poll, with per-rule cooldowns. Delivery: a typed harness eventmarket-watch/alertplus optional injection into live agent sessions. - Polling — timer-based periodic refresh of the whole watchlist (overlapping ticks are dropped; free sources are rate-limit friendly).
- Charts — ASCII column charts, sparklines, and mermaid
xychart-betablocks rendered in chat. - Dual entry — six dsh tools (
quote,list,watch,unwatch,alert,chart) and a standalone CLI (dsh-market-watch) over the same data.
Data sources and latency
| Market | Source | Notes |
|---|---|---|
| Shanghai / Shenzhen / Beijing stocks & indices | Tencent qt.gtimg.cn, web.ifzq.gtimg.cn |
Free endpoints; quotes may lag the tape by seconds to minutes. Daily bars are qfq (forward-adjusted) for stocks, raw for indices. |
| Crypto | CoinGecko public API | Free tier is rate limited (calls are spaced by coingeckoDelayMs, Retry-After honored). Prices may lag; OHLC from market_chart is bucketed to UTC days. |
All quotes carry a per-source disclaimer (delayNote). Nothing here is
investment advice; never trade on delayed free data without verification.
Requirements
- Node.js
^22.19 || >=24 - dsh
>=0.1.0-rc.6with theweborheadlessprofile
Install as a dsh bundle
A bundle is an npm package whose manifest declares dsh.bundle.patch (this
package ships cordis.patch.yml plus a lib/ build). Two typical paths:
# 1. From a local checkout of this repo:
pnpm build # or: npm run build
dsh plugin --profile web add . # relative/absolute path to this directory
# 2. Git-hosted installs run the prepare script on install; pnpm >= 10 blocks
# build scripts until allowlisted — add to the profile's pnpm-workspace.yaml:
# allowBuilds:
# dsh-market-watch: true
# then:
dsh plugin --profile web add github:some-owner/dsh-market-watch
dsh plugin adds the package to the profile's dsh.profile.bundles list
because it declares dsh.bundle; the patch inserts the market-watch row and
the plugin's tools appear in every agent prompt after a restart.
Verify:
dsh config dump | grep market-watch # row present
Configuration
Every option has a default; the bundle row ships without a config section.
Override by id in your profile's cordis.patch.yml — remember a patch
replaces the whole config value, so restate every key you keep (copy
examples/cordis.patch.example.yml):
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
Master switch for the plugin. |
pollIntervalSeconds |
60 |
Poll period (schema: 5..86400). |
dataDir |
$DSH_HOME/market-watch |
Directory holding watchlist.json. |
timeoutMs |
10000 |
Per-request network timeout. |
maxRetries |
2 |
Extra attempts after the first try. |
retryBackoffBaseMs |
500 |
Exponential backoff base. |
vsCurrency |
usd |
Crypto quote currency (CoinGecko id). |
coingeckoDelayMs |
1200 |
Min spacing between CoinGecko calls. |
agentNotify |
true |
Deliver alerts into live dsh agent sessions. |
agentWakeup |
false |
Wake idle agents on alert (followup) instead of quiet inject. |
Tools
Registered on ctx.tools; their schemas join the system prompt automatically.
| Tool | Purpose |
|---|---|
quote |
Latest quotes for codes (array), e.g. ["sh600000","000001","bitcoin"]. Optional days appends a sparkline per instrument (one history request each). |
list |
The local watchlist. |
watch |
Add instruments: codes (array), optional market (cn/crypto), kind (stock/index/crypto), name. |
unwatch |
Remove one instrument by code. |
alert |
Manage rules: action = list | add | remove. add needs code, field (changePercent/price), op (gt/gte/lt/lte), value, optional cooldownSeconds (default 300) and note; remove needs the rule id from list. |
chart |
code, optional days (default 30), format (ascii/mermaid), width, height. |
Accepted code forms for CN instruments: sh600000, 600000.sh, 600000.ss,
600000 (prefix inferred from the leading digit), 000001 with kind: index
for the Shanghai Composite. Everything else is treated as a CoinGecko id
(bitcoin, ethereum, …).
Tools never throw for expected failures — they return {ok:false, error} so
the model can act on the message.
Alerts
Rules live in the same file as the watchlist and are evaluated on every poll.
A rule fires when the comparison holds and the cooldown window since the last
trigger has elapsed; lastTriggeredAt is persisted.
Delivery channels (both best-effort, failures contained):
Harness event —
ctx.emit('market-watch/alert', alert). Any extension can listen:ctx.on('market-watch/alert', (alert) => { /* alert.message, alert.quote, alert.rule */ })Agent sessions — when
ctx.agentsis mounted, every live agent receives the alert as a plugin-sourceduser/message: quiet context for the next step (agent.inject) by default, or a full follow-up turn withagentWakeup: true.
CLI
dsh-market-watch runs on the same data directory as the plugin, so commands
act on the exact state the plugin polls.
dsh-market-watch quote sh600000 bitcoin --days 5
dsh-market-watch watch sh600000 bitcoin --name "BTC"
dsh-market-watch list
dsh-market-watch unwatch sh600000
dsh-market-watch alert list
dsh-market-watch alert add bitcoin --field price --op gte --value 70000 --cooldown 600
dsh-market-watch chart sh600000 --days 30 --format mermaid
dsh-market-watch poll --once # single pass (cron-friendly)
dsh-market-watch poll --interval 300 # keep polling every 5 min
Global flags: --data-dir <path> (or MARKET_WATCH_DATA_DIR), --help,
--version. Exit codes: 0 ok, 1 runtime error, 2 usage error.
Data files
<dataDir>/watchlist.json — JSON document {version, items, rules}. Writes
are atomic (temp file + rename), serialized through a promise chain, and a
corrupt file is quarantined (watchlist.json.corrupt-<ts>) instead of
wedging the plugin. Because the plugin and the CLI share one file, the engine
re-reads it on every poll/json quotation, so edits made in a second process
become visible without a restart.
FAQ
- Which data sources are used, and how fresh are quotes? A-share stocks and indices come from Tencent's public quote endpoint; crypto comes from CoinGecko. See "Data sources and latency" above for the refresh cadence and the documented staleness limits.
- Why is a symbol not recognized? Symbols are normalized case-insensitively
(e.g.
sh600000/600000for an SSE stock,bitcoin/BTCfor crypto). Rundsh-market-watch list --known(or themarket_knowntool) to see the accepted forms; symbols outside the provider's universe are rejected. - My alert did not fire. Alerts are evaluated on poll ticks; check the rule
operator (
gte/lte) and--cooldown, and that the field (e.g.price) exists in the quote shape. Alerts can be listed withdsh-market-watch alert listor themarket_alertstool. - Where is my watchlist stored? In
<dataDir>/watchlist.json(dataDirdefaults to the dsh profile data dir, orMARKET_WATCH_DATA_DIR/ the--data-dirflag). Writes are atomic and corrupt files are quarantined.
Development
npm install
npm run typecheck # tsc --noEmit
npm test # vitest (all network paths mocked)
npm run build # tsc -> lib/
npm run check # typecheck + test + build
Layout: src/core/ is framework-free (types, symbols, formatting, HTTP
client, JSON store, chart renderers, providers, engine) so the CLI and tests
share it without importing dsh; src/dsh/ adapts the core to Cordis (tools,
poller, notifier, entry point); src/cli/ is the standalone binary.
License
MIT — see LICENSE.
No comments yet. Be the first to write one.