UI初稿
This commit is contained in:
@@ -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("<Double-1>", 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("<KeyRelease>", lambda e: update_preview())
|
||||
entry_widget.bind("<FocusOut>", 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()
|
||||
|
||||
Reference in New Issue
Block a user