简介:一套面向量化交易与程序化开发者的期货交易系统代码包,整合Python与深度学习技术,覆盖行情接入、策略建模、回测与实盘执行等环节,适合希望在期货市场应用神经网络模型的金融编程人员参考。压缩包共729个文件,约19.56MB,主体为401个Python脚本,配合52个头文件、36个C++源文件、18个动态库及多个配置文件,构成跨语言交易框架;另有38份CSV样本数据、25个JSON参数文件与14个pkl模型文件,便于直接观察数据流与模型持久化结构。目前已有240人学习。从内容预览可见,包内含CTP接口封装相关源码及多个配置文件,可帮助理解交易柜台连接、合约查询与私有/公开流处理机制。读者既可直接运行现有策略基线,也可基于目录结构替换模型、调整参数,完成从数据到信号的深度学习链路改造。
1. 从CTP接口开始:很多人把深度学习期货交易的第一步搞反了
我拿到这套“python结合深度学习的期货程序化交易系统”时,第一反应是翻模型代码,结果发现整套系统里最有价值的反而是底层那些不起眼的文件:vnctptd.cpp、make.bat、ContractData.vt.bak。这套系统的定位很明确——把CTP柜台行情和交易能力封装成Python可调用模块,再让深度学习模型拿历史行情训练出预测信号,最后通过程序化订单落到期货公司柜台。适合谁用?已经会用pandas和PyTorch、但还没接通过期货柜台的开发者,以及想抄一套完整数据管线的量化从业者。深度学习模型随手能写,真正卡人的是把预测变成一笔能成交的委托,所以第一步从CTP接口开始。
2. 底层接口与数据结构:vnctptd.cpp、make.bat和ContractData.vt.bak到底在干什么
2.1 先分清这份资源里的关键文件,CTP接口不是黑匣子
完整看过这份资源的文件列表后,你会发现最显眼的不是策略文件,而是 vnctptd.cpp、make.bat、ContractData.vt.bak、StockMonitor.exe.config 这样一批“底层文件”。它们各自承担一个明确任务。
| 文件 | 作用 | 常规操作 |
|---|---|---|
| vnctptd.cpp | CTP交易接口的C++封装源码 | 编译成 pyd,不手工改源码 |
| make.bat | 调用Visual Studio编译vnctptd | 在VS命令提示符下执行 |
| ContractData.vt.bak | MySQL数据库备份,含合约与历史行情 | 用mysql命令恢复 |
| DialogRsp.con / QueryRsp.con 等 | CTP消息结构定义,编译期引用 | 一般不修改 |
| StockMonitor.exe.config | 监控程序配置文件 | 按实际IP与端口调整 |
| TradingDay.con | 交易日相关结构 | 随编译环境使用 |
vnctptd.cpp 解决的核心问题是:CTP柜台提供的是C++动态库,Python没办法直接调用,vn.py早期版本就是用C++写一层封装,编译成 pyd 给 Python import。很多新手看到C++源码就慌,实际上这套系统的设计就是把编译产物当成黑匣子,你只需要保证编译一次成功,后续所有工作都在Python层完成。
这里还要提一个容易混淆的点:CTP本身是一套协议,不是数据库接口。它能给到你的原始数据只有Tick和成交回报,K线合成、合约管理、历史数据存储全都要自己在应用层做。所以这份资源里ContractData.vt.bak才是深度学习能跑起来的前提——没有合约映射和历史K线,模型连训练样本都凑不齐。
2.2 恢复ContractData数据库,让合约数据先落地
把 .vt.bak 恢复成 MySQL 库是第一件实事。常见做法是用 mysql 命令行直接导入,vt 是 vn.py 默认的数据库名:
mysql -uroot -p < ContractData.vt.bak逻辑说明:这条命令会在 MySQL 里恢复出一个名为 vt 的库。恢复后重点看三张表:contract(合约代码与交易所对应关系)、dbbard(1分钟Bar历史数据)、dbtick(Tick记录)。contract 表决定了你能订阅什么合约代码,dbbard 决定了深度学习训练样本能回溯多久,dbtick 决定实盘落库的写入压力。
恢复后不要急着跑业务,先做一次数据质量检查,我一般会执行这条SQL:
use vt; select symbol, count(*) as cnt from dbbard group by symbol order by cnt desc limit 20;说明:这条SQL能看到每张合约的K线数量。数量最少的合约训练出来的模型很容易过拟合,因为样本量不足。我一般要求单合约至少5万根以上1分钟Bar才敢拿来训练,低于这个量级,深度学习模型表现大概率不如简单均线。字段方面注意:vn.py 1.x的数据库表名固定为小写,字段包括 symbol、exchange、datetime、openPrice、highPrice、lowPrice、closePrice、volume、openInterest,取数时字段名不要写错,否则后面所有查询都要返工。
恢复完成后建议重启一次MySQL服务,再用命令行或客户端确认表数量对得上。提示:数据库恢复失败后面全断,这个环节值得多花十分钟验证。
3. 把底层编译并跑通:从make.bat到第一条行情落库
3.1 编译环境准备与make.bat执行细节
编译 vnctptd 需要三样东西:Visual Studio(2015或2017都可以)、CTP 官方交易库(含 thosttraderapi_se.dll 和 thostmduserapi_se.dll)、以及对应Python版本的 include 和 libs 目录。make.bat 的执行逻辑一般长这样:
@echo off set PYTHON_INC=C:\Python36\include set PYTHON_LIB=C:\Python36\libs\python36.lib set CTP_LIB=..\lib cl.exe /c vnctptd.cpp /I %PYTHON_INC% /I ..\include link.exe /DLL vnctptd.obj %PYTHON_LIB% %CTP_LIB%\thosttraderapi_se.lib %CTP_LIB%\thostmduserapi_se.lib /OUT:.\vnctptd.pyd参数说明:/c 表示只编译不链接,/I 指定Python头文件和CTP头文件路径,/OUT 指定生成 pyd 的文件名。生成的 vnctptd.pyd 和 vnctptd.dll 要放到Python项目能搜到的目录下。链接时如果报找不到 thosttraderapi_se.lib,说明CTP lib路径不对,或者官方接口库还没下载完整。
编译前一定确认三件事:Python版本位数、Visual Studio工具集版本、CTP接口库版本。32位Python配64位CTP库,编译时直接报结构体对齐错误,这种问题玄学得很,重装环境比定位更快。
编译成功后做一次导入测试:
python -c "import vnctptd; print(vnctptd.__file__)"能正常输出路径,说明交易接口这一层通了。注意Python版本必须和编译时一致,这是编译型接口最容易翻车的地方。我见过有人拿Python 3.8编译,跑系统时用Python 3.6,一import就内存报错,查了半天才发现环境不对。
3.2 连接配置、行情订阅与数据落库的一段可抄代码
底层编译通过后,下一步是写连接脚本。CTP连接需要四个参数:服务器地址、BrokerID、用户名、密码。用SimNow模拟盘举例,完整的连接脚本长这样:
# connect_ctp.py from vnpy.trader.vtEngine import MainEngine from vnpy.trader.gateway.ctpGateway import CtpGateway setting = { "userID": "your_account", "password": "your_password", "brokerID": "9999", "tdAddress": "tcp://180.168.146.187:10130", # 交易前置 "mdAddress": "tcp://180.168.146.187:10131", # 行情前置 "productInfo": "", "appID": "", "authCode": "" } mainEngine = MainEngine() mainEngine.addGateway(CtpGateway) mainEngine.connect(setting)逻辑说明:MainEngine是vn.py框架的核心调度器,addGateway把CTP接口注册进去,connect传入的setting会被封装成CTP登录请求。tdAddress 和 mdAddress 一个负责交易、一个负责行情,很多新手只填一个导致登录成功却没行情,就是这里少配了行情前置。
连接成功后订阅合约:
mainEngine.subscribe("rb9999", "SHFE")参数说明:rb9999是螺纹钢连续合约代码,SHFE表示上期所。连续合约会自动切换到当下活跃的主力合约,对训练和实盘都方便。订阅成功后,行情会以Tick形式进入回调函数,写一个简单的落库回调:
def onTick(tick): sql = ("insert into dbtick(symbol, exchange, datetime, lastPrice, volume, openInterest) " "values('%s','%s','%s',%f,%d,%d)" % ( tick.symbol, tick.exchange, tick.datetime, tick.lastPrice, tick.volume, tick.openInterest)) cursor.execute(sql)这段代码看着简单,实际跑起来有性能问题:每笔Tick都insert,一天几十万条,MySQL容易成为瓶颈。我一般会先攒100条再批量insert,或者直接把写入队列丢到另一个线程,行情主线程只负责塞数据,不碰数据库连接。批量写法的核心是把多条sql拼成一个多值insert,一次execute提交,写入速度能快一个数量级。
行情落库之后,还需要确认“收到数据-写入数据”链路完整,方法很简单:订阅后等一分钟,然后查询dbtick表里的记录数,如果数量在增长说明链路通了。这一步跑通,深度学习模型要用的数据原料才算真正到手。
4. 让深度学习进场:用PyTorch把历史行情变成期货交易信号
4.1 特征构造与样本组织:把K线转成监督学习
模型能不能赚钱,一半取决于特征构造,而不是网络结构。期货数据和MNIST这类玩具数据不一样,它没有放之四海而皆准的规律,需要先把原始行情转化成“样本”。
我从dbbard取历史数据后,一般构造三组特征:价格类(对数收益率、收盘价相对N期均值偏离)、波动类(ATR、已实现波动率)、量仓类(成交量、持仓量变化)。取数代码:
import pandas as pd import numpy as np df = pd.read_sql( "select * from dbbard where symbol='rb9999' and interval='1min' order by datetime", con=engine ) df = df.set_index("datetime") df["ret"] = df["closePrice"].pct_change().replace([np.inf, -np.inf], 0) df["volatility"] = df["ret"].rolling(20).std() df["vol_ratio"] = df["volume"] / (df["volume"].rolling(20).mean() + 1e-8) feat_cols = ["ret", "volatility", "vol_ratio"] data = df.loc[:, feat_cols].fillna(method="ffill").fillna(0)把这段代码拆开说:pct_change 计算逐根K线的百分比变化,rolling(20)是滚动窗口长度为20根K线,加1e-8防止除零。标准化的常见做法是只在训练集上计算均值和标准差,再作用到测试集,绝对不用全量数据算均值,否则会把未来信息泄露给模型,回测曲线好看,实盘立刻翻车。
样本组织采用“用过去N根预测未来M根”的方式:
N, M = 60, 5 X, y = [], [] for i in range(N, len(data) - M): X.append(data.iloc[i - N:i].values) y.append(1 if df["closePrice"].iloc[i + M] > df["closePrice"].iloc[i] else 0) X = np.array(X, dtype=np.float32) y = np.array(y, dtype=np.int64)说明:N=60表示看过去一小时(60根1分钟K线),M=5表示预测未来5分钟涨幅方向,y=1代表未来5分钟收盘价高于当前。这个标签定义简单且可复现,但没处理手续费和滑点,实盘信号消耗要额外加阈值,这一点后面会细说。
4.2 LSTM模型与L2正则化:PyTorch里的一段训练代码
模型这块我不追求结构新奇,LSTM足够处理60步序列。重点有两件事:一是加Dropout防止时间序列过拟合,二是用L2正则化控制权重幅度。后者的作用经常被忽视,吴恩达深度学习课后题里反复讲的L2正则化,在时序模型上同样成立,具体到PyTorch就是一行参数配置。
import torch import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size=64, num_layers=2, output_size=2): super().__init__() self.lstm = nn.LSTM( input_size, hidden_size, num_layers, batch_first=True, dropout=0.3 ) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.lstm(x) logits = self.fc(out[:, -1, :]) return logits model = LSTMModel(input_size=len(feat_cols), hidden_size=64, num_layers=2) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)参数说明:dropout=0.3会在LSTM两层之间随机丢弃30%神经元,训练收敛更慢但不容易过拟合;weight_decay=1e-4是L2正则化系数,相当于对权重做惩罚,防止某个特征被过度放大。学习率用1e-3配Adam,如果验证集loss震荡,就往1e-4降。训练循环这里不展开,但数据加载器的batch_size不要太大,时间序列样本之间相关性极强,batch_size=128时采样均匀性明显好于512。
训练完成后保存权重:
torch.save(model.state_dict(), "lstm_rb.pth")以后每天收盘后可以增量训练一次,用最近一个月数据微调,但注意每次训练前把随机种子固定,否则同一份数据两次训练出的模型可能差异很大,那说明模型本身不稳定,不适合实盘。
4.3 从预测概率到交易信号:别让模型直接下单
模型输出的是两路logits,经softmax变成概率。如果概率0.55以上就开多,0.45以下开空,信号会非常频繁,手续费就能把利润打光。我的经验是加三层滤网:阈值带、连续一致性、最小持仓时间。
def signal_from_probs(probs, up=0.60, down=0.40): if probs > up: return 1 if probs < down: return -1 return 0 def filtered_signal(probs_list, lookback=3): last = probs_list[-lookback:] if len(set(last)) == 1 and last[0] > 0.60: return 1 if len(set(last)) == 1 and last[0] < 0.40: return -1 return 0逻辑说明:probs是softmax输出的上涨概率,up和down构成死区避免抖动;lookback=3要求连续三根K线预测方向一致才触发信号。程序化交易里,信号质量比信号数量重要得多,宁可一周只开两次仓,好过天天在震荡里亏损。模型预测概率长期在0.5附近徘徊是正常状态,说明当前行情没有明显方向性,这时最好的操作就是空仓等待,而不是强行开仓。
这套“概率-阈值-持仓时间”的三层结构同样适用于其他品种,只需把 up/down 阈值和 lookback 参数按品种波动率调整,波动大的品种阈值往上提,波动小的品种阈值往下放。
5. 实盘与模拟盘的排查清单:我踩过的五个坑
5.1 编译与连接阶段:三个高频坑
第一次跑通这套系统,我至少踩了五个坑,按阶段排下来,编译与连接占三个。
第一条:编译成功后 import vnctptd 报“DLL load failed”或直接内存访问错误。现象是Python进程闪退,甚至没有任何报错信息。原因是Python位数和编译时不一致,或者缺少CTP官方依赖dll。解决:确认Python是32位还是64位,重新用对应环境编译一次,同时把 thosttraderapi_se.dll 和 thostmduserapi_se.dll 复制到 pyd 同目录。
第二条:连SimNow时提示“登录失败”或连接超时。现象是用户名密码都正确,但就是连不上,反复重试结果一样。原因多半是SimNow账号密码需要在小程序里重置,或前置地址已经更换。解决:去SimNow官网确认最新前置地址,tdAddress和mdAddress都填上,不要只填交易前置。登录失败时建议先看一眼日志里返回的CTP错误码,两位数错误码比看网络状态靠谱得多。
第三条:订阅 rb9999 时日志报“不支持的合约代码”,换 rb2610 又能收到行情。现象是特定合约代码订阅失败,其他代码正常。原因是contract表里的合约列表滞后,数据库里实际不存在这个连续合约代码。解决:先查数据库,以contract表内容为准,而不是凭经验敲代码。
5.2 运行与策略阶段:两个容易翻车的地方
第四条:白天实盘跑空测时,行情偶尔出现“断层”,K线缺了十几分钟。现象是监控页面上数据断线后又续上,深度学习特征计算出现NaN。原因是CTP行情前置流控,订阅合约数量过多时被限流。解决:把不需要的订阅全部取消,设置断线重连和补数据逻辑,缺的数据用前一根Bar填充,别就地计算。
第五条:模型预测概率长期徘徊在0.5附近,信号一个月开不了几次仓。现象是模型训练曲线很漂亮,一到实盘就是停工状态。原因是训练标签用的是未来5分钟收盘价方向,未来5分钟的噪声太大,模型学到的是噪声而不是规律。解决:把预测周期拉长到15或30分钟,或者把标签改成“未来M根K线中最高价创新高”这类带路径信息的标签,真实交易中更有区分度。
这五条坑的完整排查路径我一般整理成一个checklist:编译环境一致性、前置地址有效性、合约表数据同步、订阅数量上限、标签定义合理性。每次部署新环境,按这个checklist走一遍,能省掉大部分调试时间。
6. 部署细节与次日检查:一台机器上把这套系统跑稳
6.1 模拟盘与实盘柜台的差异核对
SimNow和真实柜台接口一致,但有几个差异要提前确认。模拟盘没有真实手续费和滑点,信号过滤参数要留余量,我一般把模拟盘回测的收益预期打八折再看是否值得上实盘。实盘的行情前置通常和交易前置不在同一网段,实盘账号需要在期货公司官网的开放接口文档中查地址,不能照搬模拟盘的端口。实盘首次登录前,申请一下生产环境的权限,再按期货公司的白名单要求加IP,这些配置散落在各个文档里,最容易漏。
6.2 进程守护、日志与次日检查习惯
整套系统用一个Python进程跑MainEngine,我习惯用计划任务或supervisor做进程守护,崩溃后30秒自动拉起。日志输出到文件而不是控制台,每天做一次行情完整度检查:统计前一日应收到与实收Tick数量,偏差超过5%就要查为什么丢数据。检查SQL长这样:
select count(*) from dbtick where date(datetime) = '2025-01-15';说明:这条SQL统计某一天落库的Tick总数。交易时间按一天约4小时Tick频率计算,正常数量应在几十万级,如果明显偏少,优先检查断线重连配置和数据库写入队列是否堆积。次日开盘前,再核对一下持仓和委托状态,防止昨天遗留的未平仓单影响今天的手数计算。
从那以后,我每次部署新环境都强制走一遍同样流程:恢复ContractData → 编译vnctptd → SimNow跑通订阅 → 回测过拟合检验 → 仿真盘跑满一个交易日 → 再切实盘,检查清单随手记在配置文件夹里,每次换机器至少省半天。这套系统最值钱的部分,其实就是把底层这些不起眼的文件拼成了一条能落地的数据管线,模型反而是其中最好替换的一环。希望帮到你。
本文还有配套的精品资源,点击获取