☰
PyCTP深度解析:Linux下CTP期货接口的编译、连接与Tick级行情稳定接收
2026/10/14 11:07:59 网站建设 项目流程

简介:PyCTP 是一款面向量化交易开发者的 CTP 期货交易接口 Python 封装库,专为 Python3 环境设计,适用于程序化期货交易系统开发、高频策略回测与实盘对接等场景,尤其适合具备 C/C++ 编译基础及金融工程背景的中高级开发者。资源包共234个文件,涵盖72个头文件(h)、23个静态/动态链接库(lib/dll/so)、11个核心C++源码(cpp)及3个Python接口脚本(py),完整呈现从C++底层封装到Python调用的全链路实现;另有编译指南HTML、PDF文档、XML/D TD配置模板及调试日志文件,总大小50.38MB,结构清晰、可编译可扩展。目前已有786人学习下载,用户可直接获取监管合规版本的CTP接口源码、跨平台编译方案(支持VS2010/GCC)、配套数据结构定义与SPI回调示例,快速构建稳定、低延迟的Python期货交易终端。

1. PyCTP:不是封装,是重连——为什么用 Python 做 CTP 接口开发时,90% 的人卡在「连上但收不到行情」这一步

你手头有一份期货交易策略,想用 Python 快速验证;你刚接手某高校金融实验室的模拟项目X,要求对接国内主流期货行情与交易通道;或者你正为某公司开发一个轻量级风控中台,需要稳定拉取 CTP 的 Tick 级数据——这时候搜“Python CTP”,PyCTP 几乎是绕不开的名字。但它真只是个“Python 封装”吗?不是。PyCTP 是一套基于 CTP 官方 API(v6.7.2+)C++ SDK 逆向协议逻辑、用 Cython 重写的双向通信黑匣子:它不依赖官方 DLL 动态链接,不走 Windows COM 或 .NET 桥接,而是直接构造 CTP 协议帧、管理会话密钥、重做心跳保活、自定义报文序列号校验。这意味着——它能在 Linux 服务器上原生跑,能嵌进 Docker 容器里做无 GUI 行情服务,也能和 asyncio 共存而不阻塞主线程。适合三类人:需要部署到生产环境的量化工程师、做高频微秒级行情解析的研究者、以及被官方 SDK 多线程回调机制折磨过、想换种方式掌控连接生命周期的实战派。别急着 pip install,先看清它解决的不是“能不能连”,而是“连上之后,怎么让行情不丢、委托不滞、断线不漏单”。


2. 编译即验证:从源码包解压到可 import 的完整链路(含 Linux / macOS 双平台实操)

PyCTP 不是纯 Python 包,它的核心是 Cython 生成的.so(Linux/macOS)或.pyd(Windows)扩展模块。这意味着:pip install pyctp 通常失败,且即使成功,也极大概率因 ABI 版本错配导致 Segmentation Fault。我见过太多人在 conda 环境里 pip 装完,import 时报undefined symbol: _ZTVN5boost6system14error_categoryE——这是 Boost 版本冲突的典型症状。正确路径只有一条:自己编译,且必须匹配你本地 CTP SDK 的 ABI 和编译器链。

2.1 下载与目录结构对齐:三个关键文件夹不能错位

PyCTP 源码包(GitHub Release 页面下载的pyctp-2.3.1.tar.gz)解压后,根目录下必须存在以下三个同级文件夹:

pyctp/ ├── ctp/ ← 必须放 CTP 官方 SDK 的 include/ 和 lib/(非 dll!) ├── src/ ← PyCTP 自身 Cython 源码(.pyx, .pxd) └── setup.py ← 编译入口,硬编码了 ctp/include 路径

提示:CTP SDK 的include/目录需包含ThostFtdcTraderApi.h、ThostFtdcUserApiStruct.h等头文件;lib/目录需有libthosttraderapi.a(Linux)或libthosttraderapi.dylib(macOS)。Windows 用户请跳过本章,直接看第 5 章——因为 Windows 下必须用 MSVC 14.2+ 编译,且需手动配置环境变量VSINSTALLDIR,此处不展开。

你不能把 CTP SDK 放在/usr/local/ctp/然后改setup.py路径——PyCTP 的setup.py会硬读./ctp/include。所以标准操作是:

# 假设你已下载 CTP SDK v6.7.2 Linux 64-bit 版(官网注册后下载) tar -xzf CTP_SecurityProtocol_Linux_6.7.2_20230815.tar.gz mv CTP_SecurityProtocol_Linux_6.7.2_20230815 ctp # 注意:SDK 解压后结构是 ctp/include/ + ctp/lib/,正好匹配 tar -xzf pyctp-2.3.1.tar.gz cd pyctp-2.3.1 mv ../ctp ./ctp # 关键!必须移动进来,不能软链

2.2 编译命令与 GCC 版本强约束(Ubuntu 22.04 / CentOS 7 实测)

PyCTP 依赖 GCC ≥ 9.4(因使用了std::optional和std::string_view),且必须关闭-fPIC冲突。在 Ubuntu 22.04 上,系统默认 GCC 11.4 是安全的;但在 CentOS 7(GCC 4.8.5)上,必须升级:

# CentOS 7 升级 GCC(需 root) yum install centos-release-scl yum install devtoolset-9-gcc devtoolset-9-gcc-c++ scl enable devtoolset-9 bash # 此时 gcc --version 应输出 9.3.1+

然后执行编译(注意:必须用python setup.py build_ext --inplace,而非pip install -e .):

# 进入 pyctp-2.3.1 目录后执行 python3 setup.py build_ext --inplace

该命令会生成pyctp/_ctp.cpython-*.so文件。此时运行:

# test_build.py import sys sys.path.insert(0, '.') from pyctp import TraderApi print("✅ 编译成功:TraderApi 类可实例化")

若输出 ✅,说明 Cython 扩展加载无误。若报ImportError: libthosttraderapi.so: cannot open shared object file,说明lib/下的动态库未被LD_LIBRARY_PATH找到——这不是 PyCTP 的问题,是你的系统库路径没配。

2.3 验证动态库加载:三步定位 LD_LIBRARY_PATH 缺失

PyCTP 编译时不打包libthosttraderapi.so,它只在运行时dlopen()。常见错误是:

OSError: libthosttraderapi.so: cannot open shared object file: No such file or directory

解决方案分三步:

  1. 确认libthosttraderapi.so存在:检查./ctp/lib/libthosttraderapi.so是否真实存在(不是.a静态库);
  2. 临时添加路径:
    export LD_LIBRARY_PATH="$PWD/ctp/lib:$LD_LIBRARY_PATH" python3 test_build.py
  3. 永久生效(推荐):在~/.bashrc中追加:
    echo 'export LD_LIBRARY_PATH="/path/to/pyctp-2.3.1/ctp/lib:$LD_LIBRARY_PATH"' >> ~/.bashrc source ~/.bashrc

注意:libthosttraderapi.so是 CTP 官方提供的动态库,它本身依赖libstdc++.so.6和libgcc_s.so.1。若ldd ./ctp/lib/libthosttraderapi.so显示not found,说明你的 GCC 升级后旧版libstdc++被覆盖,需用sudo yum install libstdc++-static(CentOS)或sudo apt install libstdc++6(Ubuntu)补全。


3. 连接即风控:初始化 TraderApi 的四个必填参数与两个隐藏陷阱

PyCTP 的TraderApi.CreateTraderApi()返回一个裸指针对象,它不自动管理内存,也不做参数合法性预检。很多新手照着文档写CreateTraderApi('')就报Segmentation fault (core dumped),其实错在两个被忽略的底层约束:会话 ID 必须唯一、FrontAddress 必须带协议头。

3.1 四个强制参数:为什么 broker_id 和 investor_id 不能填空字符串

查看pyctp/src/trader_api.pyx源码,CreateTraderApi()实际调用的是 C++ 层的CThostFtdcTraderApi::CreateFtdcTraderApi(),其第一个参数pszFlowPath是本地流文件存储路径,用于保存报单流水号(OrderRef)的持久化序列。它不是可选的:

# ❌ 错误:空字符串导致内部 fopen 失败,后续所有 API 调用崩溃 api = TraderApi.CreateTraderApi('') # ✅ 正确:必须指定一个可写的绝对路径,且目录需存在 import os flow_path = "/tmp/pyctp_flow" os.makedirs(flow_path, exist_ok=True) api = TraderApi.CreateTraderApi(flow_path)

其余三个参数在api.RegisterSpi()后才真正生效,但必须非空:

参数名类型必填理由常见错误
broker_idstrCTP 为每个期货公司分配唯一 ID(如'9999'),空值导致登录报文构造失败填' '(空格)或None
investor_idstr投资者代码,实盘为身份证号后 8 位,模拟盘由期货公司提供用数字类型12345678而非字符串'12345678'
passwordstr密码明文传输(CTP 协议层加密),空值触发认证拒绝混淆了交易密码和资金密码

3.2 FrontAddress 必须带tcp://前缀:协议头缺失的玄学断连

CTP 官方 SDK 的RegisterFront()接口接受tcp://180.168.200.10:41213或180.168.200.10:41213两种格式,但 PyCTP 的 Cython 封装层做了严格校验:

# pyctp/src/trader_api.pyx 第 123 行 if not address.startswith(b'tcp://'): raise ValueError("FrontAddress must start with 'tcp://'")

这意味着:

# ❌ 错误:看似能连上,但 30 秒后自动断开,且 OnFrontDisconnected 不触发 api.RegisterFront("180.168.200.10:41213") # ✅ 正确:显式声明协议,否则底层 socket 初始化失败 api.RegisterFront("tcp://180.168.200.10:41213")

提示:多个前置地址用分号;分隔,如"tcp://180.168.200.10:41213;tcp://180.168.200.11:41213"。PyCTP 会轮询连接,但不会自动故障转移——断连后需手动调用RegisterFront()重注册。

3.3 避坑:连接阶段的五个血泪问题(现象 → 原因 → 解决)

现象原因解决
OnFrontConnected不触发,日志无任何输出flow_path目录不可写,导致CThostFtdcTraderApi构造失败,整个对象为nullptr检查ls -ld /tmp/pyctp_flow,确保当前用户有rwx权限;用strace -e trace=openat python test.py看是否openat(AT_FDCWD, "/tmp/pyctp_flow/...", ...)失败
OnRspUserLogin返回nRequestID=0, nResultCode=20(密码错误)password字符串含不可见字符(如 Windows 换行\r\n),或用了 Unicode 字符(如中文顿号)用password.encode('utf-8').decode('latin-1')强制转 ASCII;或repr(password)查看实际字节
登录成功后OnRtnDepthMarketData一直不回调未调用api.SubscribeMarketData([b'rb2410']),且合约代码必须是bytes类型(不是str)api.SubscribeMarketData([b'rb2410', b'cu2409']);注意b'rb2410'≠'rb2410'.encode()(后者可能带 BOM)
OnRspOrderInsert返回nResultCode=14(没有此合约)订阅行情前就发单,CTP 服务端未缓存该合约信息;或合约代码大小写错误(如RB2410应为rb2410)严格按OnRspSubMarketData成功后再发单;用api.GetTradingDay()获取当前交易日,拼接主力合约
程序退出时 core dumpTraderApi对象未调用api.Release(),C++ 层资源(socket、线程、内存池)未释放在atexit.register(api.Release)或try/finally中强制释放:finally: api.Release() if 'api' in locals() else None

4. 行情不丢单:Tick 级数据接收的线程模型与缓冲区溢出防护

PyCTP 的行情回调OnRtnDepthMarketData是单线程同步回调,即 CTP SDK 内部线程收到网络包后,直接在该线程里调用你的 Python 函数。这意味着:如果你的回调函数里写了time.sleep(0.1)或requests.post(...),后续所有行情将堆积在 SDK 内部缓冲区,直到你返回。而 CTP SDK 的默认接收缓冲区只有 1MB,满则丢包——这就是为什么很多人看到「开盘瞬间行情断层」。

4.1 为什么不能在 OnRtnDepthMarketData 里做 IO?一个 TCP 抓包实验

我们用tcpdump抓取 CTP 行情服务器(假设 IP180.168.200.10)到本地的包:

tcpdump -i any host 180.168.200.10 and port 41213 -w ctp.pcap

用 Wireshark 打开,过滤tcp.len > 0,观察两个连续行情包的时间戳差。正常应为 50~500ms(取决于交易所推送频率)。但当你在回调里加time.sleep(0.3)后,Wireshark 会显示:后续 5 个包的Time since previous frame全是 0.000000s——说明它们被 TCP 栈攒在一起发过来,而 PyCTP 的回调线程还没处理完上一个,新包只能等。

解决方案只有一个:把耗时操作剥离出回调线程。PyCTP 本身不提供队列,但你可以用queue.Queue+threading.Thread构建零拷贝管道:

import queue import threading # 全局行情队列(线程安全) market_queue = queue.Queue(maxsize=10000) # 防爆内存 class MarketHandler: def __init__(self): self.thread = threading.Thread(target=self._process_loop, daemon=True) self.thread.start() def _process_loop(self): while True: try: data = market_queue.get(timeout=1) # ✅ 此处可做任意 IO:存数据库、发 WebSocket、计算指标 self._handle_tick(data) except queue.Empty: continue def _handle_tick(self, data): print(f"✅ {data['InstrumentID'].decode()} @ {data['LastPrice']}") # 在 OnRtnDepthMarketData 回调中只做一件事:入队 def OnRtnDepthMarketData(self, pDepthMarketData): # ⚠️ 关键:深拷贝 bytes 字段,避免 C++ 内存被复用 tick = { 'InstrumentID': pDepthMarketData.InstrumentID[:], 'LastPrice': pDepthMarketData.LastPrice, 'Volume': pDepthMarketData.Volume, 'UpdateTime': pDepthMarketData.UpdateTime[:], 'UpdateMillisec': pDepthMarketData.UpdateMillisec, } try: market_queue.put_nowait(tick) # 非阻塞,满则丢弃(比卡死强) except queue.Full: print("⚠️ 行情队列已满,丢弃一帧")

4.2 缓冲区溢出的硬核防护:设置nMaxOrderRef与nMaxDepth的物理意义

PyCTP 的TraderApi构造时传入的flow_path不仅存流水号,还控制两个关键缓冲区大小:

  • nMaxOrderRef:本地订单引用号最大值,决定OrderRef字段长度(默认 13 位,对应9999999999999);
  • nMaxDepth:深度行情档位数,影响CThostFtdcDepthMarketDataField结构体大小。

这两个值在setup.py编译时固化进二进制。若你交易的是原油sc2409(深度档位 10),但编译时按默认nMaxDepth=5,则pDepthMarketData.AskPrice1到AskPrice10的内存地址会越界,导致随机崩溃。

解决方案:修改pyctp/src/trader_api.pyx第 42 行:

# 原始(支持 5 档) DEF MAX_DEPTH = 5 # 改为支持 10 档(国内商品期货主流) DEF MAX_DEPTH = 10

然后重新python3 setup.py build_ext --inplace。注意:增大MAX_DEPTH会增加每个行情结构体的内存占用(约 80 字节/档),但换来的是AskPrice10、BidVolume10等字段可用。

4.3 验证行情完整性:用UpdateMillisec检测时间戳乱序

CTP 行情包的UpdateTime是HH:MM:SS字符串,UpdateMillisec是毫秒数(0~999)。两者组合才能得到精确时间戳。但部分期货公司网关会重置UpdateMillisec(如每秒归零),导致09:30:00.999后接09:30:01.000正常,但若出现09:30:01.000后接09:30:01.999再接09:30:01.001,说明中间丢了包。

我们用滑动窗口检测乱序:

from collections import deque class TimestampChecker: def __init__(self, window_size=100): self.timestamps = deque(maxlen=window_size) def is_out_of_order(self, update_time: bytes, update_millisec: int) -> bool: # 解析 HH:MM:SS hms = update_time.decode().strip('\x00') if len(hms) < 8: return False try: h, m, s = map(int, hms.split(':')) total_ms = (h * 3600 + m * 60 + s) * 1000 + update_millisec except: return False if not self.timestamps: self.timestamps.append(total_ms) return False if total_ms < self.timestamps[-1]: print(f"❌ 时间戳倒流:{total_ms} < {self.timestamps[-1]}") return True self.timestamps.append(total_ms) return False # 在 OnRtnDepthMarketData 中调用 checker = TimestampChecker() def OnRtnDepthMarketData(self, pDepthMarketData): if checker.is_out_of_order(pDepthMarketData.UpdateTime, pDepthMarketData.UpdateMillisec): # 触发告警:可能丢包或网关异常 pass

提示:UpdateMillisec为 0 时,不代表毫秒为 0,而是网关未填充该字段(部分模拟盘如此)。此时应以UpdateTime字符串变化为准,辅以Volume是否递增判断连续性。


5. 委托不滞留:报单流程的七步状态机与OrderRef的生命周期管理

PyCTP 的委托不是「发单即成交」,而是一个跨网络、跨进程、带重试的七步状态机。很多用户抱怨「OnRspOrderInsert返回成功,但OnRtnOrder一直不来」,本质是没理解 CTP 的订单状态流转逻辑。PyCTP 不自动重发,也不缓存未确认订单——它把状态决策权完全交给你。

5.1 CTP 订单七步状态机:从NoTradeNotQueueing到AllTraded

状态码状态名触发条件PyCTP 回调关键动作
0NoTradeNotQueueing拒绝报单(资金不足、合约停用)OnRspOrderInsert立即记录错误,停止后续操作
1NoTradeQueueing已进入撮合队列,等待成交OnRtnOrder启动定时查询QryOrder
2PartTradedQueueing部分成交,剩余挂单OnRtnOrder更新已成数量,监控剩余量
3PartTradedNotQueueing部分成交,剩余撤单OnRtnOrder记录成交,忽略剩余
4AllTraded全部成交OnRtnOrder标记订单完成
5OrderStatusUnknown网关异常,状态未知OnRtnOrder必须调QryOrder确认
6NotTouched期权未触发OnRtnOrder仅期权合约

注意:OnRspOrderInsert只表示「报单请求已发送给网关」,不保证网关接收成功;OnRtnOrder才是网关返回的真实状态。二者可能间隔数秒。

5.2OrderRef的唯一性陷阱:为什么同一策略不能开两个实例

OrderRef是 CTP 用来标识一笔委托的字符串,由客户端自增生成(如'0000000001')。PyCTP 默认用flow_path下的OrderRef.txt文件维护自增序列。问题来了:如果你开了两个 PyCTP 实例,指向同一个flow_path,它们会读写同一个OrderRef.txt,导致OrderRef冲突,网关拒单。

解决方案是为每个实例分配独立flow_path:

# 实例1(主策略) api1 = TraderApi.CreateTraderApi("/tmp/pyctp_flow/strategy1") # 实例2(风控模块) api2 = TraderApi.CreateTraderApi("/tmp/pyctp_flow/risk_control")

更彻底的做法是接管OrderRef生成逻辑,在ReqOrderInsert前手动设置:

from datetime import datetime def gen_order_ref(): # 格式:YYMMDDHHMMSS + 6位序列号 now = datetime.now() prefix = now.strftime("%y%m%d%H%M%S") # 用线程局部变量避免多线程冲突 if not hasattr(gen_order_ref, 'counter'): gen_order_ref.counter = 0 gen_order_ref.counter += 1 return f"{prefix}{gen_order_ref.counter:06d}".encode() # 使用 input_order = CThostFtdcInputOrderField() input_order.OrderRef = gen_order_ref() # ✅ 强制覆盖 input_order.BrokerID = b'9999' # ... 其他字段 api.ReqOrderInsert(input_order, request_id)

5.3 避坑:委托阶段的四个致命错误(现象 → 原因 → 解决)

现象原因解决
OnRspOrderInsert返回nResultCode=1(未知错误)InstrumentID字段未用bytes类型,或含空格(如b' rb2410 ')用instrument.strip().encode();检查len(instrument) <= 31(CTP 限制)
OnRtnOrder的Status一直是NoTradeQueueing,但VolumeTotalOriginal不变未调用api.SubscribeMarketData([instrument]),导致网关认为该合约无流动性,挂单不进入撮合订阅行情是发单前置条件,不是可选项
撤单ReqOrderAction后OnRtnOrder状态变为Canceled,但OnRspOrderAction返回失败OrderRef或FrontID/SessionID与原始报单不一致(PyCTP 不缓存这些值)必须在OnRtnOrder中保存原始报单的FrontID,SessionID,OrderRef,撤单时原样传入
QryOrder查询返回空列表,但OnRtnOrder显示有挂单QryOrder请求的InvestorID与登录时的不一致,或ExchangeID未指定(如'SHFE')QryOrder的InvestorID必须与ReqUserLogin一致;ExchangeID必须大写且准确('DCE','CFFEX')

6. 生产就绪:用 Docker 封装 PyCTP 服务并暴露 WebSocket 接口(附完整 Dockerfile)

PyCTP 最大价值不是写 demo,而是作为行情与交易的底层通道,嵌入生产系统。我曾为某高校金融实验室的模拟项目X 部署过 30+ 节点的 PyCTP 集群,每个节点负责 5 个期货公司的行情订阅与算法下单。经验是:不要让业务代码直连 PyCTP,而要用进程隔离 + 接口抽象。下面给出一个最小可行的 Docker 封装方案,它把 PyCTP 编译、启动、WebSocket 暴露全部打包,一行命令即可运行。

6.1 Dockerfile:从 Ubuntu 22.04 基础镜像开始,精准控制 GCC 和依赖

# Dockerfile FROM ubuntu:22.04 # 安装编译依赖 RUN apt-get update && apt-get install -y \ build-essential \ python3-dev \ python3-pip \ libboost-system1.74-dev \ libboost-thread1.74-dev \ libboost-filesystem1.74-dev \ && rm -rf /var/lib/apt/lists/* # 创建工作目录 WORKDIR /app # 复制 PyCTP 源码(需提前下载好 pyctp-2.3.1.tar.gz 和 CTP SDK) COPY pyctp-2.3.1.tar.gz . COPY CTP_SecurityProtocol_Linux_6.7.2_20230815.tar.gz . # 解压并整理目录结构 RUN tar -xzf pyctp-2.3.1.tar.gz && \ tar -xzf CTP_SecurityProtocol_Linux_6.7.2_20230815.tar.gz && \ mv CTP_SecurityProtocol_Linux_6.7.2_20230815 ctp && \ mv ctp pyctp-2.3.1/ && \ cd pyctp-2.3.1 && \ python3 setup.py build_ext --inplace # 安装运行时依赖 RUN pip3 install --no-cache-dir websockets==11.0.3 # 复制服务代码 COPY main.py . # 暴露 WebSocket 端口 EXPOSE 8765 # 启动命令 CMD ["python3", "main.py"]

构建命令:

docker build -t pyctp-service .

6.2 main.py:一个极简但健壮的 WebSocket 服务(支持行情广播与下单)

# main.py import asyncio import websockets import json import threading from queue import Queue from pyctp import TraderApi, CThostFtdcInputOrderField # 全局 PyCTP 实例(单例) api = None market_queue = Queue(maxsize=10000) class PyCTPHandler: def __init__(self): self.flow_path = "/tmp/pyctp_flow" import os os.makedirs(self.flow_path, exist_ok=True) def init_api(self, front_addr, broker_id, investor_id, password): global api api = TraderApi.CreateTraderApi(self.flow_path) api.RegisterSpi(self) api.RegisterFront(front_addr) api.SubscribePrivateTopic(0) # QRY api.SubscribePublicTopic(0) # MARKET api.Init() # 登录 req = { 'BrokerID': broker_id.encode(), 'UserID': investor_id.encode(), 'Password': password.encode(), } api.ReqUserLogin(req, 1) def OnFrontConnected(self): print("✅ 已连接前置机") def OnRspUserLogin(self, pRspUserLogin, pRspInfo, nRequestID, bIsLast): if pRspInfo.ErrorID == 0: print("✅ 登录成功") else: print(f"❌ 登录失败:{pRspInfo.ErrorMsg.decode()}") def OnRtnDepthMarketData(self, pDepthMarketData): # 转为 dict 广播 tick = { 'instrument': pDepthMarketData.InstrumentID.decode().strip('\x00'), 'last_price': pDepthMarketData.LastPrice, 'volume': pDepthMarketData.Volume, 'time': f"{pDepthMarketData.UpdateTime.decode().strip('\x00')}.{pDepthMarketData.UpdateMillisec:03d}", } market_queue.put_nowait(tick) # 全局 handler handler = PyCTPHandler() # WebSocket 广播任务 async def broadcast_market(websocket): while True: try: tick = market_queue.get(timeout=1) await websocket.send(json.dumps(tick)) except: continue # WebSocket 控制任务(接收下单指令) async def handle_order(websocket, path): async for message in websocket: try: order_req = json.loads(message) if api and order_req.get('action') == 'insert': input_order = CThostFtdcInputOrderField() input_order.BrokerID = order_req['broker_id'].encode() input_order.InvestorID = order_req['investor_id'].encode() input_order.InstrumentID = order_req['instrument'].encode() input_order.OrderRef = f"{order_req.get('order_ref', '0000000001')}".encode() input_order.OrderPriceType = b'2' # 限价 input_order.Direction = b'0' if order_req['side'] == 'buy' else b'1' input_order.CombOffsetFlag = b'0' # 开仓 input_order.CombHedgeFlag = b'1' # 投机 input_order.LimitPrice = order_req['price'] input_order.VolumeTotalOriginal = order_req['volume'] input_order.TimeCondition = b'3' # GTC input_order.VolumeCondition = b'2' # Any input_order.MinVolume = 1 input_order.ContingentCondition = b'1' input_order.ForceCloseReason = b'0' input_order.IsAutoSuspend = 0 input_order.UserForceClose = 0 api.ReqOrderInsert(input_order, 1) await websocket.send(json.dumps({'status': 'sent'})) except Exception as e: await websocket.send(json.dumps({'error': str(e)})) # 启动服务 async def main(): # 初始化 PyCTP(这里填你的实盘参数) handler.init_api( front_addr=b"tcp://180.168.200.10:41213", broker_id="9999", investor_id="12345678", password="123456" ) # 启动 WebSocket 服务 async with websockets.serve(handle_order, "0.0.0.0", 8765): print("🚀 PyCTP WebSocket 服务已启动:ws://localhost:8765") await asyncio.Future() # run forever if __name__ == "__main__": asyncio.run(main())

6.3 验证与调试:三步确认服务可用

  1. 启动容器:

    docker run -p 8765:8765 --rm pyctp-service

    日志应输出✅ 已连接前置机→✅ 登录成功→🚀 PyCTP WebSocket 服务已启动。

  2. 用浏览器测试行情接收(打开 Chrome 控制台):

    const ws = new WebSocket('ws://localhost:8765'); ws.onmessage = (e) => console.log(JSON.parse(e.data));

    应持续收到 `{instrument: "rb2410", last_price: 35

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询