资讯动态

通达信DLL选股插件源码:C语言实现与离线回放验证

发布时间:2026/10/23 7:14:40 来源:尧图企业网站定制
简介面向通达信插件选股开发的VC 6.0示例源码与教程包适合具备C基础、希望理解通达信DLL插件接口和指标接口标准的量化爱好者与程序化交易开发者。压缩包共45个文件、约4.74MB包含cpp/h源码、dll动态库、dsp/dsw工程文件、doc规范与用户手册以及多张jpg操作步骤图可对照完成工程打开、编译和插件加载验证。作者对原示例代码补充了注释并修正了核心实现中的一处错误使原本晦涩的接口说明更易理解配套的编程规范、使用方法和选股结果截图能帮助读者梳理DLL插件从注册、参数传递到文本输出的基本流程。已有6420人学习下载。需要说明的是示例功能较简单重在演示编程方法距实盘应用尚有距离适合用作接口研究、二次开发起步和排错参考据此投资风险自负。1. 通达信DLL选股插件源码为什么老手宁可写 C 也不堆公式收盘后翻两百只票眼睛在 K 线和财务字段之间来回跳手上一堆条件量比、连续放量天数、均线斜率、换手率背离——这些条件单拎出来公式都能写一旦串起来就变成嵌套七八层的 IF改一个阈值要翻三屏第二年自己都看不懂。这份通达信 DLL 选股插件源码要解决的就是这个场景把整套判断逻辑从公式编辑器里搬到 C 代码里编译成 DLL 挂进通达信的公式引擎公式侧只剩一行XS_PICK(C,V,5,1.8)本质是给公式引擎加了一个自己写的内置函数。适合已经会用条件选股、但被公式复杂度和复用性卡住的从业者也适合想把自己那套选股经验固化成可交付模块的人。VC 6.0 那一代的写法看着老但在 32 位进程里它是最省心的组合。2. 调用链路拆解引擎从哪个目录把 DLL 拉进公式引擎2.1 引擎怎么找到你的 DLL通达信对自定义函数 DLL 的加载方式很朴素认目录、认导出名不认注册表也不认安装包。把编译好的 32 位 DLL 放进客户端安装目录下的扩展目录常见路径形如安装目录\T0002\dlls\重启客户端公式引擎在初始化时会把该目录下所有 DLL 的导出函数扫一遍登记成可调用函数。之后在公式编辑器里敲函数名参数提示会跟着出来跟内置函数没区别。这里有两个容易忽略的细节。第一函数名匹配是大小写敏感的你导出XS_PICK公式里写成xs_pick就找不到我一般统一全大写加下划线前缀避免和内置函数撞名。第二扫描是启动时做的换了 DLL 必须重启客户端热替换不生效——这一点在反复调试阶段特别耗时间所以下面会讲怎么用日志把调试周期压短。提示扩展目录的具体层级在不同版本里略有差别源码包里通常会带一份目录说明照着放别自己猜。2.2 参数怎么进、值怎么出调用约定与返回类型通达信调用自定义函数的方式是可变参数引擎把公式里的实参按顺序压栈同时把实参个数作为第一个参数传进来函数里用va_arg逐个取。返回值是一个浮点数公式引擎把它当成一个数值型函数结果参与后续运算——这意味着你的选股函数最终要收敛成返回 1 表示命中、返回 0 表示不命中或者返回一个连续的打分值让公式侧用阈值去做最终判断。我倾向于后者。返回连续值的好处是同一个函数既能当选股条件用也能拿去做排序和分层。比如量价背离度返回 0 到 3 之间的一个数公式里写XS_DIVERGE(C,V,20) 1.5是选股写XS_DIVERGE(C,V,20)放进排序指标就是排序一份代码两处用。参数类型上要留个心眼。绝大多数版本按 double 压栈取参写va_arg(ap, double)但也有版本对整型实参做过优化直接按键值传。稳妥做法是把公式侧的实参全部写成带小数点的形式5写成5.0让引擎统一按浮点处理省掉一类玄学问题。这条不是理论推导是踩过之后总结出来的。2.3 为什么用 VC 6.0 编运行时依赖与 32 位约束现在还在用 VC 6.0不是因为怀旧。三个现实原因一是通达信客户端是 32 位进程64 位 DLL 根本加载不进去而 VC 6.0 默认就产出 32 位目标不用折腾平台工具集二是 VC 6.0 可以非常干净地静态链接 C 运行库/MT编出来的 DLL 丢到任何一台装了客户端的机器上都能跑不会出现开发机好好的换台机器提示找不到某个运行时库三是这套源码本身就是按 VC 6.0 的工程结构组织的.dsp工程文件直接双击能开省掉迁移成本。代价也要说清楚VC 6.0 的 C 标准支持停留在上个世纪std::vector之类的能不用就不用字符串处理老老实实用char缓冲区加sprintf注意sprintf的越界问题用_snprintf并在末尾手动补\0。另外它没有 64 位整数类型的完整支持别在 DLL 里做超大数运算。2.4 一份最小可编译骨架源码包里通常会有一个统一导出声明的头文件我把它简化成下面这个样子方便你先跑通再往里塞逻辑/* tdx_export.h —— 统一导出声明避免 C 名字修饰 */ #pragma once #include windows.h /* 引擎按 (参数个数, 参数表) 的方式调用 返回值作为公式里的数值结果 */ #define TDX_EXPORT(name) \ extern C __declspec(dllexport) double WINAPI name(int nParamCount, ...)/* xs_pick.c —— 最小可跑通的选股函数骨架 */ #include tdx_export.h #include stdarg.h #include math.h /* 从可变参数里取 need 个 double取不满直接判失败 */ static int fetch_args(int nParamCount, va_list ap, double *buf, int need) { int i; for (i 0; i nParamCount; i) { double v va_arg(ap, double); /* 公式侧实参一律按 double 取 */ if (i need) buf[i] v; } return (nParamCount need) ? 1 : 0; } /* 公式写法XS_PICK(C, V, 5.0, 1.8) 参数收盘价、成交量、回看周期、量比阈值 返回1 命中 / 0 不命中 */ TDX_EXPORT(XS_PICK) { double buf[4]; double sum 0.0, ratio; int i; va_list ap; va_start(ap, nParamCount); if (!fetch_args(nParamCount, ap, buf, 4)) { va_end(ap); return 0.0; /* 参数不足安全退出别读脏栈 */ } va_end(ap); if (buf[2] 1.0 || buf[1] 0.0) return 0.0; /* 除零与非法周期拦截 */ for (i 1; i (int)buf[2]; i) sum buf[1] / (double)i; /* 占位计算 */ ratio sum / (buf[1] * buf[2]); return (ratio buf[3]) ? 1.0 : 0.0; }TDX_EXPORT宏把三件事一次做完extern C关掉 C 名字修饰__declspec(dllexport)把函数放进导出表double WINAPI固定返回类型和调用约定。少了任何一项导出名就会变成?XS_PICKYANHZZ这种带修饰的形式引擎扫不到。fetch_args里那个if (i need)判断是关键即使用户在公式里多传了两个参数也不会写越界。反过来参数少传时函数返回 0客户端不会崩——DLL 崩溃会带崩整个客户端进程未保存的自选股和画线全部丢失这个代价比算错一个信号大得多。buf[1] 0.0的拦截看似多余实盘里遇到停牌、新股首日时成交量为 0 是常态一句除零就能让客户端直接退出。3. 把选股因子写进 DLL参数入参设计与公式侧调用3.1 公式侧怎么调用DLL 放好、客户端重启之后在公式管理器里新建一个条件选股公式主体就一行XS_PICK(CLOSE, VOL, 5.0, 1.8)注意参数顺序必须和 C 代码里buf的下标严格对应公式引擎不做类型检查和个数校验传错位置它照样调用只是结果荒谬。我见过把周期和阈值写反的跑出来天天命中几十只票查了两小时才发现是5.0和1.8换了位置。调试阶段建议先建一个技术指标公式而不是条件选股公式把返回值画成曲线XS_PICK(CLOSE, VOL, 5.0, 1.8); XS_RAW(CLOSE, VOL, 5.0)第一行画 0/1 的阶跃线第二行画连续值两条线叠在一起看能立刻发现信号恒为 1或者信号恒为 0这类低级错误。这比在条件选股里反复跑全市场快得多因为指标只算当前这只票。3.2 面板数据与逐股计算的差别DLL 该做多少事一个必须想清楚的设计问题DLL 是被逐股、逐 bar 调用的引擎不会把整个市场的面板数据一次性喂给你。也就是说你在 DLL 里拿到的只有公式传进来的那几个序列的当前值做不了跨股票的比较比如全市场换手率排名前 10%。这决定了功能边界。DLL 适合做的是单只股票内部、基于时间序列的复杂计算——形态识别、多周期共振、条件组合、自定义平滑。不适合做的是横截面排序、行业相对强弱、板块内排名。后者要么放到公式侧用RANK之类的内置函数拼要么在 DLL 之外用脚本把全市场数据先算好落成文件再让 DLL 读。我一般的分工是DLL 负责这只票现在处于什么状态公式侧负责在所有处于这个状态的票里挑前 N 只。职责一分开DLL 就变成了纯粹的函数无状态、可重入、好测试。3.3 参数设计把阈值、周期做成入参而不是常量新手常犯的错是把阈值写死在 C 代码里改一次参数就要重编译一次重编译还要重启客户端一轮调参五分钟。把周期、阈值、平滑系数全部做成入参之后调参就变成了在公式编辑器里改数字秒级反馈。但入参也不是越多越好。参数超过六个公式里那一行就没法读了而且每加一个参数都要在fetch_args里对应扩buf数组容易漏。我的经验是控制在三到五个一个周期、一个主阈值、一个辅助阈值剩下的用内部常量。参数合法性检查必须写全。下面这张表是我在每个函数入口都会过一遍的清单参数合法范围非法时的处理不处理的后果周期 N1 ~ 250返回 0负数导致循环不执行结果恒为 0阈值0.01 ~ 100钳位到边界传 0 时全部命中选股失去意义价格序列 0返回 0停牌价为 0除零崩溃成交量 0返回 0同上这张表不是形式主义。第三行和第四行对应的崩溃是这类插件最常见的翻车原因而且现象很迷惑——平时跑得好好的某天扫到一只停牌股客户端直接没了。3.4 用条件选股做批量扫描与结果落盘逻辑调通之后走正式的批量路径公式管理器里把公式类型设为条件选股进入条件选股面板选好公式、设定日期范围一般取最近一个交易日点执行选股结果会落到一个临时板块里。这一步有三个实操要点。第一选股范围要先限定。全市场五千多只票每次调用都要跑一遍你的 C 函数如果函数里做了稍微重一点的计算比如嵌套循环求斜率整个扫描可能要几分钟。先用沪深A股再逐步缩小到自选板块能省一半时间。第二日期范围不要跨太长。条件选股对历史区间的处理是逐日回算跨一年等于把整个计算量乘以 240。要验证历史表现用第 6 章讲的离线回放比在客户端里硬跑高效得多。第三结果要落盘。选股结果存在临时板块里重启客户端不一定还在。我的习惯是选中结果后立刻另存为板块命名带上日期比如XS_20240315后续复盘直接调板块看不用重跑。4. VC 6.0 编译与部署一次跑通的七个核对点4.1 工程设置三个必须改的开关打开源码包里的.dsp工程先别急着编译把这三处确认一遍Project - Settings - C/C - Code Generation Use run-time library: Multithreaded (/MT) Project - Settings - Link Category: General - Output file name: 你的模块名.dll Object/library modules: kernel32.lib user32.lib Project - Settings - C/C Category: Preprocessor - Preprocessor definitions: WIN32;_WINDOWS;NDEBUG/MT是静态链接 C 运行库这一项决定了 DLL 能不能在别人的机器上跑。如果用了/MD编出来的 DLL 依赖msvcr71.dll开发机上因为装了 VC 6.0 所以有客户机上大概率没有加载直接失败而且报错信息非常含糊只提示初始化失败。kernel32.lib和user32.lib按需加。如果 DLL 里只是想写个调试日志用OutputDebugString就够了不需要额外的库但要注意这个函数在 Release 下会被编译器优化掉调试阶段用 Debug 配置编。预处理器定义里NDEBUG决定assert是否生效。发布版本一定要带上否则某个assert失败会弹出对话框阻塞引擎线程整个客户端卡死——这个现象比崩溃还难查因为进程还在就是不响应。4.2 导出表核对用 dumpbin 看一眼编译成功不等于导出正确。VC 6.0 自带dumpbin在命令行里跑dumpbin /exports 你的模块名.dll正常情况下应该看到ordinal hint RVA name 1 0 00001000 XS_PICK 2 1 00001120 XS_RAW如果看到的是?XS_PICKYANHZZ这种带问号的形式说明extern C没生效检查头文件里的宏有没有被正确包含或者源文件后缀是不是.cpp而宏里漏了extern C。如果导出表是空的多半是.def文件在工程里但没被加进链接器或者源码用了__declspec(dllexport)但函数被声明成了static。这两种情况我都遇到过前者是工程迁移时漏配后者是自己手滑。4.3 部署与位宽核对把 DLL 复制到扩展目录重启客户端。第一次部署建议做一个三步核对第一步确认目标目录对不对。用资源管理器打开客户端安装目录找那个约定好的扩展文件夹把 DLL 放进去同时确认目录里没有同名的旧版本——同名文件会被占用复制时提示文件正在使用如果强行覆盖失败实际加载的还是旧的。第二步确认位宽。在任务管理器里看客户端进程32 位进程后面会标(32 位)。如果你的 DLL 是 64 位的加载会失败但不一定有明确提示。用dumpbin /headers 你的模块名.dll看machine字段14C是 x868664是 x64。第三步确认函数可见。在公式编辑器里敲函数名如果能自动补全说明引擎扫到了如果敲完提示未定义的函数回到上一步查导出表。4.4 用日志和探针函数做黑匣子排查DLL 在引擎进程里跑出问题时没法打断点。我的做法是加一个独立的探针函数专门用来输出内部状态/* 调试探针公式里单独调用 XS_DEBUG(C,V,5.0) 看内部中间值 */ TDX_EXPORT(XS_DEBUG) { double buf[3]; va_list ap; char msg[128]; va_start(ap, nParamCount); fetch_args(nParamCount, ap, buf, 3); va_end(ap); _snprintf(msg, sizeof(msg) - 1, [XS] c%.3f v%.3f n%.0f\n, buf[0], buf[1], buf[2]); msg[sizeof(msg) - 1] \0; OutputDebugStringA(msg); /* 用 DebugView 之类的工具接 */ return buf[0]; }这个函数返回值原样透传收盘价所以把它加进指标公式不会破坏图形。日志一定要限流——逐 bar 打印会在几秒内写出几十兆文本把调试工具拖死。实际使用中我一般只在关键分支打印或者用一个静态计数器每 500 次打一条。4.5 性能预算全市场扫描的时间尺度五千只票、每只一次调用如果单次调用耗时 1 毫秒总耗时就是 5 秒可以接受如果单次 20 毫秒总耗时接近两分钟用户会以为卡死了。判断标准很简单DLL 里不要有文件 IO不要有new/delete密集操作不要有对参数序列的二次遍历嵌套。所有能在公式侧用内置函数算完的东西不要搬进 C 里重算一遍——引擎的内置函数是优化过的你手写的循环大概率更慢。5. 避坑排查五类让客户端直接退出的现场5.1 公式里能敲出来选股时报函数不存在现象在指标公式里调用一切正常切到条件选股就提示函数未定义。原因条件选股和指标公式走的是两条加载路径部分版本对条件选股里可用的自定义函数做了额外校验或者只认特定前缀。另一种可能是 DLL 放在了指标准确目录但客户端启动时该目录被安全软件锁了一下扫描跳过。解决先把 DLL 复制到扩展目录的上一级做一次对照测试再用探针函数确认引擎到底扫到了哪些导出名。如果确实只认前缀把函数名统一改成带固定前缀的形式重新编译比反复猜规则快。5.2 扫描到一半客户端直接消失现象平时正常某天扫全市场时进度条走到一半客户端进程没了没有任何错误提示。原因某只票触发了除零或数组越界。最典型的是停牌股成交量为 0或者新股上市首日历史数据不足回看周期 N 大于实际 bar 数时读到了未初始化内存。解决在函数入口把所有入参做一遍合法性拦截价格 0、成交量 0、周期大于可用 bar 数一律返回 0。同时给回看循环加上边界判断宁可少算一只票也不能让进程崩。这个坑我吃过一次丢了一整天的画线。5.3 昨天选出来 12 只今天同样的参数选出 40 只现象参数一个字没改两天的选股结果差异巨大。原因函数里用了未来数据。最常见的是在计算里引用了收盘后才更新的字段或者用了REF方向的偏移取到了当根之后的值。另一种可能是 DLL 里有静态全局变量第一只票算完残留的状态影响了第二只票导致结果和调用顺序相关。解决把 DLL 改成无状态函数所有中间变量放在栈上禁止static变量参与计算公式侧检查有没有引用未来周期的函数。验证方法很简单同一组参数连跑三次结果必须完全一致不一致就是有状态残留。5.4 开发机能跑换台机器加载失败现象自己的机器上一切正常把 DLL 连同公式导出发给同事对方导入公式后函数全部失效。原因编的是 Debug 版本或者运行库设成了/MD目标机器没装对应的运行时也可能是同事装的是不同位宽的客户端。解决发布前一律用 Release 配置加/MT重新编译用dumpbin /dependents检查依赖表只应该看到KERNEL32.dll、USER32.dll这类系统库。看到msvcr71.dll就说明静态链接没生效回去改工程设置。5.5 扫描慢到以为程序假死现象点了执行选股进度条几乎不动等了十分钟还在跑。原因函数内部做了重活。常见的是在每次调用里重新打开文件读参数表或者在循环里拼字符串做日志。五千次调用里做五千次文件打开光系统调用就能吃掉几分钟。解决参数表在 DLL 加载时读一次缓存在静态区只读不会有状态污染问题日志改成计数器限流每 N 次打一条。如果逻辑确实重把它拆成两级——粗筛用公式侧的内置函数先过滤掉 80% 的票剩下 20% 再进 DLL 精算。6. 进阶验证用离线回放把插件调成确定性函数调参调到后期最浪费时间的不是写代码是改一个数、重启客户端、跑一遍全市场、看结果这个循环。我后来把这件事拆开了把 DLL 当纯函数在客户端外面回放。具体做法是用导出的日线数据生成一份 CSV每行一只票一个交易日列就是公式会传进去的那几个序列值然后写一个小驱动用LoadLibrary把 DLL 加载起来按行喂参数/* replay.c —— 用同一份 DLL 在客户端外回放验证逻辑一致性 */ #include windows.h #include stdio.h typedef double (WINAPI *PFN)(int, ...); int main(void) { HMODULE h LoadLibraryA(你的模块名.dll); PFN fn; FILE *fp; double c, v, n, th; int hit 0, total 0; if (!h) { printf(load fail: %lu\n, GetLastError()); return 1; } fn (PFN)GetProcAddress(h, XS_PICK); /* 名字必须与导出表一致 */ if (!fn) { printf(symbol not found\n); return 1; } fp fopen(sample.csv, r); if (!fp) return 1; while (fscanf(fp, %lf,%lf,%lf,%lf, c, v, n, th) 4) { total; if (fn(4, c, v, n, th) 0.5) hit; /* 参数个数在前与引擎一致 */ } fclose(fp); FreeLibrary(h); printf(total%d hit%d rate%.4f\n, total, hit, total ? (double)hit / total : 0.0); return 0; }这个驱动的价值在于它复用了同一份 DLL、同一套参数顺序所以它算出来的命中率可以直接和客户端里的选股结果对拍。两边对不上就说明问题出在引擎调用环节参数顺序、类型、目录而不是逻辑本身——把排查范围一刀切成两半效率差别很大。用 VC 6.0 编这个控制台程序同样/MT同样 32 位免得加载 32 位 DLL 时踩位宽不匹配。回放还有一个副产品它能跑出历史每一期的命中明细把命中率、平均持有天数、最大回撤这些指标算出来。到了这一步选股插件就不再是感觉挺好用的玄学而是一个有数据支撑的模块。那张对拍表我一般会留一份格式是这样验证项客户端结果离线回放判定命中只数1212一致命中率0.02400.0240一致重复跑三次12/12/1212/12/12无状态第三行是我最看重的。只要出现 12/13/11 这种波动立刻回去查静态变量和未来数据别急着调参——参数再好逻辑不确定就是白搭。源码包里的教程部分建议按先跑通最小骨架、再加自己的因子、最后做离线对拍这个顺序看别一上来就扎进完整工程那份代码里的历史包袱不少。压缩包我放在下面了需要的话自取。从那以后我每次改完 DLL都强制走一遍导出表核对 三次一致性回放再进客户端跑全市场。这个习惯让我少加了很多次班也少丢了几次画线。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑