oopslink/trading-skills/miniqmt-cli

Claude Code and agent skill by oopslink/trading-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Operate miniQMT/xtquant via the miniqmt-cli tool -- market data queries, real-time streaming, account management, order placement with safety guards, daemon health checks, SSH tunnel management, and deployment to Windows. Use this skill whenever the user mentions miniqmt, miniQMT, xtquant, QMT trading, placing stock orders via CLI, checking positions or assets on a QMT account, streaming ticks or klines from QMT, deploying the trading daemon, or troubleshooting the miniqmt-cli daemon. Also use…

Install

npx vskill@latest install oopslink/trading-skills/miniqmt-cli

SKILL.md

miniqmt-cli: Drive miniQMT / xtquant from the Command Line

Access Policy — read first

All programmatic access to the trading daemon MUST go through `miniqmt-cli`. Direct HTTP calls to http://127.0.0.1:8765/... are no longer a supported integration surface — the URL space, payload shapes, and error semantics are internal implementation details and may change without notice.

Reasons:

  • One source of truth. The CLI's command set is the only documented API. The HTTP routes underneath exist solely to let the CLI talk to the daemon over an SSH tunnel; their paths are unstable.
  • Drift kills callers. When a parallel HTTP reference existed, a polling agent followed a stale /positions / /orders / /trades (unprefixed) path and silently 404'd for hours. The CLI shields callers from this class of bug.
  • Safety pipeline. The CLI carries the masking, idempotency client_req_id generation, and --confirm-live handshake conventions. Bypassing it weakens those guarantees even though the daemon enforces most of them.

Callers from non-Python runtimes should wrap the CLI as a subprocess (subprocess.run(["miniqmt-cli", "account", "asset", "--account", "main", "--format", "json"], capture_output=True)) rather than constructing HTTP requests by hand. SSE consumers should use miniqmt-cli stream tick|kline|order ... and parse its stdout, not subscribe to /stream/* directly.

If you're an agent on a different host than the daemon, configure remote mode in ~/.miniqmt_cli/client.toml:

toml
[client]
mode = "remote"
server_url = "http://127.0.0.1:8765" # the daemon, via SSH tunnel

Then bring up the tunnel (see SSH Tunnel below). The CLI then works transparently — no proxy config needed.

Architecture

Mac CLI (Click) --> SSH tunnel --> Windows FastAPI daemon (port 8765) --> xtquant/miniQMT

  • Client (Mac): ~/.miniqmt_cli/client.toml -- sends HTTP requests through an SSH tunnel
  • Server (Windows): ~/.miniqmt_cli/server.toml -- FastAPI daemon wrapping xtquant, runs as a Scheduled Task
  • Communication: all commands hit http://127.0.0.1:8765 via ssh -N -L 8765:127.0.0.1:8765 <host>

Prerequisites

Before running any command, verify:

  1. SSH tunnel is up -- without it, all commands fail with "cannot reach daemon"
  2. Daemon is running on Windows -- check with miniqmt-cli health
  3. miniQMT client is open on Windows -- required for xtquant to connect to the broker
/health response shape

Two independent top-level blocks. There is no flat state enum — callers compose decisions from the structured fields. (The old composite strings like daemon_up_no_trader mixed two unrelated concerns into one name and have been removed.)

json
{
"daemon": {
"state": "up", // up | degraded
"xtquant_loaded": true,
"xtquant_error": "..." // present only when state == degraded
},
"accounts": {
"main": {
"trader": {
"state": "alive", // never_connected | alive | lost
"last_connect_at": "2026-05-13T09:30:01Z",
"last_disconnect_at": null
},
"risk_breaker": "ok", // ok | tripped
"baseline": "captured" // captured | pending
}
}
}

Scope of each field:

  • daemon.state is purely process-local: this Python process is up + xtquant

module loaded. It says nothing about miniQMT or the broker.

  • accounts.<name>.trader.state reflects what the daemon last heard on

the xtquant SDK channel — alive after a successful login, lost after xtquant fires on_disconnected. The daemon cannot independently probe miniQMT's GUI or broker connectivity, so a lost flag is a positive signal (SDK told us the channel dropped), but a stale alive flag is not a guarantee the channel is currently up. The only definitive liveness probe is to run an account command — a real broker failure surfaces as trader.connect failed rc=... or trader.subscribe failed rc=....

  • risk_breaker and baseline are computed from the daemon's risk manager.
  • When the daemon was started with serve --dry-run, the daemon block adds "dry_run": true and xtquant_loaded is forced to false (xtquant is never touched in that mode). All account commands raise; this mode is for offline development only.

Reading rules of thumb:

You want to know…Read…
Daemon process alive?daemon.state == "up"
xtquant loadable?daemon.xtquant_loaded == true
Trader session for an account dropped?accounts.<name>.trader.state == "lost"
When did it drop / when did it last connect?accounts.<name>.trader.last_disconnect_at / last_connect_at
Account safe to send orders?daemon.state == "up" AND accounts.<name>.trader.state == "alive" AND accounts.<name>.risk_breaker == "ok" AND accounts.<name>.baseline == "captured"
Account has never tried to log in (fresh daemon)?accounts.<name>.trader.state == "never_connected" — run any account command to trigger lazy login

miniqmt-cli health prints a human summary of this body. Exit code rules:

  • Exit 1 when any of these are true: daemon unreachable (cannot reach daemon …); daemon.state != "up" (e.g. xtquant failed to load); any accounts.<name>.trader.state == "lost"; any accounts.<name>.risk_breaker == "tripped"; any accounts.<name>.baseline == "pending" while that account's trader is alive.
  • Exit 0 otherwise. In particular, a fresh daemon where every account is never_connected exits 0 — that's the normal lazy-load state, not an error.

Global Options

bash
miniqmt-cli [--format table|json|csv] [--config <path>] <command>

Default format comes from client.toml; override per-call with --format.

Commands Reference

Market Data
bash
# List sectors (e.g. 沪深A股, 上证50)
miniqmt-cli sector list
# List instruments in a sector
miniqmt-cli instrument list --sector "沪深A股"
miniqmt-cli instrument list --limit 10
# Instrument detail
miniqmt-cli instrument info --code 000001.SZ
# Latest tick snapshot (supports multiple codes)
miniqmt-cli tick --code 000001.SZ --code 600519.SH
# Historical K-line (periods: 1d, 1m, 5m)
miniqmt-cli kline --code 000001.SZ --period 1d --start 20260101 --end 20260415
# Historical ticks (raw tick-by-tick data)
miniqmt-cli ticks --code 000001.SZ --start 20260415093000 --end 20260415100000
Real-time Streaming (SSE)

Streams run until Ctrl+C. Output is one event per line; use --format json for programmatic consumption (the table format is for ad-hoc inspection).

Known failure mode: xtquant snapshot cache wedge. The timetag field of xtdata.get_full_tick() can lock at a past value while lastPrice / bidVol / askVol continue updating normally. Observed 2026-05-18: four codes (600760.SH / 600011.SH / 000333.SZ / 002179.SZ) all locked at timetag=20260518 09:32:06 for 13 minutes, daemon process otherwise healthy, watchdog silent, all HTTP requests returning 200, trader=alive. The daemon is a pure pass-through to xtquant — grep timetag across the daemon source returns zero hits — so the wedge lives inside xtquant's process-internal snapshot cache, not in daemon code.

Empirically supported trigger: daemon uptime crossing a market-close boundary. Access-log analysis of the 5/18 incident window (09:20–09:59) showed zero stream tick activity from any client, stable ~150 req/min on /trade/* polling, zero non-200 responses, zero WARN/ERROR lines. The only standing variable was a 64-hour daemon uptime that had spanned the weekend close. Best fit: xtquant's internal timetag write path has weak day-rollover handling. An earlier hypothesis that short-lived stream tick subscribe/unsubscribe cycles trigger the wedge is not supported by the logs and should not be relied on.

Primary mitigation: nightly daemon restart on trading days. scripts/windows/bootstrap.ps1 registers a MiniqmtDaemonNightlyRestart Scheduled Task that runs scripts/windows/nightly-restart.ps1 at 08:50 Mon-Fri — one known-clean xtquant process per day eliminates the cross-close failure mode. Polling via /data/tick reads the same xtquant cache that stream tick populates, so polling does not recover a wedged cache — only a daemon restart does.

When to use `stream tick` vs `tick` polling. Persistent stream tick is the recommended consumer pattern (lower latency than respawning miniqmt-cli tick per evaluation, no subprocess startup cost). One subscription per consumer process for the whole trading day; do not subscribe/unsubscribe per evaluation. This is a performance/ergonomics recommendation — it does not by itself prevent the wedge above; pair it with the nightly restart.

Tick stream
bash
# Interactive (Ctrl+C to stop)
miniqmt-cli stream tick --code 000001.SZ --code 600519.SH
# Programmatic consumption
miniqmt-cli --format json stream tick --code 000001.SZ --code 600519.SH

Event payload (JSON mode). First line is the subscription envelope; subsequent lines each wrap one tick event. Codes that do not tick in an interval emit nothing for that interval.

json
{"event": "subscribed", "codes": ["000001.SZ"], "seqs": [42]}
{"tick": {"code": "000001.SZ", "timetag": "20260518 09:32:06", "lastPrice": 10.48,
"high": 10.52, "low": 10.41, "open": 10.45, "lastClose": 10.40,
"volume": 12345, "amount": 128943.50,
"bidPrice": [10.47, 10.46, 10.45, 10.44, 10.43],
"askPrice": [10.48, 10.49, 10.50, 10.51, 10.52],
"bidVol": [100, 200, 300, 400, 500],
"askVol": [150, 250, 350, 450, 550]}}
{"tick": {...}, "dropped": 3}
  • event: subscribed arrives exactly once at stream start — consumers must skip it before processing ticks.
  • dropped: N appears alongside a tick when the daemon's per-stream queue overflowed since the last emit (cap = 256). The dropped events are the oldest; the emitted tick is the freshest. Log it as a backpressure warning, not an error — it means your consumer is slower than the broker pushes.
  • Field set varies by xtquant SDK version. code, timetag, lastPrice, high, low, bidVol, askVol are present on standard A-share L1 feeds; anything else is best-effort.
  • timetag is a "YYYYMMDD HH:MM:SS" string; parse before using as a sort key. If you see `timetag` static while other fields advance, you've hit the cache wedge — restart the daemon.

Consuming `stream tick` from a long-running subprocess. The boring details that bite:

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/oopslink/trading-skills/miniqmt-cli/badge)](https://verified-skill.com/skills/oopslink/trading-skills/miniqmt-cli)

Something wrong with this skill or its page? Tell us.