From 7e9e9cdc1d94ce088171bea8bbff054318e1ad15 Mon Sep 17 00:00:00 2001 From: lintaogood Date: Mon, 14 Apr 2025 19:27:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0log=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=EF=BC=8C=E6=9B=B4=E6=96=B0=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=EF=BC=8C=E9=87=8D=E6=8E=92=E7=95=8C=E9=9D=A2=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E9=AB=98=E6=98=BE=E7=A4=BA=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新了 --- 监控个股.py | 215 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 190 insertions(+), 25 deletions(-) diff --git a/监控个股.py b/监控个股.py index 0abd6b7..ccdc32d 100644 --- a/监控个股.py +++ b/监控个股.py @@ -1,4 +1,6 @@ import os +import sys +import subprocess import tkinter as tk from tkinter import ttk, messagebox, filedialog import tushare as ts @@ -19,7 +21,8 @@ class StockMonitor: self.monitor_active = True self.start_monitor() self.trigger_count = 0 - #self.trigger_count_label = None # 新增Label变量 + self.previous_trading_status = None + self.start_status_update() # 构建界面 @@ -27,8 +30,22 @@ class StockMonitor: # 创建Treeview容器框架 tree_frame = ttk.Frame(self.master) tree_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) + # tree_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True, side=tk.LEFT) + # 设置字体和样式 style = ttk.Style() style.configure('Larger.TButton', font=('微软雅黑', 12)) + # style.configure('Log.TText', font=('微软雅黑', 12)) + + # # 创建日志输出框 + log_frame = ttk.Frame(self.master) + log_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, padx=5, pady=5) + + # self.log_label = ttk.Label(log_frame, text="日志输出:", font=('微软雅黑', 12)) + self.log_text = tk.Text(log_frame, wrap=tk.WORD, height=10, state=tk.DISABLED, font=('微软雅黑', 11), fg='blue') + scrollbar = ttk.Scrollbar(log_frame, command=self.log_text.yview) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + self.log_text.config(yscrollcommand=scrollbar.set) # 创建Treeview self.tree = ttk.Treeview( @@ -37,8 +54,9 @@ class StockMonitor: show='headings', height=20 # 设置默认显示行数 ) - # 新增样式配置 - self.tree.tag_configure('triggered', foreground='red', font=('Verdana', 11, 'bold')) # 修改样式配置 + # 文字新增样式配置 + self.tree.tag_configure('triggered', foreground='red', font=('Verdana', 12, 'bold')) # 修改样式配置 + self.tree.tag_configure('wrong_buy', foreground='green', font=('Verdana', 11, 'bold')) # 修改样式配置 # 配置列属性 columns = [ @@ -70,27 +88,39 @@ class StockMonitor: # 创建控制按钮框架 control_frame = ttk.Frame(self.master) - control_frame.pack(pady=10) - + control_frame.pack(pady=20) # 文件选择按钮(原有代码保持不变) self.btn_load = ttk.Button( control_frame, text="选择监控文件", command=self.select_file, - style='Larger.TButton' # 应用样式 + style='Larger.TButton', # 应用样式 + width=18 # 新增宽度设置 ) - # self.btn_load.pack(pady=10) - self.btn_load.pack(side=tk.LEFT, padx=5) # 显式指定侧边排列 + self.btn_load.pack(side=tk.LEFT, padx=(5, 15)) # 显式指定侧边排列 - # 添加复选框 + # 文件选择按钮(原有代码保持不变) + self.btn_moniter = ttk.Button( + control_frame, + text="开始监控", + command=self.select_file, + style='Larger.TButton', # 应用样式 + width=15, # 新增宽度设置, + state=tk.DISABLED # 初始禁用 + ) + self.btn_moniter.pack(side=tk.LEFT, padx=(10, 15)) # 显式指定侧边排列 + + # 复选框-自动下单 self.auto_push_cb = ttk.Checkbutton( control_frame, text="自动下单", - variable=self.auto_push_var + variable=self.auto_push_var, + state=tk.DISABLED ) self.auto_push_cb.pack(side=tk.LEFT, padx=5) + # 显示触发股票数 status_frame = ttk.Frame(self.master) status_frame.pack(pady=5, fill=tk.X) # 创建数量显示Label @@ -100,8 +130,45 @@ class StockMonitor: font=('微软雅黑', 14), foreground='red' ) - self.trigger_count_label.pack(padx=10, pady=5, anchor=tk.W) + self.trigger_count_label.pack(padx=10, pady=5, side=tk.LEFT, anchor=tk.W) + # 开盘时间显示 + self.trading_status_label = ttk.Label( + status_frame, + text="当前状态:非开盘时间", + font=('微软雅黑', 14), + foreground='red' + ) + self.trading_status_label.pack(padx=10, pady=5, side=tk.RIGHT, anchor=tk.E) + + # 在control_frame内添加定时监控组件 + timing_frame = ttk.Frame(control_frame) + timing_frame.pack(side=tk.LEFT, padx=(5, 15)) + + # 时间输入框 + timing_label = ttk.Label(timing_frame, text="定时时间:") + timing_label.pack(side=tk.LEFT) + self.timing_time = tk.StringVar() + self.timing_entry = ttk.Entry(timing_frame, textvariable=self.timing_time, width=8) + self.timing_entry.pack(side=tk.LEFT) + + # 定时监控复选框 + self.timing_enabled = tk.BooleanVar() + self.timing_checkbox = ttk.Checkbutton( + control_frame, + text="定时监控", + variable=self.timing_enabled + ) + self.timing_checkbox.pack(side=tk.LEFT, padx=5) + + def append_log(self, message): + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 格式化到秒 + self.log_text.config(state=tk.NORMAL) + self.log_text.insert(tk.END, f"[{current_time}] {message}\n") + self.log_text.see(tk.END) + self.log_text.config(state=tk.DISABLED) + + # 选中文件读取 def select_file(self): filepath = filedialog.askopenfilename( title="选择监控列表文件", @@ -171,7 +238,7 @@ class StockMonitor: # 确保代码保持字符串格式,保留前导零 code = f"{code:0>6}" # 确保代码是6位,不足前面补零 f_code = self.format_code(code) - # current_price = parts[price_col].strip() + # 获取收盘价 if f_code not in close_prices: print(f"跳过 {code},未找到收盘价数据") @@ -185,15 +252,79 @@ class StockMonitor: # 在加载成功后更新标题 formatted_date = f"{trade_date[:4]}-{trade_date[4:6]}-{trade_date[6:]}" # 格式化为YYYY-MM-DD self.master.title(f"股票价格监控系统 - {formatted_date}") # 新增标题设置 - messagebox.showinfo("成功", f"加载文件成功:{filepath}") + self.append_log(f"加载文件成功:{filepath}") except Exception as e: - messagebox.showerror("错误", f"加载文件失败:{str(e)}") + self.append_log(f"加载文件失败:{str(e)}") + # messagebox.showerror("错误", f"加载文件失败:{str(e)}") def add_stock(self, code, name, target): """添加股票到监控列表""" codes = self.format_code(str(code)) self.tree.insert('', 'end', values=(codes, name, '-', '-', '-', target, '监控中', '')) + # 检查是否为交易日 + def is_trading_time(self): + now = datetime.datetime.now() + current_time = now.time() + weekday = now.weekday() # 0-4是周一到周五 + return ( + weekday < 5 and # 仅工作日 + ( + (datetime.time(9, 30) <= current_time <= datetime.time(11, 30)) or + (datetime.time(13, 0) <= current_time <= datetime.time(15, 0)) + ) + ) + + def update_trading_status(self): + current_status = "开盘中" if self.is_trading_time() else "非开盘时间" + color = "green" if current_status == "开盘中" else "red" + # 状态变更时才记录日志 + if current_status != self.previous_trading_status: + self.append_log(f"系统状态更新:{current_status}") + self.previous_trading_status = current_status + # 变更UI + self.master.after(0, lambda s=current_status, c=color: + self.trading_status_label.config(text=f"当前状态:{s}", foreground=c)) + + # 监控状态 + def start_status_update(self): + def _loop(): + while self.monitor_active: + self.update_trading_status() + time.sleep(1) # 每秒检查一次 + + threading.Thread(target=_loop, daemon=True).start() + + def validate_time(self, time_str): + try: + datetime.datetime.strptime(time_str, "%H:%M") + return True + except ValueError: + return False + + def on_timing_change(self, *args): + if self.timing_enabled.get(): + time_str = self.timing_time.get().strip() + if not self.validate_time(time_str): + messagebox.showerror("错误", "时间格式应为 HH:MM(如:09:30)") + self.timing_enabled.set(False) + return + self.start_monitor_at_time(time_str) + else: + self.monitor_active = True + self.start_monitor() + + def start_monitor_at_time(self, target_time): + now = datetime.datetime.now() + target = datetime.datetime.strptime(target_time, "%H:%M") + target = now.replace( + hour=target.hour, minute=target.minute, second=0, microsecond=0 + ) + if target < now: + target += datetime.timedelta(days=1) + delta = target - now + threading.Timer(delta.total_seconds(), self.start_monitor).start() + # 格式化代码 def format_code(self, code): # 为股票代码添加后缀 @@ -206,15 +337,40 @@ class StockMonitor: print(f"未知的股票代码格式: {code}") return None return f_code + + def play_audio(self, file_path): + try: + if not os.path.exists(file_path): + raise FileNotFoundError(f"文件路径不存在: {file_path}") + + if sys.platform == "win32": + os.startfile(file_path) + elif sys.platform == "darwin": + subprocess.run(["open", file_path], check=True) + else: + subprocess.run(["xdg-open", file_path], check=True) + except FileNotFoundError as e: + print(f"错误: {e}") + except subprocess.CalledProcessError as e: + print(f"播放失败: {e}") + except Exception as e: + print(f"未知错误: {e}") + def update_prices(self): codes = [self.tree.item(item)['values'][0] for item in self.tree.get_children()] codes = [self.format_code(str(code)) for code in codes] required_columns = ['TS_CODE', 'LOW', 'PRICE'] + + # 判断是否开盘时间 + if not self.is_trading_time(): + return + try: batch_size = 40 for i in range(0, len(codes), batch_size): batch = codes[i:i+batch_size] - print(f"更新批次:{batch}") + self.append_log("行情实时数据更新...") + # print(f"更新批次:{batch}") df = ts.realtime_quote(ts_code=','.join(batch)) for _, row in df.iterrows(): code = row['TS_CODE'] @@ -222,6 +378,7 @@ class StockMonitor: # 计算开盘涨幅 / (OPEN-PRE_CLOSE)/PRE_CLOSE*100 open_price = row['OPEN'] pre_close = row['PRE_CLOSE'] + high_price = row['HIGH'] open_zf = round((float(open_price) - float(pre_close)) / float(pre_close) * 100, 2) # 计算流通盘 @@ -234,8 +391,7 @@ class StockMonitor: # 设置开盘涨幅/ (OPEN-PRE_CLOSE)* self.tree.set(item, 'open_zf', f"{open_zf:.2f}%") self.tree.set(item, 'lt_pan', f"{lt_pan:.2f}亿") - # print(current_price) - # print(target) + if current_price >= target and self.tree.item(item)['values'][6] != '已触发': now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.master.after(0, self.tree.set, item, 'alert_time', now) @@ -246,21 +402,24 @@ class StockMonitor: self.tree.set(item, 'status', '已触发') self.tree.item(item, tags=('triggered',)) - messagebox.showwarning( - "价格提醒", - f"{code} 已达到目标价!当前价:{current_price}" - ) + self.append_log(f"{code} 已触发!当前价:{current_price}") + # 使用系统命令播放音频(Windows) - #os.system('start example.mp3') # 替换为你的音频文件路径 + self.play_audio("example.mp3") + + # 错买的判断 + if current_price < target and high_price >= target and self.tree.item(item)['values'][6] != '已触发': + self.tree.set(item, 'status', '错买') + self.tree.item(item, tags=('wrong_buy',)) + except Exception as e: print(f"更新失败:{str(e)}") # 在遍历完所有股票后统计数量 triggered_count = 0 for item in self.tree.get_children(): - if self.tree.item(item)['values'][6] == '已触发': + if self.tree.item(item)['values'][6] == '已触发' or self.tree.item(item)['values'][6] == '错买': triggered_count += 1 - # 更新Label显示(确保在主线程更新) - #self.master.after(0, self.trigger_count_label.config, "text", f"已触发股票数量:{triggered_count}") + # 正确写法: self.master.after(0, lambda: self.trigger_count_label.config(text=f"已触发股票数量:{triggered_count}")) @@ -271,6 +430,11 @@ class StockMonitor: return None def start_monitor(self): + if self.timing_enabled.get(): + self.monitor_active = True + else: + self.monitor_active = True # 非定时模式直接启动 + def monitor_loop(): while self.monitor_active: self.update_prices() @@ -297,6 +461,7 @@ class StockMonitor: messagebox.showinfo("下单成功", "已执行自动买入操作") + if __name__ == "__main__": root = tk.Tk() app = StockMonitor(root)