Python+TDXPystock搭建股票交易自动化系统实战解析
简介:这是一套面向Python开发者与量化交易初学者的股票自动化交易系统源码,聚焦于A股市场实时盯盘、策略执行与资金流向分析等核心场景。资源共77个文件,含49个Python脚本(覆盖数据采集、选股逻辑、北向/南向资金分析、通达信早盘数据处理、语音转文字通知等)、3个Qt Designer UI界面文件、2个HTML前端页面、6个XML配置文件及SQL数据库脚本等,包体大小为74.09MB,结构完整、模块解耦清晰。已有822人学习下载,适合希望快速构建本地化股票辅助交易工具的学习者与个人投资者。用户可直接复用龙头盯盘、操盘神器UI、北向资金分析工具等成熟功能模块,调用已封装的TDX数据读取、自定义文件写入、微信通知、Excel导出等实用脚本,并基于requirements.txt快速部署依赖环境。 最近把一套基于Python和HTML的TDXPystock股票交易自动化设计源码重新整理了一遍。它解决的核心问题,就是用程序替人盯盘、算信号、下单,把你在券商软件上重复耗时的操作自动化。说白了,你不用再每天早上打开行情软件盯着均线纠结买不买,也不用收盘后手动翻遍自选股找信号。这套系统会按照你设定的策略跑行情数据、计算买卖点、记录信号,再通过一个本地HTML面板把所有结果一屏展示出来,甚至预留了对接券商接口的模拟下单通道。如果你是一个对程序化交易感兴趣、想从手动炒股跨到半自动策略的Python开发者,或者只是想找一套结构清晰的交易自动化源码来学习,这篇文章应该能帮到你。下面我会把整套源码的架构、核心模块、部署方式和排坑经验完整拆开讲。
1. 项目整体设计与技术选型思路
1.1 为什么选 Python 做核心开发语言
做交易自动化,整个链路无非三件事:数据获取、策略计算、执行下单。这三个环节Python都有很成熟的生态支撑。数据获取层面,有足够的第三方库对接行情源;策略计算层面,pandas处理K线数据非常顺手,几百兆的日线数据在内存里做向量化计算,速度完全能接受;至于执行下单,虽然有延迟,但对我们这种按分钟级甚至日线级别跑的普通个人策略来说,根本感知不到性能瓶颈。
相比C++或者Java,Python最大的优势是开发效率。你上午冒出个策略点子,下午就能写出来跑一轮回测,这在需要快速验证想法的场景里太重要了。源码里耗时最重的历史K线拉取和指标计算都放在独立线程里跑,前端展示用的是HTML,通过HTTP接口读状态,两者互不干扰。既保证了策略主循环不被界面卡顿拖累,也把开发成本压到了最低。
1.2 HTML 前端在自动化系统里到底解决什么问题
你可能会问:策略都是脚本在跑,为什么非得多套一个HTML页面?我一开始也觉得搞前端是画蛇添足,但真正跑起来才发现,没有可视化面板,盯盘和调试都极端痛苦。你总不能每天对着黑乎乎的终端翻日志,去判断今天哪只股票触发了信号吧?
HTML面板在源码里扮演的是“操作台”和“监控屏”的双重角色。它用浏览器打开,所有数据都来自本地Python后端,不依赖外网,也不引入重框架。页面里能看到每只股票的最新价格、均线排列状态、最近一次买卖信号、策略是否在正常运行。更关键的是,前端留了一个手动按钮,可以在极端行情下随时暂停所有策略动作。这套交互虽然简单,但对日常使用来说足够实用了。
1.3 整体架构和数据流转方式
整个项目可以拆成四层:
- 数据层:通过TDXPystock这个库封装底层通达信协议,定时拉取分钟线和日线数据,同时把历史数据落盘到本地SQLite,避免每次重启都重新拉全量。
- 策略层:从数据层拿K线,计算技术指标,按策略规则生成买入和卖出信号。
- 执行层:监听策略层输出的信号,先过风控校验,再决定是模拟记录还是走券商接口下单。
- 展示层:用Flask搭建轻量HTTP服务,把运行状态、持仓列表、信号历史传给HTML页面渲染。
数据流大概是这样一个环形:定时任务先触发数据拉取,策略层拿到最新K线后计算信号,信号进入执行层做风控,最后结果推给前端更新。这套逻辑本身不复杂,但如果开始就把模块写成一个大文件,后续换数据源、加新策略都会非常痛苦。源码一开始就按这个分层去写,后面所有扩展都只需要改对应模块,不需要动主流程。
2. 核心模块详解与关键代码逻辑
2.1 行情接入:TDXPystock 封装了什么
通达信软件本身没有官方公开的API,所以社区里出现了各种基于通达信私有协议的Python库。TDXPystock的思路就是把这些底层的TCP通信协议封装成“傻瓜式”的函数调用,让开发者不需要关心报文结构,只要调用方法就能拿到标准化的K线数据帧。
在实际使用中,获取股票日K线只需要几行代码:
from tdxpystock import TdxClient client = TdxClient(host="119.147.212.81", port=7709) df = client.get_k_line("600519", category=9, count=800) # category: 9代表日K,0代表5分钟K,1代表1分钟K,8代表15分钟K print(df.tail())运行之后返回的DataFrame包含以下核心字段:
| 字段 | 含义 | 类型 |
|---|---|---|
| date | K线时间 | datetime |
| open | 开盘价 | float |
| high | 最高价 | float |
| low | 最低价 | float |
| close | 收盘价 | float |
| volume | 成交量 | int |
| amount | 成交额 | float |
这里有几个坑值得注意。第一,通达信的服务器地址列表需要定期维护,这个IP一旦失效就必须换备用地址,否则程序会一直卡在连接阶段。第二,拉取数据时尽量手动指定count,如果你一口气全量拉十年数据,很容易被服务器主动断开。TDXPystock返回的数据字段在不同版本里有稍微不同的命名风格,稳妥的做法是打印一次列名,写个映射函数做统一标准化,这样后面所有策略模块都不会踩到字段名不一致的坑。
2.2 技术指标计算与信号生成逻辑
源码默认内置了三套策略,最基础也最适合入门学习的是双均线策略。核心逻辑就是短周期均线上穿长周期均线时产生买入信号,下穿时产生卖出信号。用pandas实现非常简洁:
df["ma_short"] = df["close"].rolling(window=5).mean() df["ma_long"] = df["close"].rolling(window=20).mean() df["signal"] = 0 df.loc[df["ma_short"] > df["ma_long"], "signal"] = 1 df["signal_change"] = df["signal"].diff()signal_change从0变成1的位置就是金叉点,从1变成0的位置就是死叉点。这里就是新手最常犯错的地方:如果用实时K线反复刷新,同一个金叉信号会连续触发很多次,导致重复下单。源码里没有直接拿布尔值判断,而是用diff()抓变化沿,并且额外加了一个冷却机制,在信号触发后一段时间内不允许再次交易。
除了双均线,源码里还有MACD和RSI两个策略模块。所有策略都继承同一个基类,只需要实现generate_signals(df)方法即可,主流程完全不用改。这种抽象方式,让我后续加新策略时轻松很多,不用在策略引擎里到处加判断分支。
2.3 回测模块:先验证再进实盘
我强烈建议任何策略都不要直接接进实盘,先拿历史数据回测一遍。源码里的回测引擎做得很轻量,逻辑是遍历每一天的K线,信号触发时以收盘价买入,卖出信号触发时以收盘价卖出,最后统计总收益率、最大回撤、交易次数和胜率。
def backtest(df, initial_capital=100000): capital = initial_capital position = 0 trade_count = 0 for i in range(1, len(df)): if df["signal_change"].iloc[i] == 1 and position == 0: position = capital / df["close"].iloc[i] capital = 0 trade_count += 1 elif df["signal_change"].iloc[i] == -1 and position > 0: capital = position * df["close"].iloc[i] position = 0 trade_count += 1 final_value = capital + position * df["close"].iloc[-1] return final_value, trade_count这里有个容易误判的地方:回测收益很漂亮,不代表未来能赚钱。回测更大的意义在于排除明显有问题的策略。如果某个策略在历史震荡行情里反复被“打脸”,交易次数会异常多,手续费和滑点成本会吃掉大量利润。源码回测模块默认把交易成本设置为万分之三,并且支持自定义滑点参数。很多人回测时忽略成本,结果实盘收益和回测差一大截,就是栽在这里。
2.4 自动化下单与风控模块
执行层是整套系统里最需要谨慎对待的部分。源码默认开启“模拟盘”模式,所有买卖信号都只是记录到日志和前端面板,不会真实下单。如果要接实盘,需要自己在broker.py里实现buy()和sell()两个方法,对接券商提供的Python交易接口。
下单模块被抽象成统一接口,这样不同券商之间的差异就被隔离了。我自己在接实盘之前给执行层加了几条硬性风控:单笔下单金额不能超过总资产的一定比例;同一只股票发送买入后,30秒内不允许重复发送;每秒最多发送一次下单请求。这些限制会让策略在极端行情下显得“反应慢”,但程序化交易项目里,不出事比快更重要。
3. 源码部署与实操运行全流程
3.1 环境准备与依赖安装
建议准备一台能长时间运行的电脑,Windows或Linux都行,我自己在Windows上跑得很稳定。Python版本选3.9以上都可以,3.12也没问题。如果是完全没装过Python的新手,去官网下载安装包时记得勾选“Add Python to PATH”,不然后面命令行里敲python会提示找不到命令。
装好Python后,先创建虚拟环境:
python -m venv venv source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows然后安装依赖:
pip install -r requirements.txt源码里的requirements.txt大致包含以下内容:
pandas>=1.5.0 numpy>=1.23.0 flask>=2.2.0 requests>=2.28.0 apscheduler>=3.9.0 tdxpystock>=0.3.0有一点特别提醒:不要一股脑把这些包升级到最新版。我遇到过numpy升级后pandas版本不兼容,导致指标计算全部报错的情况。最稳妥的做法是锁定一个经过验证的版本组合,跑通整个流程后再考虑升级。
3.2 项目目录结构与配置文件解读
打开源码后建议先花几分钟把目录结构过一遍,理解每个文件的职责:
tdx_auto/ ├── config.py # 全局配置:服务器地址、标的列表、策略参数 ├── main.py # 入口文件,负责启动调度器和Web服务 ├── data_fetcher.py # 行情数据获取与缓存 ├── strategies/ │ ├── base.py # 策略基类 │ ├── dual_ma.py # 双均线策略 │ ├── macd_strategy.py # MACD策略 │ └── rsi_strategy.py # RSI策略 ├── broker.py # 券商下单接口抽象层 ├── risk_control.py # 风控模块 ├── web/ │ ├── app.py # Flask应用 │ ├── templates/ │ │ └── index.html # 前端面板 │ └── static/ │ └── app.js # 前端交互逻辑 ├── data/ │ └── history.db # SQLite历史数据库 └── logs/ └── trading.log # 交易日志config.py是整套系统的配置中心,几乎所有需要调整的参数都集中在这里。我自己改配置最多的是这几项:
# config.py 示例 CONFIG = { "tdx_servers": [ {"host": "119.147.212.81", "port": 7709}, {"host": "114.80.63.12", "port": 7709}, ], "stock_pool": ["600519", "000001", "300750"], "kline_category": 9, # 9=日K,0=5分钟 "strategy": "dual_ma", "ma_short": 5, "ma_long": 20, "broker_mode": "paper", # paper=模拟盘,live=实盘 "risk_control": { "max_single_order_ratio": 0.2, "min_interval_seconds": 30, "max_orders_per_second": 1, "price_deviation_limit": 0.01, }, "web_host": "127.0.0.1", "web_port": 5000, }把这个配置文件弄明白,整个系统就掌握一半了。换股票池、换均线参数、开关风控,基本都在这里完成。我习惯把实盘配置和模拟盘配置分开存两份,切换时直接复制改名,避免手误改坏。
3.3 启动后端和 Web 面板
入口文件是main.py,它会同时创建三样东西:定时任务调度器、Flask应用、策略引擎。启动命令很简单:
python main.py如果一切正常,控制台会输出类似“Web面板已启动: http://127.0.0.1:5000”的日志。浏览器打开这个地址后,能看到HTML面板,里面包括:标的状态表,显示每只股票的当前价格、均线排列、最新信号;信号历史区,记录每次触发的买入卖出时间点;系统状态区,显示最近一次数据更新时间、回测收益率和风控拦截次数。
Flask的后端代码非常轻量,核心路由就这么几个:
from flask import Flask, jsonify, render_template app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/api/status") def status(): return jsonify(engine.get_status()) @app.route("/api/signals") def signals(): return jsonify(engine.get_signals()) @app.route("/api/pause", methods=["POST"]) def pause(): engine.pause() return jsonify({"result": "paused"})默认只允许本机访问,如果想在局域网里用手机看面板,把config.py里的web_host改成0.0.0.0就行。但我想提醒一句:这套系统没有做登录鉴权,千万不要把它直接暴露到公网,否则任何人都能访问你的交易面板,甚至触发“暂停策略”或“下单”接口,风险非常大。
3.4 跑通第一个完整信号
为了验证整套系统是否正常,我建议第一天不要接正式交易,先开模拟盘跑一个交易日。具体步骤是:
- 在
config.py里配置好要监控的股票列表,比如“600519, 000001, 300750”。 - 把
broker_mode设为paper,确保不会真实下单。 - 启动
main.py,观察前端面板是否在预定时间拉取数据。 - 如果当天K线出现了金叉信号,确认前端面板能显示,且
logs/trading.log里记录了完整信号详情。 - 手动点击面板上的“暂停策略”按钮,验证风控和手动熔断能正常工作。
跑通这个流程后,你才算真正理解了源码的执行链路。很多新手跳过模拟盘直接去接实盘,结果连数据刷新都还没搞定,就急着真金白银上场,很容易出大问题。我的原则是:模拟盘连续正常跑满一周,日志里没有任何异常,再考虑接真实账户的小额资金。
4. 源码中的关键实现细节与避坑指南
4.1 历史数据落盘与增量更新
如果你每次启动程序都从服务器拉全量历史K线,不仅慢,还容易被行情服务器限流。源码里用了SQLite做本地缓存,第一次启动把历史数据落盘,之后每次启动先从本地读,缺失的部分才去服务器拉增量。
增量更新的逻辑很简单:数据库里记录每只股票最后一次K线的时间,运行时从那个时间点往后请求新增K线。但这里有个隐藏问题:某些行情服务器对单次请求的K线数量有限制,如果本地数据落后太久,一次请求拿不到全部新增数据,需要分批请求。我遇到过日K只返回了最新几条、导致复权数据对不齐的情况。解决方案是定期做一次全量重建,源码里默认每周日晚上全量刷新一次历史库,这个频率对个人用户来说足够了。
建表和写入的核心逻辑大概长这样:
import sqlite3 conn = sqlite3.connect("data/history.db") conn.execute(""" CREATE TABLE IF NOT EXISTS kline ( code TEXT, date TEXT, open REAL, high REAL, low REAL, close REAL, volume INTEGER, PRIMARY KEY (code, date) ) """) # 使用 INSERT OR REPLACE 实现增量更新 conn.execute( "INSERT OR REPLACE INTO kline (code, date, open, high, low, close, volume) VALUES (?,?,?,?,?,?,?)", (code, date, open, high, low, close, volume) )用INSERT OR REPLACE而不是普通INSERT,好处是重复拉取同一数据时不会产生主键冲突,直接覆盖旧数据,保持数据库干净。
4.2 复权问题与数据对齐
复权是所有拿历史数据做策略的人都绕不过去的坎。股票分红送股之后,历史价格会出现明显的跳空,不复权的话,均线、MACD这些指标都会算错,信号也容易被假跳空带偏。
通达信兼容的行情源默认返回的是不复权数据,但关于复权处理,源码里有一条专门路径:通过TDXPystock的接口获取复权因子,把历史价格乘上因子得到前复权价格。这里需要特别注意,实时价格不要做复权,因为盘中成交的是真实价格,只有做回测和长期指标计算时才需要复权。源码里区分了“实时数据”和“历史数据”两条处理路径,正是为了这个目的。
如果你用前复权数据做回测,还要记住一点:历史上每次除权除息事件都会导致整个前复权序列变化,所以复权因子需要在每次分红之后再更新,不然你回测的曲线和实际历史价格会存在偏差。
4.3 风控模块为什么是源码里的关键部分
源码里最值得细看的其实是风控模块。它不是一个简单的“总开关”,而是照顾到了大量边缘情况。比如,当程序监测到连续三次信号触发都被风控拦截时,会自动暂停整个策略,并且给前端面板推送一条警告信息。这么设计是为了防止开发者不在电脑前的时候,策略因为某些Bug进入“无脑触发信号、无脑下单”的死循环。
风控模块里还有一个容易被忽略的点:价格有效性校验。如果你在K线快收盘时下单,策略里的信号价可能和当前最新价差距很大。源码在下单前会比较信号价和当前实时价,偏差超过1%就直接拒绝这单。这个逻辑虽然很简单,但确实拦住了很多由数据异常导致的“鬼单”。
def check_price_valid(signal_price, real_time_price, limit=0.01): if real_time_price <= 0: return False deviation = abs(signal_price - real_time_price) / real_time_price return deviation <= limit提示:实盘接入前,一定要把几个风控参数理解清楚。别为了追求下单速度把风控关了。交易自动化这个领域,“不出事”永远比“跑得快”重要。
5. 常见问题与排查记录
5.1 常见报错速查表
我在部署和调试这套源码过程中,以及网友反馈里,遇到最多的几个问题如下:
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
| 启动后一直提示“连接行情服务器失败” | 通达信服务器地址失效或网络不通 | 更新config.py里的服务器地址列表,或切换备用IP |
pandas读取K线时出现KeyError: 'close' | 返回字段名因库版本不同而变化 | 打印一次返回的列名,统一做字段映射 |
| HTML页面打开后表格空白 | 后端接口没返回数据,或前端JS报错 | 按F12打开浏览器控制台,看请求失败原因 |
| 策略反复触发买入信号 | 没有对信号做变化沿去重 | 使用diff()或加入冷却时间 |
| 回测收益很好但实盘差距大 | 没考虑手续费、滑点、涨跌停限制 | 在回测参数中加上交易成本和涨跌停过滤 |
| 实盘下单时提示价格无效 | 风控的价格偏差检查拦截 | 先确认实时价是否正常,或调整阈值 |
这张表里的问题,我基本都实战踩过。其中“反复触发信号”和“字段名称不一致”是新手上路最容易卡住的点,建议先把这两个问题解决,再继续其他调试。
5.2 实测心得与调整建议
最后聊几条我个人总结下来的经验。首先是日志非常重要,源码里几乎所有关键节点都写了日志。我建议大家不要为了让控制台干净就去删日志,遇到问题老老实实翻logs/trading.log,大部分疑难杂症都能在这里找到线索。其次是永远给自动化账户留一个手动熔断开关,这个原则我每次都会强调。你永远不知道策略会在什么行情下失效,所以手动干预能力必须是最后一道防线。
关于数据源,通达信服务器虽然免费,但稳定性不如商业数据源。如果你要跑实盘,每天开盘前检查一次数据连接是否正常,是个好习惯。我还在源码里加了一个“心跳监控”,每5分钟往日志写一条心跳记录,如果某段时间心跳断了,说明程序可能已经卡死,需要人工介入。
最后说一点实际体会。程序化交易的习惯要慢慢培养。先跑模拟盘,再拿极小金额试水,最后才逐步加大资金。我看过太多人把回测曲线当成提款机,结果实盘一周就心态崩了。自动化工具能帮你省去机械重复的盯盘操作,但风险控制、仓位管理和策略验证,这些还得靠人自己把控。这套源码最核心的价值,就是把数据、策略、执行、展示这些环节打通,让你有一个可以持续观察和迭代的载体。真要说后续扩展,我已经在考虑把短线情绪因子加进去,再把信号推送做成微信通知,毕竟没人愿意时刻盯着本地页面看。
本文还有配套的精品资源,点击获取
