更新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 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: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):
# 为股票代码添加后缀
@@ -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)