ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python批量导入股票代码到通达信自选股,告别手动添加

用Python批量导入股票代码到通达信自选股,告别手动添加 先说一个我自己的真实场景。每天收盘后我要干一件特别机械的事把策略选出来的二十几只股票一只一只手动加到通达信自选股里。一开始还能忍后来选股频率提到盘中出信号手指在键盘上按到发酸还经常漏加、重复加。直到某天我翻了通达信的安装目录才意识到所谓的“自选股”压根不是什么加密数据库而是一个个明明白白的文本文件。于是我写了一个Python脚本把整个流程压缩成一句话关掉通达信双击运行脚本选股结果几十只股票一次进自选股。这个方案适合谁只要你平时用通达信看盘、有自己的选股结果不管来自公式选股、Python策略还是人工筛选并且愿意复制一段代码改个路径就能省下每天至少十分钟的机械劳动。不需要装任何第三方库纯Python标准库实现Windows系统直接用。下面我把原理、完整代码和踩坑过程一次讲透。1. 手动加自选股的痛以及为什么我盯上了blk文件1.1 手动流程到底有多浪费时间先说手动操作的完整链路打开你的选股表格或文本 → 看一只代码 → 切到通达信 → 在自选股列表里按快捷键添加 → 再切回来 → 看下一只。如果选股结果有三十只这一套动作就要重复三十遍还不算中途接电话、弹窗打断导致漏掉某只的情况。更麻烦的是很多人的选股结果是多个策略的组合。今天A策略选出10只B策略选出8只中间还有交集你手动加的时候还得自己记着哪只已经加过了。一旦某天加到一半被打断后面就很难想起来加到哪里。这个过程不只是浪费时间还很伤心态——看盘本来就需要集中注意力全耗在这种重复劳动上太亏了。1.2 两条自动化路线的取舍网上解决这个问题的方案大致分两类。第一类是模拟键鼠用AutoHotkey或Python的pyautogui读取代码之后自动切到通达信输入。这个方案我试过能用但很脆。通达信界面只要有一点变化弹个广告、行情卡顿、窗口位置偏移脚本就可能敲错地方。而且模拟键鼠占着键盘鼠标你什么都干不了盘中根本不敢用。第二类就是今天要讲的直接写通达信的自选股文件。通达信的自选股本质是一个纯文本板块文件我们只要按它的格式把代码写进去通达信再启动时就会读到。这两条路线我做过对比直接写文件明显更稳、更快、可以后台运行唯一“缺点”是写入后需要让通达信重新读取也就是重启一次通达信。相比每天省下的十几分钟重启一次完全值得。下面是三种方案的对比方案开发成本稳定性是否占键盘鼠标是否需要重启通达信手动复制粘贴无高是不需要模拟键鼠自动点击中低受界面变化影响是不需要直接写入blk文件低高否需要2. 通达信自选股的文件真相T0002\blocknew目录里有什么2.1 找对目录事情就成功了一半通达信安装完成后数据目录一般长这样安装目录\T0002\blocknew。比如我的经典版装在D盘路径就是D:\new_tdx\T0002\blocknew。但这个路径在不同版本上不完全一样券商定制版尤其喜欢改目录名常见的有new_tdx、zd_zsone、hexin等。所以最靠谱的办法是右键通达信桌面快捷方式 → 打开文件所在位置 → 在安装目录下找T0002 → 进去之后看有没有blocknew文件夹。怎么确认你找对了看这个目录里有没有.blk文件。正常情况下只要你在通达信里添加过自选股blocknew目录下就会出现类似这样的文件Z自选股.blk ZD自选股1.blk ZD自选股2.blk其中“Z自选股.blk”就是通达信默认的自选股板块。ZD开头的则是“自选股1”“自选股2”这类扩展板块。每个.blk文件都对应通达信界面里的一个板块文件名里的中文就是板块名。2.2 blk文件的编码、换行和格式用记事本打开Z自选股.blk你会看到内容非常简单就是一行一个六位股票代码没有市场前缀没有价格没有备注。比如600519 000001 300750这里有几个关键细节写脚本前必须搞明白第一文件编码是GBK简体中文Windows上通常等同于ANSI不是UTF-8。如果写入UTF-8编码的文件通达信解析时可能把第一行读坏。第二行尾建议用CRLF\r\n虽然实测通达信对单独的\n也能容忍但既然要写就写规范免得某些版本读取异常。第三代码不带sh/sz前缀纯六位数字。沪深主板、创业板、科创板、北交所在blk文件里都统一存六位。2.3 那些.dat文件要不要管blocknew目录里除了.blk还有一堆.dat文件。这些.dat文件保存的是板块的索引信息、板块名称、关联的.blk文件路径。实测下来往已有板块里加股票只需要改.blk文件就够了.dat不用动。那什么时候需要动.dat呢如果你要用脚本创建一个全新板块光写.blk可能不够通达信界面上不一定认。我的建议是板块的创建交给通达信界面来做在通达信里手动新建好“实验池”“备用池”之类的板块然后脚本只负责往现有板块文件里追加代码。这样分工最省心。容易忽略的一点如果你在通达信里从没添加过自选股blocknew目录可能根本没有Z自选股.blk。别慌先去通达信里手动加一只股票这个文件就会自动生成之后再用脚本操作。3. 开工之前Python环境和目录确认这两件事别搞错3.1 Python版本与安装检查脚本只用Python标准库不需要pip install任何东西。版本建议Python 3.8及以上太老的版本有些语法可能不支持。没装Python的话去python.org下载安装包安装时务必勾选“Add Python to PATH”这一项否则命令行里运行python会提示找不到命令。装好之后打开命令行输入python --version能输出版本号就说明环境OK。如果提示“python不是内部或外部命令”先检查是不是安装时没勾选PATH或者安装完没重新打开命令行窗口。3.2 找到并确认blocknew目录这一步非常关键很多人脚本跑不通就是路径写错了。请打开通达信安装目录找到T0002\blocknew这个文件夹把路径完整复制下来。如果你电脑上装了多个通达信比如一个自用的、一个券商定制的务必确认你日常打开的是哪一个找到对应的那个目录。我有个朋友就是路径写到了另一个没怎么用的通达信上脚本每次跑完都提示成功打开常用的通达信一看自选股纹丝不动。如果你打开blocknew目录发现是空的或者连blocknew目录都没有请先启动通达信随便加一只股票到自选股再回来刷新看这时候文件应该出现了。确认好目录后建议先手动备份一下整个blocknew目录放到一个临时文件夹里。后面无论怎么折腾至少能恢复。这一步只要一分钟但能救命。3.3 脚本文件放在哪脚本本身放哪里都行但我建议放在blocknew目录所在的T0002上一级也就是通达信安装目录下新建一个scripts文件夹。理由很简单你的Python脚本、选股结果文件、日后的定时任务都集中在一个地方不至于满硬盘找文件。路径里有中文也没关系Python 3在Windows下处理中文路径没有问题。还有一个建议不要用记事本直接编辑.blk文件然后另存为UTF-8因为你一旦动了编码通达信可能直接不认。折腾脚本比手工整理自选股省力得多但前提是别把源头文件搞乱。4. 完整代码与逐段拆解从选股结果到自选股只有一步4.1 这个脚本能干哪些事先说功能范围。脚本支持从文本文件/CSV里自动提取股票代码命令行直接传多个代码自动去重已存在的代码跳过自动备份原板块文件检测通达信是否在运行并提醒支持指定写入哪个板块支持清空板块后重新写入。对于日常“增量导入”和周末“整体刷新”这两种使用场景这套功能足够了。4.2 完整源码下面是完整代码命名我建议存为import_to_tdx.py放哪都行。代码内部控制台输出是中文Windows下默认编码可能显示乱码可以按下面注释里说的处理。# -*- coding: utf-8 -*- 一键把选股结果导入通达信自选股Python标准库实现 用法示例 python import_to_tdx.py 600519 000001 python import_to_tdx.py -f result.txt python import_to_tdx.py -f result.csv -b ZD自选股1.blk python import_to_tdx.py --clear -f new_codes.txt import os import sys import re import shutil import argparse import datetime import subprocess # 配置区改成你的实际路径 # 注意Windows路径建议用正斜杠或者写成 rD:\new_tdx\T0002\blocknew BLOCK_DIR D:/new_tdx/T0002/blocknew DEFAULT_BOARD_FILE Z自选股.blk # 如果命令行没传代码也没传文件会使用这个列表临时用 STOCK_CODES [] # def normalize_code(raw): 把各种格式的股票代码清洗成6位字符串。 if raw is None: return None code str(raw).strip() # 去掉常见市场前缀如 sh600519 / sz.000001 / bj830799 code re.sub(r^(sh|sz|bj|eq|of)[\.\s:_-]*, , code, flagsre.I) # 去掉常见市场后缀如 600519.sh / 000001.sz code re.sub(r[\.\s:_-]*(sh|sz|bj|eq|of)$, , code, flagsre.I) # 如果是纯数字且位数不足6位比如CSV里把000001读成了1补零 stripped re.sub(r[^0-9], , code) if stripped.isdigit() and len(stripped) 6: code stripped.zfill(6) if len(code) 6: return code # 从混合文本中提取连续的6位数字 m re.search(r(?!\d)(\d{6})(?!\d), code) if m: return m.group(1) return None def is_valid_code(code): 宽松校验6位数字就放行。把过滤责任交给选股源。 return len(code) 6 and code.isdigit() def parse_from_text(text): 从任意文本中提取股票代码兼容逗号、空格、换行、中文混排。 result [] seen set() for m in re.finditer(r(?!\d)(\d{6})(?!\d), text): code m.group(1) if is_valid_code(code) and code not in seen: result.append(code) seen.add(code) return result def read_text_auto(path): 自动识别UTF-8/GBK并读取文本文件。 with open(path, rb) as f: raw f.read() for enc in (utf-8-sig, utf-8, gbk): try: return raw.decode(enc) except UnicodeDecodeError: continue return raw.decode(gbk, errorsignore) def load_existing(blk_path): 读取现有blk自选股返回(保持顺序的列表, 快速去重集合)。 codes_list [] codes_set set() if not os.path.exists(blk_path): return codes_list, codes_set try: # newlineNone 表示按通用换行符读取兼容 \r\n 和 \n with open(blk_path, r, encodinggbk, errorsignore, newlineNone) as f: for line in f: code normalize_code(line) if code and code not in codes_set: codes_list.append(code) codes_set.add(code) except OSError as e: print(f读取现有自选股文件失败{e}) return codes_list, codes_set def is_tdx_running(): 粗略检测通达信相关进程是否在运行仅Windows。 try: out subprocess.run( [tasklist], capture_outputTrue, textTrue, timeout15, creationflagsgetattr(subprocess, CREATE_NO_WINDOW, 0), ).stdout for line in out.splitlines(): parts line.split() if not parts: continue name parts[0].lower() if tdx in name: return True except Exception: pass return False def write_block(blk_path, code_list, backupTrue): 把代码列表写回blk文件手动控制行尾为CRLF避免Python默认换行转换。 if backup and os.path.exists(blk_path): bak f{blk_path}.bak_{datetime.datetime.now():%Y%m%d_%H%M%S} shutil.copy2(blk_path, bak) print(f原文件已备份到{bak}) try: # newline 很关键禁用Python自动换行转换否则 \r\n 会被写成 \r\r\n with open(blk_path, w, encodinggbk, newline) as f: for code in code_list: f.write(code \r\n) except OSError as e: print(f写入文件失败{e}) raise SystemExit(1) def main(): parser argparse.ArgumentParser(description导入选股结果到通达信自选股) parser.add_argument(codes, nargs*, help股票代码如 600519 000001) parser.add_argument(-f, --file, help选股结果文件txt/csv都可以) parser.add_argument(-b, --board, defaultDEFAULT_BOARD_FILE, help板块文件名默认Z自选股.blk) parser.add_argument(--clear, actionstore_true, help清空目标板块后重新写入) parser.add_argument(--no-backup, actionstore_true, help写入前不备份原文件) parser.add_argument(--force, actionstore_true, help通达信正在运行时也强制写入) args parser.parse_args() new_codes [] if args.file: if not os.path.exists(args.file): print(f文件不存在{args.file}) sys.exit(1) new_codes.extend(parse_from_text(read_text_auto(args.file))) for raw in args.codes: code normalize_code(raw) if code: new_codes.append(code) if not new_codes: for raw in STOCK_CODES: code normalize_code(raw) if code: new_codes.append(code) # 去重保持输入顺序 unique [] seen set() for code in new_codes: if code not in seen: unique.append(code) seen.add(code) if not unique: print(没有识别到有效股票代码请检查输入。) sys.exit(1) blk_path os.path.join(BLOCK_DIR, args.board) # 以后每次跑都提示这个很多人会忽略 if is_tdx_running() and not args.force: print(检测到通达信正在运行。为防止通达信退出时用旧数据覆盖写入结果请关闭通达信后再执行。) print(如果你确认自己了解风险可以追加 --force 参数强制写入。) sys.exit(2) old_list, old_set load_existing(blk_path) already [c for c in unique if c in old_set] append [c for c in unique if c not in old_set] if already: print(f以下代码已在自选股中跳过{, .join(already)}) if args.clear: final_list append action_info f清空后写入 {len(append)} 只 else: if not append: print(没有需要新增的代码任务结束。) return final_list old_list append action_info f新增 {len(append)} 只 # 确保blocknew目录存在首次使用时可能没有 os.makedirs(BLOCK_DIR, exist_okTrue) write_block(blk_path, final_list, backupnot args.no_backup) print(f操作成功{action_info}到 {args.board}) if append: print(新增代码 , .join(append)) print(f该板块现有股票数量{len(final_list)}) print(重新打开通达信后自选股就会更新。) if __name__ __main__: main()4.3 核心函数的几个设计理由先看normalize_code。这个函数专门处理各种“脏代码”有人从Excel复制出来的是“SH600519”有人从同花顺复制出来的是“600519.SH”还有人CSV文件里把000001变成了数字1。这些情况我都遇到过。代码先用正则去掉sh/sz前缀和后缀再对纯数字补零最后用正则从混合文本里抠出连续六位。这套组合拳能覆盖绝大多数真实场景。再看read_text_auto。选股结果文件的编码不能假设很多策略脚本输出UTF-8通达信导出的文本却是GBK。我用二进制方式读取先尝试UTF-8带BOM优先再尝试GBK总能解出正确文本。load_existing返回列表和集合两个东西而不是只返回一个set原因是要保序。自选股文件里代码的顺序就是通达信界面里的显示顺序你要是用set一装顺序全乱打开通达信看到自选股乱糟糟的很难受。列表保序集合用来快速查重各司其职。write_block里那个newline可能是全脚本最容易忽略但最重要的细节。Python在Windows上写文本文件时默认会把\n自动转成\r\n。假如代码里写的是f.write(code \r\n)最终落盘就会变成\r\r\n。通达信虽然对行尾容忍度挺高但文件被写坏的时候排查起来莫名其妙。传newline之后就禁用了自动转换我们写什么就是什么。is_tdx_running这个检测函数用tasklist列出所有进程找进程名里带“tdx”的。这个模糊匹配会误伤一些名字带tdx的程序但影响不大最多多提醒一句。真正的价值在于防止通达信运行时写入文件被通达信退出时的内存数据覆盖。这是我踩过一次的坑后面详细说。5. 三种使用方式与验证方法5.1 方式一临时用直接改脚本内的STOCK_CODES如果你只是偶尔往自选股里加几只股票最省事的方式是打开脚本在配置区改STOCK_CODESSTOCK_CODES [ 600519, 000001, 300750, ]命令行进入脚本目录运行python import_to_tdx.py这个方式适合不想记命令行参数、一周只想手动跑一两次的人。缺点是每次要开编辑器改列表代码多的时候容易改错但作为临时手段完全够用。5.2 方式二从选股结果文件导入日常最推荐我的日常用法是策略脚本把选股结果输出成一个文本文件然后调通达信导入脚本。假设文件名是result.txt内容这样写600519,000001,300750 603259纯代码、逗号分隔、换行分隔、中文混排都没问题。脚本里的正则会把所有六位数字提取出来无关文字自动过滤。运行命令python import_to_tdx.py -f result.txt如果输出文件是CSV也一样处理。如果是通达信自带的选股结果导出文件通常每行是股票代码加股票名称比如“600519 贵州茅台”脚本也能正确提取600519。还有一个常见场景你从网页上复制了一段股票列表里面混杂着代码、名称、涨跌幅直接存成一个txt然后传给-f参数照样能用。5.3 方式三命令行直接传代码、换板块、清空重写进阶用户可以直接在命令行传股票代码多个代码用空格隔开python import_to_tdx.py 600519 000001 300750想写入“自选股1”而非默认的“Z自选股”用-b指定blk文件名python import_to_tdx.py -f result.txt -b ZD自选股1.blk注意这里传的是blk文件名不是板块中文名。自选股1对应的文件名是ZD自选股1.blk这个最好打开blocknew目录对照着看别猜。周末想整体刷新一个板块把旧的清空、写入全新名单python import_to_tdx.py --clear -f weekend_result.txt这个命令会先备份原文件然后清空板块再写入新代码。适合每周重新选股、不想保留旧股票的场景。5.4 运行后怎么验证脚本运行结束控制台会打印“新增代码”和“该板块现有股票数量”。如果新增数量符合预期重启通达信打开自选股板块就能看到新代码。有几个验证细节要提醒如果脚本提示“检测到通达信正在运行”而你确实开着通达信请保存通达信里的操作后退出再跑一次。看到“操作成功”但通达信自选股没变化先确认通达信是不是真的重启过不是最小化而是进程退出后重新打开。写入的是“自选股1”但你去默认“自选股”界面里找那肯定找不到。确认板块名和文件名对应关系。6. 我踩过的五个坑编码、进程占用、板块写错、重复爆炸和换行符6.1 坑一自选股全部乱码罪魁祸首是UTF-8带BOM第一次尝试写这个脚本时我用的是平时写Python的习惯open(path, w, encodingutf-8)。写完之后打开通达信第一只股票显示成了类似“锘?600519”的乱码后面股票正常。我一度以为正则提取出了问题后来用十六进制工具看了blk文件头部才发现文件里多了三个字节EF BB BF也就是UTF-8的BOM头。通达信把BOM当成股票代码的一部分导致第一行解析失败。这个问题的解法就是我上面代码里的写法写入用encodinggbk不会产生BOM。另外千万别用记事本打开.blk文件后另存为UTF-8同理。6.2 坑二写入了但自选股没变化通达信内存数据会覆盖掉你的修改这是最隐蔽的一个坑。有一阵我为了省事脚本跑完不想重启通达信直接切到软件里看。结果自选股列表一动不动我当时以为是文件没写进去。但用记事本打开blk文件代码明明在里面。真实原因是通达信启动后把自选股板块整个读进内存界面显示的是内存里的那份数据。外部改了文件界面不会实时刷新。更麻烦的是如果你在这个状态下退出通达信通达信会把内存里的旧板块数据写回文件把你刚才辛辛苦苦写进去的代码覆盖掉。所以我的脚本默认严格拒绝在通达信运行时执行。如果你确实在盘中需要写入可以加--force强写但写完最好别让通达信正常退出不然白写了。6.3 坑三板块文件写错自选股“消失”了有次我想把代码写到“自选股1”板块随手在命令行敲了-b Z自选股1.blk结果文件名和真实文件名对不上真实是ZD自选股1.blk。脚本运行完提示成功但打开通达信默认自选股还是老样子自选股1里也没新增。我一度以为脚本坏了后来打开blocknew目录一看才发现脚本创建了一个名为“Z自选股1.blk”的新文件通达信根本不认这个文件名。教训是任何自定义板块先把blocknew目录里的真实文件名列出来再复制粘贴到命令行不要靠记忆敲。用-b参数之前养成先ls一下的习惯。6.4 坑四重复导入导致自选股爆炸早期脚本没做去重我连续跑了几次“增量导入”之后某天打开自选股发现文件里有两千多行大量代码重复出现自选股列表滚动得手指冒火。手动清理那等于又回到了手工劳动。后来我才意识到脚本必须做两层去重第一层输入源内部的重复要过滤第二层与已有自选股重复的要跳过。这两个逻辑现在的代码里都有。如果哪天你发现自选股已经乱了也不用愁先用--clear清空再重新导入一份全量名单就行。6.5 坑五Python在Windows下的换行符陷阱这个问题属于“不遇到就完全想不到”的类型。一开始我用write(code \r\n)写文件自我感觉非常规范但用十六进制工具打开blk文件发现每行结尾是0D 0D 0A也就是\r\r\n多了一个回车符。原因是Python的open()在Windows上默认做换行转换text模式写入时把字符串里的\n自动替换为\r\n导致原本的\r\n被展开成了\r\r\n。通达信解析数字的时候会把行尾空白strip掉所以多数情况下看不出问题。但如果你某天写文件时不小心混入了其他字符这个多出来的\r会成为压死骆驼的那根草。解决办法就是代码里的open(..., newline)关闭自动转换我们手动控制行尾。7. 让脚本每天自动跑起来定时任务与策略联动7.1 用Windows任务计划定时执行脚本能手动跑不稀奇能每天自动跑才算真正释放双手。Windows自带的“任务计划程序”就可以干这个。创建一个基本任务触发器选每天时间设在你想要的收盘后或盘中时段操作选择“启动程序”程序填你的python.exe完整路径参数填脚本路径和选股结果文件路径。举个例子我用的是C:\Python311\python.exe D:\tdx_scripts\import_to_tdx.py -f D:\tdx_scripts\result.txt注意python.exe一定要用完整路径因为任务计划程序运行时PATH可能不包含Python。如果有人不熟悉图形界面也可以直接用命令行创建schtasks /create /tn import_tdx /tr C:\Python311\python.exe D:\tdx_scripts\import_to_tdx.py -f D:\tdx_scripts\result.txt /sc daily /st 15:30这里有个前提选股结果文件必须在定时任务触发前生成好。如果你用通达信公式选股可以先把选股结果导出到固定路径如果你用Python策略直接让策略脚本先产出结果再调导入脚本。7.2 和你的选股策略脚本联动起来如果你本身就用Python做选股那就更简单了。策略脚本跑完拿到了股票代码列表直接把导入函数拉进来用。假设你的策略输出是一个DataFrameimport subprocess def push_to_tdx(codes): subprocess.run([ python, rD:\tdx_scripts\import_to_tdx.py, *codes ], checkTrue)或者更直接一点把import_to_tdx.py里的函数import进你的策略脚本在内存里直接把结果写进blk文件。不过这样耦合度高我更喜欢用命令行参数这个口子互不干扰。7.3 更进一步盘中信号与收盘稳定的两种用法最后说下我的个人习惯。盘中策略出信号的时候我只把信号代码记到日志文件里不急着导入通达信因为盘中导入还得重启通达信而重启就断了实时行情。这时候我会等收盘后把当日所有信号统一导进去第二天开盘前就能看到板块里整理好的候选股。周末或者月末我会用--clear参数做一次全量刷新把旧的、已经走完的股票清空重新写入一份新的观察池。这样自选股永远是干净的不会变成垃圾堆积场。脚本带来的本质不是“省几分钟”而是让你敢做更多轮的选股实验。以前手工时代我每次都尽量控制选股数量因为加自选股太费劲了。现在不管选出来一百只还是两百只导进去也就一秒钟的事。这一秒钟省下来的时间我会拿去检查策略本身的问题而不是把精力消耗在复制粘贴上。这个工具不难但它是我日常流程里最值的一段代码之一。
返回列表