简介:pyautotrade_tdx 是一套面向华泰证券通达信版的股票自动化交易脚本,适合具备 Python 基础、希望用程序替代人工盯盘与手动下单的量化交易爱好者。它可同时监控 5 只股票,按预设条件自动触发买卖,单次下单耗时控制在 2 秒以内,开发环境为 Win10 64 位搭配 Python3、pywin32 与 tushare,32 位 Python3 同样可用。资源包共 12 个文件,约 166KB,以 8 张 png 界面截图、1 个 pyw 主程序、1 个 py 封装模块、1 份 license 与 1 份 md 说明为主,截图直观展示对买对卖界面与参数设置,主程序负责条件判断与下单,封装模块则调用 Windows API 完成界面交互。使用前需先启动交易软件并停留在对买对卖界面,时间条件满足后再校验价格条件,股票数量按 100 的倍数取整。目前已有 3221 人学习,配套 QQ 群可供交流排错,适合想快速搭建自动化交易原型的读者参考。
1. 股票自动化交易:从手动盯盘到脚本托管,中间隔着多少坑
做股票自动化交易这件事,很多人第一反应是「写个脚本自动下单」,但真正落地时会发现,下单只是最末端的一环。pyautotrade_tdx 这个方向,核心思路是用 Python 对接通达信(TDX)的行情与交易接口,把选股、信号计算、委托下单、持仓管理串成一条自动化的链路。它解决的不是「能不能买到」的问题,而是「能不能在无人值守的情况下,按既定策略稳定执行」的问题。适合两类人:一是已经有明确交易策略、想摆脱手动执行延迟的量化爱好者;二是想把通达信作为行情源和交易通道、用 Python 做策略中枢的开发者。前提是你得接受一个现实——自动化交易不是印钞机,它只是把你的策略执行得更纪律化,策略本身的盈亏逻辑不会因为自动化而改变。
2. 拆解 pyautotrade_tdx:行情、信号、下单三条链路怎么接
2.1 为什么选通达信作为对接目标
通达信在国内券商体系里覆盖面广,大多数券商版客户端都基于它的内核,这意味着你写的对接逻辑有较大概率能复用。常见做法是两条路:一条是读取通达信本地数据文件(日线、分钟线),另一条是通过其行情接口或窗口消息机制获取实时数据。前者适合做历史回测和盘后选股,后者适合盘中实时信号触发。我一般会先用本地数据文件跑通策略逻辑,确认信号没问题,再切到实时链路,这样能把「策略错」和「对接错」两类问题分开排查。
本地数据文件主要是.day格式的日线数据和.lc1/.lc5格式的分钟线数据,存放在通达信安装目录的vipdoc文件夹下。读取这些文件不需要启动客户端,适合做批量回测。
import struct import os from datetime import datetime def read_tdx_day(filepath): """读取通达信 .day 日线文件 每条记录 32 字节:日期(4) 开(4) 高(4) 低(4) 收(4) 成交额(4) 成交量(4) 保留(4) 价格单位是分,需要除以 100 """ data = [] with open(filepath, 'rb') as f: buf = f.read() # 每条记录 32 字节,循环解析 for i in range(0, len(buf), 32): record = buf[i:i+32] if len(record) < 32: break # 日期是 YYYYMMDD 的整数 date_int = struct.unpack('<I', record[0:4])[0] open_p = struct.unpack('<I', record[4:8])[0] / 100.0 high_p = struct.unpack('<I', record[8:12])[0] / 100.0 low_p = struct.unpack('<I', record[12:16])[0] / 100.0 close_p = struct.unpack('<I', record[16:20])[0] / 100.0 amount = struct.unpack('<f', record[20:24])[0] volume = struct.unpack('<I', record[24:28])[0] data.append({ 'date': str(date_int), 'open': open_p, 'high': high_p, 'low': low_p, 'close': close_p, 'amount': amount, 'volume': volume }) return data # 使用示例:读取某只股票的日线数据 # 路径规则:vipdoc/sh/lday/sh600000.day 或 vipdoc/sz/lday/sz000001.day filepath = r'C:\new_tdx\vipdoc\sh\lday\sh600000.day' if os.path.exists(filepath): bars = read_tdx_day(filepath) print(f'共读取 {len(bars)} 条日线数据') print(bars[-1]) # 打印最新一条这段代码的关键点在于字节序和单位。通达信.day文件用小端序(<),价格字段是整数,单位是分,所以除以 100 才是元。成交额是浮点数,成交量是整数(股数)。如果你读出来的价格明显偏大或偏小,先检查是不是单位搞错了。日期字段直接是YYYYMMDD格式的整数,不需要额外转换。
2.2 信号计算:把策略翻译成可执行的 Python 逻辑
拿到行情数据后,下一步是计算交易信号。这里最容易翻车的地方是「未来函数」——用了当天收盘后才能确定的数据去判断当天开盘该不该买。血泪经验是:回测时信号漂亮得不行,实盘一跑就亏,多半是未来函数在作祟。
一个常见的双均线策略,信号逻辑应该是:短期均线上穿长期均线时产生买入信号,下穿时产生卖出信号。计算均线时只能用当前 bar 及之前的数据。
import pandas as pd def compute_ma_signal(bars, short_window=5, long_window=20): """双均线信号计算 bars: read_tdx_day 返回的列表 short_window: 短均线周期 long_window: 长均线周期 返回带信号的 DataFrame """ df = pd.DataFrame(bars) df['close'] = df['close'].astype(float) # 计算均线,min_periods 确保前期数据不足时不产生信号 df['ma_short'] = df['close'].rolling(window=short_window, min_periods=short_window).mean() df['ma_long'] = df['close'].rolling(window=long_window, min_periods=long_window).mean() # 信号:1 买入,-1 卖出,0 无操作 df['signal'] = 0 # 短均线上穿长均线 df.loc[(df['ma_short'] > df['ma_long']) & (df['ma_short'].shift(1) <= df['ma_long'].shift(1)), 'signal'] = 1 # 短均线下穿长均线 df.loc[(df['ma_short'] < df['ma_long']) & (df['ma_short'].shift(1) >= df['ma_long'].shift(1)), 'signal'] = -1 return df # 使用示例 # df = compute_ma_signal(bars, short_window=5, long_window=20) # buy_days = df[df['signal'] == 1] # print(f'买入信号次数: {len(buy_days)}')参数说明:short_window和long_window是最核心的两个参数。短周期越小,信号越灵敏但假信号越多;长周期越大,信号越稳定但滞后越明显。我一般会先用 5 和 20 跑一遍,看看信号分布是否合理,再根据标的波动特性调整。min_periods这个参数很多人会忽略,如果不设,前期均线会用不完整的数据计算,导致前几根 bar 的信号不可靠。
2.3 下单链路:从信号到委托的最后一公里
信号算出来之后,怎么把委托发出去?常见做法有三种:一是通过券商提供的 API(如果有),二是通过通达信的窗口消息机制模拟人工操作,三是通过第三方交易接口。第一种最稳定但门槛高,第二种最通用但最脆弱,第三种需要额外依赖。
窗口消息机制的核心思路是:找到通达信下单窗口的控件句柄,填入股票代码、价格、数量,然后模拟点击「买入」按钮。这种方式的问题在于,通达信版本更新后控件布局可能变化,导致脚本失效。所以我在写这类代码时,会把控件查找逻辑封装成独立函数,方便版本变化时快速定位修改。
import win32gui import win32con import time def find_window_by_title(title_keyword): """根据标题关键词查找窗口句柄""" result = [] def callback(hwnd, _): if win32gui.IsWindowVisible(hwnd): text = win32gui.GetWindowText(hwnd) if title_keyword in text: result.append((hwnd, text)) return True win32gui.EnumWindows(callback, None) return result def send_order_via_window(stock_code, price, volume, direction='buy'): """通过窗口消息发送委托 注意:这只是一个框架示例,实际控件 ID 需要根据客户端版本调整 """ # 查找下单窗口 windows = find_window_by_title('买入') if not windows: print('未找到下单窗口,请确认客户端已打开') return False hwnd = windows[0][0] # 这里需要根据实际控件结构填写代码、价格、数量的输入框句柄 # 通常需要用 EnumChildWindows 遍历子控件 # 填入股票代码 # win32gui.SendMessage(code_input_hwnd, win32con.WM_SETTEXT, 0, stock_code) # 填入价格 # win32gui.SendMessage(price_input_hwnd, win32con.WM_SETTEXT, 0, str(price)) # 填入数量 # win32gui.SendMessage(volume_input_hwnd, win32con.WM_SETTEXT, 0, str(volume)) # 点击买入按钮 # win32gui.SendMessage(button_hwnd, win32con.BM_CLICK, 0, 0) print(f'委托已发送: {direction} {stock_code} 价格{price} 数量{volume}') return True这段代码的重点不是直接能用,而是展示窗口消息机制的结构。实际使用时,你需要用 Spy++ 或类似的工具找到目标控件的句柄和 ID。参数方面,stock_code要带市场前缀(如sh600000),price要用委托价格(不是市价),volume要是 100 的整数倍。发送委托后,一定要加延时并检查委托是否成功,不能发完就不管了。
3. 避坑指南:自动化交易里那些让你半夜爬起来的事
3.1 行情延迟导致的信号错位
现象:回测时信号在某个价格触发,实盘时成交价差了好几个点。原因通常是行情数据有延迟,或者你用的数据周期和信号计算周期不一致。解决方法是:在信号计算和下单之间加一层校验,确认当前行情时间戳和信号时间戳的差距在可接受范围内。如果差距过大,宁可放弃这次信号,也不要追。
3.2 委托被拒但脚本以为成功了
现象:脚本显示已下单,但实际持仓没变化。原因是窗口消息发送后没有检查返回结果,或者委托被券商风控拦截了。解决方法是:每次发送委托后,主动查询委托列表或持仓列表,确认委托状态。如果发现异常,记录日志并触发告警。我一般会在脚本里加一个「委托确认」环节,发送后等待 2 秒,然后检查委托编号是否生成。
3.3 客户端弹窗打断自动化流程
现象:脚本运行到一半卡住,发现通达信弹了一个公告窗口或升级提示。原因是客户端的一些非交易弹窗抢占了焦点。解决方法是:在脚本启动时先关闭所有非必要弹窗,并在主循环里定期检查是否有新弹窗出现。可以用win32gui遍历窗口,发现标题包含「公告」「提示」「升级」等关键词的窗口就自动关闭。
3.4 资金计算错误导致超买
现象:脚本连续发出多笔买入委托,导致可用资金不足,部分委托被拒。原因是没有实时更新可用资金,或者多线程同时下单时没有加锁。解决方法是:在每次下单前重新查询可用资金,并预留一定比例的安全垫。如果是多线程,必须用锁保护下单逻辑。我一般会预留 5% 的资金作为缓冲,避免因为手续费或价格波动导致资金不足。
3.5 日志缺失导致问题无法回溯
现象:脚本跑了一周,突然某天没执行,但完全不知道发生了什么。原因是没有记录关键操作的日志。解决方法是:至少记录以下几类日志——信号触发时间、委托发送时间、委托返回结果、持仓变化、异常堆栈。日志按天切分,保留至少一个月。这样出问题时能快速定位是行情问题、信号问题还是下单问题。
4. 把自动化交易跑稳的三个进阶习惯
4.1 用模拟盘先跑通全链路
很多人一上来就实盘,结果因为一个小 bug 亏了钱。我的习惯是:任何策略在实盘前,先在模拟盘跑至少两周。模拟盘能暴露大部分对接问题,比如委托格式错误、资金计算错误、弹窗干扰等。模拟盘跑稳了,再切实盘,实盘初期也只用小资金。
4.2 给脚本加一个「急停开关」
自动化交易最怕的是失控。我一般会在脚本里加一个文件监控:如果某个特定文件存在,脚本就暂停下单,只记录信号。这样在发现异常时,可以快速手动干预,而不需要直接杀进程。急停开关的检查频率不用太高,每轮循环检查一次即可。
4.3 定期校验数据完整性
通达信的本地数据文件偶尔会出现缺失或损坏,导致信号计算错误。我一般会每周跑一次数据校验:检查最近 N 天的数据是否有缺失,价格是否有异常跳变。如果发现异常,先重新下载数据,再重新计算信号。这个习惯帮我避免了好几次因为数据问题导致的错误信号。
| 校验项 | 检查方法 | 处理方式 |
|---|---|---|
| 数据缺失 | 对比交易日历,检查是否有缺失日期 | 重新下载对应日期的数据 |
| 价格异常 | 检查单日涨跌幅是否超过合理范围 | 确认是否除权除息,否则标记异常 |
| 成交量异常 | 检查成交量是否为 0 或异常放大 | 确认是否停牌或数据错误 |
| 均线断裂 | 检查均线是否有突然跳变 | 检查原始数据是否有跳变 |
这三个习惯看起来简单,但坚持下来能避开大部分低级错误。自动化交易的核心不是策略多复杂,而是执行多稳定。希望帮到你。
本文还有配套的精品资源,点击获取