From 81d0131a7bc001c0140acc72b223207e2a623886 Mon Sep 17 00:00:00 2001 From: "GDP\\solonot" Date: Wed, 12 Nov 2025 17:57:45 +0800 Subject: [PATCH] update --- config.ini | 7 +- core/heat_review/constants.py | 4 + core/heat_review/model.py | 11 + core/main_ui.py | 55 +++- core/sfgrid/sfgrid_ui.py | 602 ++++++++++++++++------------------ 5 files changed, 345 insertions(+), 334 deletions(-) create mode 100644 core/heat_review/constants.py create mode 100644 core/heat_review/model.py diff --git a/config.ini b/config.ini index 3083273..8d72d38 100644 --- a/config.ini +++ b/config.ini @@ -1,6 +1,5 @@ [config] -miniqmtpath = D:/Programs/DTQMT/userdata_mini -grid_price = 11.0,10.0,9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0 -grid_volume = 100 -account_no = 99082560 +miniqmtpath = D:/Programs/DTQMT_MN/userdata_mini +; account_no = 99082560 +account_no = 89009170 diff --git a/core/heat_review/constants.py b/core/heat_review/constants.py new file mode 100644 index 0000000..26e7f99 --- /dev/null +++ b/core/heat_review/constants.py @@ -0,0 +1,4 @@ +import xtquant.xtconstant as xtconstant + +HeatTypeUpStop = "UpStop" # 涨停 +HeatTypeDragonTiger = "DragonTiger" # 龙虎榜 diff --git a/core/heat_review/model.py b/core/heat_review/model.py new file mode 100644 index 0000000..4bba104 --- /dev/null +++ b/core/heat_review/model.py @@ -0,0 +1,11 @@ +from peewee import CharField, DateField + +from core.database import BaseModel, db + +class HeatStock(BaseModel): + stock_code = CharField(unique=True) + stock_name = CharField() + heat_type = CharField() + date = DateField() + +db.create_tables([HeatStock]) \ No newline at end of file diff --git a/core/main_ui.py b/core/main_ui.py index d0670fe..5e67b26 100644 --- a/core/main_ui.py +++ b/core/main_ui.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from core.logger import LogLevel, PrintLog +from core.logger import LogLevel from core.sfgrid.sfgrid_ui import TradeTargetUI @@ -35,16 +35,20 @@ class MainWindow: tab_bar_frame = ttk.Frame(content_area) tab_bar_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) + # 创建自定义样式 + self.create_custom_styles() + # 创建Tab按钮(垂直排列,文字垂直显示) self.tab_buttons = [] - strategy_names = ["蒙派\n策略", "涨停\n分析"] + strategy_names = ["蒙派\n策略", "涨停\n复盘"] for idx, name in enumerate(strategy_names): btn = ttk.Button( tab_bar_frame, text=name, command=lambda i=idx: self.switch_strategy_tab(i), - width=4 + width=4, + style='Bookmark.TButton' # 使用自定义书签样式 ) btn.pack(side=tk.TOP, pady=2, fill=tk.X) self.tab_buttons.append(btn) @@ -98,6 +102,38 @@ class MainWindow: # 默认显示第一个策略 self.switch_strategy_tab(0) + def create_custom_styles(self): + """创建自定义样式""" + style = ttk.Style() + + # 创建书签样式 + style.configure( + 'Bookmark.TButton', + relief='flat', + borderwidth=1, + padding=(5, 10), + foreground='black', + background='#FFE599', # 浅黄色背景,类似便签纸 + font=('Arial', 10, 'bold') + ) + + # 设置焦点样式(选中状态) + style.map( + 'Bookmark.TButton', + background=[('active', '#F1C232'), ('pressed', '#F1C232')], + relief=[('pressed', 'sunken')] + ) + + # 创建选中状态的书签样式 + style.configure( + 'SelectedBookmark.TButton', + relief='flat', + borderwidth=1, + padding=(5, 10), + background='#3D85C6', # 蓝色背景表示选中状态 + font=('Arial', 10, 'bold') + ) + def create_global_log_panel(self, parent): """创建全局日志面板""" # 日志区域(默认隐藏) @@ -143,7 +179,6 @@ class MainWindow: # 删除所有日志项 for item in self.log_table.get_children(): self.log_table.delete(item) - self.add_log(LogLevel.DEBUG, "日志已清空") def create_strategy_frames(self, strategy_names): """创建各个策略的Frame""" @@ -184,9 +219,12 @@ class MainWindow: def update_tab_button_styles(self): """更新Tab按钮的样式以显示选中状态""" - # 注意:ttk.Button的样式需要通过ttk.Style来设置 - # 这里简化处理,仅作为接口预留 - pass + # 重置所有按钮为普通书签样式 + for i, btn in enumerate(self.tab_buttons): + if i == self.current_strategy_index: + btn.configure(style='SelectedBookmark.TButton') # 选中状态 + else: + btn.configure(style='Bookmark.TButton') # 普通状态 def toggle_log_panel(self): """切换日志面板的显示/隐藏""" @@ -210,5 +248,4 @@ class MainWindow: def run(self): """运行程序""" - self.root.mainloop() - + self.root.mainloop() \ No newline at end of file diff --git a/core/sfgrid/sfgrid_ui.py b/core/sfgrid/sfgrid_ui.py index 3975659..0a359ee 100644 --- a/core/sfgrid/sfgrid_ui.py +++ b/core/sfgrid/sfgrid_ui.py @@ -8,7 +8,6 @@ import time from core import constants import core.eventbus as eBus from core.logger import LogLevel, PrintLog -from core.sfgrid.bus_events import ActionEventAddTradeTarget from core.sfgrid.model import SFGridTradeTarget import configparser import config @@ -27,9 +26,6 @@ class TradeTargetUI(ttk.Frame): self.init_trade_target_pool() eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onMarketDataUpdated) - # 创建刷新线程标志 - self.refresh_thread_running = False # 默认不启动刷新线程 - # 市场监控数据 self.marketData: dict[str, Any] = {} # 存储市场数据 {stock_code: {stock_name, last_price, time}} @@ -39,7 +35,7 @@ class TradeTargetUI(ttk.Frame): # 创建界面 self.create_ui() - self.start_ui_refresh() + self.start_refresh_thread() def onMarketDataUpdated(self, data): # 收集所有市场数据用于市场监控 @@ -48,7 +44,7 @@ class TradeTargetUI(ttk.Frame): id:int = self.stockCodeIdMap[stock_code] tradeTarget = self.tradeTargetData[id] lastPrice = float("{:.3f}".format(tickData['lastPrice'])) - print(f'股票代码: {stock_code} {id}, 市场数据更新 {lastPrice}') + PrintLog(LogLevel.INFO, f'股票代码: {stock_code} {id}, 市场数据更新 {lastPrice}') tradeTarget.market_price = lastPrice # type: ignore self.updateTradeTarget(tradeTarget) stock_controller: SFGridStrategy = self.strategy_ctrl[id] @@ -71,61 +67,41 @@ class TradeTargetUI(ttk.Frame): 'last_price': tickData['lastPrice'], 'time': current_time } - - def refresh_targets(self): - # 更新标的池 - results = SFGridTradeTarget.select() - for temp in results: - result :SFGridTradeTarget = temp - self.tradeTargetData[result.get_id()] = result - self.stockCodeIdMap[str(result.stock_code)] = result.get_id() def init_trade_target_pool(self): - self.refresh_targets() + results = SFGridTradeTarget.select() + for temp in results: + tradeTarget :SFGridTradeTarget = temp + id = tradeTarget.get_id() - for id, tradeTarget in self.tradeTargetData.items(): status = "新建" if tradeTarget.status == 0 else "已建初始仓" - PrintLog(LogLevel.INFO, f' [序号-{id}] 股票代码: {tradeTarget.stock_code}-{tradeTarget.stock_name} 当前持仓: {qmtv.getStockPosition(tradeTarget.stock_code)} 网格索引: {tradeTarget.grid_index} 基准价格 {tradeTarget.getPriceGrid()[tradeTarget.grid_index]} 状态: {status} 启用交易线程: {'自动交易中' if tradeTarget.enabled else '交易已停止'}') # type: ignore - tradeTarget.current_position = qmtv.getStockPosition(tradeTarget.stock_code) # type: ignore - result = tradeTarget.save() - PrintLog(LogLevel.INFO, f' |- 同步[{tradeTarget.stock_code}-{tradeTarget.stock_name}]持仓信息[{'成功' if result == 1 else '失败'}]') + tradeTarget.save() + PrintLog(LogLevel.INFO, f' [序号-{id}] 股票代码: {tradeTarget.stock_code}-{tradeTarget.stock_name} 当前持仓: {qmtv.getStockPosition(tradeTarget.stock_code)} 网格索引: {tradeTarget.grid_index} 基准价格 {tradeTarget.getPriceGrid()[tradeTarget.grid_index]} 状态: {status} 启用交易线程: {'自动交易中' if tradeTarget.enabled else '交易已停止'}') # type: ignore stockTradeController = SFGridStrategy(tradeTarget) # type: ignore self.strategy_ctrl[id] = stockTradeController # pyright: ignore[reportArgumentType] self.updateTradeTarget(tradeTarget) PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.tradeTargetData)} 个标的') - def start_refresh_thread(self): - """启动刷新线程""" - if not hasattr(self, 'refresh_thread') or not self.refresh_thread.is_alive(): - self.refresh_thread = threading.Thread(target=self.refresh_loop, daemon=True) - self.refresh_thread.start() - - def refresh_loop(self): - """刷新循环""" - while self.refresh_thread_running: - # 在主线程中更新UI - self.after(0, self.refresh_table) - self.after(0, self.populate_market_table) - time.sleep(0.2) # 每0.5秒刷新一次 - - def stop_refresh_thread(self): - """停止刷新线程""" - self.refresh_thread_running = False - - - def onTradeEnabled(self, target:SFGridTradeTarget): - PrintLog(LogLevel.INFO, f"交易启用: {target.stock_code} - {target.stock_name}") - - def onTradeDisabled(self, target:SFGridTradeTarget): - PrintLog(LogLevel.INFO, f"交易禁用: {target.stock_code} - {target.stock_name}") - def updateTradeTarget(self, target: SFGridTradeTarget): # 更新或添加数据到本地缓存 self.tradeTargetData[target.get_id()] = target self.stockCodeIdMap[target.stock_code] = target.get_id() # type: ignore + + def start_refresh_thread(self): + """启动刷新线程""" + self.refresh_thread = threading.Thread(target=self.refresh_loop, daemon=True) + self.refresh_thread.start() + PrintLog(LogLevel.INFO, "UI刷新线程已启动") + + def refresh_loop(self): + """刷新循环""" + while True: + self.after(0, self.refresh_table) + self.after(0, self.populate_market_table) + time.sleep(0.2) # 每0.5秒刷新一次 def create_ui(self): """创建UI界面""" @@ -144,33 +120,16 @@ class TradeTargetUI(ttk.Frame): command=self.pause_selected_trade, width=12).pack(side=tk.LEFT, padx=2) ttk.Button(toolbar_frame, text="➕ 添加标的", command=self.add_trade_target, width=12).pack(side=tk.LEFT, padx=2) - ttk.Button(toolbar_frame, text="🛠 网格配置", - command=self.grid_settings, width=12).pack(side=tk.LEFT, padx=2) ttk.Button(toolbar_frame, text="🗑 删除标的", command=self.delete_selected_trade, width=12).pack(side=tk.LEFT, padx=2) - - # 添加抽屉按钮到工具栏最右侧 - self.toggle_market_monitor_btn = ttk.Button(toolbar_frame, text="◀▶", - command=self.toggle_market_monitor, width=3) - self.toggle_market_monitor_btn.pack(side=tk.RIGHT, padx=2) + ttk.Button(toolbar_frame, text="🛠 交易设置", + command=self.trade_settings, width=12).pack(side=tk.LEFT, padx=2) + ttk.Button(toolbar_frame, text="▣ 实时监控", + command=self.toggle_market_monitor, width=12).pack(side=tk.RIGHT, padx=2) # 表格区域 self.create_tables_area(main_frame) - def start_ui_refresh(self): - """启动UI刷新线程""" - if not self.refresh_thread_running: - self.refresh_thread_running = True - self.start_refresh_thread() - PrintLog(LogLevel.INFO, "UI刷新线程已启动") - - def stop_ui_refresh(self): - """停止UI刷新线程""" - if self.refresh_thread_running: - self.stop_refresh_thread() - self.refresh_thread_running = False - PrintLog(LogLevel.INFO, "UI刷新线程已停止") - def create_tables_area(self, parent): """创建表格区域""" @@ -191,6 +150,19 @@ class TradeTargetUI(ttk.Frame): # 创建市场监控表格 self.create_market_monitor_table(self.market_frame) + + + def toggle_market_monitor(self): + """切换市场监控窗口显示/隐藏""" + if self.market_monitor_visible: + # 隐藏市场监控窗口 + self.market_frame.pack_forget() + self.market_monitor_visible = False + else: + # 显示市场监控窗口 + self.market_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0)) + self.market_monitor_visible = True + def create_trade_target_table(self, parent): """创建交易标的表格""" @@ -216,7 +188,7 @@ class TradeTargetUI(ttk.Frame): "当前订单价": (90, tk.E), "当前订单号": (90, tk.E), "当前订单类型": (90, tk.E), - "交易状态": (80, tk.E) + "交易状态": (80, tk.CENTER) } for col in columns: @@ -343,14 +315,14 @@ class TradeTargetUI(ttk.Frame): if result: # 发布事件通知主控制器添加标的 - self.onAddTradeTarget(stock_code) + self.addTradeTarget(stock_code) def get_trade_enabled_indicator(self, enabled: bool) -> str: """获取交易状态指示器""" if enabled: - return "🟢 策略运行" + return "▶ 运行中" else: - return "🟡 策略暂停" + return "⏸ 已停止" def populate_trade_table(self): """填充交易标的表格数据""" @@ -495,9 +467,8 @@ class TradeTargetUI(ttk.Frame): icon='warning' ) - id = target.get_id() if result: - # 通过事件总线发出删除动作 + id = target.get_id() try: # 从数据库中删除 target.delete_instance() @@ -553,7 +524,7 @@ class TradeTargetUI(ttk.Frame): return # 发布事件通知主控制器添加标的 - eBus.event_bus.publish(ActionEventAddTradeTarget, stock_code) + self.addTradeTarget(stock_code) add_window.destroy() def cancel_add(): @@ -595,143 +566,221 @@ class TradeTargetUI(ttk.Frame): # 刷新市场监控表格 self.populate_market_table() - def system_settings(self): - """系统设置""" + def trade_settings(self): + """网格配置功能""" + target = self.get_selected_target() + if not target: + return + # 获取顶层窗口 root = self.winfo_toplevel() - settings_window = tk.Toplevel(root) - settings_window.title("网格交易系统配置") + # 创建顶层窗口 + view_window = tk.Toplevel(root) + view_window.title(f"网格配置查看 - {target.stock_code} ({target.stock_name})") + view_window.geometry("500x450") + view_window.resizable(False, False) - # 设置窗口大小 - window_width = 700 - window_height = 550 + # 检查标的的状态,status为1时仅可查看 + # if target.status == 1: + # # 创建只读的网格配置查看窗口 + # self.create_grid_view_window(target) + # else: + # # 创建可编辑的网格配置窗口 + # self.create_grid_config_window(target) + + def create_grid_view_window(self, target: SFGridTradeTarget): + """创建网格配置查看窗口(只读)""" + # 获取顶层窗口 + root = self.winfo_toplevel() - # 先设置为模态窗口 - settings_window.transient(root) + # 创建顶层窗口 + view_window = tk.Toplevel(root) + view_window.title(f"网格配置查看 - {target.stock_code} ({target.stock_name})") + view_window.geometry("500x450") + view_window.resizable(False, False) - # 确保主窗口完全初始化 + # 设置窗口模态 + view_window.transient(root) + view_window.grab_set() + + # 居中显示 root.update_idletasks() + x = root.winfo_x() + (root.winfo_width() // 2) - 250 + y = root.winfo_y() + (root.winfo_height() // 2) - 225 + view_window.geometry(f"500x450+{x}+{y}") - # 获取主窗口的实际大小(包括边框) - # 使用winfo_rootx/rooty获取窗口在屏幕上的绝对位置 - main_x = root.winfo_rootx() - main_y = root.winfo_rooty() - main_width = root.winfo_width() - main_height = root.winfo_height() + # 创建主框架 + main_frame = ttk.Frame(view_window, padding=20) + main_frame.pack(fill=tk.BOTH, expand=True) - # 计算设置窗口相对于主窗口的居中位置 - x = main_x + (main_width - window_width) // 2 - y = main_y + (main_height - window_height) // 2 + # 显示股票信息 + info_frame = ttk.LabelFrame(main_frame, text="标的详情", padding=10) + info_frame.pack(fill=tk.X, pady=(0, 10)) - # 设置窗口大小和位置 - settings_window.geometry(f"{window_width}x{window_height}+{x}+{y}") - settings_window.resizable(False, False) + ttk.Label(info_frame, text=f"股票代码: {target.stock_code}").grid(row=0, column=0, sticky=tk.W, pady=2) + ttk.Label(info_frame, text=f"股票名称: {target.stock_name}").grid(row=0, column=1, sticky=tk.W, padx=(20, 0), pady=2) + ttk.Label(info_frame, text=f"状态: 已建初始仓(仅查看模式)").grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=2) - # 设置为模态窗口 - settings_window.grab_set() + # 创建网格配置查看框架 + config_frame = ttk.LabelFrame(main_frame, text="网格配置", padding=10) + config_frame.pack(fill=tk.X, pady=(0, 10)) - # 添加底部按钮区域(先创建,确保固定在底部) - button_frame = ttk.Frame(settings_window) - button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=10) + # 基准价格 + base_price_frame = ttk.Frame(config_frame) + base_price_frame.pack(fill=tk.X, pady=5) + ttk.Label(base_price_frame, text="基准价格:", width=15).pack(side=tk.LEFT) + ttk.Label(base_price_frame, text=f"{target.grid_start_price:.3f}", width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5) + ttk.Label(base_price_frame, text="元", foreground='gray').pack(side=tk.LEFT) - # 创建选项卡(在按钮之后创建,填充剩余空间) - notebook = ttk.Notebook(settings_window) - notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0)) + # 网格大小 + grid_size_frame = ttk.Frame(config_frame) + grid_size_frame.pack(fill=tk.X, pady=5) + ttk.Label(grid_size_frame, text="网格大小:", width=15).pack(side=tk.LEFT) + ttk.Label(grid_size_frame, text=f"{target.grid_size:.3f}", width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5) + ttk.Label(grid_size_frame, text="元", foreground='gray').pack(side=tk.LEFT) - # 基础设置 - basic_frame = ttk.Frame(notebook) - notebook.add(basic_frame, text="基础设置") + # 网格交易量 + grid_volume_frame = ttk.Frame(config_frame) + grid_volume_frame.pack(fill=tk.X, pady=5) + ttk.Label(grid_volume_frame, text="网格交易量:", width=15).pack(side=tk.LEFT) + ttk.Label(grid_volume_frame, text=str(target.grid_volume), width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5) + ttk.Label(grid_volume_frame, text="股", foreground='gray').pack(side=tk.LEFT) - # 高级设置 - advanced_frame = ttk.Frame(notebook) - notebook.add(advanced_frame, text="高级设置") + # 上方网格数量 + upper_count_frame = ttk.Frame(config_frame) + upper_count_frame.pack(fill=tk.X, pady=5) + ttk.Label(upper_count_frame, text="上方网格数量:", width=15).pack(side=tk.LEFT) + ttk.Label(upper_count_frame, text=str(target.grid_upper_count), width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5) + ttk.Label(upper_count_frame, text="格", foreground='gray').pack(side=tk.LEFT) - # 读取当前配置 - config = configparser.ConfigParser() - config_path = config.get('config', 'config_path') - config.read(config_path, encoding='utf-8') + # 下方网格数量 + lower_count_frame = ttk.Frame(config_frame) + lower_count_frame.pack(fill=tk.X, pady=5) + ttk.Label(lower_count_frame, text="下方网格数量:", width=15).pack(side=tk.LEFT) + ttk.Label(lower_count_frame, text=str(target.grid_lower_count), width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5) + ttk.Label(lower_count_frame, text="格", foreground='gray').pack(side=tk.LEFT) + + # 生成网格价格序列 + price_grid_frame = ttk.LabelFrame(main_frame, text="网格价格序列", padding=10) + price_grid_frame.pack(fill=tk.X, pady=(0, 10)) + + # 计算并显示网格价格序列 + price_list = target.getPriceGrid() + price_text = ", ".join([f"{price:.3f}" for price in price_list]) + + # 创建文本框显示网格价格序列 + text_frame = ttk.Frame(price_grid_frame) + text_frame.pack(fill=tk.BOTH, expand=True) + + text_widget = tk.Text(text_frame, height=4, wrap=tk.WORD) + text_widget.insert(tk.END, price_text) + text_widget.config(state=tk.DISABLED) # 只读 + + scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=text_widget.yview) + text_widget.configure(yscrollcommand=scrollbar.set) + + text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # 关闭按钮 + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X, pady=(10, 0)) + ttk.Button(button_frame, text="关闭", command=view_window.destroy).pack(side=tk.RIGHT, padx=5) + + def create_grid_config_window(self, target: SFGridTradeTarget): + """创建网格配置窗口(可编辑)""" + # 获取顶层窗口 + root = self.winfo_toplevel() + + # 创建顶层窗口 + config_window = tk.Toplevel(root) + config_window.title(f"网格配置 - {target.stock_code} ({target.stock_name})") + config_window.geometry("550x550") + config_window.resizable(False, False) + + # 设置窗口模态 + config_window.transient(root) + config_window.grab_set() + + # 居中显示 + root.update_idletasks() + x = root.winfo_x() + (root.winfo_width() // 2) - 275 + y = root.winfo_y() + (root.winfo_height() // 2) - 275 + config_window.geometry(f"550x550+{x}+{y}") + + # 创建主框架 + main_frame = ttk.Frame(config_window, padding=20) + main_frame.pack(fill=tk.BOTH, expand=True) + + # 显示股票信息 + info_frame = ttk.LabelFrame(main_frame, text="标的详情", padding=10) + info_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Label(info_frame, text=f"股票代码: {target.stock_code}").grid(row=0, column=0, sticky=tk.W, pady=2) + ttk.Label(info_frame, text=f"股票名称: {target.stock_name}").grid(row=0, column=1, sticky=tk.W, padx=(20, 0), pady=2) + ttk.Label(info_frame, text=f"状态: 新标的(可配置模式)").grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=2) + + # 创建网格配置框架 + config_frame = ttk.LabelFrame(main_frame, text="网格配置", padding=15) + config_frame.pack(fill=tk.X, pady=(0, 10)) # 创建输入框字典用于保存引用 entries = {} - # 网格价格计算参数 - grid_params = {} - - # 添加网格价格设置(特殊处理) - grid_price_frame = ttk.LabelFrame(basic_frame, text="网格价格设置", padding=15) - grid_price_frame.pack(fill=tk.X, padx=20, pady=10) - # 基准价格 - base_price_row = ttk.Frame(grid_price_frame) - base_price_row.pack(fill=tk.X, pady=5) - ttk.Label(base_price_row, text="基准价格:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) - base_price_entry = ttk.Entry(base_price_row, width=15, font=('Arial', 10)) - base_price_entry.insert(0, "10.0") + base_price_frame = ttk.Frame(config_frame) + base_price_frame.pack(fill=tk.X, pady=5) + ttk.Label(base_price_frame, text="基准价格:", width=15).pack(side=tk.LEFT) + base_price_entry = ttk.Entry(base_price_frame, width=15) + base_price_entry.insert(0, str(target.grid_start_price)) base_price_entry.pack(side=tk.LEFT, padx=5) - ttk.Label(base_price_row, text="元", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT) - grid_params['base_price'] = base_price_entry - - # 网格类型 - grid_type_row = ttk.Frame(grid_price_frame) - grid_type_row.pack(fill=tk.X, pady=5) - ttk.Label(grid_type_row, text="网格类型:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) - - grid_type_var = tk.StringVar(value="金额差") - ttk.Radiobutton(grid_type_row, text="百分比", variable=grid_type_var, - value="百分比", command=lambda: on_grid_type_change()).pack(side=tk.LEFT, padx=5) - ttk.Radiobutton(grid_type_row, text="金额差", variable=grid_type_var, - value="金额差", command=lambda: on_grid_type_change()).pack(side=tk.LEFT, padx=5) - - grid_params['grid_type'] = grid_type_var + ttk.Label(base_price_frame, text="元", foreground='gray').pack(side=tk.LEFT) + entries['grid_start_price'] = base_price_entry # 网格大小 - grid_size_row = ttk.Frame(grid_price_frame) - grid_size_row.pack(fill=tk.X, pady=5) - ttk.Label(grid_size_row, text="网格大小:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) - grid_size_entry = ttk.Entry(grid_size_row, width=15, font=('Arial', 10)) - grid_size_entry.insert(0, "1.0") + grid_size_frame = ttk.Frame(config_frame) + grid_size_frame.pack(fill=tk.X, pady=5) + ttk.Label(grid_size_frame, text="网格大小:", width=15).pack(side=tk.LEFT) + grid_size_entry = ttk.Entry(grid_size_frame, width=15) + grid_size_entry.insert(0, str(target.grid_size)) grid_size_entry.pack(side=tk.LEFT, padx=5) - grid_size_unit_label = ttk.Label(grid_size_row, text="元", foreground='gray', font=('Arial', 9)) - grid_size_unit_label.pack(side=tk.LEFT) - grid_params['grid_size'] = grid_size_entry - grid_params['grid_size_unit_label'] = grid_size_unit_label + ttk.Label(grid_size_frame, text="元", foreground='gray').pack(side=tk.LEFT) + entries['grid_size'] = grid_size_entry - # 网格类型改变时更新单位 - def on_grid_type_change(): - if grid_type_var.get() == "百分比": - grid_size_unit_label.config(text="%") - grid_size_entry.delete(0, tk.END) - grid_size_entry.insert(0, "1.0") - else: - grid_size_unit_label.config(text="元") - grid_size_entry.delete(0, tk.END) - grid_size_entry.insert(0, "1.0") - update_preview() + # 网格交易量 + grid_volume_frame = ttk.Frame(config_frame) + grid_volume_frame.pack(fill=tk.X, pady=5) + ttk.Label(grid_volume_frame, text="网格交易量:", width=15).pack(side=tk.LEFT) + grid_volume_entry = ttk.Entry(grid_volume_frame, width=15) + grid_volume_entry.insert(0, str(target.grid_volume)) + grid_volume_entry.pack(side=tk.LEFT, padx=5) + ttk.Label(grid_volume_frame, text="手", foreground='gray').pack(side=tk.LEFT) + entries['grid_volume'] = grid_volume_entry # 上方网格数量 - upper_grid_row = ttk.Frame(grid_price_frame) - upper_grid_row.pack(fill=tk.X, pady=5) - ttk.Label(upper_grid_row, text="上方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) - upper_grid_entry = ttk.Entry(upper_grid_row, width=15, font=('Arial', 10)) - upper_grid_entry.insert(0, "1") - upper_grid_entry.pack(side=tk.LEFT, padx=5) - ttk.Label(upper_grid_row, text="格", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT) - grid_params['upper_count'] = upper_grid_entry + upper_count_frame = ttk.Frame(config_frame) + upper_count_frame.pack(fill=tk.X, pady=5) + ttk.Label(upper_count_frame, text="上方网格数量:", width=15).pack(side=tk.LEFT) + upper_count_entry = ttk.Entry(upper_count_frame, width=15) + upper_count_entry.insert(0, str(target.grid_upper_count)) + upper_count_entry.pack(side=tk.LEFT, padx=5) + ttk.Label(upper_count_frame, text="格", foreground='gray').pack(side=tk.LEFT) + entries['grid_upper_count'] = upper_count_entry # 下方网格数量 - lower_grid_row = ttk.Frame(grid_price_frame) - lower_grid_row.pack(fill=tk.X, pady=5) - ttk.Label(lower_grid_row, text="下方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) - lower_grid_entry = ttk.Entry(lower_grid_row, width=15, font=('Arial', 10)) - lower_grid_entry.insert(0, "10") - lower_grid_entry.pack(side=tk.LEFT, padx=5) - ttk.Label(lower_grid_row, text="格", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT) - grid_params['lower_count'] = lower_grid_entry + lower_count_frame = ttk.Frame(config_frame) + lower_count_frame.pack(fill=tk.X, pady=5) + ttk.Label(lower_count_frame, text="下方网格数量:", width=15).pack(side=tk.LEFT) + lower_count_entry = ttk.Entry(lower_count_frame, width=15) + lower_count_entry.insert(0, str(target.grid_lower_count)) + lower_count_entry.pack(side=tk.LEFT, padx=5) + ttk.Label(lower_count_frame, text="格", foreground='gray').pack(side=tk.LEFT) + entries['grid_lower_count'] = lower_count_entry # 预览按钮和结果显示 - preview_row = ttk.Frame(grid_price_frame) - preview_row.pack(fill=tk.X, pady=10) + preview_frame = ttk.LabelFrame(main_frame, text="网格价格序列预览", padding=10) + preview_frame.pack(fill=tk.X, pady=(0, 10)) preview_result = tk.StringVar(value="点击'预览'查看生成的网格价格序列") @@ -740,18 +789,14 @@ class TradeTargetUI(ttk.Frame): try: base_price = float(base_price_entry.get()) grid_size = float(grid_size_entry.get()) - upper_count = int(upper_grid_entry.get()) - lower_count = int(lower_grid_entry.get()) - grid_type = grid_type_var.get() + upper_count = int(upper_count_entry.get()) + lower_count = int(lower_count_entry.get()) prices = [] # 计算上方网格价格 for i in range(upper_count, 0, -1): - if grid_type == "百分比": - price = base_price * (1 + grid_size / 100 * i) - else: # 金额差 - price = base_price + grid_size * i + price = base_price + grid_size * i prices.append(round(price, 3)) # 添加基准价格 @@ -759,10 +804,7 @@ class TradeTargetUI(ttk.Frame): # 计算下方网格价格 for i in range(1, lower_count + 1): - if grid_type == "百分比": - price = base_price * (1 - grid_size / 100 * i) - else: # 金额差 - price = base_price - grid_size * i + price = base_price - grid_size * i # 确保价格不为负 if price >= 0: prices.append(round(price, 3)) @@ -770,145 +812,75 @@ class TradeTargetUI(ttk.Frame): break return prices - except ValueError as e: + except ValueError: return None def update_preview(): - """自动更新网格价格序列预览""" + """更新网格价格序列预览""" prices = calculate_grid_prices() if prices: - price_str = ",".join([str(p) for p in prices]) + price_str = ", ".join([str(p) for p in prices]) preview_result.set(f"网格价格序列: {price_str}") else: - preview_result.set("参数错误,请检查!") + preview_result.set("参数错误,请检查输入!") # 绑定输入变化自动预览 - for entry_widget in (base_price_entry, grid_size_entry, upper_grid_entry, lower_grid_entry): + for entry_widget in entries.values(): entry_widget.bind("", lambda e: update_preview()) entry_widget.bind("", lambda e: update_preview()) + + # 预览按钮 + preview_button_frame = ttk.Frame(preview_frame) + preview_button_frame.pack(fill=tk.X, pady=5) + # ttk.Button(preview_button_frame, text="预览", command=update_preview).pack(side=tk.LEFT) + + # 预览结果显示 + preview_label = ttk.Label(preview_button_frame, textvariable=preview_result, foreground='blue') + preview_label.pack(side=tk.LEFT, padx=10) + # 初始预览 update_preview() - ttk.Label(preview_row, textvariable=preview_result, - font=('Arial', 10)).pack(side=tk.LEFT, padx=10) - # 添加其他基础配置选项 - other_basic_frame = ttk.LabelFrame(basic_frame, text="交易设置", padding=15) - other_basic_frame.pack(fill=tk.X, padx=20, pady=10) + # 按钮框架 + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X, pady=(10, 0)) - other_basic_settings = [ - ("网格交易手数", "grid_volume", config.get('config', 'grid_volume'), "每个网格的交易手数") - ] - - for i, (label, key, default, tooltip) in enumerate(other_basic_settings): - frame = ttk.Frame(other_basic_frame) - frame.pack(fill=tk.X, pady=5) - - label_widget = ttk.Label(frame, text=label + ":", width=15, font=('Arial', 10)) - label_widget.pack(side=tk.LEFT) - - entry = ttk.Entry(frame, width=15, font=('Arial', 10)) - entry.insert(0, default) - entry.pack(side=tk.LEFT, padx=5) - entries[key] = entry - - # 添加提示信息 - tip_label = ttk.Label(frame, text=tooltip, font=('Arial', 9), foreground='gray') - tip_label.pack(side=tk.LEFT, padx=5) - - # 添加高级设置选项 - account_frame = ttk.LabelFrame(advanced_frame, text="账号设置", padding=15) - account_frame.pack(fill=tk.X, padx=20, pady=10) - - # 交易账号 - account_row = ttk.Frame(account_frame) - account_row.pack(fill=tk.X, pady=5) - ttk.Label(account_row, text="交易账号:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) - account_entry = ttk.Entry(account_row, width=15, font=('Arial', 10)) - account_entry.insert(0, config.get('config', 'account_no')) - account_entry.pack(side=tk.LEFT, padx=5) - entries['account_no'] = account_entry - ttk.Label(account_row, text="QMT交易账号", font=('Arial', 9), foreground='gray').pack(side=tk.LEFT, padx=5) - - # QMT路径特殊处理 - 使用文件浏览器 - qmt_path_frame = ttk.LabelFrame(advanced_frame, text="软件路径", padding=15) - qmt_path_frame.pack(fill=tk.X, padx=20, pady=10) - - qmt_row = ttk.Frame(qmt_path_frame) - qmt_row.pack(fill=tk.X, pady=5) - - ttk.Label(qmt_row, text="QMT路径:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) - - qmt_entry = ttk.Entry(qmt_row, width=30, font=('Arial', 10)) - qmt_entry.insert(0, config.get('config', 'miniQMTPath')) - qmt_entry.pack(side=tk.LEFT, padx=5) - entries['miniQMTPath'] = qmt_entry - - def browse_qmt_path(): - """打开文件夹浏览器选择QMT路径""" - initial_dir = qmt_entry.get() if qmt_entry.get() else "/" - folder_path = filedialog.askdirectory( - title="选择miniQMT安装路径", - initialdir=initial_dir - ) - if folder_path: - qmt_entry.delete(0, tk.END) - qmt_entry.insert(0, folder_path) - - ttk.Button(qmt_row, text="📁 浏览...", command=browse_qmt_path, width=10).pack(side=tk.LEFT, padx=5) - ttk.Label(qmt_row, text="miniQMT软件安装路径", font=('Arial', 9), foreground='gray').pack(side=tk.LEFT, padx=5) - - # 定义保存和取消按钮的功能(button_frame已在上方创建) - def save_settings(): + def save_config(): """保存配置""" - # try: - # # 计算网格价格序列 - # grid_prices = calculate_grid_prices() - # if not grid_prices: - # messagebox.showerror("错误", "网格价格参数有误,请检查输入!") - # return + try: + # 获取输入值 + grid_start_price = float(base_price_entry.get()) + grid_size = float(grid_size_entry.get()) + grid_volume = int(grid_volume_entry.get()) + grid_upper_count = int(upper_count_entry.get()) + grid_lower_count = int(lower_count_entry.get()) - # grid_price_str = ",".join([str(p) for p in grid_prices]) + # 更新target对象(使用setattr来正确设置Peewee字段的值) + setattr(target, 'grid_start_price', grid_start_price) + setattr(target, 'grid_size', grid_size) + setattr(target, 'grid_volume', grid_volume) + setattr(target, 'grid_upper_count', grid_upper_count) + setattr(target, 'grid_lower_count', grid_lower_count) - # # 更新配置对象 - # config.set('config', 'miniQMTPath', entries['miniQMTPath'].get()) - # config.set('config', 'grid_price', grid_price_str) - # config.set('config', 'grid_volume', entries['grid_volume'].get()) - # config.set('config', 'account_no', entries['account_no'].get()) + # 保存到数据库 + target.save() - # # 写入配置文件 - # config_path = config.get_config_path() - # with open(config_path, 'w', encoding='utf-8') as configfile: - # config.write(configfile) + # 关闭窗口 + config_window.destroy() - # # 重新加载配置到内存中 - # config.initConfig() + # 添加日志 + PrintLog(LogLevel.INFO, f"网格配置已保存: {target.stock_code} - {target.stock_name}") + messagebox.showinfo("成功", "网格配置已保存!") - # messagebox.showinfo("成功", f"配置已保存!\n网格价格序列: {grid_price_str}\n部分配置可能需要重启程序后生效。") - # self.add_log(LogLevel.INFO, f"系统配置已更新 - 网格数量: {len(grid_prices)}") - # settings_window.destroy() - - # except Exception as e: - # messagebox.showerror("错误", f"保存配置失败:{str(e)}") - # self.add_log(LogLevel.ERROR, f"保存配置失败: {str(e)}") - pass + except ValueError: + messagebox.showerror("错误", "输入参数有误,请检查!") + except Exception as e: + messagebox.showerror("错误", f"保存配置失败:{str(e)}") + PrintLog(LogLevel.ERROR, f"保存网格配置失败: {str(e)}") - def cancel_settings(): - """取消设置""" - # settings_window.destroy() - pass - - # 在button_frame中添加按钮 - ttk.Button(button_frame, text="💾 保存配置", command=save_settings, width=15).pack(side=tk.LEFT, padx=5) - ttk.Button(button_frame, text="❌ 取消", command=cancel_settings, width=15).pack(side=tk.LEFT, padx=5) - - def grid_settings(self): - """网格修正功能""" - target = self.get_selected_target() - if not target: - return - - # 创建网格修正窗口 - self.create_grid_correction_window(target) + # 保存和取消按钮 + ttk.Button(button_frame, text="保存", command=save_config).pack(side=tk.RIGHT, padx=5) + ttk.Button(button_frame, text="取消", command=config_window.destroy).pack(side=tk.RIGHT, padx=5) def create_grid_correction_window(self, target: SFGridTradeTarget): """创建网格修正窗口""" @@ -1099,13 +1071,14 @@ class TradeTargetUI(ttk.Frame): self.update_position_status(current_position, required_position, position_status_label) - def onAddTradeTarget(self, stock_code: str): + def addTradeTarget(self, stock_code: str, gridIndex: int = 1): """处理添加交易标的事件""" try: stock_name = qmtv.getInstrumentName(stock_code) if not stock_name: PrintLog(LogLevel.ERROR, f'无法获取股票代码 {stock_code} 的名称,请检查代码是否正确') return + PrintLog(LogLevel.DEBUG, f'添加交易标的: {stock_code} {stock_name}') # 检查是否已存在该标的 existing_target = SFGridTradeTarget.get_or_none(SFGridTradeTarget.stock_code == stock_code) @@ -1120,7 +1093,7 @@ class TradeTargetUI(ttk.Frame): stock_code=stock_code, market_price=0.0, current_position=pos, - grid_index=0, + grid_index=gridIndex, last_trade_price=0.0, plan_buy_price=0.0, plan_sell_price=0.0, @@ -1148,16 +1121,3 @@ class TradeTargetUI(ttk.Frame): PrintLog(LogLevel.INFO, f"网格修正已应用: {target.stock_code} - {target.stock_name}, 网格索引: {grid_index}") except Exception as e: PrintLog(LogLevel.ERROR, f"网格修正更新失败: {str(e)}") - - def toggle_market_monitor(self): - """切换市场监控窗口显示/隐藏""" - if self.market_monitor_visible: - # 隐藏市场监控窗口 - self.market_frame.pack_forget() - self.toggle_market_monitor_btn.config(text="▶") - self.market_monitor_visible = False - else: - # 显示市场监控窗口 - self.market_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0)) - self.toggle_market_monitor_btn.config(text="◀▶") - self.market_monitor_visible = True