简介:这份面向股票技术分析初学者与通达信公式爱好者的文档,围绕一套声称准确率高达98%的自定义指标公式展开,帮助读者理解通达信平台中公式源码的编写逻辑与买卖信号设计思路。文档内含1个docx文件,压缩包约125KB,篇幅集中、便于快速浏览,适合对照软件逐段阅读与调试。源码部分呈现了较完整的变量与条件判断链:Q_1用收盘价与其前一交易日收盘价之差刻画短期动能,X2统计连续两日下跌的天数,X4比较当前趋势与前一日强弱;买进由多组条件复合而成,TJ0、TJ1则依据Q_A与Q_B的变化给出卖出提示。持股、买一绝、堪称卖绝借助STICKLINE以不同颜色与线宽在图上标注持仓及买卖节点,形成直观的图形信号。目前已有595人学习。需提醒的是,高准确率说法应经历史回测与实盘验证,指标仅作辅助工具,宜结合基本面与市场情绪综合判断。
1. 98% 准确率的通达信指标公式,先弄清这个数字在说什么
一个 docx 文件,几十行公式,标题写着准确率 98%。把它复制进通达信的公式管理器,编译通过,副图上按要求画出了红绿柱和箭头,但照着信号买进去,两个月账户是负的。这种落差我见过太多次。问题多半不在通达信,也不在公式能不能算,而在于 98% 这个数字从来没有定义过分母:是 500 次信号里 490 次次日收红,还是只在某段单边上涨行情里统计的?是收盘价确认信号,还是盘中触碰就算命中?持有几天?止损怎么算?
这篇要处理的就是这件事:把一份来源不明的通达信指标公式源码,从「看着很准」变成「口径清楚、可以自己验证」。我会沿着公式语法、导入排错、未来函数自查、Python 复现统计这条线讲下来,适合两类人:手上已经攒了一堆 docx、tn6、txt 指标文件想弄明白到底怎么回事的,以及想自己写副图指标和选股公式、不甘心只当公式搬运工的。中间会给出可直接粘贴的源码、逐条对照的报错表和能跑起来的统计脚本。
2. 通达信指标公式源码的语法骨架:主力追踪与量能饱和度共用的结构
网上流传的主力追踪、量能饱和度圆圈 1.00、三步点金这类东西,看着名字五花八门,拆开源码之后骨架高度相似:一条平滑后的方向线,一条量能归一化曲线,再加几个阈值判断和画图函数。把骨架搞清楚,比收藏一百个 docx 有用。
2.1 公式管理器里的类型划分与各自的输出约束
通达信公式管理器(默认快捷键 Ctrl+F)把公式分成技术指标、条件选股、交易系统、五彩 K 线、K 线模式、专家系统几类。同一段源码换个类型粘进去,行为完全不同,这是最先要立住的边界。
技术指标公式画在图上,可以输出多条线、柱、图标,允许DRAWICON、DRAWTEXT、STICKLINE;条件选股公式只能有一个逻辑结果,最后一行必须是布尔表达式,写了画图函数直接编译失败;交易系统公式需要买入条件和卖出条件各一组,用来做系统测试。所谓的主力追踪副图指标,本质是技术指标类公式,想拿去选股就得把画图部分删掉重写一遍。
| 公式类型 | 输出要求 | 能否画图 | 典型用途 |
|---|---|---|---|
| 技术指标 | 至少一条:输出 | 可以 | 副图指标、主图叠加 |
| 条件选股 | 最后一行布尔值 | 不可以 | 盘中预警、批量选股 |
| 交易系统 | 买入/卖出条件各一组 | 可以 | 系统测试、胜率统计 |
| 五彩 K 线 | 条件加颜色 | 只能改 K 线颜色 | 形态标记 |
| 专家系统 | 多组条件组合 | 不可以 | 复杂选股过滤 |
提示:公式名支持中文,但不要带空格和短横线,导出成 tn6 之后跨版本导入容易出现识别问题。命名用「前缀_对象_周期」这种结构,后面批量处理会省很多事。
2.2 赋值、输出与周期:源码里最容易写错的几处
通达信源码里:=和:是两个完全不同的东西。A:=MA(C,5)定义中间变量,不画线、不参与输出;A:MA(C,5)定义输出线,会在图上出现一句话。很多从网上抄来的公式第一次编译就报「变量未定义」,八成是把某个中间变量写成了:,或者反过来。
高频坑还有这么几个:docx 里复制出来的逗号、括号、分号经常是全角的,编译报错但肉眼看不出来;通达信内部函数不区分大小写,自定义变量名却区分,ma5和MA5是两个变量;REF(X,N)的 N 在多数版本上要求常量,用变量做周期会失败;CROSS(A,B)只判断当前这一根 K 线,连续上涨行情里信号会断,需要配合FILTER或BARSLAST去重;除零问题也常见,VOL/MA(V,20)在上市首日或长期停牌复牌后分母可能为 0。
| 函数 | 含义 | 注意点 |
|---|---|---|
REF(X,N) | N 周期前的 X | N 通常要求常量 |
CROSS(A,B) | A 上穿 B | 只对当前 K 线成立 |
FILTER(X,N) | X 成立后 N 周期内屏蔽 | 信号去重用 |
BARSLAST(X) | 上次 X 成立至今的周期数 | 从未成立时返回极大值 |
HHV/LLV(X,N) | N 周期最高/最低 | 含当前 K 线 |
SMA(X,N,M) | 通达信加权移动平均 | 与MA不等价 |
ZIG(K,N) | 之字转向 | 未来函数,慎用 |
2.3 主力追踪副图与量能饱和度的可复用写法
下面这段是主力追踪副图的常见结构,把量能饱和度、短期方向线和饱和标记放在一起,每一行都加了注释,可以直接贴进公式管理器测试。
{主力追踪副图:均量线 + 量能饱和度 + 短期方向线} VOLUME:VOL,VOLSTICK; {第1行:成交量柱,副图基础} VMA5:MA(VOL,5),COLORWHITE; {5日均量线} VMA20:MA(VOL,20),COLORYELLOW; {20日均量线,饱和度的分母} DEN:=MAX(VMA20,1); {兜底,防止停牌复牌后分母为0} SAT:VOL/DEN,NODRAW; {量能饱和度原始值,NODRAW 只算不画} SAT100:SAT*100,COLORCYAN; {放大100倍显示,1.00 对应 100 的饱和区} RSV:=(CLOSE-LLV(LOW,9))/(HHV(HIGH,9)-LLV(LOW,9)+0.0001)*100; K:=SMA(RSV,3,1); D:=SMA(K,3,1); J:=3*K-2*D; 方向线:EMA(J,5),COLORRED,LINETHICK2; 饱和标记:IF(SAT>=1.0,方向线,DRAWNULL),COLORMAGENTA,LINETHICK3;逻辑上分三段:第一段用VOL/MA(VOL,20)得到量能饱和度,NODRAW让原始值只参与计算不出图,方便后面复用成选股条件;第二段是经典的随机指标变形,+0.0001是为了避免一字板时高低价相等导致除零;第三段IF(SAT>=1.0,方向线,DRAWNULL)只在饱和区画粗线,DRAWNULL表示该位置不画,比用颜色隐藏干净。
参数改动的影响可以直接对照下表,不用一个个试。
| 参数 | 默认 | 调大 | 调小 |
|---|---|---|---|
| 均量周期 | 20 | 饱和信号变少,更偏中线 | 信号变多,噪声上升 |
| RSV 周期 | 9 | 方向线迟钝 | 方向线频繁翻转 |
| 方向线平滑 | 5 | 更稳,滞后加大 | 更贴价,假信号多 |
同一个逻辑改成条件选股,只保留最后判断那一行,中间变量全部用:=:
{条件选股:量能饱和度≥1 且方向线上穿 20} DEN:=MAX(MA(VOL,20),1); SAT:=VOL/DEN; RSV:=(CLOSE-LLV(LOW,9))/(HHV(HIGH,9)-LLV(LOW,9)+0.0001)*100; K:=SMA(RSV,3,1); D:=SMA(K,3,1); J:=3*K-2*D; 方向线:=EMA(J,5); XG:SAT>=1 AND CROSS(方向线,20);选股公式里不能出现画图函数,最后一行必须是一个返回真假的表达式,XG这个名字本身不重要,重要的是它带:输出。把这两段对照着看,就明白为什么同一个指标在副图上能用、拿去选股却报错了。
3. 从 docx 到可编译公式:txt 清理、tn6 导入与报错对照
拿到一份 docx,第一件事不是粘贴,而是清理。全角符号是导致首次编译失败最主要的原因,比逻辑错误还常见,因为逻辑错误至少报错行为是可预期的,全角符号报出来的行号和实际问题位置常常对不上。
3.1 docx 复制出来的源码先做四步文本清理
清理动作固定为四步:全角标点转半角;去掉 Word 智能引号;逐行去首尾空格并删空行;确认没有把:=写成全角冒号加等号。手改容易漏,用脚本更快。
import re from pathlib import Path SRC = Path("formula_raw.txt") DST = Path("formula_clean.txt") # 全角 -> 半角映射,公式里能用到的符号基本覆盖全了 FULL2HALF = { ord(","): ",", ord(";"): ";", ord(":"): ":", ord("("): "(", ord(")"): ")", ord("%"): "%", ord(" "): " ", ord("<"): "<", ord(">"): ">", ord("+"): "+", ord("-"): "-", ord("*"): "*", ord("/"): "/", } text = SRC.read_text(encoding="utf-8", errors="ignore") text = text.translate(FULL2HALF) # 第一步:全角转半角 text = text.replace("\u201c", '"').replace("\u201d", '"') # 第二步:智能引号 lines = [ln.strip() for ln in text.splitlines()] # 第三步:去首尾空白 lines = [ln for ln in lines if ln] # 第四步:删空行 DST.write_text("\n".join(lines), encoding="utf-8") print(f"清理完成,共 {len(lines)} 行")脚本里errors="ignore"是关键,docx 导出文本偶尔会混入不可见控制字符,直接解码会抛异常。清理之后不要急着粘贴,先在文本编辑器里搜一遍:、,、(,这三个出现的概率最高。
注意:清理只解决符号问题,不解决逻辑问题。一份写着 98% 准确率的公式,符号全部改对之后能不能用,仍然要看第 4 章那套验证。
3.2 tn6、txt 与公式管理器的三种流转路径
实际工作中会在三种格式之间来回倒,路径大致是固定的。
路径 A,docx 或 txt → 手动粘贴进公式管理器 → 测试公式 → 确定 → 另存为 tn6 备份。这条最可控,源码全程可见。
路径 B,别人给的 tn6 → 公式管理器里的导入公式 → 选择分组 → 确认覆盖。这条的风险在于看不到源码。
路径 C,直接把文件复制进通达信安装目录下的公式目录 → 重启客户端生效。不同版本目录结构有差异,以本地版本为准。
| 格式 | 能否读源码 | 常见来源 | 建议做法 |
|---|---|---|---|
| txt / docx | 能 | 论坛、群文件 | 先跑清理脚本再粘贴 |
| tn6 | 视加密情况 | 他人打包 | 导入前确认能否展开源码 |
| tne / tni | 视版本 | 公式导出 | 扩展名跨版本有差异,以本地为准 |
公网上流传的那类指标加密处理工具,直接用名字去搜能搜到一堆,我的建议是别用。原因不是别的,是工程问题:加密或混淆过的 tn6 你没法审源码,里面藏一个ZIG或者跨周期引用,回测曲线立刻变得非常好看,实盘就是另一回事。来路不明的公式文件导入之后,先看公式管理器里能不能展开源码,展开不了就直接删掉。
3.3 编译报错逐条对照
| 报错现象 | 常见原因 | 改法 |
|---|---|---|
| 未定义的变量 xxx | 中间变量写成了:,或大小写不一致 | 统一改成:=,全篇变量名统一大小写 |
| 参数个数不对 | REF(X,5,1)这类多传了参数 | 查函数原型,对照 2.2 的表格 |
| 语法错误,行号指向空白行 | 全角符号、括号不配对、注释{}没闭合 | 跑 3.1 的脚本,再逐行数括号 |
| 该行不是布尔表达式 | 选股公式里写了画图函数 | 删掉DRAWICON/DRAWTEXT,最后一行写成XG:... |
| 计算结果全是 0 或异常大 | 除零、分母用了MA(VOL,20) | 套一层MAX(...,1) |
排查顺序建议从最小公式开始,先确认环境没问题,再逐步加复杂度:
{自检四行:能全编译过,说明输入法和环境都正常} T1:MA(CLOSE,5); T2:REF(CLOSE,1); T3:CROSS(MA(CLOSE,5),MA(CLOSE,10)); T4:HHV(HIGH,20);这四行覆盖了序列引用、均线、交叉判断、区间极值四类基本操作。如果连它们都编译不过,问题在环境或输入法,不在你抄来的那份公式上,别在源码里瞎改。
4. 98% 从哪来:未来函数自查、Python 复现回测与参数过拟合
准确率虚高通常来自三个来源:用了未来函数、统计口径放松、参数过拟合。这三件事可以分别验证,不需要信任任何人的口头声明。
4.1 未来函数自查:抓住 ZIG、BACKSET 和跨周期引用
未来函数指的是在某一根 K 线位置上,用到了当时还不可能知道的信息。通达信里最典型的有三类。
第一类是ZIG(K,N)及其配套的PEAK、TROUGH、PEAKBARS、TROUGHBARS。之字转向只有在后续高低点确认之后才能画出拐点,所以最后一个拐点的位置会随着新 K 线不断移动。用它标注的顶底,历史回看极其精准,实盘那个点位根本还没确认。
第二类是BACKSET(X,N),把当前条件回填到前 N 个周期,直接制造出提前 N 天就知道的假象。
第三类是跨周期和大周期引用,比如日线公式里引用周线收盘价,或者调用DYNAINFO系列取当日动态数据,这些值在盘中会变。
自查方法有两个。慢的那个:把公式挂到副图,记下今天所有信号位置,隔几天用同一份数据重新加载,对比历史信号有没有漂移。快的那个:用两段不同结束日期的数据跑同一份公式,取重叠区间比对信号位置,全部一致才说明没有未来函数。
提示:
REF(X,1)、MA、EMA、HHV都不是未来函数,它们的共同点是只使用当前和过去的数据。看到公式里出现ZIG或者BACKSET,先把那个准确率数字打个对折再说。
4.2 用 Python 复现指标口径并统计信号胜率
通达信导出的日线数据可以直接读出成 CSV,用 pandas 复现公式,好处是口径完全掌握在自己手里。先定义通达信EMA的等价实现,它的平滑系数是2/(N+1),和 pandas 默认的span一致,但要显式关掉adjust。
import pandas as pd import numpy as np df = pd.read_csv("sz000001.csv", parse_dates=["date"]).sort_values("date").reset_index(drop=True) def tdx_ema(s, n): """通达信 EMA(X,N) 等价实现:alpha = 2/(N+1)""" return s.ewm(alpha=2 / (n + 1), adjust=False).mean() df["dif"] = tdx_ema(df["close"], 12) - tdx_ema(df["close"], 26) df["dea"] = tdx_ema(df["dif"], 9) df["macd"] = (df["dif"] - df["dea"]) * 2 # 通达信 CROSS(A,B) 等价于:当日 A>B 且 前一日 A<=B prev_dif, prev_dea = df["dif"].shift(1), df["dea"].shift(1) df["signal"] = (df["dif"] > df["dea"]) & (prev_dif <= prev_dea)信号定义好之后,统计部分要同时给出两种口径,只报一种都容易误导。
HOLD, TARGET = 5, 0.03 # 持有 5 个交易日,目标涨幅 3% def evaluate(df, hold=HOLD, target=TARGET): rows = [] for i in df.index[df["signal"]]: if i + hold >= len(df): continue entry = df.loc[i + 1, "open"] # 次日开盘成交,避免用信号当日收盘价的偷价 window = df.loc[i + 1:i + hold] rows.append({ "date": df.loc[i, "date"], "ret": df.loc[i + hold, "close"] / entry - 1, # 持有到期收益 "max_up": window["high"].max() / entry - 1, # 期间最大浮盈 "max_dn": window["low"].min() / entry - 1, # 期间最大浮亏 }) r = pd.DataFrame(rows) loose = (r["max_up"] >= target).mean() # 口径一:期间曾达标 strict = (r["ret"] > 0).mean() # 口径二:持有到期为正 return len(r), round(loose, 4), round(strict, 4), round(r["max_dn"].mean(), 4) print(evaluate(df))参数说明:HOLD是持有周期,改大改小会明显改变结论,短线信号建议 3 到 5;TARGET是宽松口径的达标线,一般取 0.02 到 0.05;entry用次日开盘价而不是信号日收盘价,是为了避开偷价,这一点对准确率的影响往往比指标本身还大。输出里最后一个值是平均最大浮亏,账户扛不住这个数字的话,前面两项再好看也没意义。
| 统计口径 | 计算方式 | 数值通常偏高还是偏低 |
|---|---|---|
| 宽松:期间曾达标 | max_up >= TARGET | 明显偏高,容易到 80% 以上 |
| 严格:持有到期为正 | ret > 0 | 接近真实体感 |
| 平均最大浮亏 | max_dn的均值 | 用于判断是否扛得住 |
| 有效样本数 | 信号条数 | 少于 30 条基本没有统计意义 |
把这份脚本套回 docx 里的公式,如果严格口径掉到 50% 上下、平均最大浮亏超过 8%,那么 98% 这个数字的来源基本就清楚了:它统计的是宽松口径,而且样本区间大概率是精心挑过的。
4.3 参数扫描与稳健性判断:别只看最优那一组
参数能调,就一定能调出一组历史数据上好看的。判断是否过拟合的办法是做网格扫描,然后看最优值周围的情况,而不是只看最优点本身。
from itertools import product def win_rate(df, fast, slow, sig, hold=5): dif = tdx_ema(df["close"], fast) - tdx_ema(df["close"], slow) dea = tdx_ema(dif, sig) cross = (dif > dea) & (dif.shift(1) <= dea.shift(1)) rets = [] for i in df.index[cross]: if i + hold >= len(df): continue rets.append(df.loc[i + hold, "close"] / df.loc[i + 1, "open"] - 1) if len(rets) < 30: # 样本太少直接丢弃,不参与比较 return 0, np.nan return len(rets), float((pd.Series(rets) > 0).mean()) # 沿 MACD 经典参数 (12,26,9) 的邻域扫描 rows = [] for f, s, g in product(range(6, 20, 2), range(18, 34, 4), range(4, 14, 2)): if f >= s: continue n, wr = win_rate(df, f, s, g) rows.append({"fast": f, "slow": s, "signal": g, "trades": n, "win": wr}) grid = pd.DataFrame(rows).dropna(subset=["win"]).sort_values("win", ascending=False) print(grid.head(8)) print("胜率分布:", grid["win"].describe()[["mean", "std", "min", "max"]].round(4).to_dict())逻辑上做了两件事:一是限定fast < slow,排除无意义的组合;二是把交易次数少于 30 的组合直接返回nan丢掉,避免少量样本刷出极端值。判断结论看的是分布而不是榜首:如果最优那一组 78%,但邻域普遍在 45% 到 55%,标准差 0.06,那 78% 就是噪声。真正稳健的参数区间,是整片区域的胜率都差不多,而不是单独冒出一个尖峰。改到这一步,再回头去看那份 docx,基本就能判断它值不值得留在自己的公式库里。
5. 自建通达信指标公式源码的长期维护:命名规范、批量预检与信号对拍
公式攒到几十份之后,靠记忆管理就不行了。有一阵子我手上同时有七八个版本的「主力追踪」,改到后来自己都分不清哪个是哪个,最后是靠命名规范加脚本预检重新理清楚的。
5.1 命名规范是批量处理的前提
文件名和公式名保持一致,格式用前缀_对象_周期_版本,比如ZL_主力追踪_D_V2。前缀用两三个 ASCII 字母,避免跨版本导入时的编码问题;对象名用中文,方便搜索;周期用D、W、60这种短标记;版本号必须加,改一次进一位,旧版本不要删,留着做信号对拍。
比命名更省事的是加一条黑名单预检。很多问题在导入通达信之前就能查出来,没必要等到编译报错。
import re from pathlib import Path # 未来函数与动态数据黑名单,命中就人工复核 BAN = ["ZIG", "PEAK", "TROUGH", "BACKSET", "DYNAINFO"] def lint(path: Path) -> list[str]: errs, text = [], path.read_text(encoding="utf-8", errors="ignore") for i, ln in enumerate(text.splitlines(), 1): if re.search(r"[,;:()%]", ln): errs.append(f"{path.name}:{i} 含全角符号") if ln.count("(") != ln.count(")"): errs.append(f"{path.name}:{i} 括号不配对") if ln.count("{") != ln.count("}"): errs.append(f"{path.name}:{i} 注释未闭合") for kw in BAN: if kw in ln.upper(): errs.append(f"{path.name}:{i} 命中黑名单 {kw}") return errs for p in sorted(Path("./formulas").glob("*.txt")): for e in lint(p): print(e)这个脚本检查四件事:全角符号、圆括号配对、注释花括号配对、黑名单函数。前两项是语法层面的,第三项能拦住那种注释写一半导致后面全被吞掉的诡异问题,第四项是提醒你这不是语法错误,而是逻辑风险,需要人工确认。
5.2 信号对拍:用 Python 校准通达信的实际输出
写完代码不等于算对。最可靠的做法是拿同一只股票、同一段区间,让通达信和 Python 各出一份信号日期列表,然后做差集比对。
tdx = pd.read_csv("tdx_signals.csv", parse_dates=["date"])["date"] py_signals = set(df.loc[df["signal"], "date"]) only_tdx = [d for d in tdx if d not in py_signals] only_py = [d for d in py_signals if d not in set(tdx)] print(f"通达信有、Python 没有:{len(only_tdx)} 条,前 5 条 {only_tdx[:5]}") print(f"Python 有、通达信没有:{len(only_py)} 条,前 5 条 {only_py[:5]}")对不上的原因通常就那么几个:复权方式不同,通达信默认前复权,导出的原始数据可能是不复权,均线数值会整体偏移;停牌日处理不同,通达信会跳过停牌 K 线,Python 里如果数据是完整交易日历就会出现位置错位;还有就是CROSS在边界值相等时的判定差异,通达信把A==B归入未交叉,Python 里要写成严格大于当前、小于等于前值才对得上。
先把差集数量降到零,再去调参数和加过滤条件。顺序反了的话,后面所有优化都建立在一份口径不一致的数据上,等于白做。过滤条件我一般只加两条:剔除信号当日一字涨停的标的,因为买不进;剔除信号前 60 日累计换手率过低的标的,因为量能饱和度在这种标的上的参考价值会被严重稀释。
本文还有配套的精品资源,点击获取