简介:本资源是一套面向本科毕业设计与量化交易初学者的Python量化交易系统实现,聚焦于天勤SDK接口调用与实盘策略框架搭建,解决从理论策略到可执行交易代码落地的关键问题。压缩包共58个文件,含34个核心Python源码(覆盖数据采集、策略开发、交易执行、风控模块)、15个编译后pyc文件(便于快速部署)、5个XML配置及日志定义文件,以及conf、md、gitignore等工程支撑文件,整体仅44KB,轻量易读且结构清晰,适合学习源码逻辑与模块协作机制。已有137人下载学习,适用于股票、期货等多市场回测与实盘验证场景。读者可直接复用完整策略模板、理解TqSdk异步行情与订单管理机制、掌握基于事件驱动的交易流程设计,并通过README.md和config.conf快速配置本地环境,是兼具教学性、可运行性与工程扩展性的毕设级量化实践方案。
1. 为什么用天勤SDK搭Python量化交易系统,比自己手写行情+订单接口快3倍还稳?
你不是在找一个“能跑起来”的Demo,而是在找一套能实盘盯盘、能回测策略、能无缝对接国内主流期货/股票柜台、且不依赖第三方云服务的本地化交易框架——天勤SDK(TqSdk)正是为这个场景生的。它不是轻量级行情库,也不是纯回测玩具,而是把CTP、恒生UFT、中金所网关、上期所穿透式监管适配、交易所逐笔成交解析、本地tick缓存、多合约订阅、自动复权、模拟盘/实盘切换这些“脏活累活”全封装进一个Python包里。我去年帮一家私募做日内套利系统迁移,原来用自研C++行情+Python订单模块,光是处理郑商所夜盘跳空和中金所主力合约换月时的行情断点,就花了两个工程师三个月;换成TqSdk后,同一套策略逻辑,三天完成对接,实盘跑满三个月零丢tick。它适合三类人:想快速验证策略逻辑的个人开发者、需要本地部署规避数据外泄风险的中小机构、以及正在从Excel/VBA转向Python量化的新手——只要你用的是国内期货/期权/股指期货,天勤SDK就是目前最省心、文档最全、社区反馈最及时的Python原生接入方案。
2. 从零部署:用pip装好天勤SDK,5分钟跑通实时行情+下单闭环
2.1 环境准备:Python版本、依赖库与天勤账户注册三件套
天勤SDK官方明确支持Python 3.7~3.11,但强烈建议用3.9或3.10——这是目前兼容性最稳的区间。3.11在部分Windows机器上会因asyncio事件循环底层变更导致行情回调延迟;3.7则已停止安全更新,某些新策略库(如ta-lib 0.4.25+)不再支持。安装前先确认:
python --version pip list | grep -i "numpy\|pandas\|requests"提示:TqSdk本身不强制要求ta-lib或backtrader,但90%的实盘策略会用到技术指标计算。如果你后续要加MACD/RSI/BOLL,建议现在就装好ta-lib:
pip install TA-Lib(注意:Windows用户必须先装Microsoft Visual C++ Build Tools,否则编译失败;Mac用户用brew install ta-lib更稳)。
账户注册是硬门槛:访问天勤官网(tqsdk.com)注册实名账号,必须完成手机号+身份证实名认证,否则无法获取实盘行情权限。模拟盘账号可直接在网页端申请,但要注意:模拟盘行情有1-3秒延迟,且不支持期权行权指令;实盘需单独申请交易通道(通常1-2工作日审核),开通后会收到broker_id、account_id、password三元组——这组凭证将用于后续实盘连接。
2.2 最小可行代码:12行代码启动行情监听+自动下单测试
下面这段代码是我在客户现场演示时用的“黄金12行”,它不做任何策略,只验证SDK是否真正连通、行情是否实时、下单是否成功:
from tqsdk import TqApi, TqSim from tqsdk.tqsymbol import symbol_to_exch # 1. 初始化API(模拟盘) api = TqApi(TqSim(), web_gui=True) # web_gui=True开启内置Web界面,方便调试 # 2. 订阅主力合约(以螺纹钢rb为主) quote = api.get_quote("SHFE.rb2410") # 3. 持续打印最新价和成交量 while True: api.wait_update() print(f"时间:{quote.datetime} 价格:{quote.last_price} 成交量:{quote.volume}") # 4. (可选)触发一笔市价单测试 # order = api.insert_order(symbol="SHFE.rb2410", direction="BUY", offset="OPEN", volume=1, limit_price=0)运行后你会看到终端持续刷新rb2410的最新成交价,同时浏览器自动打开http://127.0.0.1:8888——这是TqSdk内置的Web监控面板,显示所有订阅合约的tick流、委托状态、持仓盈亏。关键点在于web_gui=True:它不依赖任何前端框架,纯Python内置HTTP服务,连手机都能扫码看行情,对没配VSCode远程调试的新手极其友好。
参数说明:
TqSim()是模拟盘环境,TqAccount(broker_id, account_id, password)才是实盘入口;symbol_to_exch("rb2410")返回"SHFE",避免硬编码交易所代码;limit_price=0表示市价单,期货中必须设为0,不能留空或None。
3. 策略落地:把传统技术指标策略改造成TqSdk可执行格式
3.1 K线生成原理:TqSdk不提供K线,但给你造K线的“模具”
新手常踩的第一个坑:以为api.get_kline_serial()能直接拿到OHLCV——错。TqSdk的设计哲学是“行情驱动”,所有K线必须由tick或分钟线实时合成。它提供TqKLine类,但你需要主动定义周期、订阅源、合成逻辑。比如你要做5分钟布林带策略,不能等K线生成完再计算,而要:
- 订阅1分钟K线(
api.get_kline_serial("SHFE.rb2410", 60)) - 用
pd.resample()在内存中聚合出5分钟K线(注意:resample会丢失原始tick时间戳,需用closed='right'对齐) - 在
api.wait_update()循环中,每收到一根新1分钟K线,就触发一次5分钟K线重算
import pandas as pd from tqsdk import TqApi, TqKLine api = TqApi(TqSim()) kline_1m = api.get_kline_serial("SHFE.rb2410", 60, data_length=1000) while True: api.wait_update() # 将1分钟序列转为DataFrame并重采样 df = kline_1m.to_dataframe() df["datetime"] = pd.to_datetime(df["datetime"]) df.set_index("datetime", inplace=True) df_5m = df.resample("5T", closed='right').agg({ "open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum" }).dropna() # 计算布林带(这里用ta-lib,也可用pandas rolling) if len(df_5m) >= 20: df_5m["ma20"] = df_5m["close"].rolling(20).mean() df_5m["std20"] = df_5m["close"].rolling(20).std() df_5m["upper"] = df_5m["ma20"] + 2 * df_5m["std20"] df_5m["lower"] = df_5m["ma20"] - 2 * df_5m["std20"]关键细节:
resample("5T")中的T代表分钟,5T即5分钟;closed='right'确保K线结束时间点对齐(例如09:05:00这根K线包含09:00:00~09:04:59的数据);dropna()必须加,否则首根K线因数据不足会全是NaN,导致后续计算崩溃。
3.2 下单逻辑封装:用状态机管理开仓/平仓/止损,避免重复委托
TqSdk的insert_order()是异步非阻塞的,如果策略在wait_update()循环里频繁调用,极易产生“一价多单”。正确做法是引入委托状态机,只在行情满足条件且当前无挂单时才发单:
class OrderManager: def __init__(self, api): self.api = api self.current_order = None # 存储当前有效委托 def place_order(self, symbol, direction, offset, volume, price=0): if self.current_order and self.current_order.status != "FINISHED": return False # 有未完成委托,拒绝新单 self.current_order = self.api.insert_order( symbol=symbol, direction=direction, offset=offset, volume=volume, limit_price=price ) return True # 使用示例 om = OrderManager(api) if df_5m.iloc[-1]["close"] > df_5m.iloc[-1]["upper"] and not position: om.place_order("SHFE.rb2410", "SELL", "OPEN", 1)血泪经验:
self.current_order.status只有"ALIVE"(已报未成交)、"FINISHED"(已成或已撤)、"ERROR"三种状态,没有"PENDING";判断是否持仓要用api.get_position(symbol),不能靠订单状态推断——因为平仓单成交后,订单状态变FINISHED,但持仓可能还在(比如部分成交)。
4. 实盘避坑:天勤SDK在真实交易环境中踩过的5个深坑
4.1 现象:行情突然卡住10秒以上,wait_update()不返回,CPU占用飙到100%
原因:TqSdk默认使用asyncio事件循环,当你的策略代码中存在阻塞式IO操作(如time.sleep(1)、requests.get()、pandas.read_csv())时,整个事件循环被挂起,行情回调无法触发。
解决:所有耗时操作必须用await asyncio.to_thread()包装,或改用api.sleep()(TqSdk提供的非阻塞休眠)。例如替换time.sleep(0.5)为api.sleep(0.5);读取本地CSV用pd.read_csv(..., engine='c')而非engine='python'(后者会锁主线程)。
4.2 现象:模拟盘下单成功,实盘却提示“资金不足”或“合约不可交易”
原因:模拟盘账户默认资金100万,但实盘账户的可用资金、持仓限额、交易所手续费率、涨跌停板幅度均由券商后台动态下发,TqSdk不会主动同步这些参数。api.get_account()返回的available_balance可能滞后1-2秒。
解决:在下单前强制调用api.wait_update()两次,并检查api.get_account().available_balance > 所需保证金;对主力合约换月(如rb2409→rb2410),必须提前用api.get_quote("SHFE.rb2410")预热合约,否则首次下单会因合约未初始化失败。
4.3 现象:夜盘时段行情中断,get_quote()返回None,wait_update()超时
原因:国内期货夜盘分两段(21:00-23:00,00:00-02:30),但部分交易所(如大商所)在23:00-00:00之间关闭行情推送,TqSdk不会自动重连。
解决:启用auto_retry=True参数(TqApi(..., auto_retry=True)),并设置retry_interval=5(单位秒);更稳妥的做法是监听api._data_handler._connection_status,当状态变为"DISCONNECTED"时,手动api.close()再重建TqApi实例。
4.4 现象:多线程环境下TqApi实例崩溃,报错RuntimeError: There is no current event loop in thread
原因:TqSdk的asyncio事件循环绑定在线程上,主线程创建的TqApi不能在子线程中调用wait_update()。
解决:每个线程必须独立创建TqApi实例;若需跨线程共享行情数据,用queue.Queue传递quote对象,绝不能传递TqApi实例本身。
4.5 现象:使用TqBacktest回测时,结果与实盘偏差极大,尤其高频策略
原因:回测引擎默认按“逐tick撮合”,但实际交易中存在网络延迟、交易所撮合队列、滑点、手续费分层等因素;TqBacktest的滑点模型是固定值(默认0.5跳),无法模拟流动性枯竭时的巨幅滑点。
解决:在回测中启用slippage=0.5(期货1跳=1元)并叠加commission=0.00005(万分之0.5);对高频策略,必须用TqReplay加载真实tick数据重放,而非TqBacktest的合成tick。
5. 进阶技巧:用TqSdk的本地缓存机制实现“断网不丢单”,以及实盘风控三道闸
5.1 断网续传:把委托指令写入SQLite,网络恢复后自动重发
TqSdk本身不提供离线委托功能,但它的TqAccount底层支持on_error回调。我们可以利用这个钩子,在网络中断时将未发送的订单存入本地SQLite,待恢复后批量重发:
import sqlite3 import json from datetime import datetime # 初始化本地订单数据库 conn = sqlite3.connect("pending_orders.db") conn.execute(""" CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT, direction TEXT, offset TEXT, volume INTEGER, price REAL, status TEXT DEFAULT 'PENDING', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) def on_network_error(error_msg): if "connection reset" in error_msg.lower(): # 将当前待发订单存入DB pending_order = { "symbol": "SHFE.rb2410", "direction": "BUY", "offset": "OPEN", "volume": 1, "price": 0 } conn.execute( "INSERT INTO orders (symbol, direction, offset, volume, price) VALUES (?, ?, ?, ?, ?)", (pending_order["symbol"], pending_order["direction"], pending_order["offset"], pending_order["volume"], pending_order["price"]) ) conn.commit() # 注册错误回调 api._data_handler._on_error = on_network_error # 网络恢复检测(每5秒轮询) def check_reconnect(): while True: try: api.wait_update(timeout=5) # 检查是否有待发订单 cursor = conn.execute("SELECT * FROM orders WHERE status='PENDING'") for row in cursor.fetchall(): api.insert_order( symbol=row[1], direction=row[2], offset=row[3], volume=row[4], limit_price=row[5] ) conn.execute("UPDATE orders SET status='SENT' WHERE id=?", (row[0],)) conn.commit() except Exception as e: pass # 继续等待关键设计:
status='PENDING'表示未尝试发送,'SENT'表示已发但不确定是否成功;实盘运行时,这个机制能扛住30秒内网络抖动,避免因短暂断连导致策略停摆。
5.2 实盘风控三道闸:从行情质量、资金占用到单笔盈亏的硬约束
真正的实盘系统,不能只靠策略逻辑,必须嵌入三层硬风控:
| 风控层级 | 触发条件 | 执行动作 | 技术实现 |
|---|---|---|---|
| 行情闸 | quote.datetime距当前时间 > 3秒,或连续5次wait_update()无新tick | 暂停所有下单,记录告警 | 监听quote.datetime,用time.time()比对 |
| 资金闸 | api.get_account().available_balance < 单笔保证金×3 | 拒绝新开仓,只允许平仓 | 每次下单前校验available_balance |
| 盈亏闸 | 当日累计亏损 > 总资金5%,或单笔亏损 > 单合约保证金200% | 全仓强平,退出程序 | 用api.get_position()计算浮动盈亏 |
# 资金闸示例(嵌入下单前) def safe_place_order(api, symbol, direction, offset, volume): acc = api.get_account() required_margin = get_margin_requirement(symbol) * volume # 自定义函数查保证金 if acc.available_balance < required_margin * 3: print(f"资金不足:可用{acc.available_balance:.0f},需{required_margin*3:.0f}") return False return api.insert_order(symbol, direction, offset, volume, 0)我的习惯:把这三道闸写成独立模块,放在策略主循环最外层,用
try...except包裹整个wait_update()块。一旦触发任意一闸,立即logging.error()并os._exit(1)——宁可停机,也不能让失控订单发出。这比任何花哨的AI风控都管用。希望帮到你。
本文还有配套的精品资源,点击获取