更新log日志显示,更新显示,重排界面,提高显示稳定性

更新了
This commit is contained in:
2025-04-14 19:27:40 +08:00
parent a7c6c48d28
commit 7e9e9cdc1d

View File

@@ -1,4 +1,6 @@
import os import os
import sys
import subprocess
import tkinter as tk import tkinter as tk
from tkinter import ttk, messagebox, filedialog from tkinter import ttk, messagebox, filedialog
import tushare as ts import tushare as ts
@@ -19,7 +21,8 @@ class StockMonitor:
self.monitor_active = True self.monitor_active = True
self.start_monitor() self.start_monitor()
self.trigger_count = 0 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容器框架 # 创建Treeview容器框架
tree_frame = ttk.Frame(self.master) 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)
# tree_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True, side=tk.LEFT)
# 设置字体和样式
style = ttk.Style() style = ttk.Style()
style.configure('Larger.TButton', font=('微软雅黑', 12)) 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 # 创建Treeview
self.tree = ttk.Treeview( self.tree = ttk.Treeview(
@@ -37,8 +54,9 @@ class StockMonitor:
show='headings', show='headings',
height=20 # 设置默认显示行数 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 = [ columns = [
@@ -70,27 +88,39 @@ class StockMonitor:
# 创建控制按钮框架 # 创建控制按钮框架
control_frame = ttk.Frame(self.master) control_frame = ttk.Frame(self.master)
control_frame.pack(pady=10) control_frame.pack(pady=20)
# 文件选择按钮(原有代码保持不变) # 文件选择按钮(原有代码保持不变)
self.btn_load = ttk.Button( self.btn_load = ttk.Button(
control_frame, control_frame,
text="选择监控文件", text="选择监控文件",
command=self.select_file, 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, 15)) # 显式指定侧边排列
self.btn_load.pack(side=tk.LEFT, padx=5) # 显式指定侧边排列
# 添加复选框 # 文件选择按钮(原有代码保持不变)
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( self.auto_push_cb = ttk.Checkbutton(
control_frame, control_frame,
text="自动下单", text="自动下单",
variable=self.auto_push_var variable=self.auto_push_var,
state=tk.DISABLED
) )
self.auto_push_cb.pack(side=tk.LEFT, padx=5) self.auto_push_cb.pack(side=tk.LEFT, padx=5)
# 显示触发股票数
status_frame = ttk.Frame(self.master) status_frame = ttk.Frame(self.master)
status_frame.pack(pady=5, fill=tk.X) status_frame.pack(pady=5, fill=tk.X)
# 创建数量显示Label # 创建数量显示Label
@@ -100,8 +130,45 @@ class StockMonitor:
font=('微软雅黑', 14), font=('微软雅黑', 14),
foreground='red' 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): def select_file(self):
filepath = filedialog.askopenfilename( filepath = filedialog.askopenfilename(
title="选择监控列表文件", title="选择监控列表文件",
@@ -171,7 +238,7 @@ class StockMonitor:
# 确保代码保持字符串格式,保留前导零 # 确保代码保持字符串格式,保留前导零
code = f"{code:0>6}" # 确保代码是6位不足前面补零 code = f"{code:0>6}" # 确保代码是6位不足前面补零
f_code = self.format_code(code) f_code = self.format_code(code)
# current_price = parts[price_col].strip()
# 获取收盘价 # 获取收盘价
if f_code not in close_prices: if f_code not in close_prices:
print(f"跳过 {code},未找到收盘价数据") 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 formatted_date = f"{trade_date[:4]}-{trade_date[4:6]}-{trade_date[6:]}" # 格式化为YYYY-MM-DD
self.master.title(f"股票价格监控系统 - {formatted_date}") # 新增标题设置 self.master.title(f"股票价格监控系统 - {formatted_date}") # 新增标题设置
messagebox.showinfo("成功", f"加载文件成功:{filepath}") self.append_log(f"加载文件成功:{filepath}")
except Exception as e: 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): def add_stock(self, code, name, target):
"""添加股票到监控列表""" """添加股票到监控列表"""
codes = self.format_code(str(code)) codes = self.format_code(str(code))
self.tree.insert('', 'end', values=(codes, name, '-', '-', '-', target, '监控中', '')) 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:MM09: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): def format_code(self, code):
# 为股票代码添加后缀 # 为股票代码添加后缀
@@ -206,15 +337,40 @@ class StockMonitor:
print(f"未知的股票代码格式: {code}") print(f"未知的股票代码格式: {code}")
return None return None
return f_code 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): def update_prices(self):
codes = [self.tree.item(item)['values'][0] for item in self.tree.get_children()] codes = [self.tree.item(item)['values'][0] for item in self.tree.get_children()]
codes = [self.format_code(str(code)) for code in codes] codes = [self.format_code(str(code)) for code in codes]
required_columns = ['TS_CODE', 'LOW', 'PRICE'] required_columns = ['TS_CODE', 'LOW', 'PRICE']
# 判断是否开盘时间
if not self.is_trading_time():
return
try: try:
batch_size = 40 batch_size = 40
for i in range(0, len(codes), batch_size): for i in range(0, len(codes), batch_size):
batch = codes[i:i+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)) df = ts.realtime_quote(ts_code=','.join(batch))
for _, row in df.iterrows(): for _, row in df.iterrows():
code = row['TS_CODE'] code = row['TS_CODE']
@@ -222,6 +378,7 @@ class StockMonitor:
# 计算开盘涨幅 / (OPEN-PRE_CLOSE)/PRE_CLOSE*100 # 计算开盘涨幅 / (OPEN-PRE_CLOSE)/PRE_CLOSE*100
open_price = row['OPEN'] open_price = row['OPEN']
pre_close = row['PRE_CLOSE'] pre_close = row['PRE_CLOSE']
high_price = row['HIGH']
open_zf = round((float(open_price) - float(pre_close)) / float(pre_close) * 100, 2) open_zf = round((float(open_price) - float(pre_close)) / float(pre_close) * 100, 2)
# 计算流通盘 # 计算流通盘
@@ -234,8 +391,7 @@ class StockMonitor:
# 设置开盘涨幅/ (OPEN-PRE_CLOSE)* # 设置开盘涨幅/ (OPEN-PRE_CLOSE)*
self.tree.set(item, 'open_zf', f"{open_zf:.2f}%") self.tree.set(item, 'open_zf', f"{open_zf:.2f}%")
self.tree.set(item, 'lt_pan', f"{lt_pan:.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] != '已触发': if current_price >= target and self.tree.item(item)['values'][6] != '已触发':
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.master.after(0, self.tree.set, item, 'alert_time', now) self.master.after(0, self.tree.set, item, 'alert_time', now)
@@ -246,21 +402,24 @@ class StockMonitor:
self.tree.set(item, 'status', '已触发') self.tree.set(item, 'status', '已触发')
self.tree.item(item, tags=('triggered',)) self.tree.item(item, tags=('triggered',))
messagebox.showwarning( self.append_log(f"{code} 已触发!当前价:{current_price}")
"价格提醒",
f"{code} 已达到目标价!当前价:{current_price}"
)
# 使用系统命令播放音频Windows # 使用系统命令播放音频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: except Exception as e:
print(f"更新失败:{str(e)}") print(f"更新失败:{str(e)}")
# 在遍历完所有股票后统计数量 # 在遍历完所有股票后统计数量
triggered_count = 0 triggered_count = 0
for item in self.tree.get_children(): 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 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}")) self.master.after(0, lambda: self.trigger_count_label.config(text=f"已触发股票数量:{triggered_count}"))
@@ -271,6 +430,11 @@ class StockMonitor:
return None return None
def start_monitor(self): def start_monitor(self):
if self.timing_enabled.get():
self.monitor_active = True
else:
self.monitor_active = True # 非定时模式直接启动
def monitor_loop(): def monitor_loop():
while self.monitor_active: while self.monitor_active:
self.update_prices() self.update_prices()
@@ -297,6 +461,7 @@ class StockMonitor:
messagebox.showinfo("下单成功", "已执行自动买入操作") messagebox.showinfo("下单成功", "已执行自动买入操作")
if __name__ == "__main__": if __name__ == "__main__":
root = tk.Tk() root = tk.Tk()
app = StockMonitor(root) app = StockMonitor(root)