通达信外部信号显示教程
环境:通达信 v7.72,Python 3.x,安装路径
C:/new_tdx64/
一、方法概览
将外部 Python 生成的 B/S 信号显示在通达信 K 线图上,主要有以下几种方式:
| 方法 | 函数 | 原理 | 状态 |
|---|---|---|---|
| ① TQ 策略接口 | SIGNALS_TQ(ID, TYPE) |
Python 通过 tqcenter 推送数据到通达信,公式读取 | ✅ 已验证可用 |
| ② 自定义数据(序列) | SIGNALS_USER(ID, IDX) |
Python 写二进制 .dat 文件,通达信读取 | ⏳ 待验证(日线级可用,分钟级待确认) |
| ③ 扩展数据 | EXTDATA_USER(编号) |
扩展数据管理器,每股一个快照值,无时间轴 | ⏳ 适合截面数据,不适合分时信号 |
| ④ DLL 外部指标 | TDXDLL1(nFuncMark,...) |
编写 C DLL,通达信加载,DLL 读 Python 写的 CSV 文件 | ✅ 已验证可用,实时生效无需手动刷新 |
二、方法一:TQ 策略接口(已验证)
2.1 原理
Python 脚本(外部)
↓ tqcenter.tq.send_bt_data()
通达信 TQ 策略管理器(内部运行)
↓ SIGNALS_TQ(ID, TYPE)
K 线图公式显示 B/S 信号
关键点: Python 脚本必须通过通达信的 TQ策略管理器 运行,不能直接在外部终端执行。
2.2 前置条件
- 通达信支持 TQ 插件,
C:/new_tdx64/PYPlugins/user/tqcenter.py(安装之后这个文件存在就符合要求) - 通达信处于登录状态(进入行情界面)
- 顶部菜单栏有 TQ策略 菜单
2.3 第一步:编写 Python 策略脚本
将脚本放到 C:/new_tdx64/PYPlugins/user/ 目录下。
示例:send_001896_s_1007_tq.py(支持全周期显示)
from datetime import datetime, time, timedelta
from tqcenter import tq
STOCK_CODE = "001896.SZ"
TRADE_DATE = "20260401"
SIGNAL_TIME = datetime.strptime("20260401 10:07", "%Y%m%d %H:%M")
def _build_bars(trade_date, step_minutes, sessions):
d = datetime.strptime(trade_date, "%Y%m%d").date()
out = []
for st, et in sessions:
cur = datetime.combine(d, st)
end_dt = datetime.combine(d, et)
while cur <= end_dt:
out.append(cur)
cur += timedelta(minutes=step_minutes)
return out
def build_1m(td):
return _build_bars(td, 1, [(time(9,31), time(11,30)), (time(13,1), time(15,0))])
def build_5m(td):
return _build_bars(td, 5, [(time(9,35), time(11,30)), (time(13,5), time(15,0))])
def build_15m(td):
return _build_bars(td, 15, [(time(9,45), time(11,30)), (time(13,15), time(15,0))])
def build_30m(td):
return _build_bars(td, 30, [(time(10,0), time(11,30)), (time(14,0), time(15,0))])
def build_60m(td):
d = datetime.strptime(td, "%Y%m%d").date()
return [datetime.combine(d, t) for t in
[time(10,30), time(11,30), time(14,0), time(15,0)]]
def send_period(bars, signal_dt, label):
"""找到包含信号时间的 bar,发送该周期数据。"""
signal_bar = next((b for b in bars if b >= signal_dt), None)
if signal_bar is None:
print(f"[{label}] no matching bar")
return
time_list = [b.strftime("%Y%m%d%H%M%S") for b in bars]
data_list = [["0", "1" if b == signal_bar else "0"] for b in bars]
res = tq.send_bt_data(stock_code=STOCK_CODE,
time_list=time_list, data_list=data_list,
count=len(time_list))
print(f"[{label}] signal_bar={signal_bar.strftime('%H:%M')} res={res}")
def main():
tq.initialize(__file__)
try:
send_period(build_1m(TRADE_DATE), SIGNAL_TIME, "1m")
send_period(build_5m(TRADE_DATE), SIGNAL_TIME, "5m")
send_period(build_15m(TRADE_DATE), SIGNAL_TIME, "15m")
send_period(build_30m(TRADE_DATE), SIGNAL_TIME, "30m")
send_period(build_60m(TRADE_DATE), SIGNAL_TIME, "60m")
# 日线
res = tq.send_bt_data(stock_code=STOCK_CODE,
time_list=[TRADE_DATE], data_list=[["0","1"]], count=1)
print(f"[daily] res={res}")
finally:
tq.close()
if __name__ == "__main__":
main()
data_list 格式说明:
time_list[i] = "YYYYMMDDHHMMSS" 每根 bar 的时间
data_list[i] = ["v1", "v2", ...] 该 bar 对应的多列数据(字符串)
SIGNALS_TQ(1, 0) → 读取 data_list[i][0](第1列)
SIGNALS_TQ(2, 0) → 读取 data_list[i][1](第2列)
SIGNALS_TQ(N, 0) → 读取 data_list[i][N-1](第N列)
2.4 第二步:在 TQ 策略管理器中注册并运行
- 通达信菜单栏 → TQ策略 → TQ策略管理
- 点 新建,选择脚本文件,保存
- 选中该策略,点 运行
- 等待运行完成,输出窗口出现类似:
send_bt_data: {'ErrorId': '0', 'Msg': '发送TQ数据成功.', 'run_id': '1'}
2.5 第三步:创建通达信公式
在通达信 公式管理器 中新建指标:
- 名称:
PY_BS_TQ - 类型: 主图叠加
{只显示卖出信号}
SELL_SIGNAL:=SIGNALS_TQ(2,0)>0;
DRAWICON(SELL_SIGNAL,HIGH,2);
DRAWTEXT(SELL_SIGNAL,HIGH,'S'),COLORRED;
如果同时需要买入信号:
BUY_SIGNAL:=SIGNALS_TQ(1,0)>0;
SELL_SIGNAL:=SIGNALS_TQ(2,0)>0;
DRAWICON(BUY_SIGNAL,LOW,1);
DRAWICON(SELL_SIGNAL,HIGH,2);
DRAWTEXT(BUY_SIGNAL,LOW,'B'),COLORGREEN;
DRAWTEXT(SELL_SIGNAL,HIGH,'S'),COLORRED;
SIGNALS_TQ 参数说明:
| 参数 | 说明 |
|---|---|
| ID | 数据列序号,从 1 开始,对应 data_list 每行的第 ID 个元素 |
| TYPE=0 | 不做平滑,无数据的 bar 返回 0 |
| TYPE=1 | 平滑处理,无数据时返回上一根的值 |
| TYPE=2 | 无数据时返回 0(与 0 类似) |
2.6 第四步:在 K 线图上显示,并触发刷新
- 打开目标股票的 K 线图(任意周期)
- 加载
PY_BS_TQ指标(主图叠加) - 信号时间点对应的 K 线上会显示 B/S 文字和箭头图标
- 1m / 5m / 15m / 30m / 60m / 日线均可显示(脚本已为每个周期发送对应 bar 数据)
分时走势图(F5)不支持
SIGNALS_TQ,请在 1 分钟 K 线图(F6) 查看,1 分钟图的信号时刻与分时走势图完全对应。
⚠️ 2.6.1 关键步骤:触发图表刷新(必做,否则信号不显示)
公式加载后,图表不会自动刷新,必须手动触发一次数据加载:
TQ策略管理 → 策略管理器 → 策略数据 → 双击想要查看的股票
操作步骤:
1. 在 TQ 策略管理窗口,切换到 策略管理器 标签
2. 切换到 策略数据 子标签
3. 在股票列表中找到目标股票(如 001896.SZ),双击它
4. 此时通达信 K 线图才会刷新并显示信号
注意: 每次重新运行脚本推送新信号后,都需要重复以上双击操作,图表才能更新。
2.7 更新信号的流程
每次需要推送新信号时:
- 修改脚本中的
STOCK_CODE、TRADE_DATE、BUY_TIME、SELL_TIME等参数 - 在 TQ 策略管理器中重新运行一次
- TQ策略管理 → 策略管理器 → 策略数据 → 双击目标股票,触发图表刷新
2.8 清空旧信号的方法
tqcenter 没有专用的 clear API。清空旧信号的标准做法是:先发一遍全零数据覆盖,再发实际信号。
# 第一步:全零覆盖(清空)
zero_data_list = [["0", "0"] for _ in bars]
tq.send_bt_data(stock_code=STOCK_CODE, time_list=time_list,
data_list=zero_data_list, count=len(time_list))
# 第二步:发实际 B/S 信号
tq.send_bt_data(stock_code=STOCK_CODE, time_list=time_list,
data_list=actual_data_list, count=len(time_list))
发送完成后同样需要在 策略数据 → 双击股票 触发刷新。
2.9 本次修复摘要(为什么分时和1分钟变正常)
这次联调里真正起作用的是以下 4 点:
- 按周期分别发送,不混在一个时间轴里
- 正确做法:
1m / 5m / 15m分别构造各自bars,分别调用send_bt_data。 -
不要把多周期信号塞到同一个
time_list里再靠公式PERIOD分流,否则容易出现“信号挤到开盘附近/错位”。 -
每个周期先做“信号时刻 -> 该周期bar”映射
- 用规则:
signal_bar = first(bar_time >= signal_time)。 -
例如
13:06:- 1m ->
13:06 - 5m ->
13:10 - 15m ->
13:15
- 1m ->
-
公式保持简单:固定读取 ID1/ID2
BUY_SIGNAL:=SIGNALS_TQ(1,0)>0;SELL_SIGNAL:=SIGNALS_TQ(2,0)>0;-
由“每周期独立发送”保证各周期都取到正确 B/S,不再在公式里做复杂分支。
-
脚本尾部增加短暂 sleep,避免误判异常退出
- 发送完成后很快退出是正常行为;
- 增加
sleep(3~5)让策略管理器显示更稳定; - 同时保留
finally: tq.close(),避免残留运行状态影响下次启动。
三、待验证方法
方法二:SIGNALS_USER(文件方式)
思路: Python 写二进制 .dat 文件 → 通达信读取,无需 TQ 插件。
- 文件路径:
C:/new_tdx64/T0002/signals/signals_user_<ID>/<market>#<code>.dat - 记录格式:
uint32 date_yyyymmdd + uint32 seconds + 22×float32 = 96 字节/条 - 需在 自定义数据管理器 中注册(属性选"序列(日期,数值)")
- 已有脚本:
write_signals_user_bs.py - 结论:分钟级数据无法正常读取,日线级可用,不推荐用于分时信号
三、方法三:DLL 外部指标(已验证)✅
3.1 原理
Python 写信号文件(CSV)
↓
DLL 实时读取文件,按 DATE/TIME 匹配每根 bar
↓ TDXDLL1(nFuncMark, DATE, TIME, 0)
K 线图公式显示 B/S 信号(实时生效,无需手动刷新)
优势: 公式每次重算时 DLL 自动检测文件变动并重新加载,更新信号只需覆盖 CSV 文件,无需重启通达信。
3.2 编译环境
本节所有内容在以下环境验证通过:
| 组件 | 版本 |
|---|---|
| OS | Windows 10 (x64) |
| 通达信 | v7.72,64位(安装目录含 new_tdx64) |
| 编译器 | Visual Studio 2017 Community,MSVC 14.16.27023(v15.9.50) |
| Windows SDK | 10.0.17763.0 |
| Shell | Cygwin bash 3.4.8 |
| Python | 3.9(仅用于写信号 CSV,不参与编译) |
注意: 编译器使用的是 Visual Studio 2017 的 MSVC,通过
vcvars64.bat初始化 x64 编译环境。不需要 MinGW,也不需要 Cygwin 参与编译。
参考代码来源:通达信官方示例 https://help.tdx.com.cn/book.html(DLL函数编程规范,含 TestPluginTCale 示例工程)
3.3 官方 DLL 接口规范
来源:TestPluginTCale/TCalcFuncSets.cpp(通达信官方示例)
// 函数签名(注意:pfOUT 是第2个参数,不是最后一个)
void MyFunc(int DataLen, float* pfOUT, float* pfINa, float* pfINb, float* pfINc);
// 注册函数数组(以 {0, NULL} 结尾)
PluginTCalcFuncInfo g_CalcFuncSets[] =
{
{1, (pPluginFUNC)&MyFunc1},
{2, (pPluginFUNC)&MyFunc2},
{0, NULL}, // 结尾标记,必须有
};
// 导出函数
BOOL RegisterTdxFunc(PluginTCalcFuncInfo** pFun)
{
if (*pFun == NULL) {
*pFun = g_CalcFuncSets;
return TRUE;
}
return FALSE;
}
⚠️ 常见错误: 函数签名里 pfOUT 必须是第2个参数。写成最后一个参数会导致读到垃圾数据(funcNo = -2147483648)。
TDXDLL1 参数映射:
公式:TDXDLL1(nFuncMark, Param1, Param2, Param3)
↓
DLL:MyFunc(DataLen, pfOUT, pfINa=Param1, pfINb=Param2, pfINc=Param3)
nFuncMark:选择调用哪个注册函数(对应g_CalcFuncSets中的序号)- 最少 4 个参数,否则通达信报"参数太少"错误,第4个可传
0占位
TDX 内置变量格式:
| 变量 | 格式 | 示例 |
|---|---|---|
DATE |
yyyymmdd - 19000000 |
20260403 → 1260403 |
TIME |
HHMM(4位整数) |
09:50 → 950,10:10 → 1010 |
3.4 信号文件格式
文件放在通达信安装目录下:<TDX安装目录>\T0002\signals\tdx_signal.csv
# type,yyyymmdd,HHMM
B,20260403,950
S,20260403,1010
type:B=买入,S=卖出yyyymmdd:完整日期(DLL 内部自动转换为 TDX DATE 格式)HHMM:4位时间,与 TDXTIME变量一致
Python 写信号脚本: tdx_signal_dll/write_tdx_signal_csv.py
3.5 DLL 源码
文件:tdx_signal_dll/TdxSignal.cpp
核心逻辑:
- RegisterTdxFunc:注册两个函数,nFuncMark=1(买入),nFuncMark=2(卖出)
- CalcCore:检测信号文件变动 → 重新加载 → 遍历每根 bar 匹配 DATE+TIME
- 匹配命中则 pfOUT[i] = 1.0f,否则 0.0f
- 调试日志输出到 <TDX安装目录>\T0002\signals\tdx_signal_debug.log
3.6 编译脚本(已验证)
文件:tdx_signal_dll/build.bat,双击运行,自动寻找 VS2017 编译环境:
@echo off
setlocal
REM 自动寻找 VS2017 vcvars64.bat
set VCVARS=
for /f "delims=" %%i in ('dir /b /s "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\*\VC\Auxiliary\Build\vcvars64.bat" 2^>nul') do (
set VCVARS=%%i
goto :found_vc
)
echo [ERROR] 未找到 VS2017,请确认已安装 Visual Studio 2017
pause & exit /b 1
:found_vc
echo [INFO] 使用: %VCVARS%
call "%VCVARS%"
cd /d "%~dp0"
cl.exe /nologo /O2 /EHsc /LD ^
/DWIN32 /D_WINDOWS /D_USRDLL /DPLUGIN_EXPORTS ^
TdxSignal.cpp ^
/link /DLL /OUT:TdxSignal.dll /MACHINE:X64 ^
kernel32.lib
if %ERRORLEVEL% neq 0 (
echo [ERROR] 编译失败
pause & exit /b 1
)
echo [OK] 编译成功: TdxSignal.dll
pause
编译产物 TdxSignal.dll 手动复制到 <TDX安装目录>\T0002\dlls\ 目录下。
注意: build.bat 必须在 Windows 命令提示符(cmd) 或 文件管理器双击 运行,不能直接在 Cygwin bash 里执行。从 Cygwin/Python 调用的方式见下方。
从 Python 调用编译(适合在 Cygwin 环境下):
import subprocess
vcvars = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat'
src_dir = r'<项目目录>\tdx_signal_dll'
cmd = f'cmd /c "call "{vcvars}" && cd /d "{src_dir}" && cl.exe /nologo /O2 /EHsc /LD /DWIN32 /D_WINDOWS /D_USRDLL /DPLUGIN_EXPORTS TdxSignal.cpp /link /DLL /OUT:TdxSignal.dll /MACHINE:X64 kernel32.lib"'
subprocess.run(cmd, shell=True)
3.7 在通达信中注册 DLL
- 公式管理器 → DLL函数 → 新建
- 选择文件
<TDX安装目录>\T0002\dlls\TdxSignal.dll - 注册为第1号函数(对应公式里的
TDXDLL1)
更新 DLL:需先关闭通达信,替换文件,再重启。DLL 被通达信进程锁住时无法覆盖。
3.8 通达信公式
公式名:PY_BS_DLL,类型:主图叠加
B:=TDXDLL1(1,DATE,TIME,0);
S:=TDXDLL1(2,DATE,TIME,0);
DIST:=ATR(14);
{箭头:紧贴K线}
DRAWICON(B,LOW-DIST*0.3,1);
DRAWICON(S,HIGH+DIST*0.3,2);
{文字:离箭头更远,用ATR动态偏移}
DRAWTEXT(B,LOW-DIST*1.8,'▲ BUY'),COLORRED;
DRAWTEXT(S,HIGH+DIST*1.8,'▼ SELL'),COLORGREEN;
说明:
- TDXDLL1(1,...) → 调用 nFuncMark=1(买入信号)
- TDXDLL1(2,...) → 调用 nFuncMark=2(卖出信号)
- 第4个参数 0 为占位符(必须,否则报"参数太少")
- ATR(14) 动态控制偏移距离,适应不同价格量级的股票
- FONTSIZE 通达信不支持,用全角字符/更长文字代替
- 1分钟和5分钟通用:只要信号时刻恰好是5分钟bar整点(如09:50、10:10),同一公式两个周期均可显示
3.9 更新信号流程
- 修改
write_tdx_signal_csv.py中的日期和时间 - 运行脚本(外部终端即可,无需通达信内部运行)
- 切换一下 K 线周期(触发公式重算),信号立即更新
无需重启通达信,无需手动双击刷新。
3.10 调试方法
DLL 会将每次加载和命中写入日志:<TDX安装目录>\T0002\signals\tdx_signal_debug.log
日志内容示例:
[reload] loading tdx_signal.csv
[reload] type=1 date=1260403 time=950
[reload] type=2 date=1260403 time=1010
[calc] sigType=1 DataLen=420
[calc] pfINa[0]=1260402.000000(date?) pfINb[0]=931.000000(time?)
[calc] HIT type=1 date=1260403 time=950 bar=259
如果日志为空:DLL 未被加载(检查注册和文件路径)。
如果有 [reload] 但无 [HIT]:DATE 或 TIME 格式不匹配(对照日志中实际值)。
3.11 编译常见问题
Q: build.bat 在 Cygwin bash 里执行没有反应
- bat 文件必须在 Windows cmd 环境下运行,Cygwin 的 bash 不识别 .bat
- 解决:在文件管理器双击 build.bat,或用 Python subprocess 调用
Q: 编译报 fatal error C1083: 无法打开包含文件 "windows.h"
- vcvars64.bat 没有被正确调用,或 Windows SDK 未安装
- 确认 vcvars64.bat 路径正确,且该脚本 call 执行后 INCLUDE 环境变量已包含 SDK 路径
Q: 编译成功但通达信加载 DLL 报错
- 确认编译为 x64(/MACHINE:X64),通达信 64 位版不能加载 32 位 DLL
Q: 替换 DLL 时提示"拒绝访问" - 通达信运行时锁住 DLL,必须先关闭通达信再覆盖
Q: 公式调用时 pfOUT 里读到 -2147483648(INT_MIN)
- 函数签名参数顺序写错了,pfOUT 必须是第2个参数:
void Func(int DataLen, float* pfOUT, float* pfINa, float* pfINb, float* pfINc)
Q: 信号文件更新了但 DLL 没有重新加载
- DLL 通过 GetFileAttributesEx 检测文件 LastWriteTime 来判断是否重新加载
- 确认文件确实被写入(覆盖),而不仅仅是内容相同
五、常见问题
Q: 信号只在 1 分钟图显示,其他周期没有(TQ方式)
- 脚本只发送了 1 分钟数据,需要为每个周期分别发送对应 bar 的时间戳
- 使用上面的完整版脚本(含 send_period 函数),会自动处理 1m/5m/15m/30m/60m/日线
- 不要把 1m/5m/15m 同时塞到同一个 time_list + 多ID 里再用 PERIOD 分流,实测容易错位
Q: TQ 公式加载后没有任何信号显示 - 首先检查:是否完成了"策略数据双击"步骤(TQ策略管理 → 策略管理器 → 策略数据 → 双击目标股票),这是触发图表刷新的必要操作,极易遗漏 - 确认通达信处于登录状态 - 确认 TQ 策略已从策略管理器内运行(外部终端运行无效) - 确认公式类型为主图叠加,不是副图 - 确认在 1 分钟 K 线图(F6),不是分时走势图(F5)
Q: SIGNALS_TQ 返回值全是 0 - 重新从 TQ 策略管理器运行一次脚本 - 检查 data_list 的列序号是否与 SIGNALS_TQ 的 ID 参数对应
Q: DLL 公式报"参数太少"错误
- TDXDLL1 至少需要 4 个参数,第4个传 0 占位:TDXDLL1(1,DATE,TIME,0)
Q: DLL 公式无报错但信号不显示
- 查看调试日志 C:\new_tdx64\T0002\signals\tdx_signal_debug.log
- 日志为空 → DLL 未被加载,检查注册和文件路径
- 有 [reload] 无 [HIT] → DATE 或 TIME 格式不匹配,对照日志中实际值
- 常见原因:CSV 里 TIME 写成了 6 位 HHMMSS(如 95000),但 TDX 传入的是 4 位 HHMM(如 950)
Q: 替换 DLL 时提示权限拒绝 - 通达信运行时会锁住 DLL 文件,必须先关闭通达信再覆盖文件
Q: TDXDLL1 公式里能否用 FONTSIZE 设置字号
- 通达信不支持 FONTSIZE 参数,用更长文字或全角字符(如 ▲ BUY)代替
Q: 如何同时为多只股票推送信号(TQ 方式)
- 多次调用 send_bt_data,每次传不同的 stock_code
- 或在脚本中循环处理股票列表