Library / SDK
nooperpudd/ctpwrapper avatar
nooperpudd/ctpwrapper

ctpwrapper: a Python binding for the Shanghai Futures Exchange CTP interface

上海期货交易所CTP接口 Shanghai Future CTP Interface CTP Python API Wrapper

618 stars170 forksPythonNOASSERTION

At a glance

What is it?
ctpwrapper is a Cython based Python package that exposes the Shanghai Futures Exchange CTP client API, the market data and trading protocol used by Chinese futures brokers. It suits developers building market data or order handling systems in Python, and it comes with a documented set of quirks that a production deployment has to work around.
Who is it for?
ctpwrapper is a practical choice for a Python team that has to speak the CTP protocol to a Chinese futures broker and wants the vendor's own C++ interface without writing the binding from scratch, and the fact that the vendor SDK sources and the binding generator ship in the repository makes a fork realistic.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 139 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the CTP interface is and who needs a wrapper for it

The CTP interface is the client API of the Shanghai Futures Exchange, and it is what Chinese futures brokers expose to their own trading clients. ctpwrapper is a Python wrapper for that interface, and the repository describes itself as the CTP futures API Python wrapper with the Chinese and English names side by side.

The audience is narrow and specific. It is a developer who needs market data or order handling from a CTP broker, prefers Python to C++, and does not want to write and maintain a binding to a vendor C++ SDK as a side project. The repository topics list names the SHFE, futures, trading API and CTP, and the package metadata keywords are CTP, Future, SHFE and Shanghai Future Exchange.

The wrapping is a Cython binding rather than a pure Python client. The build system in the manifest requires setuptools and Cython, the package code lives in a `ctpwrapper/` directory, and the vendor interface itself sits in a `ctp/` directory with per platform subdirectories, including a `linux` directory whose shared objects are shipped as package data. That layout tells you the wrapper is a thin compiled layer over the exchange's own C++ libraries rather than a reimplementation of the protocol.

The repository also carries the scripts that regenerate the binding, `generate.py` and `generate_structure.py`, plus `makefile`, `make`-driven build helpers and a `docker/` directory. That combination of vendor SDK, generator and finished package is what makes the project more than a binary distribution, and it is the reason the difference between ctpwrapper and hand-rolled bindings is smaller than it first appears.

How the binding is assembled: Cython over the vendor C++ SDK

The build logic in `setup.py` shows the whole construction, and it is worth reading because it explains several of the package's constraints.

The extension modules are produced with Cython's `cythonize` and `build_ext`, and the header directories come from three places. One is the vendor's own header directory inside the `ctp/` tree, one is a `headers` directory inside the package, and one is a `cppheader` directory that holds the generated C++ headers for the binding.

python
if platform.architecture()[0] != "64bit":
    raise EnvironmentError("Please install Python x86-64")

That check runs at build and install time, and it is the first hard constraint on a user. A 32 bit Python cannot install the package at all, and the README states the platform support as Linux 64bit and Windows 64bit with a Python requirement of x86-64, and it calls out special support for PyPy3 3.6 on Linux 64bit.

The platform selection happens in the same file. On Linux the library directory is taken from the `linux` subdirectory of the vendor tree, the shared objects are added to the package data list, and the compile and link flags are set. Other platforms follow a parallel branch.

python
if sys.platform == "linux":
    lib_dir = os.path.join(ctp_dir, "linux")
    package_data.append("*.so")
    extra_compile_args = ["-Wall"]

The version number is not hardcoded in the packaging logic. `setup.py` reads `__version__` out of the package's init file with a regular expression, following the single source version recommendation, and raises a runtime error if it cannot find the string. The project metadata records the same value, 6.7.13, as the version of the CTP client being wrapped, and the README shows the same number under a Version heading along with a link to the CTP website for the interface download.

Installing from PyPI, with Cython installed first

The installation is short, and the order matters. The README states that before installing the ctpwrapper package you need to make sure you have already installed the cython package, and then gives the two commands.

bash
    >>>pip install cython --upgrade
    >>>pip install ctpwrapper --upgrade

The `--upgrade` flag on both is deliberate, since the wrapper compiles Cython sources at install time and an older Cython in the environment is a common cause of a failed build.

A source install from the repository follows the same dependency requirement. The build backend is setuptools with Cython listed in the build requirements, and `requirements.txt` contains a single line, `cython`. The manifest also pins `setuptools>=82.0.1`, and the project metadata requires Python 3.10 or newer and declares support for CPython and PyPy across versions 3.10 through 3.14.

The package is published on PyPI as ctpwrapper, and the README points to the samples directory for example code. Three samples are visible in the repository layout: `samples/trader_main.py`, `samples/md_main.py` and `samples/config.json`, which is the natural pairing, a market data program and a trading program, both configured from a JSON file.

The documentation is a separate directory, `doc/ctp/`, holding the CTP documentation rather than a Python API reference, so the samples are the practical entry point for learning the surface area.

Two runtime quirks the README tells you to expect

The most useful part of the README for a production user is the section describing behaviour that will otherwise look like a bug in your own code.

The first is timing. The notice explains that sometimes you quote the market futures data, but there is no trading data from the API stream, and recommends adding a `time.sleep(2)` call after the `Init()` method is invoked, which it says helps to solve the no data response issue. A two second pause after initialisation is not a design anyone would choose deliberately; it is a workaround for a race in the interface's own handshake, and the fact that the maintainer documents it this way tells you the wrapper does not paper over it.

The second is character decoding. The issues section points at issue 62 and describes it as a temporary solution for the UnicodeDecodeError issue, with a traceback showing the failure inside the wrapper's own base attribute handling, where a value is decoded with the gbk codec and raises on an incomplete multibyte sequence.

python
def OnRspQryTradingAccount(self, pTradingAccount, pRspInfo, nRequestID, bIsLast):
     print("OnRspQryTradingAccount")
     print("nRequestID:", nRequestID)
     print("bIsLast:", bIsLast)
     print("pRspInfo:", pRspInfo)
     print("pTradingAccount:", pTradingAccount)
     # need to check is last message from the server.
     global error_message
     if not bIsLast:
        error_message+=pRspInfo.ErrorMsg
     else:
        error_message+=pRspInfo.ErrorMsg
        if isinstance(error_message,bytes):
            error_message.decode("gbk")

The shape of the sample matters more than the fix. The callback accumulates the error message across the pagination of a multi-part response, and the decode call is made without using its return value, so the bytes are decoded for the side effect of raising, not to produce a string. A user who adopts this pattern has to decide for themselves how the decoded text should be handled, and the README does not document a supported way of doing that.

The encoding problem is structural rather than incidental. The exchange interface speaks GBK encoded Chinese text, and the wrapper decodes with the gbk codec by default, so a truncated multibyte sequence at the end of a packet will fail. Any deployment that handles real broker traffic needs a decoding policy that tolerates partial sequences, and the repository does not ship one.

Version cadence, the licence question and the packaging metadata

The release history is a bit lumpy, and the packaging metadata carries a discrepancy worth knowing about.

Releases are v6.7.13 on 2026-06-08, 6.7.9 on 2025-05-03 and v6.7.0 on 2023-07-05, and the last push to the repository was on 2026-06-08. The version number tracks the CTP client release being wrapped, so an upgrade means picking up a newer exchange interface rather than a new wrapper design. The naming is not consistent between tags, which is a small annoyance for automation that parses releases.

The licence is where the metadata is worth a second look. The project metadata in `pyproject.toml` declares `license = "LGPL-3.0-or-later"`, while the repository's licence field is reported as a custom licence that GitHub cannot classify. The LICENSE file at the top of the repository is the authoritative text, and anyone linking this into a distributed product should read it rather than rely on the metadata string. This is not legal advice, but a mismatch between a metadata field and the licence file is exactly the kind of thing that needs checking before a commercial deployment.

Otherwise the packaging is careful. The project is marked as Development Status 5, Production/Stable, and it declares itself for POSIX and Windows, for CPython and PyPy, and for Python 3.10 to 3.14. The classifers do not claim support for ARM, which matches the x86-64 requirement stated in the README and enforced in `setup.py`.

The repository also shows continuous integration through GitHub Actions, an AppVeyor build for Windows, and a Codacy grade badge, and it includes a `tests/` directory with a `pytest.ini`, so there is a test surface even though the README does not describe what it covers.

The alternative: generate the bindings yourself, or write against the vendor SDK

The realistic alternatives to ctpwrapper are not other Python wrappers so much as two different levels of the same work.

The first alternative is the vendor C++ SDK used directly. You write the callback handling, the connection lifecycle and the message marshalling in C++, and expose it to your Python code through your own binding. The difference in approach is that ctpwrapper has already made the choices about which Cython directives to use, how the callback objects are shaped, and how the vendor headers are exposed, and you inherit those decisions instead of making them. For a team that has to ship a trading system, inheriting them is usually the point.

The second alternative is the path the repository itself supports, because it ships the pieces. The `ctp/` directory holds the vendor interface with its per platform binaries, the `headers` and `cppheader` directories hold the binding surface, and `generate.py` and `generate_structure.py` are the scripts that produce the generated code from the interface definitions. A team that needs a behaviour the wrapper does not have, or needs to fix the GBK decoding in a controlled way, can regenerate or patch the binding rather than waiting for an upstream fix, and the `docker/` directory suggests the project is also used that way.

There is a third practical option that is not a wrapper question at all: the wrapper exposes the CTP API, and the CTP API is the broker facing protocol. Whether a data feed, a broker's own gateway or a simulated environment is the right source for your price and order data is a separate decision that the wrapper does not make for you, and the samples directory ships a `config.json` precisely because the connection settings differ between environments.

What the wrapper does not do is shield you from the interface's behaviour. The sleep after `Init()`, the GBK decoding and the pagination of multi-part responses are all still yours to handle, which is the real cost of the abstraction.

Editorial conclusion

ctpwrapper is a practical choice for a Python team that has to speak the CTP protocol to a Chinese futures broker and wants the vendor's own C++ interface without writing the binding from scratch, and the fact that the vendor SDK sources and the binding generator ship in the repository makes a fork realistic. It is the wrong choice for a 32 bit or ARM Python environment, for a team that cannot tolerate a package which still raises UnicodeDecodeError on some settlement responses, and for anyone who needs the new interface generations the wrapper has not caught up with. Verify first that the version you install matches the API version your broker serves, and reproduce the GBK decoding path and the Init() timing behaviour on your own connection before you rely on it in a trading session.

Frequently asked questions

How do I install ctpwrapper?

Install Cython first, because the package compiles Cython sources at install time, with `pip install cython --upgrade`, then run `pip install ctpwrapper --upgrade`. The project metadata requires Python 3.10 or newer.

What is ctpwrapper used for?

It is a Python wrapper for the Shanghai Futures Exchange CTP client interface, so a Python program can consume market data and drive the trading API that Chinese futures brokers expose to their clients.

Does ctpwrapper work on 32 bit Python or ARM?

No. The build checks the platform architecture and raises an EnvironmentError asking for Python x86-64, and the README states the supported platforms as Linux 64bit and Windows 64bit with special support for PyPy3 3.6 on Linux.

Why do I get no market data right after connecting?

The README documents this as a known behaviour and recommends adding a `time.sleep(2)` call after the `Init()` method is invoked, which it says helps to solve the no data response issue.

What causes the UnicodeDecodeError in ctpwrapper?

The wrapper decodes response values with the gbk codec, and a truncated multibyte sequence at the end of a message raises UnicodeDecodeError inside the base attribute handling. The README points at issue 62 as a temporary solution and shows a sample callback that accumulates the error message across a multi-part response.

What licence is ctpwrapper released under?

The project metadata in pyproject.toml declares LGPL-3.0-or-later, and the repository also holds a LICENSE file. The authoritative terms are in the LICENSE file, which should be read before the package is linked into a distributed product.

Official sources

  1. Issues
  2. nooperpudd/ctpwrapper on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nooperpudd-ctpwrapper.svg)](https://hysenlabs.com/projects/nooperpudd-ctpwrapper)

Was this useful?