恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
通达信DLL公式C接口MA算法:从零实现自定义均线
首页
资讯中心
/
通达信DLL公式C接口MA算法:从零实现自定义均线
通达信DLL公式C接口MA算法:从零实现自定义均线
发布时间:2026/10/20 18:01:59
简介面向需要在通达信软件中扩展自定义技术指标的开发者这份压缩包演示了如何用C语言编写移动平均线MA算法并通过通达信DLL公式接口集成到行情分析软件中。资源共12个文件整体约10KB以cpp源文件和h头文件为核心辅以dsp、dsw、plg等工程配置方便在Visual C环境下加载、编译与调试。压缩包中包含通达信插件工程框架、函数集实现以及MA计算相关代码适合对C语言和通达信API有一定基础、希望理解DLL公式调用流程的读者参考。已有780人学习下载工程规模小巧、结构清楚可作为开发自有通达信插件指标的起点也可用于研究指标计算与行情接口的对接方式。1. 通达信DLL公式的C接口MA算法自己写均线之前必须先懂的事通达信DLL公式的C接口MA算法听起来像一串术语堆叠其实就是一件事让通达信调用你自己用C语言写的移动平均线。某开发者曾为一条对不上的MA20查了两天资料后来发现内置均线用的数据口径、复权处理和自己手算的并不等价指标框里那个MA是个黑匣子。标题里这串东西——TestPluginTCale.zip里的C接口、MA算法、通达信DLL公式、通达信接口——是一条完整的落地路径用C实现自定义移动平均编译成DLL公式再交还给通达信画线输出。适合三类人嫌内置指标不可控的量化分析者、想在通达信里跑自定义均线算法的开发者、手里有现成C算法想接进行情软件的工程师。下面直接讲接口机制和能抄的代码。2. 通达信DLL公式的调用机制C接口怎么把几千根K线递进你的函数2.1 从 TestPluginTCale.zip 名字说起PluginTCalc 接口长什么样通达信外部DLL公式的常见做法是让DLL导出一个固定名字的接口函数行情软件负责把K线数组塞进来你算完再把结果数组塞回去。TestPluginTCale.zip 这个命名看着绕拆开就是 Test Plugin TCalc 的压缩包TCalc 是这类插件里约定俗成的计算接口前缀。不同版本的通达信对外DLL接口签名会略有差异但核心思路一致在函数级别做一次「数据进、结果出」的交换。先看最常见的接口形态后续代码都基于它// 通达信外部DLL公式的常见接口形态不同版本有差异 __declspec(dllexport) int PluginTCalc( char* pszFuncName, // 公式里调用的函数名 float fCalcParam[], // 公式里传入的静态参数如MA周期N int nNumOfParam, // 静态参数的个数 float fResult[], // 输出结果数组有几个输出就预留几组 int nNumOfResult, // 输出结果组数 int nNumOfData, // K线数据根数 float fData[], // 行情数据体 int nDataType // 数据组织方式标识 )这几个参数是判断你会不会用的分水岭。pszFuncName 是通达信调用时传进来的函数名同一个DLL里可以注册多个公式函数靠它区分。fCalcParam 装的是公式里写的参数比如你写 MA55 就通过这个数组传进来。fData 封装了全部K线数据nNumOfData 告诉你有多少根fResult 是你回填结果的输出区。接口返回 0 表示计算成功非 0 表示失败失败时通达信对应位置显示空值而不是画一条乱线。参数里最容易理解偏的是 fResult 与 nNumOfResult。通达信允许一个公式输出多条线比如 MA5 和 MA10 一起输出那么 nNumOfResult 就是 2fResult 预留的长度就是 nNumOfData 乘以 2。所有数据统一用 float 传递而不是 double这是老接口的传统也是后面精度坑的来源。我一般在入口先做参数校验nNumOfData 小于 1 或周期参数大于数据根数就直接返回 -1宁可不画也不能画出错误曲线。参数名作用常见坑点pszFuncName区分DLL内的多个公式函数字符串比较要精确匹配fCalcParam公式传入的静态参数周期取整前先判断范围和下限fResult输出结果数组按组偏移不是按K线交叉nNumOfResult输出线数与参数个数要一一对应nNumOfDataK线根数小于周期时提前返回fData行情数据布局需反向验证nDataType数据排列标识不同版本含义不同2.2 MA算法在C接口里的数据流一个数组进来几组结果出去通达信DLL公式和普通C函数的本质差别在于它不是「调用一次返回一个数」而是「调用一次处理整段K线回填整段结果」。你的MA算法不是在单根K线上算一个值而是在整个数组上做滚动计算。以简单移动平均为例MA(N) 在第 t 根K线上的定义是最近 N 根收盘价的算术平均常规实现直接开循环void compute_ma_simple(const float* close, int nNum, int period, float* out) { // close: 收盘价数组按时间升序 // nNum: 数据根数 // period: 均线周期 // out: 输出数组同样按时间升序 for (int t 0; t nNum; t) { if (t period - 1) { out[t] 0.0f; // 数据不足周期数结果置空 continue; } float sum 0.0f; for (int k 0; k period; k) { sum close[t - k]; // 累加最近N根 } out[t] sum / period; } }这是最直白的版本逻辑一眼看穿前 period-1 根算不出均值输出0从第 period 根开始不断滑动窗口求和取平均。这里用 close[t - k] 往回倒着取语义上更贴近「最近N根」。对几千根K线来说就是几万次加法性能完全不是问题。如果你想优化可以用递推公式把 O(N*M) 降成 O(N)但 float 下的累积误差会来惹事第5章会展开讲。数据流还有一个关键点通达信传进来的 fData 是按什么顺序排的。不同版本通达信的 nDataType 值不同有的把开高低收紧密排列有的按价格类型分段排列。我吃过亏后养成的习惯是拿到接口后先用已知数据反向试探布局而不是直接信注释。做法是在DLL里把 fData[0] 到 fData[3] 原样输出成结果线再去通达信里对比这几根数值和开高低收的对应关系一下就试出来了。为什么不直接在通达信公式里写 MA(CLOSE,N)因为公式语言是解释执行的能调用的函数受限做不了复杂循环和内存管理。C接口让你能做预处理、条件分支、甚至读文件这是DLL公式存在的意义。理解了这个数据流接下来就可以开工程了。3. 建一个能编译的C接口DLL工程从空目录到出DLL3.1 工程配置DLL、平台位数、导出符号三件事在常用的C编译器里新建一个动态库空项目名字随意。打开项目属性把配置类型改成「动态库(.dll)」。有一个选项很多人第一次会漏目标平台位数。通达信主程序如果是32位DLL就要编译成 x86如果主程序是64位就编译 x64。混了会直接加载失败甚至报「无法定位程序输入点」。判断主程序位数的方法很简单在操作系统的进程列表里看通达信进程名后面有没有带32位标记或者看安装目录下是否有x64子目录。导出符号有两种方式一种是在函数定义前写 __declspec(dllexport)前面代码用的就是这种另一种是写 .def 模块定义文件把函数名列在 EXPORTS 段里。我习惯用第一种源码里能看到导出意图。这里还要注意一个关键点接口的实际调用约定通常是 __stdcall需要在函数声明上明确写 WINAPI 或 __stdcall否则函数名会被修饰成 C 风格通达信找不到入口。某开发者曾在这里翻车声明写 __stdcall、定义不写编译出两个不同符号通达信永远找不到函数。编译完后用工具看一眼导出表是最快的验证dumpbin /exports TestPluginTCale.dll如果输出里出现了带 数字的符号比如 _PluginTCalc32 或 PluginTCalc32说明 __stdcall 生效了如果只有光秃秃的 PluginTCalc多半是 __cdecl。 后面的 32 是参数总字节数8 个参数在 32 位下正好是 32 字节这个数字对得上说明参数声明也没写错。这一步不做后面进通达信崩了都不知道去看哪里。3.2 核心源码MA算法与导出函数完整实现下面给出一份能直接编译的最小实现把上一章的MA内核和接口封装拼在一起并做了参数校验#include windows.h #include string.h static void compute_ma(const float* data, int nNum, int period, float* out) { for (int t 0; t nNum; t) { if (t period - 1) { out[t] 0.0f; continue; } float sum 0.0f; for (int k 0; k period; k) { sum data[t - k]; } out[t] sum / period; } } __declspec(dllexport) int WINAPI PluginTCalc( char* pszFuncName, float fCalcParam[], int nNumOfParam, float fResult[], int nNumOfResult, int nNumOfData, float fData[], int nDataType ) { // 只处理注册为 MA 的公式函数 if (strcmp(pszFuncName, MA) ! 0) { return -1; } // 周期参数从公式传入没有参数就默认10 int period 10; if (nNumOfParam 0 fCalcParam[0] 1.0f) { period (int)fCalcParam[0]; } // 输出至少1条数据根数不能小于周期 if (nNumOfResult 1 || nNumOfData 1 || nNumOfData period) { return -1; } // nDataType 决定 fData 里如何取收盘价 // 常见一种是 OHLC 紧密排列步长4收盘在第4位 // 不同版本布局不同先用试探法确认再填死 const float* close fData; compute_ma(close, nNumOfData, period, fResult[0]); return 0; }这段代码的取舍要说清楚。我把 nDataType 的分支先留成从 fData 直接取是因为不同版本通达信的布局真的不一样贸然填一个默认值反而会坑了照做的人。正确流程是先让通达信把原始 fData 的若干数值画出来验证排列方式再固定这个分支。fResult 在这里只写了第一条线如果要多输出把结果写到 fResult 的不同段位上。注意 fResult 是按组连续排的不是按K线交叉排这一点搞错画出来的线会像被抽了丝一样断断续续。周期参数的校验也值得多说一句fCalcParam 是 float 数组拿到的 5.0 要先转成 int。如果用户在公式里传了 0 或负数直接按默认10处理如果传了大于数据根数的周期整个计算没有意义返回 -1 让通达信显示空值。这些都是接口联调里的常规防御写习惯了能省掉很多莫名其妙的曲线。3.3 不打开行情软件就能验算本地模拟接口调用的测试方法编译出DLL后不要急着进通达信先用一小段测试代码在本地模拟一次接口调用。这个习惯能过滤掉七成低级错误。做法是写一个控制台程序加载DLL后手工构造一组假K线调用 PluginTCalc再把输出打印出来和手算的MA对比// 本地验证模拟通达信调用DLL typedef int(WINAPI* PFN)(char*, float*, int, float*, int, int, float*, int); HMODULE h LoadLibraryA(TestPluginTCale.dll); PFN p (PFN)GetProcAddress(h, PluginTCalc); float data[12] {1,2,3,4,5,6,7,8,9,10,11,12}; // 12根假收盘价 float out[12] {0}; float params[1] {5}; int ret p(MA, params, 1, out, 1, 12, data, 0); // 期望第0~3根为0第4根开始依次为3,4,5,6,7,8,9,10这一步能确认MA周期、索引、返回值全对。我本地测试时通常会故意传几个边界值周期1、周期等于数据根数、周期大于数据根数、参数数组为空看程序会不会崩。边界测完再进通达信遇上的就只剩通达信环境本身的交互问题。本地测试还有一个好处可以用 debugger 单步走看每一根K线的累加过程这比在通达信里对着一条线猜快得多。本地测试通过不等于通达信里能跑但至少把算法层的错误清干净了。下一步进通达信主要看接口约定和数据显示是否符合预期。4. 把DLL公式装进通达信并跑出第一条自绘均线4.1 安装与发现DLL放哪里、公式管理器怎么识别通达信安装目录下通常有一个专门放外部插件的目录不同发行版位置不一样有的叫 TDXWDLL有的叫 Download还有的散落在主程序目录下的子文件夹。最靠谱的找法在通达信菜单里打开「公式管理器」找「DLL函数」或「外部DLL」这类入口它会直接显示当前扫描哪些目录。有的版本需要把DLL丢进去后重启客户端有的做一次公式刷新就能发现。装好之后在公式管理器里应该能看到 TestPluginTCale 或者DLL文件名对应的条目。安装时注意几个细节文件名不要用中文和空格解压 TestPluginTCale.zip 后直接把里面的DLL文件拷进扫描目录文件名保持和包内一致如果压缩包里附带了说明文档先读文档里写的参数个数和输出条数那可能是作者联调过的版本信息。不同版本通达信对DLL公式数量上限、公式名长度都有限制名字起短一点省得踩边界。DLL放好后在公式管理器的DLL列表里如果看不到它检查一下位数是否匹配这是最常见的静默失败原因。4.2 在公式编辑器里按通达信规则调用C接口DLL文件被识别后新建一个指标公式引用DLL的语法通常是类似 TDXDLL1(函数名, 参数...) 的格式。前缀里的数字1表示调用第几个DLL具体前缀写法看你手上的通达信版本。示例MYMA: TDXDLL1(MA, 5, CLOSE);这句公式的含义调用DLL列表里的第一个DLL传入函数名MA静态参数5数据序列取CLOSE。通达信会把当前股票的收盘价序列和参数5打包进 fCalcParam 和 fData调用 PluginTCalc把返回的 fResult 画在当前图上。同一指标里调用两个周期就写两行MA5: TDXDLL1(MA, 5, CLOSE), COLORWHITE; MA10: TDXDLL1(MA, 10, CLOSE), COLORYELLOW;这里的 TDXDLL1 只是常见写法实际要以你的通达信版本为准。公式编辑器里如果列出了DLL的注册函数直接点插入最保险手动敲名字敲错一个字母得到的是一条静默失败的空白线连报错都不给。这是通达信DLL公式最让人头疼的一点函数不存在时不弹错误框只是不画线。排查时先看公式管理器里DLL是否已被识别再看函数名和参数个数是否匹配。4.3 用叠加对比确认C接口MA和内置MA一致画出来之后第一件事不是欣赏曲线而是叠加对比。新建一个指标公式把内置MA和DLL算的MA画在一起MA5_IN: MA(CLOSE, 5), COLORRED; MA5_DLL: TDXDLL1(MA, 5, CLOSE), COLORGREEN;正常情况下两条线应该完全重合。如果出现偏差用十字光标逐根看数值整体右移一根是索引对齐问题数值在小数位上有差是复权和float精度问题前几根一个为空一个为0是空值处理方式不同。把对比结果记下来下一章的坑会逐条对号入座。叠加对比的选股范围也讲究先拿一只上市超过一年的票看长周期MA60再拿次新股看数据不足周期时的空值表现最后拿停牌复牌的票看接口在数据断层时的行为。三种样本各看一遍DLL公式才算真的能交付使用。5. 通达信DLL公式的踩坑清单五个让MA计算结果对不上的细节5.1 坑一均线整体右移一根索引对齐差一位现象DLL画的MA和内置MA形状完全一样但整体往右偏一根最新一根K线对应的是前一根的MA值。原因本地验证用的是从0开始的数组下标而通达信部分序列对象隐含从1开始的历史。接口文档里 nNumOfData 与 fData 的对齐关系在不同版本有一根之差本地测试数组完全复现不了这个环境差异。解决在通达信里画线对比后把计算起点加一个偏移或者改用K线序号减一来取最新收盘价。这个偏移量不是玄学是接口版本定义试一次确定下来写进代码注释顶部下次换通达信版本时第一个查这里。5.2 坑二递推优化带来float累积误差长周期MA翻车现象MA60在数据量超过两三千根后DLL算出的数值与内置MA稳定差0.003到0.02偏差随K线数量增大而变大。原因通达信接口里所有行情数据都是float用单精度累加几千次再减掉舍入误差不断积累。递推公式 MA_t MA_{t-1} (P_t - P_{t-N}) / N 在数学上等价但在计算机里每一步都有舍入误差滚雪球。解决放弃增量递推回到滑动窗口全量求和。对几千根K线O(N*M) 耗时几毫秒完全感知不到误差归零。如果实在想用递推把累计变量改成 double输出前再转 float。这是用正确性换速度的不值得的教训算力便宜时间贵。5.3 坑三__cdecl 和 __stdcall 混用通达信一加载就崩现象DLL放进去公式管理器里能看到但把公式拖到图上时通达信闪退或报错。原因导出函数符号是C风格修饰通达信按StdCall协议找入口约定不一致会导致栈平衡被破坏。本地测试不一定暴露因为本地 LoadLibrary 和 GetProcAddress 时函数指针声明可以按发出方约定来写掩盖了导出符号的问题。解决检查导出函数声明统一使用 __declspec(dllexport) int WINAPI不要声明和定义混写。用 dumpbin /exports 确认导出名带 32 后缀没有这个后缀就是 __cdecl改完重新编译。进通达信之前先验导出名能省下很多闪退后的重启时间。5.4 坑四数据口径不一致DLL拿到的是复权价现象用不复权的日K收盘价手工算MA和DLL算出来的结果在小数位对不上差得不多但永远不相等。原因通达信传给DLL公式的数据序列受当前界面复权设置影响前复权、后复权、不复权传给 fData 的收盘价不一样。DLL公式本身是无辜的它只是如实计算你喂给它的数据。解决在通达信设置里统一复权方式手工计算也要使用相同复权价。我习惯在DLL说明文档里写明「请在复权一致条件下使用」不然换台电脑数值就变了。这属于通达信接口的黑匣子部分没经历过的人很难第一秒想到但它造成的对不上让我浪费过一个下午。5.5 坑五static全局变量在多标的并行计算时互相污染现象同一个DLL公式在自选股里按顺序刷新前面的股票MA值偶尔延续到后一只股票曲线开头出现一段奇怪的旧数据。原因代码里用了一个 static 数组做中间缓存以为只是加速用但通达信可能多窗口并行调用同一个DLL多个股票共用同一份静态区数据互相串了。解决把中间缓存全部改成函数内局部变量或通过调用上下文传入禁止在DLL里使用可写的 static/全局状态。DLL公式调用有并发可能任何可写静态区都是定时炸弹。这个现象最像玄学排查到最后就是一行 static 的问题改完药到病除。6. 把单个MA扩展成一套可复用的C接口均线内核6.1 用参数组驱动多周期MA一份源码支持任意参数与其为MA5、MA10、MA20写三份函数不如让同一个DLL公式函数处理不同参数。fCalcParam 数组支持多个参数公式里可以传周期N和输出模式。修改第3章的代码把 period 从固定一个改成遍历参数列表每个参数对应一组输出一份DLL就变成了多周期MA计算器。额外的好处是调参不用重新编译DLL直接在通达信公式里改参数就行。6.2 一次调用同时输出MA5、MA10、MA20为后续策略留数据多输出组的价值在策略联动。如果只输MA5不给MA20你要在通达信公式里再调用一次DLL多一次环境间数据往返。一次输出三组就是一次接口调用同时拿到三条线。内核函数可以改造成这样for (int g 0; g nNumOfResult; g) { int period (int)fCalcParam[g]; // 第g组对应的周期 float* out fResult g * nNumOfData; // 结果区按组偏移 compute_ma(close, nNumOfData, period, out); }多组输出时代价是 fResult 占用倍数内存K线数量大时略有压力但只要不超过DLL公式的结果上限就行。还要保证参数个数和输出组数一致参数不够或多余都会让计算错位这个检查放在函数入口返回 -1 比算错好。最后补一个我的个人习惯每次交付DLL公式都留下一份最小复现工程包括源码、本地测试用的假数据、以及通达信公式文本。这样别人拿到手不需要猜接口直接能改参数跑避免很多次「你怎么算的和我算的不一样」的来回扯皮。这套C接口MA方案从原理到踩坑的完整流程就是这样希望帮到你。本文还有配套的精品资源点击获取