Python client and macOS service manager for a local tdx-api market data service.
Project description
pytdxfeed
English | 简体中文
pytdxfeed is a Python client and macOS service manager for a local
tdx-api market-data service. It gives
Python applications one strict HTTP client contract and, on macOS, installs a
pinned native service without requiring Docker or a local Go toolchain.
The service is always local: the host is fixed to 127.0.0.1, the default port
is 8080, and callers may select another explicit port.
Installation
Python 3.11-3.13 is supported.
uv add pytdxfeed
Or install it into the current uv environment:
uv pip install pytdxfeed
Quick Start
On macOS, ensure_service() installs or upgrades the bundled service, registers
its per-user LaunchAgent, waits for strict health, and returns its runtime
details. On other operating systems it only verifies an already running local
service.
from pytdxfeed import TdxApiClient, ensure_service
service = ensure_service()
print(service.base_url) # http://127.0.0.1:8080
with TdxApiClient(timeout_sec=10.0) as client:
print(client.health())
raw_daily = client.kline_all(
"sh600000",
source="tdx",
interval="day",
limit=20,
)
qfq_daily = client.kline_all(
"sh600000",
source="ths",
interval="day",
limit=20,
)
index_daily = client.index_all("sh000300", interval="day", limit=20)
quotes = client.batch_quote(["sh600000", "sz000001"])
recent_trades = client.trade("sh600000")
Use the same explicit port for service management and client requests:
from pytdxfeed import TdxApiClient, ensure_service
service = ensure_service(port=18080)
with TdxApiClient(port=service.port) as client:
client.health()
The port must be an integer from 1 to 65535. The host and full base URL are
not configurable. The runtime never scans for a free port.
Near-tick Polling
batch_quote() provides the current quote and five-level order-book snapshot,
while trade() returns a rolling window of recent transaction records. Polling
both endpoints can provide near-tick behavior for a small symbol set:
from time import monotonic, sleep
from pytdxfeed import TdxApiClient, TdxApiError
CODES = ("sh600000", "sz000001")
POLL_INTERVAL_SEC = 1.0
TRADE_KEY_FIELDS = ("Time", "Price", "Volume", "Status", "Number")
def trade_rows(payload: object) -> list[dict]:
if not isinstance(payload, dict):
return []
data = payload.get("data")
if not isinstance(data, dict):
return []
rows = data.get("List")
return [row for row in rows if isinstance(row, dict)] if isinstance(rows, list) else []
def trade_key(row: dict) -> tuple:
return tuple(row.get(field) for field in TRADE_KEY_FIELDS)
previous_windows: dict[str, set[tuple]] = {}
with TdxApiClient(timeout_sec=3.0) as client:
while True:
started = monotonic()
try:
quote_snapshot = client.batch_quote(CODES)
new_trades: dict[str, list[dict]] = {}
for code in CODES:
rows = trade_rows(client.trade(code))
current_keys = {trade_key(row) for row in rows}
previous_keys = previous_windows.get(code)
new_trades[code] = (
[]
if previous_keys is None
else [row for row in rows if trade_key(row) not in previous_keys]
)
previous_windows[code] = current_keys
# Replace this with a queue, callback, or strategy handler.
print({"quotes": quote_snapshot, "new_trades": new_trades})
except TdxApiError as exc:
print(f"poll failed: {exc}")
elapsed = monotonic() - started
sleep(max(0.0, POLL_INTERVAL_SEC - elapsed))
The first response establishes a baseline instead of replaying up to 1,800 old records. Later responses emit rows not present in the previous rolling window. This is best-effort polling: if more records arrive between polls than the endpoint retains, intermediate trades can still be missed. Reduce request load or increase the interval as the symbol count grows.
Capabilities
| Capability | Public API | Scope |
|---|---|---|
| Strict health check | health() |
Requires the exact healthy response from the pinned service |
| Stock K-lines | kline_all() |
Raw TDX or THS-adjusted stock data |
| Index K-lines | index_all() |
Full index history for one interval |
| Quote snapshots | batch_quote() |
1-50 explicit symbols per request |
| Recent trades | trade() |
Polling snapshot of up to 1,800 recent transaction records |
| Near-tick polling | batch_quote() + trade() |
Caller-controlled polling with incremental trade detection |
| macOS lifecycle | ensure_service() |
Installs, upgrades, starts, migrates ports, and checks the LaunchAgent |
| Other platforms | ensure_service() |
Health-checks an existing service; it does not install one |
| Push tick stream | Not available | No WebSocket, server-side subscription, or exchange event stream |
| Storage API | Not available | The package returns upstream JSON and does not write user market data |
pytdxfeed therefore supports near-tick polling and transaction-level records, but does not claim exchange-native push delivery or lossless tick capture.
Public Methods
| Method | Important arguments | Upstream endpoint |
|---|---|---|
ensure_service() |
port=8080, timeout_sec=2.0, startup_timeout_sec=120.0 |
Service lifecycle and /api/health |
TdxApiClient() |
timeout_sec=10.0, port=8080 |
Creates thread-local HTTP sessions |
health() |
None | GET /api/health |
kline_all() |
code, source, interval, optional limit |
`GET /api/kline-all/{tdx |
index_all() |
code, interval, optional limit |
GET /api/index/all |
batch_quote() |
Iterable of 1-50 codes | POST /api/batch-quote |
trade() |
One code | GET /api/trade |
Codes use the upstream lower-case exchange prefix, for example sh600000,
sz000001, or bj920002. Responses are the upstream JSON objects; pytdxfeed
does not rename fields, rescale prices, or convert results to pandas objects.
K-line Coverage
| Source | Intervals | Adjustment | Typical coverage and limits |
|---|---|---|---|
tdx stock |
minute1, minute5, minute15, minute30, hour |
Raw | About 24,000 bars per interval; the calendar span grows with the interval |
tdx stock |
day, week, month, quarter, year |
Raw | Usually listing-to-present; the service joins underlying 800-bar batches |
ths stock |
day, week, month |
Forward-adjusted | Listing-to-latest data available from the THS endpoint |
| Index | Same interval names as TDX | Index points | Coverage depends on the selected index and upstream servers |
The minute history is bar-count limited, not year limited. In a check on
sh600000 dated 2026-07-20, minute1 returned 23,520 bars from 2026-02-26 and
minute5 returned 23,952 bars from 2024-06-28. Daily TDX data returned 6,349
bars from the 1999-11-10 listing date. Different symbols and upstream servers
can produce different ranges.
Calls fetch the latest data available from the selected endpoint. pytdxfeed has no scheduler and makes no freshness guarantee beyond the successful upstream response.
Failure Semantics
The client does not retry requests or switch sources. This keeps the selected market-data source observable.
from pytdxfeed import (
TdxApiClient,
TdxApiDeploymentError,
TdxApiResponseError,
TdxApiTransportError,
)
try:
with TdxApiClient(timeout_sec=10.0) as client:
payload = client.kline_all(
"sh600000",
source="tdx",
interval="minute1",
)
except TdxApiTransportError as exc:
print(f"HTTP connection failed: {exc}")
except TdxApiResponseError as exc:
print(f"tdx-api rejected the request: {exc}")
except TdxApiDeploymentError as exc:
print(f"the managed service could not start: {exc}")
If the selected port is occupied by another strictly healthy tdx-api, it is
reused as an external service. If an unknown process returns an unhealthy or
incompatible response, startup fails without terminating that process.
Managed Runtime
| Setting | Value |
|---|---|
| Host | 127.0.0.1 |
| Default port | 8080 |
| Managed platforms | macOS arm64 and x86_64 |
| Runtime root | ~/.pytdxfeed |
| Persistent service data | ~/.pytdxfeed/data/database |
| LaunchAgent label | com.hermanzhaozzzz.pytdxfeed.tdx-api |
| Logs | ~/.pytdxfeed/logs |
Changing the configured port rebuilds the managed plist and restarts the owned service while preserving its data directory. Newer compatible artifact generations are never downgraded.
Bundled tdx-api
| Item | v1.0.1 value |
|---|---|
| Upstream repository | oficcejo/tdx-api |
| Upstream commit | ea07dccb67aeb92ebde851ac31503b4bf457a318 |
| Commit date | 2026-06-27 |
| Service API major | 1 |
| Artifact generation | 3 |
| Bundled architectures | darwin-arm64, darwin-x86_64 |
| Reviewed patches | THS response validation; strict TDX_API_PORT support |
The upstream project declares the MIT License in its README. pytdxfeed's own source and release metadata are provided under the MIT License.
Performance
The following local measurements used an Apple M2 Mac mini (8 cores, 16 GB),
macOS 26.5.2, sh600000, a warm service on loopback, one warm-up request, and
five measured requests on 2026-07-20.
| Operation | Returned data | p50 latency |
|---|---|---|
| Health | 41-byte response | 0.5 ms |
| Batch quote | 2 symbols | 44.7 ms |
| Recent trades | 1,800 records | 50.2 ms |
| Full daily history | 6,349 bars / about 1.0 MiB | 381.5 ms |
| Full 1-minute history | 23,520 bars / about 3.5 MiB | 1.406 s |
These measurements are examples, not a service-level guarantee. Latency varies with symbol history, response size, upstream server selection, network quality, and market hours.
Run the reproducible benchmark against your local service:
uv run python benchmarks/benchmark_client.py \
--symbol sh600000 \
--second-symbol sz000001 \
--port 8080 \
--iterations 5
The script reports rows, response size, minimum, p50, p95, and maximum latency as a Markdown table.
Distribution
| Channel | Purpose |
|---|---|
| PyPI | Primary installation channel |
| GitHub Releases | Release notes plus matching wheel and source archive |
| GitHub Packages | Not used because GitHub does not provide a Python/PyPI package registry |
Development
git clone https://github.com/hermanzhaozzzz/pytdxfeed.git
cd pytdxfeed
uv sync --locked --all-groups
uv run ruff check .
uv run ruff format --check .
uv run pytest
uv build
uvx --from twine twine check dist/*
Regenerate the deterministic macOS assets only when the locked upstream commit or reviewed patch set changes:
uv run python scripts/build_assets.py
The build checks out the exact upstream commit, verifies a clean tree, applies the reviewed patches, runs the upstream Go tests, and records SHA-256 hashes in the asset manifest.
License
MIT. See LICENSE.
Star History
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytdxfeed-1.0.1.tar.gz.
File metadata
- Download URL: pytdxfeed-1.0.1.tar.gz
- Upload date:
- Size: 12.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4fc4e1ad62f121cb9245e12ea24eba9db9c8697cf1df00cb0d80e71e7d4769c6
|
|
| MD5 |
9303007d1c21f3e4b178989ffd66f7f1
|
|
| BLAKE2b-256 |
b569c3a2407f96c0c3934e3a0fb24586f9f32b6052aa820de713410b50ad197b
|
File details
Details for the file pytdxfeed-1.0.1-py3-none-any.whl.
File metadata
- Download URL: pytdxfeed-1.0.1-py3-none-any.whl
- Upload date:
- Size: 12.8 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c49f543a3ab745843da78429aa84c8e9ca532f3426864a5d5ea30ecc9aa5705
|
|
| MD5 |
d5c31d6054c8d19c4e3c8cfc33aed94f
|
|
| BLAKE2b-256 |
de2bd2eb9d53b533072db13f66716caff530b8d94efd2b1a80b0414dc06f7e80
|