简介:面向希望掌握通达信DLL选股插件开发的VC++ 6.0用户,这套源码与教程组合针对通达信官方插件规范晦涩难懂、示例代码缺乏注释的问题,将原示例逐段注释,并修正了MyPlugin.cpp中的一处错误,帮助读者理清DLL指标接口的调用流程、数据传递方式与选股插件的工作原理。压缩包共45个文件、约4.74MB,包含C++源码、接口头文件、编译生成的DLL与导入库,同时保留了工程配置、调试符号、说明文档和多张操作截图;其中规范文档与分步截图覆盖了从工程搭建、代码编译、DLL生成到选股结果验证的完整过程。示例侧重教学演示,不构成实际投资建议,但在导出函数、结构体定义、调用约定等关键点做了详细标注,能显著缩短上手时间,并启发读者在此基础上扩展自己的选股策略。资源另附小闪客自动交易伴侣用户手册,便于了解自动化交易对接方式;已有6406人学习下载,适合具备C/C++基础、希望深入通达信插件接口或尝试自动选股的技术人员。
1. 通达信DLL选股插件:把C/C++选股逻辑跑进条件选股公式
做条件选股最憋屈的时候,就是你在通达信里明明算得出来、但公式语言就是写不出来的时候。通达信DLL选股插件能把用C/C++写的选股逻辑编译成DLL,让通达信在条件选股时把K线数据传进DLL、算完拿结果选股,相当于给公式语言开了一个扩展口,外部数据、复杂统计、自定义模型都能从这里接进去。本文直接给你一套能编译能加载的最小源码:接口约定、VS工程配置、四个导出函数、一个均线金叉选股实例,再加上我自己踩过的一堆坑。适合对C语言有基础、用过条件选股、想把更复杂逻辑跑进通达信的读者。
2. 通达信DLL选股插件接口约定:四个导出函数与CalcInfo数据结构
2.1 通达信加载DLL选股插件的调用链
通达信对外部DLL的调用,本质上是一份写死的函数契约。公式管理器在加载DLL时,通过GetProcAddress按函数名查找导出函数,找到之后把DLL当成一个选股公式来注册;选股时再按同样的约定把数据传进去、把结果拿回来。也就是说,通达信并不关心DLL内部怎么实现,它只认导出函数名和参数形式。
为什么非要DLL?通达信公式语言能写大部分技术指标,但它是解释执行的,遇到两类需求会非常吃力:一是需要外部数据,比如读一个文本文件里的因子值参与选股,公式语言没有文件接口;二是需要复杂计算,比如布林带里套回归、对历史区间做统计检验,公式语法写起来绕且慢。DLL方案等于把计算部分完全外包给C/C++,通达信只负责传K线数据和接收结果。
我一般会在动手写代码之前先把调用链在纸上画一遍,不然很容易在结果上钻牛角尖:
- 公式管理器注册DLL,逐个查找SetPluginName、PluginInfo、GetInfo、Calc四个导出函数。
- 调用SetPluginName获取插件名,调用PluginInfo获取插件类型和版本信息。
- 条件选股启动后,通达信遍历股票池,对每只股票取出K线数据。
- 通达信把K线数组打包进CalcInfo结构体,调用Calc。
- Calc把每根K线的判定结果写入输出数组,返回。
- 通达信取输出数组的最后一位,为1就入选,为0就落选。
注意第6步。很多刚接触DLL选股的人以为Calc返回一个int就行,实际上Calc的返回值是void,结果要写进输出数组,通达信只看数组的最后一个元素。这个约定直接决定了源码里怎么写结果。
2.2 CalcInfo结构体里到底传了什么数据
CalcInfo是通达信和DLL之间传递数据的核心结构体。网上流传的多个版本源码里,字段顺序和类型略有差异,但主体思路一致:前两个字段是K线数量和错误码,中间是一组float指针,指向开盘、最高、最低、收盘、成交量、成交额数组,最后一个字段是输出结果数组。
常见定义如下:
typedef struct { int nNum; // K线数量 int nErr; // 错误码,正常为0 float* pfOpen; // 开盘价数组 float* pfHigh; // 最高价数组 float* pfLow; // 最低价数组 float* pfClose; // 收盘价数组 float* pfVolume; // 成交量数组 float* pfAmount; // 成交额数组 float* pfOut; // 输出数组,长度与nNum一致 } CalcInfo;字段含义对照:
| 字段 | 类型 | 说明 |
|---|---|---|
| nNum | int | 本次传入的K线根数 |
| nErr | int | 返回给通达信的错误码,正常置0 |
| pfOpen | float* | 每根K线的开盘价 |
| pfHigh | float* | 每根K线的最高价 |
| pfLow | float* | 每根K线的最低价 |
| pfClose | float* | 每根K线的收盘价 |
| pfVolume | float* | 每根K线的成交量 |
| pfAmount | float* | 每根K线的成交额 |
| pfOut | float* | 每根K线的判定结果,非0表示满足 |
这里有个容易翻车的细节:有些版本把nNum和nErr定义成short,有些定义成int。如果结构体定义和通达信实际版本不一致,轻则数据错位,重则直接崩溃。我的习惯是先在Calc里写日志,把nNum、pfClose[0]、pfClose[nNum-1]打出来,和通达信界面上最后一根K线收盘价对一下,确认结构体没问题再往下写。
注意:不同版本字段定义有差异,编译前先用日志验证字段偏移。
2.3 选股结果是怎么返回给通达信的
DLL选股的返回值约定,和通达信内置条件选股公式的语义一致:公式对每根K线算出一个布尔值,最后一位决定当前是否入选。DLL里对应为pfOut数组,从第一根K线开始遍历,条件满足就把pfOut[i]置1,不满足置0或保持0。
这里要说明一点:选股只看最后一位,但历史回测和阶段选股会用到整段数组。所以不要只在最后一位写1,而是把每一根K线的判定都写进去,这样通达信在选股范围里指定历史区间时结果也是对的。我见过只写最后一位的代码,单日选股没问题,一到回测就全军覆没。
Calc的返回值是void,出错时通过nErr上报。nErr不为0时通达信会认为该股票计算失败,通常表现为跳过或提示错误。我一般会把nErr当作调试窗口,内部malloc失败、入参为空都置一个不同的负数,方便定位。
GetInfo这个函数在不同版本里语义差别最大。有的版本通过它询问DLL是否需要财务数据、扩展数据,有的版本只是走过场。最小实现里把所有值置0返回,等于告诉通达信我只需要K线数据,不需要额外字段。如果你的版本加载DLL后公式能列出但选股结果为全0,且日志显示数据正常,回头查GetInfo的实现是不是符合你的版本约定,这个函数写错了不会导致崩溃,但可能导致通达信不把附加数据传进来。
3. 从零写通达信DLL选股插件:VS工程配置与最小DLL编译
3.1 工程创建与三个关键编译选项
开发环境我一般用Visual Studio Community,2019和2022都行。新建一个空项目,在项目属性里把配置类型改成动态库(.dll),然后有三个选项必须留心,任何一个错了都会在加载阶段出问题。
第一个是平台。多数通达信客户端是32位程序,32位进程不能加载64位DLL,所以平台要选x86。第二个是运行库。项目属性 -> C/C++ -> 代码生成 -> 运行库,选多线程(/MT),这样DLL静态链接C运行时,不依赖目标机器上有没有装VC运行库。第三个是字符集。项目属性 -> 配置属性 -> 常规 -> 字符集,选使用多字节字符集,因为通达信传给DLL的字符串缓冲区是ANSI的,用Unicode字符集会写出宽字节数据,插件名会显示成乱码甚至截断。
这三个选项里,/MT最容易被忽略。很多人编译完DLL在自己机器上能加载,换一台机器就提示无法定位程序输入点或找不到MSVCP140.dll,就是运行时库动态依赖造成的。用/MT之后,DLL体积会大几十KB,但换来的是到处能跑。
注意:/MT会增加DLL体积,但换来的兼容性值得。
3.2 四个导出函数的实现代码
一个最小的DLL选股插件,导出函数就四个。我用extern "C"包住,避免C++名称修饰,同时全部声明成WINAPI,也就是__stdcall调用约定,这是通达信调用DLL时的约定。
#include <windows.h> #include <string.h> #include <stdlib.h> #define MAX_NAME_LEN 64 // CalcInfo 结构体定义 typedef struct { int nNum; int nErr; float* pfOpen; float* pfHigh; float* pfLow; float* pfClose; float* pfVolume; float* pfAmount; float* pfOut; } CalcInfo; // 返回插件名,通达信会向这个缓冲区写入 extern "C" __declspec(dllexport) void WINAPI SetPluginName(char* szPluginName) { strncpy(szPluginName, "MyDllSelect", MAX_NAME_LEN - 1); szPluginName[MAX_NAME_LEN - 1] = '\0'; } // 返回插件信息:版本号、类型、名称、作者 extern "C" __declspec(dllexport) void WINAPI PluginInfo( short* nVersion, short* nType, char* szName, char* szAuthor) { *nVersion = 1; *nType = 1; // 1表示条件选股类型,具体取值看版本说明 strncpy(szName, "简单DLL选股", MAX_NAME_LEN - 1); strncpy(szAuthor, "Developer", MAX_NAME_LEN - 1); } // 向通达信声明所需数据,最小实现不申请额外数据 extern "C" __declspec(dllexport) void WINAPI GetInfo( short* nVersion, short* nType, short* nInfo, short* nNum, short* nErr) { *nVersion = 1; *nType = 0; *nInfo = 0; *nNum = 0; *nErr = 0; } // 核心函数:遍历K线,把判定结果写入pfOut extern "C" __declspec(dllexport) void WINAPI Calc(CalcInfo* lpCalcInfo) { if (lpCalcInfo == NULL || lpCalcInfo->pfClose == NULL || lpCalcInfo->pfOut == NULL) { if (lpCalcInfo) lpCalcInfo->nErr = -1; return; } int nNum = lpCalcInfo->nNum; for (int i = 0; i < nNum; i++) lpCalcInfo->pfOut[i] = 0.0f; // 演示逻辑:收盘价高于前一根就标记 for (int i = 1; i < nNum; i++) { if (lpCalcInfo->pfClose[i] > lpCalcInfo->pfClose[i - 1]) lpCalcInfo->pfOut[i] = 1.0f; } lpCalcInfo->nErr = 0; }代码逻辑说明:SetPluginName负责把插件名写入通达信给的缓冲区;PluginInfo返回版本号、类型、名称和作者;GetInfo在最小实现里把所有值置0,不申请任何附加数据;Calc是干活的函数,先把输出数组整体清零,再逐根K线判断,最后把错误码置0。这样一通操作下来,通达信加载DLL时四个函数都能找到,选股时就有数据流回来了。
参数说明:nType在PluginInfo里填1是多数版本条件选股插件的约定,如果你的通达信版本加载后公式类型不对,改成0或2试一下,以公式管理器里注册成功后的类型为准。GetInfo的nNum字段不是K线数量,不要和CalcInfo里的nNum搞混,这里在最小实现里置0即可。
还有两个容易忽视的小点。一是szPluginName和szName的缓冲区长度,我保守地按MAX_NAME_LEN=64处理,但实际通达信给的缓冲区不一定有这么大,名字尽量短一点,不要超过32字节。二是strncpy之后手动补'\0',避免缓冲区没有结尾符导致通达信读取越界。
3.3 编译、检查导出符号并在通达信里加载
编译之前,我建议加一个.def文件,把要导出的函数名写死。原因是即使加了extern "C",不同编译器仍然可能在导出名上做手脚,用.def可以确保导出名和通达信查找的名字完全一致。
LIBRARY "MyDllSelect" EXPORTS SetPluginName PluginInfo GetInfo Calc在VS里把上面内容保存成MyDllSelect.def,加入工程,然后在项目属性 -> 链接器 -> 输入 -> 模块定义文件里填这个文件名,重新编译。
编译完成后,用Developer Command Prompt里自带的dumpbin工具检查导出符号:
dumpbin /exports MyDllSelect.dll输出里应该能看到SetPluginName、PluginInfo、GetInfo、Calc四个名字。如果看到的是?SetPluginName@@之类带装饰符的名字,说明extern "C"或.def没有生效,回到代码检查。这一步查一次只要几秒钟,能省掉后面在通达信里反复试错的大量时间。
加载到通达信:多数版本把DLL放到安装目录的T0002\dlls子目录下,然后在公式管理器里新建条件选股公式,公式类型选DLL/外部公式那一类,选择DLL文件,确认插件名和类型能识别出来。不同券商定制版菜单命名不一样,找不到就搜一下你用的版本里外部DLL入口,核心动作都是选DLL文件、识别导出函数这两步。加载成功之后,这个DLL会出现在条件选股公式列表里,可以直接执行测试。
4. 把均线多头排列条件写进DLL:完整Calc实现与参数说明
4.1 选股逻辑拆解与输出数组约定
上一章的演示代码只是验证整个链路通不通,真正要用于选股,得把条件写成具体的交易逻辑。我用一个最经典的例子:5日均线上穿10日均线,且10日均线大于20日均线,翻译成通达信公式就是CROSS(MA(C,5),MA(C,10)) AND MA(C,10)>MA(C,20)。
拆成三个条件:
- MA5在今天大于MA10;
- MA5在昨天小于等于MA10,即今天刚发生金叉;
- MA10本身大于MA20,说明短中期均线处于多头排列。
把这个逻辑写进DLL,关键不是均线计算本身,而是输出数组的判定节奏。通达信条件选股只看最后一根K线的结果,但历史选股和阶段选股会看整段数组。所以我在Calc里对每一根K线都做一次判定,pfOut[i]=1表示第i根K线满足条件,这样无论通达信只看最后一位还是做区间扫描,结果都是对的。
4.2 滑动窗口均线与金叉判断代码
均线计算我用滑动窗口,而不是每根K线都重新求和,复杂度从O(n*m)降到O(n)。K线数量通常在300根以内,两种写法性能都够,但滑动窗口写法能展示一个更稳的实现思路,后面如果你的选股条件里有更重的计算,可以直接套。
// 计算简单移动平均序列 // data: 原始价格数组,len: 数组长度,period: 均线周期 // 返回堆内存数组,使用后调用free释放;前period-1个位置置0 static float* BuildMA(const float* data, int len, int period) { float* ma = (float*)malloc(len * sizeof(float)); if (ma == NULL) return NULL; float sum = 0.0f; for (int i = 0; i < len; i++) { if (i < period - 1) { sum += data[i]; ma[i] = 0.0f; continue; } if (i == period - 1) { sum += data[i]; } else { sum += data[i] - data[i - period]; } ma[i] = sum / period; } return ma; } // 在Calc里完成均线金叉多头排列判断 extern "C" __declspec(dllexport) void WINAPI Calc(CalcInfo* lpCalcInfo) { if (lpCalcInfo == NULL || lpCalcInfo->pfClose == NULL || lpCalcInfo->pfOut == NULL) { if (lpCalcInfo) lpCalcInfo->nErr = -1; return; } int nNum = lpCalcInfo->nNum; for (int i = 0; i < nNum; i++) lpCalcInfo->pfOut[i] = 0.0f; const float* close = lpCalcInfo->pfClose; // 均线参数:短、中、长 const int MA_FAST = 5; const int MA_SLOW = 10; const int MA_TREND = 20; // 从第20根K线开始才有完整的三条均线 if (nNum < MA_TREND) { lpCalcInfo->nErr = 0; return; } float* maFast = BuildMA(close, nNum, MA_FAST); float* maSlow = BuildMA(close, nNum, MA_SLOW); float* maTrend = BuildMA(close, nNum, MA_TREND); if (maFast == NULL || maSlow == NULL || maTrend == NULL) { free(maFast); free(maSlow); free(maTrend); lpCalcInfo->nErr = -2; return; } for (int i = MA_TREND - 1; i < nNum; i++) { // 金叉:MA5从下方上穿MA10 int crossUp = (maFast[i] > maSlow[i]) && (maFast[i - 1] <= maSlow[i - 1]); // 多头排列:MA10在MA20之上 int bullAlign = (maSlow[i] > maTrend[i]); if (crossUp && bullAlign) lpCalcInfo->pfOut[i] = 1.0f; } free(maFast); free(maSlow); free(maTrend); lpCalcInfo->nErr = 0; }代码逻辑说明:BuildMA用滑窗累加,第二个窗口之后每次加一个新值、减一个旧值,均线数组就全部算出来了。Calc里先对输出数组整体清零,再计算三条均线,从第20根K线开始逐根判断金叉和多头排列。金叉判断用的是前后两根均线的相对位置变化,而不是均线本身的数值,这和通达信CROSS函数语义一致。
参数说明:MA_FAST、MA_SLOW、MA_TREND三个常量就是均线周期,想改成10日上穿20日,把这三个值改成10、20、60就行。周期改大以后,起始判断位置也要相应变大,代码里是从MA_TREND-1开始的,所以只要MA_TREND是三者中最大的,这个位置就是安全的。BuildMA里malloc失败会返回NULL,我在Calc里对三条均线的分配结果做了统一检查,任何一个失败就释放已分配的内存并返回-2,避免内存泄漏和空指针崩溃。
4.3 用通达信公式结果验证DLL选股
DLL写完之后,验证它算得对不对,最直接的办法是在通达信里建一个同名公式,和DLL跑同一批股票,对比结果。
在通达信公式编辑器里新建条件选股公式:
CROSS(MA(C,5),MA(C,10)) AND MA(C,10)>MA(C,20)然后用同一个选股范围分别跑公式选股和DLL选股,对两个结果列表做差集。正常情况下差集为空,两个方案选出的股票完全一致。
可能出现的差异和对应排查方向:
| 差异现象 | 可能原因 |
|---|---|
| DLL多选出股票 | 最后一根K线处理不一致,比如DLL把当天未收盘的数据也算进去了 |
| DLL少选出股票 | 起始位置不同,DLL从第20根开始,公式从有数据开始 |
| 结果完全不一致 | CalcInfo结构体定义错位,数据本身是错的 |
| 结果系统性偏差 | 界面复权设置与DLL收到的数据复权状态不一致 |
这里有个小技巧:通达信公式和DLL用的K线数据源相同,但公式在早期K线因为均线数据不足会不输出结果,DLL则把那一段置0,两者在选股只看最后一位的前提下没有影响。如果怀疑DLL数据错位,回到第二章写日志的方法,打印最后一根K线的收盘价和通达信界面显示值对比,这是最快定位手段。
另一个容易忽略的点是复权。通达信界面可能正在显示前复权数据,DLL接口传的数据未必跟随界面复权状态,两套结果对不上时优先排查这个,把界面复权方式切换一遍再跑一次对比。
5. 通达信DLL选股插件避坑:5个常见问题与排查方法
这个环节的经验来自我维护某跨平台系统兼容多个通达信版本时的踩坑记录。规则很简单:先确认DLL能被加载,再确认数据是准的,最后确认判定逻辑是对的,按这个顺序排查,绝大多数问题能在半小时内定位。下面五条是出现频率最高的。
5.1 DLL加载提示无效:运行时依赖与导出名问题
现象:在公式管理器里选好DLL文件后,通达信提示不是有效的DLL或直接没有任何反应;有时同一份DLL在某台机器能加载,换一台就失败。
原因分两类。一类是DLL依赖了目标机器没有的VC运行库,编译时用的是/MD动态运行库,导致加载时找不到MSVCP140.dll或VCRUNTIME140.dll。另一类是导出函数名不对,没加extern "C"或用C++编译器直接导出,函数名被修饰成带问号和@的形式,通达信按SetPluginName这个名字找不到函数。
解决:编译时项目属性里运行库选多线程(/MT),这是最省事的方案,DLL体积变大但不再依赖运行库。导出名问题用.def文件写死四个导出函数名,编译后用dumpbin /exports确认输出里的函数名是干净的。如果你拿到一个别人给的通达信DLL加载失败,先用dumpbin看它的导出函数,立刻就能判断是不是这一类。
另外检查一下有没有把DLL的文件名和工程名改来改去,.def里的LIBRARY名字和实际文件不一致不会导致加载失败,但会让部分版本在识别插件名时显示异常,保持三者一致最省心。
5.2 选股结果全是1或全是0:输出数组区间没写对
现象:DLL能正常加载,选股也能跑,但结果要么全市场都入选,要么一个都不入选。
原因:输出数组的写入区间和通达信的读取区间不一致。常见的写法是只在pfOut[nNum-1]写结果,但通达信做区间扫描时读的是整段数组;另一种是代码里把输出数组初始化成了1,导致没有条件命中时每根K线都满足。还有一种隐蔽情况:Calc被通达信对同一只股票调用多次,每次nNum长度不同,代码里用了上一次调用遗留的长度。
解决:Calc开头必须先把整个pfOut数组清零,再逐根K线写判定值。写完之后在通达信里用一个骁知肯定满足的股票做单股票测试,比如收盘价大于开盘价这种弱条件,确认DLL输出能选中它,再逐步收紧条件。如果单股票测试都选不中,优先怀疑pfOut指针地址或结构体偏移有问题,用日志打印pfOut[0]的写入值和读回值。
判断输出数组有没有写进去还有一招:在Calc里强制把pfOut[nNum-1]写成1.0f,如果这时候全市场都入选,说明输出通路没问题,问题在判定逻辑;如果仍然全不选,就往结构体和调用次数方向查。
5.3 K线数据明显错位:结构体字段定义不一致
现象:DLL能加载能跑,但算出来的均线值和通达信界面显示的MA5完全对不上,甚至出现最高价小于最低价这种不可能的数据。
原因:CalcInfo结构体字段顺序或字段类型和你使用的通达信版本不一致。流传的接口定义里,nNum和nErr有int和short两种版本,字段排列顺序也有差异。你的结构体定义错了,读pfClose实际读到的是pfOpen或其他字段,整个计算建立在错误数据上。
解决:做一次数据回显验证。Calc里用日志把nNum、pfOpen[0]、pfHigh[0]、pfLow[0]、pfClose[0]、pfClose[nNum-1]全部打印出来,逐项对照通达信界面。对不上就调整结构体定义,一次调一个字段,直到所有值吻合。这个验证值得在换版本、换机器后重做一遍,通达信版本升级通常不改接口,但券商定制版之间差别不小。
还有一点,float精度不用过度担心,通达信内部K线数据本身就是float,标记结果也只要区分0和非0,没必要用double和它较劲。
5.4 选股过程直接崩溃:32位/64位匹配问题
现象:DLL加载成功,但一开始选股通达信就闪退,或者一执行条件选股就报内存位置访问无效。
原因:最常见的是DLL位数和通达信进程不匹配。通达信客户端若为32位,DLL必须编译成x86;若为64位,DLL必须编译成x64。检查方法是用任务管理器看通达信进程,32位程序会显示通达信(32位)或在进程列表里有*32标记。另一个原因是结构体定义里用了pragma pack,改变了字段对齐方式,和通达信内部结构体对齐不一致,也会导致访问非法地址。
解决:确认通达信位数后,用相同位数重新编译DLL。不要用#pragma pack,除非你确认目标版本的CalcInfo本身就是紧凑对齐。崩溃问题的定位可以用Windows事件查看器里的应用程序日志,崩溃模块是MyDllSelect.dll就基本锁定了DLL内部问题,模块是通达信主程序则先检查位数和结构体。
如果事件查看器里看不到崩溃现场,用调试器直接把DLL挂上去。通达信进程启动前先启动调试器,设置为调试通达信,崩溃时能看到调用栈,能精确到Calc里哪一行访问非法。
5.5 自选股和条件选股结果对不上:K线数量与末根判定
现象:DLL在条件选股里选出的股票,和你在通达信里肉眼看到的K线状态不一致,比如明明刚金叉却没选出来。
原因:条件选股的K线数量和你在界面里看到的K线数量可能不一样。通达信给DLL传的是它内部缓存的一整段行情,包含上市以来或最近的N根K线,判断基于最后一根。如果你用选股范围限定在最近几天,通达信会从限定日期开始传数据,此时最后一根仍然是当前K线,但计算所需的前置K线可能不足。DLL里如果起始位置判断不够健壮,前置数据不足时输出0,结果就漏选了。
解决:Calc里对nNum做下限检查,当nNum小于所需最长期平时直接返回0并标记nErr正常,而不是越界计算。选股范围设置成全部或覆盖到足够长的前置区间,让DLL拿到完整数据。我在代码里从MA_TREND-1开始判断,就是给数据不足的情况留了缓冲,前19根K线无论如何不会产生误判。
最后留个习惯:每次改动DLL源码后,在通达信里用一个已知条件先做冒烟测试,比如收盘价大于开盘价,确认链路没断,再换成正式条件。这个习惯能帮你把逻辑改坏了和环境出问题了两类错误分开。
6. 进阶:DLL里写日志文件定位数据,再用外部数据桥接选股
6.1 在Calc里写调试日志:验证传入数据与结果
Calc每次被调用都是独立入参,非常适合在调试阶段往日志文件里打一行关键数据。我习惯在Calc开头和结尾各打开一次日志文件,写入nNum、首尾收盘价、输出最后一位,然后关闭。
FILE* fp = fopen("D:\\tdx_dll_debug.log", "a"); if (fp != NULL) { fprintf(fp, "nNum=%d first=%.2f last=%.2f out=%.2f\n", nNum, lpCalcInfo->pfClose[0], lpCalcInfo->pfClose[nNum - 1], lpCalcInfo->pfOut[nNum - 1]); fclose(fp); }路径一定要写绝对路径。通达信的工作目录不一定是安装目录,相对路径可能写到莫名其妙的位置,还容易被找不到。日志文件写完就关闭,虽然选股时会频繁打开关闭,但调试期间完全够用,定位完问题记得删掉这段代码。
6.2 用文件数据桥把外部因子读进选股DLL
选股条件如果你要接外部数据,比如自己算的另一个因子、从网络抓的数据或其他程序算好的评分,一个实用做法是文件桥:外部程序把因子值写入文本文件,DLL在Calc里读取后参与判定。
这里有个顺序问题要提前想清楚。CalcInfo里不一定能拿到当前在算哪只股票,所以文件里的因子顺序必须和通达信遍历股票池的顺序一致。我一般先跑一次空选股,通过日志确认股票遍历顺序,再按这个顺序生成因子文件。这个方案不优雅,但确实管用。
读文件放在首次Calc时加载到内存,避免每只股票都读一遍磁盘。用一个全局标记判断是否已加载,后续调用直接用缓存数组。文件打开失败时打印日志并用默认值,绝不能静默失败,否则整个选股结果会不明不白地偏掉。
我在做模拟项目X时,最初把因子文件名写错了一个字母,DLL的fopen返回空指针又没有任何提示,结果选股结果里所有股票都不入选,查了一天才定位到是文件路径问题。从那以后给自己定了个规矩:DLL里凡是打开外部文件,失败必须写日志、必须有默认值,绝不让程序无声失败,这个习惯帮我挡掉了后面很多次半夜的排查。希望帮到你。
本文还有配套的精品资源,点击获取