Skip to main content

Python client and macOS service manager for a local tdx-api market data service.

Project description

pytdxfeed

PyPI Python CI License

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

Star History Chart

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

pytdxfeed-1.0.1.tar.gz (12.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytdxfeed-1.0.1-py3-none-any.whl (12.8 MB view details)

Uploaded Python 3

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

Hashes for pytdxfeed-1.0.1.tar.gz
Algorithm Hash digest
SHA256 4fc4e1ad62f121cb9245e12ea24eba9db9c8697cf1df00cb0d80e71e7d4769c6
MD5 9303007d1c21f3e4b178989ffd66f7f1
BLAKE2b-256 b569c3a2407f96c0c3934e3a0fb24586f9f32b6052aa820de713410b50ad197b

See more details on using hashes here.

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

Hashes for pytdxfeed-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8c49f543a3ab745843da78429aa84c8e9ca532f3426864a5d5ea30ecc9aa5705
MD5 d5c31d6054c8d19c4e3c8cfc33aed94f
BLAKE2b-256 de2bd2eb9d53b533072db13f66716caff530b8d94efd2b1a80b0414dc06f7e80

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page