简介:这是一份面向股票软件二次开发者的C语言接口模板,用于在大智慧新一代行情软件中编写外部扩展函数。资源以WIN32动态链接库形式实现,适合具备一定C/C++基础、希望为大智慧公式系统添加自定义功能的开发者参考使用。压缩包共6个文件,包含2个cpp源文件、2个h头文件以及dsw、dsp工程文件,整体约6KB,体量轻巧,工程结构完整,可直接用VC打开编译。其中源文件承载扩展函数的具体实现逻辑,头文件负责接口声明与常量定义,工程文件则保留了原始开发环境配置,便于快速还原编译条件。目前已有1860人学习下载,说明该接口模板在股票软件扩展开发领域具有一定参考价值。读者可借此了解大智慧外部函数的注册方式、参数传递约定与动态库导出规范,并在此基础上替换或新增自定义函数,快速搭建属于自己的公式扩展工程,减少从零摸索接口细节的时间成本。
1. 拆开一个行情软件的 C 接口:它到底能拿到什么
如果你做过程序化交易或者行情工具,大概率遇到过这种局面:看盘软件里指标跑得好好的,想把它接到自己的 C++ 程序里做批量计算、回测或者信号推送,结果发现官方只给了个公式编辑器,数据出不来。我最近拆的就是这么一份资源——大智慧新一代的 C 语言接口,核心是一套 C/C++ 头文件和配套的动态库调用约定,让你能在自己的工程里直接调用行情公式引擎,把原本锁在客户端里的公式计算结果取出来。
它解决的不是“看行情”的问题,而是“把行情软件的公式能力变成你程序里的一个函数”。适合两类人:一类是做量化回测、需要批量跑自定义指标的开发者;另一类是做行情中转、想把公式信号接进自己系统的工程师。前提是你得熟悉 C/C++ 的编译链接流程,纯脚本选手上手会有点磕绊。下面按“接口是什么 → 怎么接进去 → 参数怎么调 → 哪里会翻车”的顺序拆。
2. 接口的调用模型:从公式引擎到你的 C++ 工程
2.1 这套接口的本质是“宿主 + 回调”
大智慧新一代的公式系统本身是一个独立的计算引擎,接口做的事情是把这个引擎以动态库的形式暴露出来,你的程序作为宿主去加载它、喂数据、取结果。常见做法是三步:初始化引擎上下文、注册数据回调、调用公式计算函数。接口头文件里一般会定义几类东西——引擎句柄类型、数据结构的结构体、以及一组导出函数。
理解这个模型很关键,因为它决定了你的程序结构。你不能像调一个普通数学库那样“传参进去、拿结果出来”,而是要先让引擎“活”起来,它会在计算过程中反过来向你要数据(比如某只标的某段周期的开高低收),你通过注册的回调把这些数据喂回去。这个反向依赖是后面很多坑的根源。
从选型角度看,为什么用 C 接口而不是直接读客户端的数据文件?因为文件格式是私有的、随版本变动,而接口是官方约定的稳定层。代价是你必须接受它的调用节奏,不能随心所欲地并发。
2.2 最小可运行骨架
下面是一个典型的接入骨架,语言用 C++,展示加载、初始化、注册回调、调用公式的完整链路。函数名和结构体字段以实际头文件为准,这里给的是通用形态。
#include "dzx_engine.h" // 接口头文件,实际名称以资源包为准 #include <cstdio> #include <vector> // 1. 数据回调:引擎向宿主索要行情数据时被调用 // symbol: 标的代码, period: 周期, field: 字段类型, buf: 输出缓冲, count: 请求条数 static int OnRequestData(const char* symbol, int period, int field, double* buf, int count, void* user) { // user 是注册时透传的上下文指针,通常指向你的数据源对象 auto* src = static_cast<MyDataSource*>(user); // 从本地缓存/数据库取出 count 条数据填进 buf return src->Fill(symbol, period, field, buf, count) ? count : 0; } int main() { // 2. 创建引擎实例 DzxHandle engine = Dzx_Create(); if (!engine) { printf("engine create failed\n"); return -1; } // 3. 注册回调,user 指针把上下文带进去 MyDataSource src; Dzx_SetDataCallback(engine, OnRequestData, &src); // 4. 加载公式(公式源码或已编译的公式标识) if (Dzx_LoadFormula(engine, "MA(CLOSE,5)") != 0) { printf("formula load failed\n"); Dzx_Destroy(engine); return -1; } // 5. 触发计算,结果通过输出缓冲返回 double out[256]; int n = Dzx_Execute(engine, "600000", 0 /*日线*/, out, 256); for (int i = 0; i < n; ++i) printf("%.4f\n", out[i]); // 6. 释放 Dzx_Destroy(engine); return 0; }逻辑说明:Dzx_Create拿到引擎句柄,这是所有后续调用的入口;Dzx_SetDataCallback把宿主的数据供给能力和引擎绑定,user指针是唯一能把你的对象传进回调的通道,别丢;Dzx_LoadFormula负责把公式文本或标识交给引擎解析;Dzx_Execute是同步阻塞调用,返回实际写入的条数。
参数说明:period一般用整数枚举表示周期(0 日线、1 周线等,具体映射看头文件);field区分开高低收、成交量等字段;count是引擎一次请求的条数,可能大于你单次能给的量,回调里要处理“给不满”的情况并返回实际条数。out缓冲大小要留够,公式里如果有跨周期引用,实际需要的条数会比你想的多。
提示:回调是同步的,引擎在
Dzx_Execute内部会反复调它。回调里不要做重 IO,否则整个计算会被拖死。
3. 数据回调与公式参数:把结果调准的几个关键设置
3.1 回调里最容易搞错的三件事
第一件是数据对齐。引擎请求的周期和字段是组合出现的,你返回的数据必须和请求的symbol + period + field严格对应,错一个字段,公式结果就是错的,而且不会报错,只会静默算歪。我一般会在回调入口加一层断言,把请求参数打日志,第一次接入时逐条核对。
第二件是数据长度。引擎可能一次要 500 条,你本地只有 300 条,这时候不能返回 0 了事,也不能硬凑。正确做法是返回实际条数,引擎会按你给的长度调整计算窗口。如果公式需要的前置数据不足,结果会从有效位置开始,前面是无效值。
第三件是复权处理。行情数据有前复权、后复权、不复权之分,公式引擎默认按你喂进去的数据算,它不知道你用的是哪种。如果你的回测和客户端显示对不上,八成是复权口径不一致。常见做法是在回调里统一按前复权输出,并在配置里固定下来。
3.2 公式加载与参数传递
公式加载有两种常见形态:一种是直接传公式文本,引擎现场解析;另一种是传已编译的公式标识,加载更快但需要先用配套工具编译。文本方式适合调试,编译方式适合生产。
参数传递方面,很多公式带外部参数(比如MA(CLOSE, N)里的 N)。接口一般提供设置参数值的函数,或者允许你在公式文本里直接写死。生产环境建议用参数设置接口,避免每次改参数都要重新加载公式。
// 设置公式外部参数:第 0 个参数设为 10 Dzx_SetParam(engine, 0, 10.0); // 重新执行,结果会按新参数计算 int n = Dzx_Execute(engine, "600000", 0, out, 256);参数说明:Dzx_SetParam的第一个整数是参数序号,从 0 开始,对应公式里第几个外部参数;第二个是值。注意参数设置后需要重新执行才生效,且不同公式的参数序号是独立的,切换公式后要重新设。
3.3 周期与字段的映射表
不同版本的接口对周期和字段的枚举值可能不同,下面是一份常见映射,实际以你拿到的头文件为准。
| 周期枚举 | 含义 | 字段枚举 | 含义 |
|---|---|---|---|
| 0 | 日线 | 0 | 开盘 |
| 1 | 周线 | 1 | 最高 |
| 2 | 月线 | 2 | 最低 |
| 3 | 分钟线 | 3 | 收盘 |
| 4 | 季线 | 4 | 成交量 |
这张表建议直接抄进你的代码注释里,回调调试时对着看,比翻头文件快。字段顺序如果搞反,最高价当最低价用,公式结果会离谱到你自己都不敢信。
4. 避坑与排查:接口接入时最常见的五类翻车
4.1 加载动态库失败,报找不到符号
现象:程序启动时Dzx_Create返回空,或者加载库直接失败,提示找不到某个导出函数。原因通常是库文件架构不匹配(32 位程序加载 64 位库,或反之),或者库依赖的其他运行库缺失。解决:先用工具确认库的位数和依赖,确保和你的工程一致;把库放在可执行文件同目录或系统搜索路径下;如果是 C++ 导出,注意名字修饰问题,接口一般用extern "C"包裹,你的包含方式要对。
4.2 公式能加载但结果全是零
现象:Dzx_Execute返回了条数,但缓冲里全是 0 或无效值。原因多半是回调没被正确触发,或者回调返回了 0 条数据。解决:在回调入口打日志,确认它被调用了;检查user指针是否为空;确认symbol和period在你的数据源里确实有数据。还有一种可能是公式本身引用了你没提供的字段,引擎拿不到数据就按空算。
4.3 结果和客户端显示对不上
现象:同一只标的、同一段周期,你的程序算出来和客户端里的指标值有偏差。原因按概率排:复权口径不一致、数据条数不够导致前置窗口不足、周期映射搞错、字段顺序颠倒。解决:先固定复权方式,再核对数据条数是否满足公式最大回溯需求,最后逐字段比对。我一般会拿一段已知结果的数据做单元测试,把偏差定位到具体字段。
4.4 多线程调用崩溃
现象:单线程跑得好好的,一开多线程就随机崩溃或结果错乱。原因:引擎句柄通常不是线程安全的,一个句柄同时被多个线程执行会踩内存。解决:每个线程创建独立引擎实例,或者用锁把执行串行化。独立实例更稳,代价是内存占用上升。别想着共享一个句柄省资源,这是血泪经验。
4.5 长时间运行内存持续上涨
现象:程序跑几个小时内存一直涨。原因:每次Dzx_LoadFormula或Dzx_Execute可能在内部分配了缓存,如果公式频繁切换而不释放,就会累积。解决:公式加载一次后复用,不要放在循环里反复加载;确认Dzx_Destroy在退出时被调用;如果接口提供单独的公式释放函数,切换公式前先释放旧的。
注意:排查这类问题,日志比调试器好用。把每次回调的请求参数和执行返回条数记下来,出问题时一眼能看出是哪一环断了。
5. 进阶用法:把接口包成你自己的行情服务
接口本身只是能力,真正好用是把包一层。我一般会做三件事:把引擎实例封装成一个类,内部管理生命周期;把回调的数据源抽象成接口,方便换成本地缓存或远程数据;把公式执行结果做一层缓存,相同参数短时间内不重复算。
class FormulaService { public: FormulaService() { engine_ = Dzx_Create(); } ~FormulaService() { if (engine_) Dzx_Destroy(engine_); } // 禁止拷贝,避免句柄被复制后双重释放 FormulaService(const FormulaService&) = delete; FormulaService& operator=(const FormulaService&) = delete; bool Load(const std::string& formula) { return Dzx_LoadFormula(engine_, formula.c_str()) == 0; } std::vector<double> Run(const std::string& symbol, int period) { std::vector<double> out(512); int n = Dzx_Execute(engine_, symbol.c_str(), period, out.data(), 512); out.resize(n > 0 ? n : 0); return out; } private: DzxHandle engine_ = nullptr; };逻辑说明:构造函数创建引擎,析构函数释放,RAII 保证不泄漏;禁用拷贝是因为句柄复制会导致双重释放;Run把裸缓冲包成vector,调用方不用管内存。参数说明:缓冲大小 512 是经验值,公式回溯越长需要越大,可以按公式的最大周期数乘以一个系数来定。
验证方法上,我习惯用一组已知输入的公式做基准测试:拿一段固定历史数据,手工算出预期结果,跑接口对比。偏差在浮点误差范围内才算通过。这套基准用例留着,以后换版本、换库都能快速回归。
从那以后我每次接这类接口,都强制先跑一遍最小骨架加基准用例,确认数据链路通了再往上堆业务逻辑。希望帮到你。
本文还有配套的精品资源,点击获取