|
炒股的时候,最怕的不是没信号,而是信号太多,吵得你耳朵嗡嗡响,脑子嗡嗡响。 通达信那个预警窗口,你懂的。一到下午两点半,跟菜市场的大喇叭似的,叽叽喳喳全是股票代码。这边喊一个“放量突破”,那边吼一个“金叉买入”。哪个是主线?哪个是跟风杂鱼?根本分不清,看多了眼睛都花了。 那么,我们能不能给这个大嗓门的喇叭,配一个冷静、理智能快速整理信息的AI秘书? 我琢磨了一下,还真搞出来了这么个小东西。
给股票代码穿上“身份马甲”这个秘书的工作,本质上就干一件事:给所有从通达信大喇叭里喊出来的股票,立刻穿上“身份马甲”,然后分门别类地站好队。 它的工作流程是这样的: - 1. 监听“大喇叭”:秘书有个超灵敏的耳朵,时刻听着通达信导出的那个实时预警txt文件。只要这文件一动弹,有新股票喊出来了,它立马就知道了。
- 2. 翻“秘密花名册”:我给了它一本秘密花名册,这里面提前写好了各种“山头名册”(板块)的txt静态文件。比如,哪些是我的“亲兵卫队”(自选股),哪些是“近期起义的热门将领”(热门概念股),哪些是“国家队重点观察对象”(沪深300成分股)……
- 3. 实时贴标签、穿马甲:这是最精彩的一步。只要“大喇叭”里喊出一个股票代码,我这AI秘书就会一秒钟翻开花名册。
- • 查无此人?那就是个陌生来客,在网页里就显示个普通样貌,不施粉黛。
- • 查有此人?那可不的。它立刻判断这个股票属于哪个山头,然后“啪”地给它贴上一个电子标签,还刷上专属颜色。
- • 比如,“自选股”来的,刷成大红色,这是我的嫡系部队,必须重点照顾。
- • “热门概念”来的,刷成闪光的绿色,市场热点,值得关注。
- • “权重股”来的,刷成稳重的蓝色, affecting the index, 注意大盘节奏。
- 4. 汇报“战况看板”:所有整理得漂漂亮亮的情报,最终都会呈现在一个自动刷新的HTML网页上。这个网页就像一个实时战况看板,一行行股票代码整整齐齐地列着,戴着不同颜色的标签。
效果如何?效果就是:降噪、提纯。 原本是嘈杂无序的菜市场,现在变成了队列整齐的阅兵场。我一眼扫过去,就知道: - • 今天我自选股里有几个动了。
- • 市场热点轮动到了哪个板块。
- • 有没有陌生人突然闯进来。
我不再需要费劲去一个一个搜代码,去回忆这个股票是干啥的。因为它“穿的马甲”已经告诉我一切了。 这东西解决的不是“如何发现信号”的问题,而是“如何高效处理你已经收到的信号”的问题。它帮你完成了从信息接收,到信息整合,再到信息呈现的最后,也是最重要的一公里。
通达信预警监控代码全解析下面从代码结构、核心功能、运行逻辑三个维度,拆解这段代码,你可以把它理解为“一个带可视化界面的股票预警监控工具”: 一、代码整体结构(先认“骨架”)这段代码分为4个核心部分: - 1. 导入依赖:拿取实现功能需要的“工具包”;
- 2. 定义核心类StockMonitorApp:封装所有界面和监控逻辑(代码的核心);
- 3. 定义启动函数main:创建窗口并启动工具;
- 4. 程序入口:触发main函数,让工具跑起来。
二、逐部分拆解(再看“血肉”)1. 导入依赖(代码第1-8行)import threading # 多线程工具:让监控和界面操作同时进行(比如一边监控股票,一边能点按钮)
import time # 时间工具:控制“多久查一次股票预警”
import os # 文件路径工具:找文件、存文件的位置
import json # 数据存储工具:保存/读取你的设置(比如选的文件、刷新间隔)
import tkinter as tk # GUI界面工具:做窗口、按钮、表格
from tkinter import ttk, filedialog, messagebox # GUI的扩展工具:更美观的控件、文件选择框、提示弹窗
from 通达信预警到html import StockMonitor # 自定义工具:处理股票预警的核心逻辑(别人写好的“专用功能”)2. 核心类StockMonitorApp(代码第10行开始)这个类是工具的“本体”,所有功能都在里面,我们按“工具的生命周期”拆解: (1)初始化:工具刚启动时做什么(__init__方法)def __init__(self, root):
self.root = root # 把主窗口“绑定”到工具上
self.root.title("通达信预警监控配置") # 给窗口起名字
# 初始化监控相关的“开关/变量”
self.monitor = None# 监控核心实例(一开始为空)
self.monitor_thread = None# 监控线程(一开始不干活)
self.running = False# 监控运行状态(一开始是“关”)
# 配置文件路径:和脚本存在同一个文件夹,文件名叫tdx_alert_gui_config.json
self.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tdx_alert_gui_config.json")
self._build_ui() # 第一步:搭建界面
self._load_config() # 第二步:读取上次保存的设置
self._schedule_refresh() # 第三步:设置表格自动刷新 核心作用:给工具“初始化参数”,并触发界面搭建、读取配置、表格刷新。 (2)搭建界面:做可视化窗口(_build_ui方法)这部分是“画界面”,把窗口分成4个区域,对应你能看到的所有控件: 区域 功能
参数设置区 选“通达信预警文件”“静态标签文件”,设置“刷新间隔”(默认3秒)
控制区 启动/停止监控按钮,显示当前状态(未启动/运行中/已停止)
预警表格区 显示最近20条预警,列包括股票代码、名称、预警时间、价格等(带滚动条)
底部信息区 显示预警信息生成的HTML文件路径 核心作用:把“看不见的代码”变成“看得见的窗口”,让你能手动操作。 (3)配置保存/读取:记住你的设置(_load_config/_save_config)- • _load_config:打开tdx_alert_gui_config.json文件,把上次选的文件、设的刷新间隔填回输入框(比如你上次设了5秒刷新,下次打开工具还能保留);
- • _save_config:把当前输入框里的内容(选的文件、刷新间隔)存到JSON文件里(怕你关掉工具后忘了设置)。
核心作用:让工具“有记忆”,不用每次打开都重新设置。 (4)文件选择:找需要的文件(browse_dynamic/browse_static)点击“浏览...”按钮时触发,弹出系统的文件选择框,选好文件后自动填到输入框里,同时保存配置。
核心作用:不用手动输文件路径,点点鼠标就能选文件。 (5)启动监控:让工具开始干活(start_monitor方法)这是核心功能之一,步骤如下: - 1. 先校验参数:没选预警文件/文件不存在/刷新间隔不是正整数 → 弹提示框报错;
- 2. 校验通过后,保存当前设置;
- 3. 创建StockMonitor实例(启动“股票预警处理核心”);
- 4. 切换按钮状态:启动按钮变灰(不能点),停止按钮点亮(能点),状态改成“运行中”;
- 5. 开一个新线程(monitor_loop):让工具每隔设置的秒数,调用StockMonitor查一次预警(线程的作用:查预警时不卡界面,你还能点按钮)。
核心作用:启动监控逻辑,让工具自动盯股票预警。 (6)停止监控:让工具歇着(stop_monitor方法)- 1. 把“运行开关”(self.running)关掉,监控线程会自动停止;
- 2. 切换按钮状态:启动按钮点亮,停止按钮变灰,状态改成“已停止”;
- 3. 保存当前设置。
核心作用:手动停止监控,避免工具一直跑。
(7)表格刷新:实时显示预警(_refresh_table/_schedule_refresh)- • _refresh_table:清空表格旧数据,从StockMonitor里拿最近20条预警,填到表格里;
- • _schedule_refresh:用root.after(1000, ...)让表格每隔1秒刷新一次(1000毫秒=1秒)。
核心作用:让你能实时看到最新的股票预警。
3. 启动函数main+程序入口def main():
root = tk.Tk() # 创建主窗口
app = StockMonitorApp(root) # 把工具绑定到主窗口
root.mainloop() # 让窗口一直显示(直到你关掉)
if __name__ == "__main__":
main() # 程序启动时,先执行main函数 核心作用:触发工具启动,显示窗口。 三、运行逻辑(工具的“工作流程”)- 1. 你双击运行代码 → 执行main函数 → 弹出主窗口;
- 2. 窗口初始化:读取上次的设置(如果有),表格开始每秒刷新;
- 3. 你选好预警文件、设置刷新间隔,点击“启动监控”;
- 4. 工具校验参数 → 启动监控线程 → 每隔N秒查一次预警 → 表格实时显示;
- 5. 你点击“停止监控” → 监控线程停止 → 保存当前设置;
- 6. 你关掉窗口 → 程序结束。
四、关键细节(新手必懂)- • 多线程:threading.Thread创建的监控线程是“后台线程”(daemon=True),关掉窗口时会自动结束,不会残留;
- • 容错处理:读/写配置、刷新表格时加了try/except,就算出错也不会直接崩溃;
- • 状态控制:self.running是“开关变量”,监控线程里的while self.running会一直循环,直到开关关掉。
打包见下:通过网盘分享的文件:通达信预警监控v1.0.zip链接: https://pan.baidu.com/s/1eIZ-IY3YYLiEUwFhZQXnEg?pwd=wx36 提取码: wx36 --来自百度网盘超级会员v7的分享
源码如下:
- import threading
- import time
- import os
- import json
- import tkinter as tk
- from tkinter import ttk, filedialog, messagebox
- from 通达信预警到html import StockMonitor
- class StockMonitorApp:
- def __init__(self, root):
- self.root = root
- self.root.title("通达信预警监控1.0--仓鼠量化")
- self.monitor = None
- self.monitor_thread = None
- self.running = False
- # 配置文件路径(与脚本同目录)
- self.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tdx_alert_gui_config.json")
- self._build_ui()
- self._load_config()
- self._schedule_refresh()
- def _build_ui(self):
- # 参数区
- param_frame = ttk.LabelFrame(self.root, text="参数设置")
- param_frame.pack(fill=tk.X, padx=10, pady=5)
- # 通达信预警文件
- ttk.Label(param_frame, text="通达信预警文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
- self.dynamic_var = tk.StringVar()
- self.dynamic_entry = ttk.Entry(param_frame, textvariable=self.dynamic_var, width=60)
- self.dynamic_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
- ttk.Button(param_frame, text="浏览...", command=self.browse_dynamic).grid(row=0, column=2, padx=5, pady=5)
- # 静态标签文件
- ttk.Label(param_frame, text="静态标签文件:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
- self.static_var = tk.StringVar(value="static_codes.txt")
- self.static_entry = ttk.Entry(param_frame, textvariable=self.static_var, width=60)
- self.static_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
- ttk.Button(param_frame, text="浏览...", command=self.browse_static).grid(row=1, column=2, padx=5, pady=5)
- # 刷新间隔
- ttk.Label(param_frame, text="刷新间隔(秒):").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
- self.interval_var = tk.StringVar(value="3")
- self.interval_entry = ttk.Entry(param_frame, textvariable=self.interval_var, width=10)
- self.interval_entry.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W)
- # 控制区
- control_frame = ttk.LabelFrame(self.root, text="控制")
- control_frame.pack(fill=tk.X, padx=10, pady=5)
- self.start_button = ttk.Button(control_frame, text="启动监控", command=self.start_monitor)
- self.start_button.grid(row=0, column=0, padx=5, pady=5)
- self.stop_button = ttk.Button(control_frame, text="停止监控", command=self.stop_monitor, state=tk.DISABLED)
- self.stop_button.grid(row=0, column=1, padx=5, pady=5)
- self.status_var = tk.StringVar(value="状态: 未启动")
- ttk.Label(control_frame, textvariable=self.status_var).grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
- # 最近预警表格
- table_frame = ttk.LabelFrame(self.root, text="最近预警 (最多显示最近20条)")
- table_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
- columns = ("code", "name", "time", "price", "inc", "tag", "reason")
- self.tree = ttk.Treeview(table_frame, columns=columns, show="headings", height=12)
- self.tree.heading("code", text="股票代码")
- self.tree.heading("name", text="股票名称")
- self.tree.heading("time", text="预警时间")
- self.tree.heading("price", text="预警价格")
- self.tree.heading("inc", text="预警涨幅")
- self.tree.heading("tag", text="静态标签")
- self.tree.heading("reason", text="预警原因")
- self.tree.column("code", width=80, anchor=tk.CENTER)
- self.tree.column("name", width=100, anchor=tk.W)
- self.tree.column("time", width=80, anchor=tk.CENTER)
- self.tree.column("price", width=80, anchor=tk.E)
- self.tree.column("inc", width=80, anchor=tk.E)
- self.tree.column("tag", width=100, anchor=tk.W)
- self.tree.column("reason", width=200, anchor=tk.W)
- self.tree.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
- scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
- self.tree.configure(yscroll=scrollbar.set)
- scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
- # 底部信息
- bottom_frame = ttk.Frame(self.root)
- bottom_frame.pack(fill=tk.X, padx=10, pady=5)
- html_path = os.path.abspath("stock_alerts_table.html")
- self.html_info_var = tk.StringVar(value=f"HTML 输出: {html_path}")
- ttk.Label(bottom_frame, textvariable=self.html_info_var).pack(side=tk.LEFT)
- def _load_config(self):
- """从本地JSON配置文件加载上次使用的参数"""
- if not os.path.exists(self.config_path):
- return
- try:
- with open(self.config_path, "r", encoding="utf-8") as f:
- cfg = json.load(f)
- dynamic_file = cfg.get("dynamic_file", "")
- static_file = cfg.get("static_file", "static_codes.txt")
- interval = str(cfg.get("interval", 3))
- if dynamic_file:
- self.dynamic_var.set(dynamic_file)
- if static_file:
- self.static_var.set(static_file)
- if interval:
- self.interval_var.set(interval)
- except Exception:
- # 配置损坏时忽略,使用默认值
- pass
- def _save_config(self):
- """将当前参数保存到本地JSON配置文件"""
- cfg = {
- "dynamic_file": self.dynamic_var.get().strip(),
- "static_file": self.static_var.get().strip(),
- "interval": int(self.interval_var.get().strip()) if self.interval_var.get().strip().isdigit() else 3,
- }
- try:
- with open(self.config_path, "w", encoding="utf-8") as f:
- json.dump(cfg, f, ensure_ascii=False, indent=2)
- except Exception:
- # 保存失败时静默忽略,不影响程序运行
- pass
- def browse_dynamic(self):
- path = filedialog.askopenfilename(title="选择通达信预警文件", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
- if path:
- self.dynamic_var.set(path)
- self._save_config()
- def browse_static(self):
- path = filedialog.askopenfilename(title="选择静态标签文件", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
- if path:
- self.static_var.set(path)
- self._save_config()
- def start_monitor(self):
- if self.running:
- return
- dynamic_file = self.dynamic_var.get().strip()
- static_file = self.static_var.get().strip()
- interval_str = self.interval_var.get().strip()
- if not dynamic_file:
- messagebox.showwarning("提示", "请先选择通达信预警文件")
- return
- if not os.path.exists(dynamic_file):
- messagebox.showerror("错误", f"通达信预警文件不存在:\n{dynamic_file}")
- return
- if not interval_str.isdigit() or int(interval_str) <= 0:
- messagebox.showerror("错误", "刷新间隔必须是正整数")
- return
- update_interval = int(interval_str)
- # 参数校验通过后立即保存配置
- self._save_config()
- try:
- self.monitor = StockMonitor(dynamic_file, static_file)
- except Exception as e:
- messagebox.showerror("错误", f"创建监控实例失败:\n{e}")
- self.monitor = None
- return
- self.running = True
- self.start_button.configure(state=tk.DISABLED)
- self.stop_button.configure(state=tk.NORMAL)
- self.status_var.set("状态: 运行中")
- def monitor_loop():
- while self.running:
- try:
- if self.monitor is not None:
- self.monitor.process_once()
- except Exception as e:
- # 在后台线程中不能直接弹窗,只更新状态
- self.status_var.set(f"状态: 运行中(发生错误: {e})")
- time.sleep(update_interval)
- self.monitor_thread = threading.Thread(target=monitor_loop, daemon=True)
- self.monitor_thread.start()
- def stop_monitor(self):
- if not self.running:
- return
- self.running = False
- self.start_button.configure(state=tk.NORMAL)
- self.stop_button.configure(state=tk.DISABLED)
- self.status_var.set("状态: 已停止")
- # 停止时也保存一次当前参数
- self._save_config()
- def _refresh_table(self):
- if self.monitor is not None:
- try:
- df = self.monitor.get_recent_alerts(20)
- # 清空表格
- for item in self.tree.get_children():
- self.tree.delete(item)
- # 插入新的行
- if not df.empty:
- for _, row in df.iterrows():
- self.tree.insert("", tk.END, values=(
- row.get("股票代码", ""),
- row.get("股票名称", ""),
- row.get("预警时间", ""),
- row.get("预警价格", ""),
- f"{row.get('预警涨幅', '')}%",
- row.get("静态标签", ""),
- row.get("预警原因", ""),
- ))
- except Exception:
- # 刷新失败时静默忽略,避免打断界面
- pass
- def _schedule_refresh(self):
- self._refresh_table()
- self.root.after(1000, self._schedule_refresh)
- def main():
- root = tk.Tk()
- app = StockMonitorApp(root)
- root.mainloop()
- if __name__ == "__main__":
- main()
复制代码
|