通达信缠论DLL开发:C++编译、x86兼容与指标集成指南
2026/9/14 4:48:47 网站建设 项目流程

简介:本资源为通达信平台缠论技术分析功能的C++ DLL模块开源实现,面向量化交易开发者、证券软件二次开发工程师及缠论算法研究者,提供可嵌入通达信客户端的底层指标计算逻辑。源码基于VC++6.0开发,完整呈现缠论笔、线段、中枢等核心结构的C++建模与递归判定逻辑,适合作为学习经典技术分析算法工程化落地的实践范例。压缩包为RAR格式,共含若干C++源文件(.cpp/.h)、项目工程文件(.dsp/.dsw)及编译说明,总大小891KB,结构紧凑,便于快速编译调试与逻辑验证。已有1201人学习下载,读者可直接获取可运行的DLL接口定义、缠论各级别走势分解的完整状态机实现、以及适配通达信数据结构的封装层代码,对理解技术指标DLL开发规范与缠论自动化识别具有较强参考价值。

1. 缠论DLL不是“插件包”,而是通达信指标生态里可编译、可调试、可嵌入的C++计算内核

很多刚接触通达信量化开发的人,看到“缠论DLL”第一反应是下载一个现成的.dll文件双击安装——结果发现根本无法加载,或者加载后指标不显示、K线不识别、分笔出错。这不是你电脑缺VC运行库,也不是通达信版本太低,而是从根本上混淆了“DLL”的角色:它不是封装好的黑盒工具,而是用C++实现缠论核心逻辑(如分笔、画线、中枢识别、走势类型判定)的动态链接库,必须与通达信的指标调用协议严格对齐,且需在Visual Studio中编译为x86架构、/MT静态链接、无CRT依赖的纯Win32 DLL。这类源码通常由资深交易者或C++工程师维护,比如标题中提到的southern977perhapsryf,他们公开的代码不是为了“一键导入”,而是提供可审计、可定制、可对接实盘信号的底层计算能力。适合三类人:想把缠论规则真正落地为自动识别逻辑的量化开发者;需要在通达信主图/副图中嵌入高精度缠论结构标记的策略研究员;以及正在从公式语言(TDX公式)转向C++高性能计算的进阶用户。它解决的不是“怎么画线”,而是“为什么这样画线才符合缠论原文定义”。


2. 用Visual Studio 2019编译缠论C++源码生成x86通达信兼容DLL的最小可行流程

通达信所有版本(含最新V7.85)仅支持x86架构DLL,且明确要求导出函数使用__stdcall调用约定、无C++异常传播、不依赖外部DLL(如MSVCP140.dll)。这意味着你不能直接用CMakeLists.txt生成默认配置的DLL,也不能用MinGW或Clang编译。必须用Visual Studio原生工具链,并手动关闭所有可能引入运行时依赖的选项。

2.1 创建空DLL项目并配置关键编译参数

打开Visual Studio 2019 → 新建项目 → “动态链接库(DLL)” → 项目名设为ChanLunCore→ 目标平台选“x86”(不是x64!)→ 点击“创建”。
进入项目属性页(右键项目 → 属性),按顺序修改以下五项:

  • 配置属性 → 常规 → 字符集:设为“使用多字节字符集”(不是Unicode)
  • 配置属性 → C/C++ → 代码生成 → 运行时库:设为/MT(多线程,静态链接)
  • 配置属性 → 链接器 → 高级 → 导入库:清空该字段(避免生成.lib)
  • 配置属性 → 链接器 → 高级 → 入口点:填入DllMain(确保Windows加载时能初始化)
  • 配置属性 → 链接器 → 输入 → 忽略所有默认库:设为“是”(/NODEFAULTLIB)

提示:/MT是硬性要求。若用/MD(动态链接CRT),通达信加载时会报OSERROR: [WinError 1114] 动态链接库(DLL)初始化例程失败——这不是DLL损坏,而是CRT初始化冲突。/NODEFAULTLIB则防止链接器偷偷拉入kernel32.lib等非必要依赖。

2.2 实现通达信DLL标准导出接口:GetIndicatorData

通达信通过固定函数名调用DLL,最核心的是GetIndicatorData。它接收K线数据指针、周期参数、输出缓冲区地址,返回计算结果。以下是精简但完整的实现骨架(需替换为你实际的缠论逻辑):

// ChanLunCore.cpp #include "stdafx.h" #include <vector> #include <algorithm> // 通达信要求的导出函数签名(必须__stdcall,参数顺序不可变) extern "C" __declspec(dllexport) int __stdcall GetIndicatorData( double* pOpen, // 开盘价数组,按时间倒序(最新在前) double* pHigh, // 最高价 double* pLow, // 最低价 double* pClose, // 收盘价 double* pVolume, // 成交量 int nCount, // K线总数(通常≥500) double* pOutBuffer, // 输出缓冲区,长度由调用方分配 int nOutLen, // 输出缓冲区总长度(单位:double) int nParam1, // 用户自定义参数1(如分笔阈值) int nParam2, // 用户自定义参数2(如中枢震荡次数) int nParam3, // 用户自定义参数3(如走势类型过滤开关) char* pStrParam // 字符串参数(如“笔起点标记”) ) { if (nCount < 3 || !pOutBuffer || nOutLen < 100) return -1; // 步骤1:将倒序K线转为正序(缠论计算需从左到右遍历) std::vector<double> open(pOpen, pOpen + nCount); std::reverse(open.begin(), open.end()); // 同理处理high/low/close/volume... // 步骤2:执行缠论分笔(示例:简单顶底分型+包含处理) std::vector<int> biStarts; // 存储笔起点索引(正序索引) for (int i = 2; i < nCount - 2; ++i) { bool isTop = (open[i-2] < open[i-1]) && (open[i-1] < open[i]) && (open[i] > open[i+1]) && (open[i+1] > open[i+2]); bool isBottom = (open[i-2] > open[i-1]) && (open[i-1] > open[i]) && (open[i] < open[i+1]) && (open[i+1] < open[i+2]); if (isTop || isBottom) biStarts.push_back(i); } // 步骤3:写入输出缓冲区(通达信要求:每组数据占3个double,[x,y,value]) int writePos = 0; for (size_t j = 0; j < biStarts.size() && writePos + 3 <= nOutLen; ++j) { int idx = biStarts[j]; // 正序索引 pOutBuffer[writePos++] = static_cast<double>(idx); // X坐标(K线索引) pOutBuffer[writePos++] = open[idx]; // Y坐标(价格) pOutBuffer[writePos++] = 1.0; // value(标记类型,1=笔起点) } return writePos / 3; // 返回有效点数 }
参数说明与通达信调用逻辑:
  • pOpen等数组是倒序排列(最新K线在索引0),这是通达信为兼容历史数据设计的约定,必须反转后再做缠论分析;
  • nOutLen是调用方预分配的double数量,不是字节数,每个绘图点需3个double(X,Y,Value),因此最大可写点数为nOutLen/3
  • nParam1~3对应通达信公式中的PARAM#1PARAM#2等,在TDX公式里写DRAWICON(REF(C,1)>REF(C,2) AND ... , L, 10)时,10即传给nParam1
  • pStrParam用于传递字符串指令,如"DRAWLINE"表示启用连线,"NO_LABEL"表示禁用文字标注。

2.3 添加DllMain确保线程安全与资源隔离

通达信可能在多个窗口同时加载同一DLL,必须保证全局状态不冲突。DllMain是唯一安全的初始化入口:

// DllMain.cpp #include "stdafx.h" // 每个线程独立的缠论计算上下文(避免static变量跨线程污染) __declspec(thread) struct ChanContext { std::vector<int> lastBiPoints; int lastCalcTime; } g_threadContext; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // 进程加载时,可初始化全局只读资源(如小数点精度表) break; case DLL_THREAD_ATTACH: // 线程加载时,清空本线程上下文 g_threadContext.lastBiPoints.clear(); g_threadContext.lastCalcTime = 0; break; case DLL_THREAD_DETACH: // 线程卸载时,释放本线程独占资源 g_threadContext.lastBiPoints.clear(); break; case DLL_PROCESS_DETACH: // 进程卸载时,释放全局资源(如有) break; } return TRUE; }

注意:通达信指标计算是单线程调用,但DllMain仍需按规范实现DLL_THREAD_ATTACH/DETACH,否则在多窗口场景下可能出现内存越界。__declspec(thread)是Windows特有语法,确保每个线程有独立副本,比static更安全。


3. 在通达信中注册并调用缠论DLL:TDX公式语法与调试定位技巧

编译生成的ChanLunCore.dll不能直接拖入通达信目录,必须通过TDX公式显式声明调用路径,并严格匹配参数类型与返回值。通达信的DLL调用机制是“弱类型”设计,错误不会报详细异常,只会静默失败或返回0,因此调试必须前置。

3.1 TDX公式中声明DLL函数并绑定参数

在通达信公式编辑器中新建公式,输入以下内容(注意路径、函数名、参数个数必须与C++源码完全一致):

// 缠论笔识别主图指标 {参数} N1:=10; // 分笔波动阈值(%) N2:=3; // 中枢最小段数 N3:=1; // 1=启用笔标记,0=仅计算不绘图 // 声明DLL函数(路径必须是绝对路径或相对通达信根目录) DLLName:='ChanLunCore.dll'; FuncName:='GetIndicatorData'; // 调用DLL(参数顺序:OPEN,HIGH,LOW,CLOSE,VOLUME, COUNT, OUTPUT_BUFFER, OUTPUT_LEN, PARAM1, PARAM2, PARAM3, STR_PARAM) Result := CALLDLL(DLLName, FuncName, OPEN, HIGH, LOW, CLOSE, VOL, DATACOUNT, REF(BI_BUFFER,1), 1000, N1, N2, N3, 'DRAWICON'); // 解析返回结果(每个点占3个元素:X,Y,Value) IF Result > 0 THEN BEGIN FOR I := 0 TO Result-1 DO BEGIN X := REF(BI_BUFFER,1)[I*3+0]; // K线索引(正序) Y := REF(BI_BUFFER,1)[I*3+1]; // 价格 V := REF(BI_BUFFER,1)[I*3+2]; // 类型 IF V = 1 THEN DRAWICON(X, Y, 10); // 画笔起点图标(图标ID 10) END; END;
关键细节解析:
  • REF(BI_BUFFER,1)BI_BUFFER是公式中预定义的ARRAY型变量(长度≥1000),REF(...,1)取其上一根K线的值,确保DLL写入时缓冲区已分配;
  • DATACOUNT:通达信内置函数,返回当前周期K线总数,不是BARPOS(BARPOS是当前K线索引,从0开始);
  • 'DRAWICON'字符串参数:C++代码中用strcmp(pStrParam, "DRAWICON")==0判断是否启用图标绘制;
  • 图标ID10:对应通达信内置图标库,DRAWICON(X,Y,10)在坐标(X,Y)处画一个小三角形,常用于标记笔起点。

3.2 定位DLL加载失败的三大日志入口

当指标不显示、CALLDLL返回-1或0时,不要盲目重装VC运行库,按以下顺序排查:

排查层级操作方法典型现象与原因
文件层ChanLunCore.dll复制到TdxW.exe同目录(如C:\tdx\Trade\),用Dependency Walker(x86版)打开DLL,检查右侧列表是否只有KERNEL32.dllUSER32.dll等系统DLL若出现MSVCP140.dllVCRUNTIME140.dll,说明未设/MT,需回VS重新编译
注册层在通达信公式编辑器中,点击“测试”按钮,观察右下角状态栏是否提示“DLL加载成功”若提示“找不到指定模块”,检查DLL路径是否含中文、空格或长路径(建议放根目录)
函数层在C++代码GetIndicatorData开头插入OutputDebugStringA("DLL called");,用DebugView(Sysinternals工具)捕获输出若无任何输出,说明通达信根本未调用该函数——检查FuncName拼写、大小写、是否多空格

提示:通达信的CALLDLL函数在x86模式下对函数名大小写敏感,getindicatordataGetIndicatorData被视为不同函数。务必与__declspec(dllexport)声明的名称完全一致。


4. 缠论DLL性能优化:减少内存拷贝、规避浮点精度陷阱、适配通达信实时行情流

通达信实时行情推送频率可达毫秒级,而缠论计算(尤其中枢递归)复杂度为O(n²),若每次Tick都全量重算500根K线,CPU占用会飙升至30%以上。必须采用增量计算与缓存策略,且警惕通达信数据精度导致的分型误判。

4.1 实现K线增量更新机制:只重算最后N根

通达信在实时行情中,DATACOUNT每新来一根K线就+1,但旧K线数据不变。因此无需每次调用都处理全部K线,只需关注新增部分:

// 在GetIndicatorData中添加增量逻辑 static int s_lastCount = 0; static std::vector<int> s_cachedBiPoints; if (nCount > s_lastCount) { // 新增K线数 int newBars = nCount - s_lastCount; // 只对最后200根K线做分笔(覆盖至少3个笔的长度) int startIdx = std::max(0, nCount - 200); // 从startIdx开始重算,复用s_cachedBiPoints中旧结果 std::vector<int> newBiPoints = CalculateBiFrom(startIdx, nCount, ...); // 合并:保留旧缓存中未被新计算覆盖的部分 + 新增点 s_cachedBiPoints.clear(); s_cachedBiPoints.insert(s_cachedBiPoints.end(), s_cachedBiPoints.begin(), s_cachedBiPoints.begin() + (startIdx > 0 ? startIdx : 0)); s_cachedBiPoints.insert(s_cachedBiPoints.end(), newBiPoints.begin(), newBiPoints.end()); s_lastCount = nCount; } // 输出s_cachedBiPoints...
参数选择依据:
  • 200是经验值:缠论一笔至少5根K线,一个标准中枢需至少9根(3段),200根可覆盖约40笔,足够应对日线/30分钟线;
  • startIdx动态计算,避免固定偏移导致漏判(如跳空缺口后分型位置偏移);
  • 缓存std::vector而非全局数组,利用RAII自动管理内存,防止DllMainDLL_PROCESS_DETACH时野指针。

4.2 处理通达信价格精度陷阱:用整数比替代浮点比较

通达信内部价格以double存储,但实际精度仅到小数点后2~3位(取决于品种)。直接用>比较pHigh[i] > pHigh[i-1]在极小波动时可能因浮点误差失效:

// 错误写法(受浮点舍入影响) if (pHigh[i] > pHigh[i-1] && pLow[i] > pLow[i-1]) { /* 上移中枢 */ } // 正确写法:转换为整数比较(假设价格精度为0.01) long long high1 = static_cast<long long>(pHigh[i-1] * 100 + 0.5); long long high2 = static_cast<long long>(pHigh[i] * 100 + 0.5); if (high2 > high1) { /* 确认真实上涨 */ }
精度映射表(通达信常见品种):
品种类型价格单位推荐放大倍数示例:10.23410234
A股股票0.01元×10010.234 * 100 = 1023(截断)→ 应+0.5四舍五入
期货主力0.001元×10002345.678 * 1000 = 2345678
港股通0.001港币×1000同上
加密货币0.00000001 BTC×100000000long long容纳

注意:+0.5是关键。static_cast<long long>(x * 100)直接截断,x=10.235会变成1023而非1024,加0.510.235*100+0.5=1024.0,再转long long得正确值。

4.3 适配通达信通讯协议:接收Level2逐笔委托数据

部分高级缠论实现需结合逐笔成交(Level2)判断买卖力衰竭,此时DLL需主动连接通达信行情服务器。通达信提供TDXTcpClient类(需引用TdxApi.h),但必须在DLL中单独开线程接收,不能阻塞GetIndicatorData主线程

// 在DllMain DLL_PROCESS_ATTACH中启动接收线程 HANDLE hRecvThread = CreateThread(NULL, 0, [](LPVOID) -> DWORD { TDXTcpClient client; client.Connect("127.0.0.1", 7709); // 通达信Level2端口 while (g_running) { TdxPacket pkt; if (client.RecvPacket(&pkt)) { // 解析逐笔数据,更新买卖盘强度缓存 UpdateBuySellStrength(pkt); } } return 0; }, NULL, 0, NULL);
安全约束:
  • g_running为全局volatile bool,DLL_PROCESS_DETACH时置falseWaitForSingleObject(hRecvThread, 1000)
  • TDXTcpClient必须静态链接(#pragma comment(lib, "TdxApi.lib")),避免运行时DLL缺失;
  • 逐笔数据不参与GetIndicatorData主计算流,仅作为pStrParam"USE_LEVEL2"开关的辅助信号源。

5. 验证缠论DLL输出正确性的三步交叉校验法:用Python解析二进制缓冲区

通达信不提供DLL内部变量查看功能,CALLDLL返回的BI_BUFFER是二进制double数组,肉眼无法验证计算是否符合缠论原文定义(如“连续三段重叠构成中枢”)。必须脱离通达信环境,用Python独立解析输出缓冲区,与手工标注对比。

5.1 提取通达信输出缓冲区到文件

在TDX公式中添加调试语句,将BI_BUFFER内容导出为二进制文件:

// 在CALLDLL后添加 IF Result > 0 THEN BEGIN // 将BI_BUFFER前Result*3个double写入文件 FileName := 'C:\tdx\debug\bi_output.bin'; FileWrite(FileName, REF(BI_BUFFER,1), Result*3*8); // 8字节/double END;

5.2 Python脚本解析并可视化笔结构

import numpy as np import matplotlib.pyplot as plt # 读取二进制文件(double数组) with open(r'C:\tdx\debug\bi_output.bin', 'rb') as f: data = np.frombuffer(f.read(), dtype=np.float64) # 每3个元素为一组:[X索引, Y价格, Value类型] points = data.reshape(-1, 3) print(f"共解析 {len(points)} 个笔点") # 过滤Value==1的笔起点 bi_starts = points[points[:, 2] == 1.0] print(f"笔起点:{bi_starts.shape[0]} 个,索引范围 {bi_starts[:,0].min():.0f} ~ {bi_starts[:,0].max():.0f}") # 绘制验证图(需配合原始K线数据) plt.figure(figsize=(12,6)) plt.plot(range(int(bi_starts[:,0].max())+10), np.random.rand(int(bi_starts[:,0].max())+10)*10+10, 'k-', alpha=0.3) # 模拟K线 plt.scatter(bi_starts[:,0], bi_starts[:,1], c='r', s=50, zorder=5, label='笔起点') plt.xlabel('K线索引(正序)') plt.ylabel('价格') plt.legend() plt.grid(True) plt.title('缠论DLL输出笔起点验证') plt.show()
校验逻辑:
  • bi_starts[:,0]应为递增整数序列(如[5, 12, 23, 35,...]),若出现重复索引或乱序,说明C++中biStarts向量未按时间顺序push;
  • bi_starts[:,1]价格值应与通达信主图对应K线的LOWHIGH吻合(笔起点必为顶或底分型);
  • 数量级应合理:日线图上500根K线通常产生30~80个笔,若仅2~3个,大概率是分型识别阈值nParam1设得过大。

5.3 对照缠论原文关键条款反向验证

最后一步,拿输出结果对照《缠论》原文第三课“走势分解”中的硬性条件:

缠论原文条款DLL应满足的代码逻辑验证失败时的修复方向
“笔必须有顶分型和底分型构成”GetIndicatorDatabiStarts必须交替出现顶/底标识(可用pStrParam传入"MARK_TYPE"让DLL返回类型)在C++中增加isTop/isBottom标志位,输出时第4个double存类型
“中枢至少由三个连续次级别走势重叠”若DLL支持中枢计算,pOutBuffer中应有Value==2的中枢区间点,且X坐标跨度≥9检查中枢识别循环是否用了for (int i=0; i<nCount-8; i++)确保覆盖9根K线
“走势类型完成需离开中枢”笔终点价格与中枢区间无重叠,可用std::set_intersection验证在C++中增加中枢区间缓存,计算笔终点时做区间判断

提示:真正的缠论DLL不是“画线工具”,而是可验证的数学结构提取器。每一次CALLDLL调用,都应能回答:“这个笔,是否满足原文第X章第Y条定义?”——这才是southern977perhapsryf开源代码的核心价值。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询