
简介面向需要借助通达信DLL接口实现自定义选股策略的投资者与VC开发者这份资料以可编译运行的示例工程为核心将通达信官方插件编程规范中晦涩的说明逐行注释并修正了原有示例中MyPlugin.cpp的错误降低了入门门槛。压缩包共45个文件、4.74MB主要包含VC 6.0工程文件、插件源码、编译生成的dll与lib以及doc格式的编程规范和自动交易伴侣用户手册10张步骤截图直观展示配置与选股操作流程。目前已有6333人学习下载。读者既能对照说明文档快速定位关键代码也可直接打开工程验证DLL插件效果适合想理解通达信插件机制并在此基础上做二次开发的初学者。值得注意的是示例仅为说明编程方法距实盘应用尚有较大距离据此投资需自担风险。 我最早接触通达信的DLL选股插件是因为公式语言实在扛不住我的策略需求。平时用通达信公式写写均线、MACD这类标准指标很顺手可一旦涉及多周期共振、自定义统计量、甚至想把核心算法藏起来不被别人抄走公式编辑器里的那套语法就变得非常拧巴。后来花了一个周末把DLL插件这条路跑通才发现所谓“源码级选股”并没有想象中神秘——核心就是按通达信的接口约定导出一个函数然后用C/C把选股逻辑写进这个动态库里。这篇教程不打算讲虚的直接给你一份可以照着敲的通达信DLL选股插件源码再配合注册、调用、调试的完整流程。适合已经会写基本通达信公式、又想往底层多走一步的朋友。1. 公式语言扛不住的需求DLL插件为什么能接住1.1 公式语言的四道坎通达信公式语言的上手门槛确实低输入MA(CLOSE,5)就能得到一条均线但低门槛也意味着低天花板。我长期用下来总结出四个绕不开的痛点。第一是计算性能。公式语言的循环能力很弱嵌套循环写不了几层遇到需要逐根K线回看几十根甚至百根做统计计算的场景公式会非常吃力选股时能明显感觉到卡顿。第二是数据结构匮乏。公式里的变量本质都是一条序列没有结构体、没有自定义函数封装、没有文件I/O。策略一旦需要“先算一个因子A再用另一个因子B过滤A最后还要把结果跟历史分布做对比”公式代码会膨胀到几乎没法维护。第三是外部数据访问困难。公式语言基本不提供“读本地文件”的能力你拿不到自建的股票池、行业权重、财报因子这类数据。而DLL里你爱怎么读文件就怎么读。第四是源码保护。公式源码在通达信里要么是明文要么是弱加密别人拿到你的指标文件就能把核心策略抄走。核心逻辑一旦编译进DLL别人拿到的只是一个二进制文件想要逆向的成本就完全不一样了。1.2 DLL插件在选股链路里的真实定位很多人以为DLL插件是来替代通达信的这是个误解。通达信负责行情展示、选股调度、结果列表DLL只负责一件事把复杂的信号计算做成一个“函数”给公式系统调用。打个比方通达信公式系统像一个按规则运作的流水线普通公式是流水线上的标准模块而DLL插件是你自己单独建的一个黑盒加工车间。行情数据从流水线送进黑盒黑盒算完后返回一条结果序列流水线再根据这条结果判定是否选入股票。选股逻辑和界面完全解耦这就是DLL方案最舒服的地方。对比一下普通公式和DLL插件的差异对比项普通公式DLL插件开发语言通达信公式语法C/C循环与算法能力弱嵌套受限完整无限制外部文件读取不支持支持核心源码保护弱编译进二进制调试体验弱只能靠裸数据查看打日志、用调试工具上手难度低稍高2. 通达信DLL接口约定一个函数签名背后的规则2.1 接口长什么样通达信官方并没有公开一份正式的DLL开发SDK文档但DLL公式接口的约定在开发者圈子里流传很广协议本身非常固定。所谓“插件”就是一个普通Windows DLL它必须导出一个指定签名的函数通达信才会认得它。统一约定如下extern C __declspec(dllexport) int WINAPI TdxFunc( int DataLen, float* pfOUT, float* pfINa, float* pfINb, float* pfINc);逐参数解释一下DataLen传入的K线序列长度也就是当前选股周期里有多少根K线。pfOUT输出缓冲区长度至少是DataLen你的计算结果往这里面写就行。pfINa、pfINb、pfINc三个输入序列。通达信在公式里调用DLL时传入的实参会依次对应到这里。比如选股公式里写TdxFunc(CLOSE, VOL, AMOUNT)那pfINa就是CLOSE序列pfINb是VOL序列pfINc是AMOUNT序列。返回值返回1表示计算成功返回0表示失败。失败时通达信会认为这次计算无效。调用约定是WINAPI在32位环境下就是__stdcall。这一点不能写错下面踩坑部分会重点讲。2.2 只有三个输入序列不够用怎么办这个接口的输入通道固定只有三个刚接触时我也觉得太受限。实际用下来发现三个序列足够覆盖绝大多数选股需求因为通达信公式语言本身可以帮你做“预加工”。比如你想同时看“量比”和“价格突破”完全可以在调用DLL之前先把原始行情加工成想要的序列再传进去。VOL / MA(VOL, 20)算出一个量能倍数序列CLOSE / MA(CLOSE, 20)算出一个价格位置序列这两个序列各自传进DLLDLL只需要专注做信号判定不用关心底层行情怎么算。如果确实需要策略参数比如均线周期、突破阈值这种东西那就不该每次改代码重新编译——直接在DLL里读取同目录下的配置文件就行后面我会专门讲这套参数化配置的做法。3. 手写一个均线多头排列选股DLL完整源码拆解3.1 开发环境准备开发环境方面我推荐直接用 Visual Studio 2022 社区版免费装C桌面开发组件即可。也可以使用MinGW-w64但VS对工程配置和调试更省心。工程创建步骤新建一个C空项目在配置管理器里把平台改成x86也就是Win32项目属性里把配置类型改成“动态库(.dll)”。这里有个硬性约束通达信主程序目前是32位所以它加载的DLL也必须是32位。直接编译成x64在选股时会提示DLL加载失败这是新手最常踩的坑。3.2 均线多头排列选股的完整源码下面是一份可以直接编译的源码逻辑是经典的均线多头排列MA5大于MA10、MA10大于MA20同时当日收盘价站上MA5。满足条件时输出1否则输出0。#include windows.h extern C __declspec(dllexport) int WINAPI MultiMA5_10_20( int DataLen, float* pfOUT, float* pfINa, float* pfINb, float* pfINc) { if (DataLen 0 || !pfOUT || !pfINa) return 0; const int SHORT 5; const int MID 10; const int LONG 20; double sumShort 0.0, sumMid 0.0, sumLong 0.0; for (int i 0; i DataLen; i) { sumShort pfINa[i]; sumMid pfINa[i]; sumLong pfINa[i]; if (i SHORT) sumShort - pfINa[i - SHORT]; if (i MID) sumMid - pfINa[i - MID]; if (i LONG) sumLong - pfINa[i - LONG]; if (i LONG - 1) { pfOUT[i] 0.0f; continue; } double maS sumShort / SHORT; double maM sumMid / MID; double maL sumLong / LONG; int ok (maS maM maM maL pfINa[i] maS) ? 1 : 0; pfOUT[i] (float)ok; } return 1; }3.3 为什么这么写几个关键设计点extern C的作用是避免C对函数名做名字修饰让导出函数名保持板正。WINAPI就是__stdcall的宏定义对应通达信要求的调用约定。这两个少了任何一个通达信都可能找不到这个函数。窗口均值用了滚动求和而不是每次重新累加。如果每次对20根K线求和两层循环复杂度是O(n*m)而滚动窗口是O(n)。当前这个示例数据量不大效率差距体感不明显但选股是全市场扫票函数会被调用成千上万次能省一点是一点。前19根K线输出0是因为均线还没满20根此时信号无意义。这种“预热期”的处理逻辑要在代码里显式写好否则会用不完整的均值算出错误的信号。输出用0和1的开关值而不是连续浮点数是为了让条件选股公式直接拿结果做判定。你当然也可以改成输出连续值比如多头排列的强度得分然后由选股公式设定一个阈值来过滤这是一个扩展方向。3.4 用.def文件固定导出名这一步很容易忽略但非常重要。32位环境下即使写了extern C__stdcall导出函数名仍然可能被修饰成类似_MultiMA5_10_2020的形态。为了防止通达信按原始名字查找时扑空最好在工程里新增一个 .def 文件内容如下LIBRARY MultiMA EXPORTS MultiMA5_10_20然后在项目属性 → 链接器 → 输入 → 模块定义文件里填上这个.def。编译后用Dependency Walker或者Visual Studio自带的dumpbin /exports查看导出表能看到函数名干干净净地暴露在那里。4. 把DLL装进通达信并跑通第一次选股4.1 DLL文件要放对位置把编译出来的MultiMA.dll复制到通达信安装目录下的T0002\dlls目录没有这个目录就自己新建。部分券商定制版可能放在安装根目录下的dlls目录或者主程序同目录不同版本略有差异。如果你用的是券商定制版建议先翻一下它的目录结构确认dll文件夹的位置。如果通达信提示DLL加载失败先检查文件是否被杀毒软件拦了或者DLL文件被另一个进程锁住。后者在反复编译调试时很常见——通达信一直开着旧DLL被加载到进程里编译时就会报文件占用错误。4.2 在公式管理器里注册DLL函数打开通达信进入“功能 → 公式系统 → 公式管理器”新建公式公式类型选择“DLL函数”。不同版本叫法可能不一样有的叫“扩展函数”有的直接叫“DLL”但本质都是同一个东西。关键填写项填写项值公式名称MULTIMA公式类型DLL函数DLL文件名MultiMA.dll调用函数名MultiMA5_10_20保存之后MULTIMA这个名字就相当于通达信内置函数了可以在任何公式里被调用。4.3 写条件选股公式并执行第一次选股注意DLL函数公式本身不是一个可以直接跑选股的公式它只是一个函数。真正执行选股需要再包一层条件选股公式。新建一个条件选股公式代码如下TMP : MULTIMA(CLOSE, HIGH, LOW); XG : TMP 1;第一行把三条价格序列传入DLLDLL返回的0/1序列存到TMP。第二行定义选股条件只有最新一根K线输出为1才选中。然后进“功能 → 选股器 → 条件选股”选这个公式执行选股。4.4 用日志验证DLL真的在干活第一次跑通后别急着高兴先用一个最笨也最靠谱的方法验证结果肉眼核对。选一只明显走出均线多头形态的股票把TMP : MULTIMA(CLOSE, HIGH, LOW);单独放进一个副图指标挂在K线图上看看信号位置是否合理。开发阶段最强的调试手段是写日志。DLL跑在通达信进程里没法直接断点但可以在代码里加一行FILE* f fopen(D:\\tdx_log.txt, a); if (f) { fprintf(f, i%d out%f close%f\n, i, pfOUT[i], pfINa[i]); fclose(f); }跑完选股打开这个txt看看每个索引的输出与收盘价是不是对得上。注意日志有IO消耗调试完记得移除否则全市场选股会被拖慢。5. 参数化配置与多策略改造别让DLL变成一块铁板5.1 用INI文件把均线周期变成可调参数第一版DLL把周期常量写死在代码里改一次参数就要重新编译一次用起来太僵硬。实战中我更建议把参数放进INI配置文件DLL每次被调用时读取设置。读取配置有个细节需要注意用GetModuleFileNameA获取路径时如果传入NULL拿到的是通达信主程序的路径不是DLL路径。要拿到DLL自身路径得先通过GetModuleHandleA拿到当前模块句柄再取路径然后截到目录位置拼接配置文件。否则配置文件放DLL旁边也没用它会去通达信目录里找。char szIni[MAX_PATH] {0}; HMODULE hMod GetModuleHandleA(MultiMA.dll); GetModuleFileNameA(hMod, szIni, MAX_PATH); char* pSlash strrchr(szIni, \\); if (pSlash) *(pSlash 1) 0; strcat_s(szIni, MultiMA.ini); int nShort GetPrivateProfileIntA(MultiMA, MA_SHORT, 5, szIni); int nMid GetPrivateProfileIntA(MultiMA, MA_MID, 10, szIni); int nLong GetPrivateProfileIntA(MultiMA, MA_LONG, 20, szIni);配置文件MultiMA.ini放在DLL同目录[MultiMA] MA_SHORT5 MA_MID10 MA_LONG20以后想改成8、13、34日这样的斐波那契组合直接改INI文件重启选股就行不用重新编译DLL。这个技巧能让你在策略调参时省下大量时间。5.2 扩展示例布林带上轨放量突破选股参数化之后DLL就从一个单策略代码变成可复用的策略容器了。下面再给一个完全不同的策略思路展示怎么在这个框架里换逻辑布林带20日上轨突破同时当日成交量是前一日成交量1.5倍以上。for (int i 19; i DataLen; i) { double sum 0.0; for (int j i - 19; j i; j) sum pfINa[j]; double ma sum / 20.0; double variance 0.0; for (int j i - 19; j i; j) variance (pfINa[j] - ma) * (pfINa[j] - ma); double sd sqrt(variance / 20.0); double upper ma 2.0 * sd; int volumeUp (i 1 pfINb[i] pfINb[i - 1] * 1.5) ? 1 : 0; pfOUT[i] (pfINa[i] upper volumeUp) ? 1.0f : 0.0f; }这里把CLOSE和VOL分别作为pfINa和pfINb传入pfINc暂时闲置。整个计算逻辑完全是C自由发挥的空间这正是DLL方案相比公式语言的降维打击之处。6. 实测踩坑记录加载、崩溃与调试的那些事6.1 高频问题速查表把这段时间我和身边朋友踩过的坑汇总成一张表建议收藏现象根因对策通达信提示DLL加载失败编译成了x64架构平台改成x86重新编译提示找不到指定函数__stdcall导出名被修饰用.def文件固定导出名选股结果全部为0输出序列前段留0或阈值设置不当加日志验证单根K线输出行情软件直接崩溃DLL数组越界或空指针访问所有传入指针先判空写入前检查下标编译时报文件被占用通达信正加载旧DLL关掉通达信再重新编译杀毒软件拦截DLL未知动态库被误判添加白名单或减少可疑API调用6.2 调用约定不一致这个坑函数签名里的WINAPI不是随便写的它决定了参数怎么压栈、函数结束后谁来清理栈。如果声明成_cdecl或者漏写了extern C轻则通达信找不到函数重则栈被破坏导致行情软件闪退。我建议从第一天开始就保持统一的模板extern C __declspec(dllexport) int WINAPI 函数名(...)编译完成后用dumpbin查一下导出表确认函数名没有奇怪的后缀再交给通达信去加载能省掉大量排查时间。6.3 选股器里找不到公式DLL函数公式注册成功后它并不会出现在“条件选股”的公式列表里因为它只是一个函数。必须在条件选股公式里通过TMP : MULTIMA(CLOSE, HIGH, LOW); XG : TMP 1;这种包装方式去引用。很多朋友卡在这一步以为是DLL没注册成功其实是理解错了公式类型的分工。另外某些券商精简版客户端会禁用DLL公式功能现象是注册时提示类型不存在或无法选择。遇到这种版本换一个完整版客户端即可源码本身不用动。6.4 调试DLL的两种有效手段断点没法直接打到通达信进程里但调试手段还是有的。第一种是日志文件法前面讲过适合看逐K线的数值输出直观但有IO开销。第二种是OutputDebugStringA配合DebugView工具适合看流程走到哪一步开销小不需要打开文件。OutputDebugStringA(MultiMA calc start);两种手段配合使用定位问题非常顺。真实经历是我碰到过一次选股结果偏少的问题加了一天日志后发现是均线预热期处理有误前19根K线里有一段输出了1导致选股器取了错误的信号。这类问题如果不靠日志靠猜基本猜不出来。最后再分享一个习惯每次改完DLL源码先写一个小测试程序直接调用这个函数传一组构造好的K线数据验证输出符合预期再放进通达信跑全市场选股。这套开发链路我用了很久最值钱的其实不是某一个DLL而是你掌握了一种把任意复杂逻辑接入行情软件的能力。希望这篇教程能让你少走我走过的弯路。本文还有配套的精品资源点击获取