Learn

⌂Dashboard◈Learn

Practice

⌁Charts◷Replay↻Review

My learning

▥Stats☆Bookmarks⌕Search✦AI

Learning principle

Understand risk before practising decisions.

Trade ButyFree · Neutral
👤 Log in
📚Learn📈Markets⏮Replay✎Review🔍Search🤖AI👤 Log in
Trade Buty

A free & neutral trading education platform for Chinese speakers worldwide. Structured courses (learn) × live charts & replay (practice).

⚠️ Risk notice: All content is for study and research only and does not constitute investment advice. Markets are risky.

Navigate

LearnMarketsReplaySearchAIStatsPrivacy PolicyContent from kline-butyFeedback
© 2026 sun1090 · MIT LicenseContent from kline-buty

On this page

  • 1. What Is CTP
  • 2. Environment Setup
  • 2.1 Downloading the Interface and Choosing a Version
  • 2.2 Production vs Simulation (SimNow)
  • 2.3 Credentials and Version Differences
  • 3. Development Flow
  • 4. Key Structs and Call Conventions
  • 4.1 Request/Callback Pairing
  • 4.2 Threading Model: Callbacks Fire on a Separate Thread
  • 4.3 Commonly Used Structs
  • 5. Flow Control: Query and Order Frequency
  • 6. Connection Management
  • 6.1 Disconnect and Reconnect
  • 6.2 Settlement Confirmation (SettlementConfirm)
  • 7. Example Code: Minimal Python Skeleton
  • 8. Common Pitfall Checklist
  • Risk Warning

Chapter progress

10 · System Integration

Every earlier chapter was written for traders: how to read the market, how to manage positions, how to avoid pitfalls. T

0/11 lessons0%

Next chapter →

12 · Market Ecosystem→

The earlier chapters taught you to "read the rules and read the charts." This chapter asks you to step back and take a m

Learn/10 · System Integration
Lesson 10/10 / 11 lessons

10 · CTP Integration in Practice: From Zero to First Order

The full CTP futures interface workflow in practice, from environment setup and the development flow to a minimal runnable example and common pitfalls.

📖 ~11 min read
On this page▾
  • 1. What Is CTP
  • 2. Environment Setup
  • 2.1 Downloading the Interface and Choosing a Version
  • 2.2 Production vs Simulation (SimNow)
  • 2.3 Credentials and Version Differences
  • 3. Development Flow
  • 4. Key Structs and Call Conventions
  • 4.1 Request/Callback Pairing
  • 4.2 Threading Model: Callbacks Fire on a Separate Thread
  • 4.3 Commonly Used Structs
  • 5. Flow Control: Query and Order Frequency
  • 6. Connection Management
  • 6.1 Disconnect and Reconnect
  • 6.2 Settlement Confirmation (SettlementConfirm)
  • 7. Example Code: Minimal Python Skeleton
  • 8. Common Pitfall Checklist
  • Risk Warning

The previous article covered FIX; this one covers the de facto standard for domestic Chinese futures — CTP. It is the interface most quant teams and software companies deal with: C++ DLL, callback model, GBK encoding, authentication + double login… every step has its traps.

This article walks through a real project in order: what CTP is → environment setup → development flow → key call conventions → flow control → connection management → minimal runnable example → common pitfalls.


1. What Is CTP

CTP (Comprehensive Transaction Platform) was developed by SFIT (Shanghai Futures Information Technology Co., Ltd.) and is the de facto standard trading and market data interface of China's domestic futures market:

FactDetail
DeveloperSFIT (the technical company under SHFE)
StandingThe most widely deployed counter interface among domestic futures brokers, highest market share, the de facto standard
AudienceFutures brokers / software companies, not retail traders (retail uses broker trading software)
Interface shapeNative C++ dynamic libraries (Windows DLL / Linux .so), exporting two APIs: Trader and MD (market data)
Official docsInterface headers (.h) and documents must be requested through a futures broker under an NDA; after version iterations, fields are governed by the official headers
DerivativesCTPMini (simplified), SPT (Esunny's analogue), etc., with behavioral and field differences — defer to each one's documentation

API shape: C++ DLL + language wrappers

  • The official release is C++ only; the industry wraps it in C#/Java/Python (open-source projects like ctp-python, plus in-house wrappers at various companies). A wrapper is essentially just "a bridge to the DLL's exported functions".
  • The wrapper version must strictly match the actual counter version — version mismatch is the single most common first-class problem in CTP integration (struct sizes, added/removed fields all go out of sync).
  • The market data API and the trading API are two independent objects with two separate connections: CThostFtdcMdApi (market data) + CThostFtdcTraderApi (trading), each with its own login and its own callbacks.

Comparison with FIX: FIX is an industry-standard protocol of "text + dictionary"; CTP is a proprietary interface of "private binary structs + callbacks". CTP's advantage is easy integration (call it as soon as you have the DLL); its disadvantage is that all behavior is defined by the official implementation — details you can't see in the docs can only be learned by testing.


2. Environment Setup

2.1 Downloading the Interface and Choosing a Version

ItemNotes
Official interfaceObtained by request from the broker's IT department (most brokers have a "programmatic trading" section on their site); SFIT's website also provides some materials
Version choiceConfirm the broker's counter version, then pick the matching API version; upgrading the counter requires upgrading the API in sync — mixing is forbidden
Sim vs productionSimNow (simulation) and production interfaces are usually the same version, different addresses

2.2 Production vs Simulation (SimNow)

EnvironmentPurposeHow to get itCharacteristics
ProductionLive tradingOpen an account with a broker + apply for trading/market data permissions + AppIDReal money; strict monitoring (abnormal trading behavior, cancel rate); strictly forbidden for integration debugging
SimNow (SFIT simulation)Development, integration testing, strategy validationRegister a simulation account on SFIT's websiteMarket data is real simulated data; matching rules approximate live; funds are virtual; interface behavior matches production, but latency/rate limits/order book depth differ
Broker simulation counterIntegration against a specific broker's counterApply to the futures brokerCloser to that production counter's behavior; offered by some brokers

Iron rule: all development, integration testing, and regression testing happens in simulation; the production account is only for final pre-launch verification. One important SimNow detail: it is unavailable between the daily close and next day's open (not 7×24); automation scripts must handle "connection failure outside trading hours".

2.3 Credentials and Version Differences

  • The three authentication pieces: the broker-assigned AppID and AuthCode, plus the broker-provided trading/market data server addresses and ports, and the investor account and password.
  • Version differences: across versions, the auth flow, the login sequence after OnRspAuthenticate, and field naming may all differ; some counters use the newer trading interface (e.g. v6.x) while a few old counters still run older versions — your header files are the only source of truth.

What the industry calls "trade/quote" or "md/trader" interfaces are just different names for the same thing: TraderApi (trading / orders / queries) + MdApi (market data). The former handles authenticate → login → query → order → report; the latter handles market data login → subscription.


3. Development Flow

Standard flow (each step has a matching request function and callback):

text
① Initialize: create MdApi / TraderApi instances, register callbacks, connect to servers
        │
        ▼
② Authenticate: ReqAuthenticate (AppID + AuthCode) ──▶ OnRspAuthenticate
        │
        ▼
③ Login: ReqUserLogin (separate logins for trading and market data) ──▶ OnRspUserLogin
        │
        ▼
④ Settlement confirmation (trading side): ReqSettlementInfoConfirm ──▶ OnRspSettlementInfoConfirm
        │
        ▼
⑤ Queries: ReqQryInstrument / ReqQryTradingAccount / ReqQryInvestorPosition
        │    (query results arrive via OnRspQry* callbacks; today's orders/fills come via
        │     OnRtnOrder/OnRtnTrade or query callbacks)
        ▼
⑥ Market data: ReqSubscribeMarketData (subscribe contracts) ──▶ OnRtnDepthMarketData (tick stream)
        │
        ▼
⑦ Place order: ReqOrderInsert ──▶ OnRspOrderInsert (accepted or not)
        │                      └─▶ OnRtnOrder (order status) / OnRtnTrade (fill reports)
        ▼
⑧ Cancel: ReqOrderAction ──▶ OnRtnOrder (cancel result) / OnRspOrderAction

Two easily misunderstood points:

  • The OnRsp family ≠ the OnRtn family: OnRspOrderInsert only means "the counter received your request" (or rejected it), which does not mean the order filled; the order's true status lives in OnRtnOrder, and fills in OnRtnTrade.

⚠️ Order accepted does not mean filled

OnRspOrderInsert only means "the counter received your request" (or rejected it) — it does not mean the order filled. The order's real status lives in OnRtnOrder, and fills in OnRtnTrade. If you treat "order accepted" as "filled" just by watching OnRsp, your positions will never reconcile.

  • Query callbacks are batched: when results are large, one query triggers multiple OnRspQry* callbacks (with an IsLast flag); only when IsLast=true is the query complete.

4. Key Structs and Call Conventions

4.1 Request/Callback Pairing

All CTP interaction falls into three categories: "request (Req prefix) → response callback (OnRsp prefix) → push notification (OnRtn prefix)":

CategoryExamplesSemantics
RequestReqUserLogin, ReqOrderInsert, ReqOrderAction, ReqQryInstrumentInitiated by the client
ResponseOnRspUserLogin, OnRspOrderInsert, OnRspQryInstrumentThe counter's answer about "the request itself" (accepted/rejected)
Push notificationOnRtnOrder, OnRtnTrade, OnRtnTradingAccount, OnRtnDepthMarketDataEvent stream pushed proactively by the counter

Each request function takes a CThostFtdcInputXXXField* (input struct) and an int nRequestID (request ID); each callback carries a CThostFtdcRspXXXField* (response struct) and a CThostFtdcRspInfoField* (error info, where ErrorID != 0 means failure).

4.2 Threading Model: Callbacks Fire on a Separate Thread

This is the single most important concept in CTP integration:

  • The API maintains its own internal thread, and all callbacks fire on that API-internal thread — not on your business thread.
  • Never do slow work inside a callback (database writes, HTTP calls, heavy logging, synchronous waits): it blocks every subsequent report, causes market data gaps and delayed order reports, and can even trigger counter-side timeouts.
  • Standard practice: callbacks do nothing but fast enqueue; business threads consume the queue (producer-consumer). Data shared between callbacks and business threads must be locked or passed via queue — no naked sharing.

💀 Never do slow work inside a callback

Never do slow work inside a callback (database writes, HTTP calls, heavy logging, synchronous waits): it blocks every subsequent report, causing market data gaps, delayed order reports, and even counter-side timeouts. Standard practice: callbacks only fast-enqueue; business threads consume the queue — shared data must be locked or passed via a queue; no naked sharing.

text
CTP callback thread ──▶ thread-safe queue ──▶ business thread (consumes: persistence / state machine updates / strategy notifications)
    (pushes only)                     (all processing logic lives here)

4.3 Commonly Used Structs

StructPurposeKey fields (official headers are authoritative)
CThostFtdcReqUserLoginFieldLoginBrokerID, UserID, Password
CThostFtdcReqAuthenticateFieldAuthenticationBrokerID, UserID, AppID, AuthCode
CThostFtdcInputOrderFieldOrder placementInstrumentID, ExchangeID, Direction, CombOffsetFlag, LimitPrice, VolumeTotalOriginal, OrderPriceType, TimeCondition
CThostFtdcInputOrderActionFieldCancelInstrumentID, OrderSysID (counter order ID), or OrderRef + FrontID/SessionID
CThostFtdcOrderFieldOrder reportOrderStatus, VolumeTraded, VolumeTotal, LimitPrice
CThostFtdcTradeFieldFill reportTradeID, Price, Volume, Direction, OffsetFlag
CThostFtdcDepthMarketDataFieldMarket data tickLastPrice, Volume, BidPrice1/2/3…, AskPrice1/2/3…

5. Flow Control: Query and Order Frequency

CTP publishes no fixed rate-limit table; the following is common industry knowledge (defer to official docs and observed counter behavior):

Operation typeIndustry rule of thumbNotes
Queries (ReqQry*)About once per secondToo-frequent queries get rejected by the counter (flow-control error code), e.g. ReqQryTradingAccount / ReqQryPosition
Orders (ReqOrderInsert)About twice per secondHigh-frequency ordering triggers counter/exchange flow control and abnormal-trading surveillance
Cancels (ReqOrderAction)Same as ordersCancels are also throttled, and frequent place-and-cancel draws exchange scrutiny
Market data subscriptionConnection/subscription caps existManage subscriptions by reusing connections and aggregating subscriptions

Engineering countermeasures:

  • Query queue: route all ReqQry* calls into a single rate-limited serial queue, spaced at least 500ms apart (conservatively 1s).
  • Order throttler: control order pacing per instrument/account; an intraday order-to-cancel ratio above limits triggers exchange abnormal-trading alerts — reduce useless cancels at the strategy level too.
  • Distinguish error codes: on receiving a flow-control error code (e.g. "CTP: too many requests"), back off and wait instead of resending immediately (resending only makes it worse).
  • Note: SimNow's flow control is looser than production; a frequency that passes on SimNow may be flat-out rejected in production — re-verify frequency parameters against production before launch.

6. Connection Management

6.1 Disconnect and Reconnect

  • CTP connections have no heartbeat message (you detect disconnection only via the OnFrontDisconnected callback, plus self-checks when market data stops updating).
  • Standard recovery sequence: clean up the old connection → reconnect → re-authenticate → re-login → re-confirm settlement → re-subscribe market data → pull today's orders/positions to rebuild local state → resume ordering. State rebuild must complete before resuming orders (see 04-Trading Interfaces and Order Lifecycle.md↗).
  • Reconnects need backoff (e.g. 1s/2s/5s… capped at 30s) to avoid a reconnect storm against the counter; allow only one reconnect flow at a time.

6.2 Settlement Confirmation (SettlementConfirm)

  • Why the first login each day must confirm settlement: Chinese futures settle daily; after settlement, floating P&L rolls into the balance. The counter requires clients to confirm the day's settlement statement (funds / positions / fee breakdown) before trading is allowed — this is how brokers fulfill their disclosure obligation, implemented directly as "settlement unconfirmed → reject orders/reject fund queries".
  • Flow: after successful login call ReqSettlementInfoConfirm; only after receiving a successful OnRspSettlementInfoConfirm may you continue querying funds/positions and placing orders.
  • Common bug: confirming settlement only on first startup, then crossing into a new trading day without re-confirming → every morning order gets rejected with "CTP: please confirm settlement statement first".
  • Recommendation for automated systems: login → query settlement info (ReqQrySettlementInfo) → confirm → then enter business logic; encode "settlement confirmed" as a precondition in your startup state machine.

⚠️ Confirming settlement only on first startup means morning orders get rejected after the day rollover

If you confirm settlement only on first startup and never re-confirm on a new trading day, all your morning orders get rejected with "CTP: please confirm settlement statement first". The CTP counter requires clients to confirm the day's funds, positions, and fee breakdown after daily settlement before trading is allowed — always redo settlement confirmation after a day rollover.


7. Example Code: Minimal Python Skeleton

The following is a teaching-oriented pseudocode-style snippet (based on typical CTP Python wrappers). It demonstrates only the minimal closed loop of "connect + login + subscribe + print ticks" and is not production-ready.

python
# Teaching example: minimal CTP skeleton (connect + login + subscribe + print ticks)
# Requires: some CTP Python wrapper (vnpy's core, ctpbee, or in-house), matched to the counter version

class MdHandler:
    def on_front_connected(self):
        # 1. Market data login
        self.api.req_user_login(user_id=CFG.USER, password=CFG.PWD)

    def on_rsp_user_login(self, data, error, request_id, is_last):
        if error and error["ErrorID"] != 0:
            print("market data login failed:", error)
            return
        # 2. Subscribe contracts after successful login
        self.api.subscribe_market_data(contracts=["rb2610", "cu2610"])

    def on_rtn_depth_market_data(self, tick):
        # 3. Tick callback: printing only (teaching); enqueue in production, never do slow work here
        print(f"{tick.instrument_id} last={tick.last_price} "
              f"bid1={tick.bid_price_1}@{tick.bid_volume_1} "
              f"ask1={tick.ask_price_1}@{tick.ask_volume_1} time={tick.update_time}")

class TradeHandler:
    def on_front_connected(self):
        self.api.req_authenticate(app_id=CFG.APP_ID, auth_code=CFG.AUTH_CODE)

    def on_rsp_authenticate(self, data, error, request_id, is_last):
        if error and error["ErrorID"] != 0:
            print("authentication failed:", error); return
        self.api.req_user_login(user_id=CFG.USER, password=CFG.PWD)

    def on_rsp_user_login(self, data, error, request_id, is_last):
        if error and error["ErrorID"] != 0:
            print("trading login failed:", error); return
        # First login each day must confirm settlement, otherwise no trading
        self.api.req_settlement_info_confirm()

    def on_rsp_settlement_info_confirm(self, data, error, request_id, is_last):
        if error and error["ErrorID"] != 0:
            print("settlement confirmation failed:", error); return
        print("settlement confirmed; queries and orders now allowed")
        # Query funds / positions (mind rate limits: about once per second)
        self.api.req_qry_trading_account()

def main():
    md_api = create_md_api(MdHandler())      # market data: connect to the MD address
    trade_api = create_trader_api(TradeHandler())  # trading: connect to the trade address
    md_api.connect(CFG.MD_HOST, CFG.MD_PORT)
    trade_api.connect(CFG.TRADE_HOST, CFG.TRADE_PORT)
    # Production: main thread consumes the callback queue and processes signals; teaching example just idles
    wait_forever()

Key takeaways (teaching vs production gap):

  • Callbacks only enqueue; business logic runs on the main thread. Direct printing in the example is for demonstration only.
  • Market data and trading are two connections with two callback sets, each with independent disconnect/reconnect.
  • Field names follow the wrapper and counter version's official headers (names in this example are just a common mapping).

8. Common Pitfall Checklist

#PitfallSymptomCountermeasure
1GBK encodingGarbled Chinese contract names/error messages, unreadable logsDecode CTP strings as GBK before they enter your system (see 02-Exchanges and OMSs.md↗ 9.2)
2Time field formatsOrder/market timestamps look like 20260816 10:00:00 or concatenated yyyymmdd-hh:mm:ss valuesParse per official field definitions, normalize to timestamps for storage
3Order accepted ≠ filledTreating OnRspOrderInsert as a fill, positions don't reconcileTrust fills only from OnRtnTrade; trust status only from OnRtnOrder
4Cancel conflictsCanceling right as the order fills → cancel fails / already fully filled; duplicate cancelsCheck status before canceling (only cancel when OrderStatus allows); treat failed cancels as exceptions
5Forgetting daily settlement confirmationFirst order of a new trading day rejected: "confirm settlement statement first"Make "settlement confirmed" a mandatory precondition in the login state machine
6Unthrottled queriesHigh-frequency ReqQry* rejected by flow control; retries pile upQuery queue: about once per second
7Slow work inside callbacksReports/market data back up, latency spikesCallbacks only enqueue
8Version mismatchMisaligned structs, garbage field values, crashesWrapper version must strictly match the counter version
9Night session/day rolloverFriday night session belongs to the next trading day; connections dropped during settlement windowDrive everything off the trading calendar; re-confirm settlement after rollover
10Order ID conflictsDuplicate OrderRef/ClOrdID rejected by the counterGlobally auto-incremented IDs that survive restarts

Risk Warning

⚠️ Risk Warning

A direct CTP connection is a channel for real money, and the error window is tiny: cancel conflicts, status corruption, missed settlement confirmation after a day rollover, callback blocking backing up reports — any one of them can cause irreversible losses within seconds. Always remember: development and integration testing happen only on SimNow/broker simulation environments; production accounts are strictly off-limits for debugging. Production must enable order throttling, query rate limiting, and an independent risk-control process; complete fault-injection tests for reconnect, rejection, and cancel conflicts before launch; all fields, rate-limit values, and flows defer to the official CTP API documentation and the broker's technical specs — this article reflects common industry knowledge only.

📝 系统对接篇 · 随堂测

3 concept questions · instant grading

📖 Done reading? See the real market

Find the concepts from this lesson on the live chart — understand before you continue.

Open live chart →
🤖Ask AI: 10 · CTP Integration in Practice: From Zero to First Order→

Related lessons

  • →01 · Integration Overview and Role Division: Draw the Map Before Writing Code
  • →02 · Exchanges and OMSs: Which Layer Your System Actually Connects To
  • →03 · Market Data Systems: The Eyes of Trading Software
  • →04 · Trading Interfaces and Order Lifecycle: The Heart of the System
  • →05 · Risk Controls and Capital Management: The Last Line of Defense Must Be Your Own

Next

11 · Matching Engine Principles: How Orders Become Fills

→