From be9633d9e8e5355de45bd7da95f159ea4acb6188 Mon Sep 17 00:00:00 2001 From: AdamGao Date: Wed, 5 Nov 2025 00:28:01 +0800 Subject: [PATCH] =?UTF-8?q?UI=E5=88=9D=E7=A8=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.ini | 12 +- core/main_controller.py | 3 - starter.py | 73 ++-- ui.py | 862 ++++++++++++++++++++++++---------------- 4 files changed, 573 insertions(+), 377 deletions(-) diff --git a/config.ini b/config.ini index 40b0856..c2efe00 100644 --- a/config.ini +++ b/config.ini @@ -1,9 +1,7 @@ [config] -miniQMTPath=D:\\Programs\\DTQMT\\userdata_mini -; miniQMTPath=D:\\Programs\\DTQMT_MN\\userdata_mini ; 测试账号 -grid_price=1.665,1.660,1.655,1.650,1.645,1.640,1.635,1.630,1.625,1.620,1.615,1.610 -; grid_price=11.0,10.0,9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0 +miniqmtpath = /Users/gao/Workspace/quant +grid_price = 10.9,10.0,9.1,8.2,7.3,6.4,5.5,4.6,3.7,2.8,1.9,1.0 grid_volume = 200 -account_no = '99082560' -; account_no = '89009170' ; 测试账号 -max_enabled_targets = 10 \ No newline at end of file +account_no = 99082560 +max_enabled_targets = 10 + diff --git a/core/main_controller.py b/core/main_controller.py index d57678f..60cfe06 100644 --- a/core/main_controller.py +++ b/core/main_controller.py @@ -21,9 +21,6 @@ class SFGridController(XtQuantTraderCallback): super().__init__() xtdata.enable_hello = False - strategy_db.db.connect() - strategy_db.db.create_tables([strategy_db.TradeTarget]) - print('- [成功]数据库模块初始化') session_id = int(time.time()) diff --git a/starter.py b/starter.py index dce97c3..f86468e 100644 --- a/starter.py +++ b/starter.py @@ -2,10 +2,11 @@ from mimetypes import init import sys sys.stdout.reconfigure(encoding='utf-8') # 设置标准输出编码为UTF-8 # type: ignore -from core.main_controller import SFGridController -import core.util as util +from core import strategy_db +# from core.main_controller import SFGridController +# import core.util as util import sfgrid_constants as sdConstants -from xtquant import xtdata +# from xtquant import xtdata import ui def interact(): @@ -13,35 +14,35 @@ def interact(): import code code.InteractiveConsole(locals=globals()).interact() -def startMarketData(): - ctrl.startMarketData() +# def startMarketData(): +# ctrl.startMarketData() -def stopMarketData(): - ctrl.stopMarketData() +# def stopMarketData(): +# ctrl.stopMarketData() -def pool(): - ctrl.print_pool() +# def pool(): +# ctrl.print_pool() -def addTarget(stock_code): - ctrl.add_trade_target(stock_code) +# def addTarget(stock_code): +# ctrl.add_trade_target(stock_code) -def delTarget(index:int): - ctrl.del_trade_target(index) +# def delTarget(index:int): +# ctrl.del_trade_target(index) -def accountInfo(): - ctrl.print_account_info() +# def accountInfo(): +# ctrl.print_account_info() -def positionInfo(): - ctrl.print_position_info() +# def positionInfo(): +# ctrl.print_position_info() -def startTrade(index:int): - ctrl.start_stock_trade(index) +# def startTrade(index:int): +# ctrl.start_stock_trade(index) -def pauseTrade(index:int): - ctrl.pause_stock_trade(index) +# def pauseTrade(index:int): +# ctrl.pause_stock_trade(index) -def stockTradeCtrl(index: int): - return ctrl.stock_trade_ctrl[ctrl.instrument_pool[index].stock_code] +# def stockTradeCtrl(index: int): +# return ctrl.stock_trade_ctrl[ctrl.instrument_pool[index].stock_code] def help(): print("基础指令:") @@ -61,13 +62,21 @@ def help(): print(" ctrl - 访问控制器实例") if __name__ == '__main__': - # app = ui.ProfessionalTradeUI(trade_targets=ctrl.instrument_pool) - # app.run() + + strategy_db.db.connect() + strategy_db.db.create_tables([strategy_db.TradeTarget]) + print('- [成功]数据库模块初始化') + + targets = strategy_db.TradeTarget.select() + + + app = ui.TradeTargetUI(trade_targets=targets) + app.run() - sdConstants.initConfig() - print(f'{sdConstants.account_no} : {sdConstants.miniQMTPath}') - ctrl: SFGridController = SFGridController(sdConstants.account_no, sdConstants.miniQMTPath) - if ctrl.inited: - interact() - else: - print("控制器初始化失败") + # sdConstants.initConfig() + # print(f'{sdConstants.account_no} : {sdConstants.miniQMTPath}') + # ctrl: SFGridController = SFGridController(sdConstants.account_no, sdConstants.miniQMTPath) + # if ctrl.inited: + # interact() + # else: + # print("控制器初始化失败") diff --git a/ui.py b/ui.py index e24be14..e61ab56 100644 --- a/ui.py +++ b/ui.py @@ -1,14 +1,21 @@ import random import tkinter as tk -from tkinter import ttk -from typing import List +from tkinter import ttk, messagebox, filedialog +from typing import List, Optional from datetime import datetime from core.strategy_db import TradeTarget +import configparser +import sfgrid_constants class TradeTargetUI: - def __init__(self): + def __init__(self, trade_targets: Optional[List[TradeTarget]] = None): + if trade_targets is not None: + self.trade_targets = trade_targets + else: + self.trade_targets = [] + self.root = tk.Tk() - self.root.title("交易标的监控系统") + self.root.title("三疯交易系统") self.root.geometry("1200x700") # 创建界面 @@ -16,67 +23,101 @@ class TradeTargetUI: def create_ui(self): """创建UI界面""" + # 创建菜单栏 + self.create_menu_bar() + # 主框架 main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表格区域 self.create_tables_area(main_frame) + + def create_menu_bar(self): + """创建菜单栏""" + menubar = tk.Menu(self.root) + self.root.config(menu=menubar) - # 按钮区域 - self.create_buttons_area(main_frame) + # 系统菜单 + system_menu = tk.Menu(menubar, tearoff=0) + menubar.add_cascade(label="系统", menu=system_menu) + system_menu.add_command(label="系统设置", command=self.system_settings) + system_menu.add_separator() + system_menu.add_command(label="退出", command=self.root.quit) def create_tables_area(self, parent): """创建表格区域""" - tables_frame = ttk.Frame(parent) - tables_frame.pack(fill=tk.BOTH, expand=True) + # 上方交易标的区域 + trade_frame = ttk.LabelFrame(parent, text="交易标的详情", padding=10) + trade_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5)) - # 左侧表格框架 - left_frame = ttk.LabelFrame(tables_frame, text="交易标的详情", padding=10) - left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5)) + # 创建交易标的表格 + self.create_trade_target_table(trade_frame) - # 右侧表格框架 - right_frame = ttk.LabelFrame(tables_frame, text="操作日志", padding=10) - right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0)) + # 下方操作日志区域(默认隐藏) + self.log_frame = ttk.LabelFrame(parent, text="操作日志", padding=10) + # 默认不显示,通过工具栏按钮控制 + # self.log_frame.pack(fill=tk.X, pady=(5, 0)) + self.log_visible = False # 日志区域可见性标志 - # 创建左侧交易标的表格 - self.create_trade_target_table(left_frame) - - # 创建右侧操作日志表格 - self.create_log_table(right_frame) + # 创建操作日志表格 + self.create_log_table(self.log_frame) def create_trade_target_table(self, parent): """创建交易标的表格""" - # 创建Treeview - columns = ( - "id", "stock_code", "stock_name", "current_position", "grid_index", - "last_trade_price", "current_buy_price", "current_sell_price", - "current_buy_order_no", "current_sell_order_no", - "status", "enabled" + # 创建工具栏 + toolbar_frame = ttk.Frame(parent) + toolbar_frame.pack(fill=tk.X, pady=(0, 10)) + + # 工具栏按钮 + ttk.Button(toolbar_frame, text="▶️ 启动交易", + command=self.start_selected_trade, width=12).pack(side=tk.LEFT, padx=2) + ttk.Button(toolbar_frame, text="⏸ 暂停交易", + 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.delete_selected_trade, width=12).pack(side=tk.LEFT, padx=2) + + # 添加分隔符 + ttk.Separator(toolbar_frame, orient='vertical').pack(side=tk.LEFT, fill=tk.Y, padx=10) + + # 日志显示/隐藏按钮 + self.log_toggle_btn = ttk.Button(toolbar_frame, text="📋 显示日志", + command=self.toggle_log_panel, width=12) + self.log_toggle_btn.pack(side=tk.LEFT, padx=2) + + # 添加分隔线 + ttk.Separator(parent, orient='horizontal').pack(fill=tk.X, pady=5) + + columns = ("ID", + "股票代码", "股票名称", "持仓数量", "网格索引", + "最新成交价", "计划买入价", "买入订单号", "计划卖出价", "卖出订单号", + "启用状态", "交易状态" ) - self.trade_table = ttk.Treeview(parent, columns=columns, show='headings', height=12) + self.trade_table = ttk.Treeview(parent, columns=columns, show='headings', height=15) - # 定义列标题和宽度 + # 专业化的列配置 column_configs = { - "id": ("ID", 80), - "stock_code": ("股票代码", 80), - "stock_name": ("股票名称", 100), - "current_position": ("持仓数量", 90), - "grid_index": ("网格索引", 80), - "last_trade_price": ("最新成交价", 90), - "current_buy_price": ("当前买入价", 90), - "current_sell_price": ("当前卖出价", 90), - "current_buy_order_no": ("买入订单号", 120), - "current_sell_order_no": ("卖出订单号", 120), - "status": ("状态", 70), - "enabled": ("启用状态", 80) + "ID": (50, tk.CENTER), + "股票代码": (90, tk.CENTER), + "股票名称": (100, tk.CENTER), + "持仓数量": (90, tk.CENTER), + "网格索引": (80, tk.CENTER), + "最新成交价": (100, tk.CENTER), + "计划买入价": (100, tk.CENTER), + "买入订单号": (100, tk.CENTER), + "计划卖出价": (100, tk.CENTER), + "卖出订单号": (100, tk.CENTER), + "启用状态": (80, tk.CENTER), + "交易状态": (80, tk.CENTER) } for col in columns: - title, width = column_configs[col] - self.trade_table.heading(col, text=title) - self.trade_table.column(col, width=width, anchor=tk.CENTER) + width, anchor = column_configs[col] + self.trade_table.heading(col, text=col) + self.trade_table.column(col, width=width, anchor=anchor) # type: ignore # 填充数据 self.populate_trade_table() @@ -85,24 +126,56 @@ class TradeTargetUI: scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.trade_table.yview) self.trade_table.configure(yscrollcommand=scrollbar.set) - # 布局 self.trade_table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 绑定双击事件 self.trade_table.bind("", self.on_table_double_click) + def get_status_indicator(self, target: TradeTarget) -> str: + """获取状态指示器(带颜色色块的文本)""" + if target.status == 1: + # 绿色圆点表示交易中 + return "🟢 已建仓" + elif target.status == 0: + # 黄色圆点表示暂停 + return "🟡 未建仓" + else: + return "🔴 错误状态" + + def get_trade_status_indicator(self, status: int) -> str: + """获取交易状态指示器""" + if status == 1: + return "🟢 策略运行" + else: + return "🟡 策略暂停" + def populate_trade_table(self): """填充交易标的表格数据""" - # 颜色标签 - self.trade_table.tag_configure('enabled', background='#f0f8ff') # 淡蓝色 - self.trade_table.tag_configure('disabled', background='#f5f5f5') # 淡灰色 + for temp in self.trade_targets: + target: TradeTarget = temp + values = [ + target.id, # type: ignore + target.stock_code, + target.stock_name, + target.current_position, + target.grid_index, + f"{target.last_trade_price:.2f}", + f"{target.current_buy_price:.2f}", + target.current_buy_order_no, + f"{target.current_sell_price:.2f}", + target.current_sell_order_no, + self.get_status_indicator(target), + self.get_trade_status_indicator(target.status) # type: ignore + ] + + self.trade_table.insert('', tk.END, values=values) def create_log_table(self, parent): """创建操作日志表格""" columns = ("timestamp", "level", "message") - self.log_table = ttk.Treeview(parent, columns=columns, show='headings', height=12) + self.log_table = ttk.Treeview(parent, columns=columns, show='headings', height=8) log_column_configs = { "timestamp": ("时间", 120), @@ -136,118 +209,6 @@ class TradeTargetUI: self.log_table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - def create_buttons_area(self, parent): - """创建按钮区域""" - buttons_frame = ttk.LabelFrame(parent, text="操作面板", padding=15) - buttons_frame.pack(fill=tk.X, pady=(10, 0)) - - # 创建五个功能按钮 - self.create_five_buttons(buttons_frame) - - def create_five_buttons(self, parent): - """创建五个功能按钮""" - button_configs = [ - ("刷新数据", self.refresh_data, "🔄"), - ("启用交易", self.enable_trading, "✅"), - ("禁用交易", self.disable_trading, "⛔"), - ("修改参数", self.modify_parameters, "⚙️"), - ("系统设置", self.system_settings, "🔧") - ] - - for text, command, icon in button_configs: - btn = ttk.Button( - parent, - text=f"{icon} {text}", - command=command, - width=15 - ) - btn.pack(side=tk.LEFT, padx=8) - - def refresh_data(self): - """刷新数据功能""" - # 模拟数据更新 - for target in self.trade_targets: - if target.enabled: - # 随机更新价格和仓位 - target.last_trade_price += round((random.random() - 0.5) * 0.5, 2) - target.current_position += random.randint(-50, 50) - - # 重新填充表格 - for item in self.trade_table.get_children(): - self.trade_table.delete(item) - - for target in self.trade_targets: - values = [ - target.id, - target.stock_code, - target.stock_name, - target.current_position, - target.grid_index, - target.last_trade_price, - target.current_buy_price, - target.current_buy_order_no, - target.current_sell_price, - target.current_sell_order_no, - self.get_status_text(target.status), - "是" if target.enabled else "否" - ] - - tags = ('enabled',) if target.enabled else ('disabled',) - self.trade_table.insert('', tk.END, values=values, tags=tags) - - # 添加日志 - self.add_log("INFO", "数据刷新完成") - - def enable_trading(self): - """启用交易功能""" - selected = self.trade_table.selection() - if selected: - item = selected[0] - values = self.trade_table.item(item)['values'] - # 在实际应用中,这里会调用实际的启用逻辑 - self.add_log("INFO", f"启用交易标的: {values[0]}") - - def disable_trading(self): - """禁用交易功能""" - selected = self.trade_table.selection() - if selected: - item = selected[0] - values = self.trade_table.item(item)['values'] - self.add_log("INFO", f"禁用交易标的: {values[0]}") - - def modify_parameters(self): - """修改参数功能""" - selected = self.trade_table.selection() - if selected: - item = selected[0] - values = self.trade_table.item(item)['values'] - # 弹出修改对话框 - self.show_parameter_dialog() - - def system_settings(self): - """系统设置功能""" - # 弹出系统设置对话框 - settings_window = tk.Toplevel(self.root) - settings_window.title("系统设置") - settings_window.geometry("400x300") - - ttk.Label(settings_window, text="网格交易系统设置", font=('Arial', 12, 'bold')).pack(pady=20) - - # 添加一些设置选项 - options = [ - ("自动刷新间隔", "5秒"), - ("价格变动阈值", "0.1%"), - ("最大持仓数量", "10000"), - ("最小交易数量", "100") - ] - - for label, value in options: - frame = ttk.Frame(settings_window) - frame.pack(fill=tk.X, padx=20, pady=10) - - ttk.Label(frame, text=label, width=20).pack(side=tk.LEFT) - ttk.Label(frame, text=value).pack(side=tk.RIGHT) - def get_status_text(self, status): """获取状态文本""" status_map = { @@ -264,179 +225,169 @@ class TradeTargetUI: values = self.trade_table.item(item)['values'] self.add_log("DEBUG", f"双击查看详情: {values[0]} - {values[1]}") + def get_selected_target(self): + """获取选中的交易标的""" + selected = self.trade_table.selection() + if not selected: + messagebox.showwarning("未选中", "请先选择一个交易标的") + return None + + # 获取选中行的ID + item = selected[0] + values = self.trade_table.item(item)['values'] + target_id = values[0] + + # 从列表中找到对应的target对象 + for target in self.trade_targets: + if target.id == target_id: # type: ignore + return target + + return None + + def start_selected_trade(self): + """启动选中的交易""" + target = self.get_selected_target() + if not target: + return + + if target.enabled: # type: ignore + messagebox.showinfo("提示", f"{target.stock_code} ({target.stock_name}) 已经在运行中") + return + + result = messagebox.askyesno( + "确认启动", + f"确定要启动以下交易标的吗?\n\n" + f"股票代码: {target.stock_code}\n" + f"股票名称: {target.stock_name}" + ) + + if result: + target.enabled = True # type: ignore + self.add_log("INFO", f"已启动交易: {target.stock_code} - {target.stock_name}") + self.refresh_table() + messagebox.showinfo("启动成功", f"已启动 {target.stock_code} ({target.stock_name}) 的交易") + + def pause_selected_trade(self): + """暂停选中的交易""" + target = self.get_selected_target() + if not target: + return + + if not target.enabled: # type: ignore + messagebox.showinfo("提示", f"{target.stock_code} ({target.stock_name}) 已经是暂停状态") + return + + result = messagebox.askyesno( + "确认暂停", + f"确定要暂停以下交易标的吗?\n\n" + f"股票代码: {target.stock_code}\n" + f"股票名称: {target.stock_name}" + ) + + if result: + target.enabled = False # type: ignore + self.add_log("INFO", f"已暂停交易: {target.stock_code} - {target.stock_name}") + self.refresh_table() + messagebox.showinfo("暂停成功", f"已暂停 {target.stock_code} ({target.stock_name}) 的交易") + + def delete_selected_trade(self): + """删除选中的交易标的""" + target = self.get_selected_target() + if not target: + return + + result = messagebox.askyesno( + "确认删除", + f"确定要删除以下交易标的吗?\n\n" + f"股票代码: {target.stock_code}\n" + f"股票名称: {target.stock_name}\n\n" + f"⚠️ 此操作不可恢复!", + icon='warning' + ) + + if result: + try: + self.trade_targets.remove(target) + self.add_log("WARNING", f"已删除交易标的: {target.stock_code} - {target.stock_name}") + self.refresh_table() + messagebox.showinfo("删除成功", f"已删除 {target.stock_code} ({target.stock_name})") + except Exception as e: + self.add_log("ERROR", f"删除失败: {str(e)}") + messagebox.showerror("删除失败", f"删除交易标的时出错:{str(e)}") + + def add_trade_target(self): + """添加新的交易标的""" + # TODO: 实现添加交易标的的对话框 + messagebox.showinfo("提示", "添加交易标的功能待实现") + self.add_log("INFO", "点击添加交易标的按钮") + + def toggle_log_panel(self): + """切换日志面板的显示/隐藏""" + if self.log_visible: + # 隐藏日志面板 + self.log_frame.pack_forget() + self.log_visible = False + self.log_toggle_btn.config(text="📋 显示日志") + else: + # 显示日志面板 + self.log_frame.pack(fill=tk.X, pady=(5, 0)) + self.log_visible = True + self.log_toggle_btn.config(text="📋 隐藏日志") + + def refresh_table(self): + """刷新表格数据""" + # 清空表格 + for item in self.trade_table.get_children(): + self.trade_table.delete(item) + + # 重新填充 + self.populate_trade_table() + def add_log(self, level, message): """添加日志记录""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.log_table.insert('', 0, values=(timestamp, level, message)) - def run(self): - """运行程序""" - # 初始填充数据 - for target in self.trade_targets: - values = [ - target.stock_code, - target.stock_name, - target.current_position, - target.grid_index, - target.last_trade_price, - target.current_buy_price, - target.current_buy_order_no, - target.current_sell_price, - target.current_sell_order_no, - self.get_status_text(target.status), - "是" if target.enabled else "否" - ] - - tags = ('enabled',) if target.enabled else ('disabled',) - self.trade_table.insert('', tk.END, values=values, tags=tags) - - self.root.mainloop() - -# 更专业的版本,集成实际的TradeTarget对象 -class ProfessionalTradeUI(TradeTargetUI): - def __init__(self, trade_targets: List[TradeTarget] = None): - if trade_targets is not None: - self.trade_targets = trade_targets - else: - self.trade_targets = self.create_advanced_sample_data() - super().__init__() - - def create_trade_target_table(self, parent): - """专业版的交易标的表格""" - columns = ("ID", - "股票代码", "股票名称", "持仓数量", "网格索引", - "最新成交价", "当前买入价", "当前买入订单号", "当前卖出价", "当前卖出订单号", - "启用状态", "交易状态" - ) - - self.trade_table = ttk.Treeview(parent, columns=columns, show='headings', height=15) - - # 专业化的列配置 - column_configs = { - "ID": (50, tk.CENTER), - "股票代码": (90, tk.CENTER), - "股票名称": (100, tk.CENTER), - "持仓数量": (90, tk.CENTER), - "网格索引": (80, tk.CENTER), - "最新成交价": (100, tk.CENTER), - "当前买入价": (100, tk.CENTER), - "当前买入订单号": (100, tk.CENTER), - "当前卖出订单号": (100, tk.CENTER), - "当前卖出价": (100, tk.CENTER), - "启用状态": (80, tk.CENTER), - "交易状态": (80, tk.CENTER), - "买入订单号": (120, tk.CENTER), - "卖出订单号": (120, tk.CENTER) - } - - for col in columns: - width, anchor = column_configs[col] - self.trade_table.heading(col, text=col) - self.trade_table.column(col, width=width, anchor=anchor) - - # 带颜色的标签 - self.trade_table.tag_configure('trading', background='#e8f5e8') # 交易中的绿色背景 - self.trade_table.tag_configure('paused', background='#fff3cd') # 暂停的黄色背景 - self.trade_table.tag_configure('inactive', background='#f8d7da') # 禁用的红色背景 - - # 填充数据 - for temp in self.trade_targets: - target:TradeTarget = temp - values = [ - target.id, - target.stock_code, - target.stock_name, - target.current_position, - target.grid_index, - f"{target.last_trade_price:.2f}", - f"{target.current_buy_price:.2f}", - target.current_buy_order_no, - f"{target.current_sell_price:.2f}", - target.current_sell_order_no, - "已启用" if target.enabled else "未启用", - "交易中" if target.status == 1 else "新标的" - ] - - # 根据状态设置标签 - if target.enabled and target.status == 1: - tags = ('trading',) - elif target.enabled and target.status == 0: - tags = ('paused',) - else: - tags = ('inactive',) - - self.trade_table.insert('', tk.END, values=values, tags=tags) - - # 滚动条 - scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.trade_table.yview) - self.trade_table.configure(yscrollcommand=scrollbar.set) - - self.trade_table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - - def refresh_data(self): - """专业版的刷新数据功能""" - for target in self.trade_targets: - if target.enabled: - # 模拟实时的价格变动 - price_change = (random.random() - 0.5) * 0.1 - target.last_trade_price = round(target.last_trade_price + price_change, 2) - - # 更新表格 - for item in self.trade_table.get_children(): - self.trade_table.delete(item) - - for target in self.trade_targets: - values = [ - target.id, - target.stock_code, - target.stock_name, - target.current_position, - target.grid_index, - f"{target.last_trade_price:.2f}", - f"{target.current_buy_price:.2f}", - f"{target.current_sell_price:.2f}", - "已启用" if target.enabled else "未启用", - "交易中" if target.status == 1 else "新标的" - ] - - # 设置标签 - if target.enabled and target.status == 1: - tags = ('trading',) - elif target.enabled and target.status == 0: - tags = ('paused',) - else: - tags = ('inactive',) - - self.trade_table.insert('', tk.END, values=values, tags=tags) - - self.add_log("INFO", "专业版数据刷新完成 - 包含实时价格模拟") - - def show_parameter_dialog(self): - """显示参数修改对话框""" - dialog = tk.Toplevel(self.root) - dialog.title("交易参数设置") - dialog.geometry("500x400") - - # 创建配置表单 - ttk.Label(dialog, text="交易标的参数配置", font=('Arial', 12, 'bold')).pack(pady=20) - - # 创建标签和输入框 - form_frame = ttk.Frame(dialog) - form_frame.pack(fill=tk.BOTH, expand=True, padx=20) - - # 这里可以添加具体的参数配置表单 - ttk.Label(form_frame, text="双击表格项可查看详情", font=('Arial', 10)).pack(pady=10) - def system_settings(self): - """专业版的系统设置""" + """系统设置""" settings_window = tk.Toplevel(self.root) settings_window.title("网格交易系统配置") - settings_window.geometry("600x400") - # 创建选项卡 + # 设置窗口大小 + window_width = 700 + window_height = 600 + + # 先设置为模态窗口 + settings_window.transient(self.root) + + # 确保主窗口完全初始化 + self.root.update_idletasks() + + # 获取主窗口的实际大小(包括边框) + # 使用winfo_rootx/rooty获取窗口在屏幕上的绝对位置 + main_x = self.root.winfo_rootx() + main_y = self.root.winfo_rooty() + main_width = self.root.winfo_width() + main_height = self.root.winfo_height() + + # 计算设置窗口相对于主窗口的居中位置 + x = main_x + (main_width - window_width) // 2 + y = main_y + (main_height - window_height) // 2 + + # 设置窗口大小和位置 + settings_window.geometry(f"{window_width}x{window_height}+{x}+{y}") + settings_window.resizable(False, False) + + # 设置为模态窗口 + settings_window.grab_set() + + # 添加底部按钮区域(先创建,确保固定在底部) + button_frame = ttk.Frame(settings_window) + button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=10) + + # 创建选项卡(在按钮之后创建,填充剩余空间) notebook = ttk.Notebook(settings_window) - notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0)) # 基础设置 basic_frame = ttk.Frame(notebook) @@ -446,21 +397,262 @@ class ProfessionalTradeUI(TradeTargetUI): advanced_frame = ttk.Frame(notebook) notebook.add(advanced_frame, text="高级设置") - # 添加具体的配置选项 - settings = [ - ("网格宽度", "0.5%"), - ("网格数量", "10层"), - ("交易间隔", "30秒"), - ("单笔数量", "100股") + # 读取当前配置 + config = configparser.ConfigParser() + config.read('config.ini') + + # 创建输入框字典用于保存引用 + 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_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 + + # 网格大小 + 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_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 + + # 网格类型改变时更新单位 + 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() + + # 上方网格数量 + 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 + + # 下方网格数量 + 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 + + # 预览按钮和结果显示 + preview_row = ttk.Frame(grid_price_frame) + preview_row.pack(fill=tk.X, pady=10) + + preview_result = tk.StringVar(value="点击'预览'查看生成的网格价格序列") + + def calculate_grid_prices(): + """计算网格价格序列""" + 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() + + 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 + prices.append(round(price, 3)) + + # 添加基准价格 + prices.append(base_price) + + # 计算下方网格价格 + 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 + # 确保价格不为负 + if price >= 0: + prices.append(round(price, 3)) + else: + break + + return prices + except ValueError as e: + return None + + def update_preview(): + """自动更新网格价格序列预览""" + prices = calculate_grid_prices() + if prices: + price_str = ",".join([str(p) for p in prices]) + preview_result.set(f"网格价格序列: {price_str}") + else: + preview_result.set("参数错误,请检查!") + + # 绑定输入变化自动预览 + for entry_widget in (base_price_entry, grid_size_entry, upper_grid_entry, lower_grid_entry): + entry_widget.bind("", lambda e: update_preview()) + entry_widget.bind("", lambda e: update_preview()) + # 初始预览 + 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) + + other_basic_settings = [ + ("网格交易手数", "grid_volume", config.get('config', 'grid_volume'), "每个网格的交易手数"), + ("最大启用目标数", "max_enabled_targets", config.get('config', 'max_enabled_targets'), "同时运行的最大标的数量") ] - for i, (label, default) in enumerate(settings): - frame = ttk.Frame(basic_frame) - frame.pack(fill=tk.X, padx=20, pady=10) - ttk.Label(frame, text=label, width=15).pack(side=tk.LEFT) - entry = ttk.Entry(frame, width=20) + 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.RIGHT) + 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(): + """保存配置""" + try: + # 计算网格价格序列 + grid_prices = calculate_grid_prices() + if not grid_prices: + messagebox.showerror("错误", "网格价格参数有误,请检查输入!") + return + + grid_price_str = ",".join([str(p) for p in grid_prices]) + + # 更新配置对象 + 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()) + config.set('config', 'max_enabled_targets', entries['max_enabled_targets'].get()) + + # 写入配置文件 + with open('config.ini', 'w') as configfile: + config.write(configfile) + + # 重新加载配置到内存中 + sfgrid_constants.initConfig() + + messagebox.showinfo("成功", f"配置已保存!\n网格价格序列: {grid_price_str}\n部分配置可能需要重启程序后生效。") + self.add_log("INFO", f"系统配置已更新 - 网格数量: {len(grid_prices)}") + settings_window.destroy() + + except Exception as e: + messagebox.showerror("错误", f"保存配置失败:{str(e)}") + self.add_log("ERROR", f"保存配置失败: {str(e)}") + + def cancel_settings(): + """取消设置""" + settings_window.destroy() + + # 在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 run(self): + """运行程序""" + self.root.mainloop() + # 使用示例 if __name__ == "__main__": @@ -471,5 +663,5 @@ if __name__ == "__main__": print(" - 底部五个功能按钮提供操作") # 创建并运行界面 - app = ProfessionalTradeUI() + app = TradeTargetUI() app.run()