1145 lines
51 KiB
Python
1145 lines
51 KiB
Python
from typing import Any
|
||
|
||
import tkinter as tk
|
||
from tkinter import ttk, messagebox, filedialog
|
||
from datetime import datetime
|
||
import threading
|
||
import time
|
||
from core import constants
|
||
import core.eventbus as eBus
|
||
from core.logger import LogLevel, PrintLog
|
||
from core.sfgrid import bus_events
|
||
from core.sfgrid.model import SFGridTradeTarget
|
||
import configparser
|
||
import config
|
||
from core.sfgrid.objects import GridFixData
|
||
from core.qmt import qmtv
|
||
from core.sfgrid.sfgrid_strategy import SFGridStrategy
|
||
|
||
|
||
class TradeTargetUI(ttk.Frame):
|
||
def __init__(self, parent):
|
||
super().__init__(parent)
|
||
self.tradeTargetData:dict[int, SFGridTradeTarget] = {} # id->trade_target
|
||
self.stockCodeIdMap:dict[str, int] = {}
|
||
self.strategy_ctrl:dict[int, SFGridStrategy] = {} # stock_code->trade_target
|
||
self.listening_stock = []
|
||
self.init_trade_target_pool()
|
||
eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onMarketDataUpdated)
|
||
eBus.event_bus.subscribe(bus_events.EventTradeTargetUpdate, self.onStrategyUpdate)
|
||
|
||
# 市场监控数据
|
||
self.marketData: dict[str, Any] = {} # 存储市场数据 {stock_code: {stock_name, last_price, time}}
|
||
|
||
# 市场监控窗口显示状态
|
||
self.market_monitor_visible = True
|
||
|
||
# 创建界面
|
||
self.create_ui()
|
||
|
||
|
||
def init_trade_target_pool(self):
|
||
results = SFGridTradeTarget.select()
|
||
for temp in results:
|
||
tradeTarget :SFGridTradeTarget = temp
|
||
id = tradeTarget.get_id()
|
||
|
||
status = "新建" if tradeTarget.status == 0 else "已建初始仓"
|
||
tradeTarget.current_position = qmtv.getStockPosition(tradeTarget.stock_code) # type: ignore
|
||
# 计算计划交易价格
|
||
if tradeTarget.grid_index > 0:
|
||
tradeTarget.plan_buy_price = tradeTarget.getPriceGrid()[tradeTarget.grid_index - 1] # type: ignore
|
||
if tradeTarget.grid_index < len(tradeTarget.getPriceGrid()) - 1:
|
||
tradeTarget.plan_sell_price = tradeTarget.getPriceGrid()[tradeTarget.grid_index + 1] # type: ignore
|
||
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
|
||
PrintLog(LogLevel.INFO, f' [序号-{id}] 股票代码: {tradeTarget.stock_code}-{tradeTarget.stock_name}: {tradeTarget.plan_buy_price} {tradeTarget.plan_sell_price}') # type: ignore
|
||
self.updateTradeTarget(tradeTarget)
|
||
|
||
PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.tradeTargetData)} 个标的')
|
||
|
||
|
||
# 收集所有市场数据用于市场监控
|
||
def onMarketDataUpdated(self, data):
|
||
for stock_code, tickData in data.items():
|
||
if stock_code in self.stockCodeIdMap:
|
||
id:int = self.stockCodeIdMap[stock_code]
|
||
PrintLog(LogLevel.INFO, f'股票代码: {stock_code} in trade pool, 市场数据更新 {tickData["lastPrice"]}')
|
||
tradeTarget = self.tradeTargetData[id]
|
||
lastPrice = float("{:.3f}".format(tickData['lastPrice']))
|
||
tradeTarget.market_price = lastPrice # type: ignore
|
||
self.updateTradeTarget(tradeTarget)
|
||
stock_controller = self.strategy_ctrl[id]
|
||
stock_controller.onDataUpdate(tradeTarget)
|
||
else:
|
||
# 非目标交易,发布市场数据更新事件用于市场监控
|
||
lastPrice = tickData['lastPrice']
|
||
if lastPrice == 10 or stock_code in self.listening_stock:
|
||
PrintLog(LogLevel.INFO, f'股票代码: {stock_code} 监听中, 市场数据更新 {tickData["lastPrice"]}')
|
||
# 发布市场数据更新事件用于市场监控
|
||
market_target = SFGridTradeTarget()
|
||
market_target.stock_code = stock_code
|
||
market_target.stock_name = qmtv.getInstrumentName(stock_code) # type: ignore
|
||
market_target.market_price = lastPrice # type: ignore
|
||
if stock_code not in self.listening_stock:
|
||
self.listening_stock.append(stock_code)
|
||
# 更新市场监控数据用于UI显示
|
||
current_time = datetime.now().strftime("%H:%M:%S")
|
||
self.marketData[str(stock_code)] = {
|
||
'stock_name': qmtv.getInstrumentName(stock_code),
|
||
'last_price': tickData['lastPrice'],
|
||
'time': current_time
|
||
}
|
||
|
||
|
||
# 来自策略的数据更新
|
||
def onStrategyUpdate(self, target: SFGridTradeTarget):
|
||
PrintLog(LogLevel.INFO, f'策略更新: {target.stock_code}-{target.stock_name}')
|
||
self.updateTradeTarget(target)
|
||
|
||
|
||
def updateTradeTarget(self, target: SFGridTradeTarget):
|
||
id = target.get_id()
|
||
status = "新建" if target.status == 0 else "已建初始仓"
|
||
PrintLog(LogLevel.INFO, f' [序号-{id}] 股票代码: {target.stock_code}-{target.stock_name} 当前持仓: {target.current_position} 网格索引: {target.grid_index} 基准价格 {target.getPriceGrid()[1]} 状态: {status} 启用交易线程: {'自动交易中' if target.enabled else '交易已停止'}')
|
||
PrintLog(LogLevel.INFO, f' [序号-{id}] 股票代码: {target.stock_code}-{target.stock_name}: {target.plan_buy_price} {target.plan_sell_price}') # type: ignore
|
||
# 更新或添加数据到本地缓存
|
||
self.tradeTargetData[id] = target
|
||
|
||
if id not in self.strategy_ctrl:
|
||
self.stockCodeIdMap[target.stock_code] = id # type: ignore
|
||
self.strategy_ctrl[id] = SFGridStrategy(target) # pyright: ignore[reportArgumentType]
|
||
|
||
# UI CREATE
|
||
def create_ui(self):
|
||
"""创建UI界面"""
|
||
# 主框架(使用self作为父容器)
|
||
main_frame = ttk.Frame(self)
|
||
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||
|
||
# 创建工具栏
|
||
toolbar_frame = ttk.Frame(main_frame)
|
||
toolbar_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
# 工具栏按钮
|
||
ttk.Button(toolbar_frame, text="➕ 添加标的",
|
||
command=self.btnHandlerAddTradeTarget, width=12).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(toolbar_frame, text="🗑 删除标的",
|
||
command=self.btnHandlerDelSelectedTradeTarget, width=12).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(toolbar_frame, text="▶️ 启动交易",
|
||
command=self.btnHandlerStartSelectedTrade, width=12).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(toolbar_frame, text="⏸ 暂停交易",
|
||
command=self.btnHandlerStopSelectedTrade, width=12).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(toolbar_frame, text="🛠 交易设置",
|
||
command=self.btnHandlerTradeSettings, width=12).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(toolbar_frame, text="🛠 网格修正",
|
||
command=self.btnHandlerGridCorrect, width=12).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(toolbar_frame, text="▣ 实时监控",
|
||
command=self.btnHandlerToggleMarketMonitor, width=12).pack(side=tk.RIGHT, padx=2)
|
||
|
||
# 表格区域
|
||
self.create_tables_area(main_frame)
|
||
|
||
# 启动刷新线程
|
||
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_tables_area(self, parent):
|
||
"""创建表格区域"""
|
||
# 创建主表格框架(水平排列)
|
||
tables_frame = ttk.Frame(parent)
|
||
tables_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5))
|
||
|
||
# 左侧交易标的区域
|
||
trade_frame = ttk.LabelFrame(tables_frame, text="交易标的详情", padding=10)
|
||
trade_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
|
||
|
||
# 创建交易标的表格
|
||
self.create_trade_target_table(trade_frame)
|
||
|
||
# 右侧市场监控区域
|
||
self.market_frame = ttk.LabelFrame(tables_frame, text="市场监控", padding=10)
|
||
self.market_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
|
||
|
||
# 创建市场监控表格
|
||
self.create_market_monitor_table(self.market_frame)
|
||
|
||
|
||
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),
|
||
"股票名称": (80, tk.E),
|
||
"市场价": (70, tk.E),
|
||
"持仓数量": (80, tk.E),
|
||
"最新成交价": (90, tk.E),
|
||
"计划买入价": (90, tk.E),
|
||
"计划卖出价": (90, tk.E),
|
||
"当前订单价": (90, tk.E),
|
||
"当前订单号": (90, tk.E),
|
||
"当前订单类型": (90, tk.E),
|
||
"交易状态": (80, 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) # type: ignore
|
||
|
||
# 填充数据
|
||
self.populate_trade_table()
|
||
|
||
# 滚动条
|
||
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 create_market_monitor_table(self, parent):
|
||
"""创建市场监控表格"""
|
||
columns = ("时间", "股票名称", "最新价格")
|
||
|
||
self.market_table = ttk.Treeview(parent, columns=columns, show='headings', height=15)
|
||
|
||
# 列配置
|
||
column_configs = {
|
||
"时间": (50, tk.CENTER),
|
||
"股票名称": (80, tk.CENTER),
|
||
"最新价格": (50, tk.CENTER)
|
||
}
|
||
|
||
for col in columns:
|
||
width, anchor = column_configs[col]
|
||
self.market_table.heading(col, text=col)
|
||
self.market_table.column(col, width=width, anchor=anchor) # type: ignore
|
||
|
||
# 滚动条
|
||
scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.market_table.yview)
|
||
self.market_table.configure(yscrollcommand=scrollbar.set)
|
||
|
||
self.market_table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||
|
||
# 绑定双击事件
|
||
self.market_table.bind("<Double-1>", self.on_market_table_double_click)
|
||
|
||
# 填充初始数据
|
||
self.populate_market_table()
|
||
|
||
def populate_market_table(self):
|
||
"""填充市场监控表格数据"""
|
||
# 保存当前选中的项
|
||
selected_items = self.market_table.selection()
|
||
selected_values = []
|
||
for item in selected_items:
|
||
values = self.market_table.item(item)['values']
|
||
if values:
|
||
selected_values.append(values[1]) # 保存股票代码
|
||
|
||
# 清空现有数据
|
||
for item in self.market_table.get_children():
|
||
self.market_table.delete(item)
|
||
|
||
# 填充市场数据
|
||
tmp = self.marketData.copy()
|
||
for stock_code, data in tmp.items():
|
||
# 处理时间格式,仅显示 hh:mm:ss
|
||
time_str = data['time']
|
||
# 如果时间字符串包含空格,说明包含日期和时间,只取时间部分
|
||
if ' ' in time_str:
|
||
time_str = time_str.split(' ')[1]
|
||
|
||
# 确保时间格式为 hh:mm:ss,如果只有 hh:mm 则补充 :00
|
||
if ':' in time_str:
|
||
time_components = time_str.split(':')
|
||
if len(time_components) == 2:
|
||
# 只有小时和分钟,补充秒
|
||
time_str = f"{time_components[0]}:{time_components[1]}:00"
|
||
elif len(time_components) >= 3:
|
||
# 有小时、分钟和秒,只取前三个部分
|
||
time_str = f"{time_components[0]}:{time_components[1]}:{time_components[2]}"
|
||
|
||
values = [
|
||
time_str,
|
||
data['stock_name']+f"{stock_code}",
|
||
f"{data['last_price']:.3f}",
|
||
stock_code
|
||
]
|
||
self.market_table.insert('', tk.END, values=values)
|
||
|
||
# 恢复之前选中的项
|
||
if selected_values:
|
||
for item in self.market_table.get_children():
|
||
values = self.market_table.item(item)['values']
|
||
if values and values[1] in selected_values: # 比较股票代码
|
||
self.market_table.selection_add(item)
|
||
|
||
def on_market_table_double_click(self, event):
|
||
"""市场监控表格双击事件"""
|
||
selected = self.market_table.selection()
|
||
if selected:
|
||
item = selected[0]
|
||
values = self.market_table.item(item)['values']
|
||
print(values)
|
||
stock_name = values[1]
|
||
last_price = values[2]
|
||
stock_code = values[3]
|
||
|
||
# 检查是否已在交易池中
|
||
is_in_trade_pool = any(target.stock_code == stock_code for target in self.tradeTargetData.values())
|
||
|
||
if is_in_trade_pool:
|
||
messagebox.showinfo("提示", f"{stock_code} ({stock_name}) 已在交易池中")
|
||
else:
|
||
result = messagebox.askyesno(
|
||
"添加交易标的",
|
||
f"确定要将以下股票添加到交易池吗?\n\n"
|
||
f"股票代码: {stock_code}\n"
|
||
f"股票名称: {stock_name}\n"
|
||
f"最新价格: {last_price}"
|
||
)
|
||
|
||
if result:
|
||
# 发布事件通知主控制器添加标的
|
||
self.addTradeTarget(stock_code)
|
||
|
||
def get_trade_enabled_indicator(self, enabled: bool) -> str:
|
||
"""获取交易状态指示器"""
|
||
if enabled:
|
||
return "▶ 运行中"
|
||
else:
|
||
return "⏸ 已停止"
|
||
|
||
def populate_trade_table(self):
|
||
"""填充交易标的表格数据"""
|
||
for temp, target in self.tradeTargetData.items():
|
||
if target.current_order_type == constants.OrderTypeBuy:
|
||
orderTypeStr = "买"
|
||
elif target.current_order_type == constants.OrderTypeSell:
|
||
orderTypeStr = "卖"
|
||
elif target.current_order_type == constants.OrderTypeInit:
|
||
orderTypeStr = "建仓"
|
||
else:
|
||
orderTypeStr = "未知"
|
||
values = [
|
||
target.id, # type: ignore
|
||
target.stock_code,
|
||
target.stock_name,
|
||
"-" if target.market_price is None else f"{target.market_price:.3f}",
|
||
target.current_position,
|
||
'-' if target.last_trade_price is None else f"{target.last_trade_price:.3f}",
|
||
'-' if target.plan_buy_price is None else f"{target.plan_buy_price:.3f}",
|
||
'-' if target.plan_sell_price is None else f"{target.plan_sell_price:.3f}",
|
||
'-' if target.current_order_price is None else f"{target.current_order_price:.3f}",
|
||
target.current_order_no,
|
||
orderTypeStr,
|
||
self.get_trade_enabled_indicator(target.enabled) # type: ignore
|
||
]
|
||
|
||
self.trade_table.insert('', tk.END, values=values)
|
||
|
||
|
||
def get_status_text(self, status):
|
||
"""获取状态文本"""
|
||
status_map = {
|
||
0: "新标的",
|
||
1: "交易中"
|
||
}
|
||
return status_map.get(status, "未知")
|
||
|
||
def on_table_double_click(self, event):
|
||
"""表格双击事件"""
|
||
selected = self.trade_table.selection()
|
||
if selected:
|
||
item = selected[0]
|
||
values = self.trade_table.item(item)['values']
|
||
PrintLog(LogLevel.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 id in self.tradeTargetData:
|
||
if int(target_id) == id: # type: ignore
|
||
return self.tradeTargetData[id]
|
||
|
||
return None
|
||
|
||
def refresh_table(self):
|
||
"""刷新表格数据"""
|
||
# 保存当前选中的项
|
||
selected_items = self.trade_table.selection()
|
||
selected_values = []
|
||
for item in selected_items:
|
||
values = self.trade_table.item(item)['values']
|
||
if values:
|
||
selected_values.append(values[0]) # 保存ID
|
||
|
||
# 清空表格
|
||
for item in self.trade_table.get_children():
|
||
self.trade_table.delete(item)
|
||
|
||
# 重新填充
|
||
self.populate_trade_table()
|
||
|
||
# 恢复之前选中的项
|
||
if selected_values:
|
||
for item in self.trade_table.get_children():
|
||
values = self.trade_table.item(item)['values']
|
||
if values and values[0] in selected_values:
|
||
self.trade_table.selection_add(item)
|
||
|
||
# 刷新市场监控表格
|
||
self.populate_market_table()
|
||
|
||
def create_grid_view_window(self, target: SFGridTradeTarget):
|
||
"""创建网格配置查看窗口(只读)"""
|
||
# 获取顶层窗口
|
||
root = self.winfo_toplevel()
|
||
|
||
# 创建顶层窗口
|
||
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}")
|
||
|
||
# 创建主框架
|
||
main_frame = ttk.Frame(view_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=10)
|
||
config_frame.pack(fill=tk.X, pady=(0, 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)
|
||
|
||
# 网格大小
|
||
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)
|
||
|
||
# 网格交易量
|
||
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)
|
||
|
||
# 上方网格数量
|
||
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)
|
||
|
||
# 下方网格数量
|
||
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 = {}
|
||
|
||
# 基准价格
|
||
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_frame, text="元", foreground='gray').pack(side=tk.LEFT)
|
||
entries['grid_start_price'] = base_price_entry
|
||
|
||
# 网格大小
|
||
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)
|
||
ttk.Label(grid_size_frame, text="元", foreground='gray').pack(side=tk.LEFT)
|
||
entries['grid_size'] = grid_size_entry
|
||
|
||
# 网格交易量
|
||
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_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_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_frame = ttk.LabelFrame(main_frame, text="网格价格序列预览", padding=10)
|
||
preview_frame.pack(fill=tk.X, pady=(0, 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_count_entry.get())
|
||
lower_count = int(lower_count_entry.get())
|
||
|
||
prices = []
|
||
|
||
# 计算上方网格价格
|
||
for i in range(upper_count, 0, -1):
|
||
price = base_price + grid_size * i
|
||
prices.append(round(price, 3))
|
||
|
||
# 添加基准价格
|
||
prices.append(base_price)
|
||
|
||
# 计算下方网格价格
|
||
for i in range(1, lower_count + 1):
|
||
price = base_price - grid_size * i
|
||
# 确保价格不为负
|
||
if price >= 0:
|
||
prices.append(round(price, 3))
|
||
else:
|
||
break
|
||
|
||
return prices
|
||
except ValueError:
|
||
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 entries.values():
|
||
entry_widget.bind("<KeyRelease>", lambda e: update_preview())
|
||
entry_widget.bind("<FocusOut>", 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()
|
||
|
||
# 按钮框架
|
||
button_frame = ttk.Frame(main_frame)
|
||
button_frame.pack(fill=tk.X, pady=(10, 0))
|
||
|
||
def save_config():
|
||
"""保存配置"""
|
||
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())
|
||
|
||
# 更新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)
|
||
|
||
# 保存到数据库
|
||
target.save()
|
||
|
||
# 关闭窗口
|
||
config_window.destroy()
|
||
|
||
# 添加日志
|
||
PrintLog(LogLevel.INFO, f"网格配置已保存: {target.stock_code} - {target.stock_name}")
|
||
messagebox.showinfo("成功", "网格配置已保存!")
|
||
|
||
except ValueError:
|
||
messagebox.showerror("错误", "输入参数有误,请检查!")
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"保存配置失败:{str(e)}")
|
||
PrintLog(LogLevel.ERROR, f"保存网格配置失败: {str(e)}")
|
||
|
||
# 保存和取消按钮
|
||
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 decrease_grid_index(self, grid_index_var: tk.IntVar, target: SFGridTradeTarget, required_position_label: ttk.Label, position_status_label: ttk.Label):
|
||
"""减少网格序号"""
|
||
current_value = grid_index_var.get()
|
||
if current_value > 0:
|
||
grid_index_var.set(current_value - 1)
|
||
# 同步更新需求持仓量和持仓状态
|
||
self.update_required_position_and_status(grid_index_var.get(), target, required_position_label, position_status_label)
|
||
|
||
def increase_grid_index(self, grid_index_var: tk.IntVar, max_index: int, target: SFGridTradeTarget, required_position_label: ttk.Label, position_status_label: ttk.Label):
|
||
"""增加网格序号"""
|
||
current_value = grid_index_var.get()
|
||
if current_value < max_index:
|
||
grid_index_var.set(current_value + 1)
|
||
# 同步更新需求持仓量和持仓状态
|
||
self.update_required_position_and_status(grid_index_var.get(), target, required_position_label, position_status_label)
|
||
|
||
|
||
def create_grid_correction_window(self, target: SFGridTradeTarget):
|
||
"""创建网格修正窗口"""
|
||
# 获取顶层窗口
|
||
root = self.winfo_toplevel()
|
||
|
||
# 创建顶层窗口
|
||
correction_window = tk.Toplevel(root)
|
||
correction_window.title(f"网格修正 - {target.stock_code} ({target.stock_name})")
|
||
correction_window.geometry("500x400")
|
||
correction_window.resizable(False, False)
|
||
|
||
# 设置窗口模态
|
||
correction_window.transient(root)
|
||
correction_window.grab_set()
|
||
|
||
# 居中显示
|
||
root.update_idletasks()
|
||
x = root.winfo_x() + (root.winfo_width() // 2) - 250
|
||
y = root.winfo_y() + (root.winfo_height() // 2) - 200
|
||
correction_window.geometry(f"500x400+{x}+{y}")
|
||
|
||
# 创建主框架
|
||
main_frame = ttk.Frame(correction_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)
|
||
|
||
# 创建修正选项框架
|
||
options_frame = ttk.LabelFrame(main_frame, text="修正选项", padding=10)
|
||
options_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
# 网格序号
|
||
grid_index_frame = ttk.Frame(options_frame)
|
||
grid_index_frame.pack(fill=tk.X, pady=5)
|
||
|
||
ttk.Label(grid_index_frame, text="网格序号:", width=12).pack(side=tk.LEFT)
|
||
|
||
# 网格序号调整控件
|
||
grid_index_var = tk.IntVar(value=getattr(target, 'grid_index'))
|
||
|
||
# 当前持仓量
|
||
position_frame = ttk.Frame(options_frame)
|
||
position_frame.pack(fill=tk.X, pady=5)
|
||
|
||
current_position_value = getattr(target, 'current_position')
|
||
ttk.Label(position_frame, text="当前持仓量:", width=12).pack(side=tk.LEFT)
|
||
ttk.Label(position_frame, text=str(current_position_value), width=10, anchor=tk.CENTER).pack(side=tk.LEFT, padx=5)
|
||
|
||
# 需求持仓量
|
||
required_position_frame = ttk.Frame(options_frame)
|
||
required_position_frame.pack(fill=tk.X, pady=5)
|
||
|
||
grid_index_value = getattr(target, 'grid_index')
|
||
required_position = grid_index_value * target.grid_volume
|
||
ttk.Label(required_position_frame, text="需求持仓量:", width=12).pack(side=tk.LEFT)
|
||
required_position_label = ttk.Label(required_position_frame, text=str(required_position), width=10, anchor=tk.CENTER)
|
||
required_position_label.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 持仓量状态提示
|
||
position_status_frame = ttk.Frame(options_frame)
|
||
position_status_frame.pack(fill=tk.X, pady=5)
|
||
|
||
position_status_label = ttk.Label(position_status_frame, text="", foreground="red")
|
||
position_status_label.pack(side=tk.LEFT, padx=(12, 0))
|
||
|
||
# 初始化持仓量状态
|
||
self.update_position_status(current_position_value, required_position, position_status_label)
|
||
|
||
# 网格序号显示和按钮
|
||
grid_index_label = ttk.Label(grid_index_frame, textvariable=grid_index_var, width=10, anchor=tk.CENTER)
|
||
grid_index_label.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 减少按钮
|
||
ttk.Button(grid_index_frame, text="-", width=3,
|
||
command=lambda: self.decrease_grid_index(grid_index_var, target, required_position_label, position_status_label)).pack(side=tk.LEFT, padx=(5, 5))
|
||
|
||
# 增加按钮
|
||
ttk.Button(grid_index_frame, text="+", width=3,
|
||
command=lambda: self.increase_grid_index(grid_index_var, len(target.getPriceGrid())-1, target, required_position_label, position_status_label)).pack(side=tk.LEFT, padx=(5, 0))
|
||
|
||
# 当前价格(实时更新)
|
||
price_frame = ttk.Frame(options_frame)
|
||
price_frame.pack(fill=tk.X, pady=5)
|
||
|
||
ttk.Label(price_frame, text="当前价格:", width=12).pack(side=tk.LEFT)
|
||
current_price_var = tk.StringVar(value="-")
|
||
current_price_label = ttk.Label(price_frame, textvariable=current_price_var, width=10, anchor=tk.CENTER)
|
||
current_price_label.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 保存按钮框架
|
||
button_frame = ttk.Frame(main_frame)
|
||
button_frame.pack(fill=tk.X, pady=(10, 0))
|
||
|
||
# 保存按钮
|
||
ttk.Button(button_frame, text="确认修正",
|
||
command=lambda: self.save_grid_correction(correction_window, target, grid_index_var.get())).pack(side=tk.RIGHT, padx=5)
|
||
|
||
# 取消按钮
|
||
ttk.Button(button_frame, text="取消",
|
||
command=correction_window.destroy).pack(side=tk.RIGHT, padx=5)
|
||
|
||
# 监听市场数据更新
|
||
def on_market_data_update(updated_target: SFGridTradeTarget):
|
||
if updated_target.get_id() == target.get_id():
|
||
current_price_var.set(f"{updated_target.market_price:.3f}" if updated_target.market_price else "-")
|
||
|
||
# 订阅市场数据更新事件
|
||
eBus.event_bus.subscribe(eBus.MarketDataUpdate, on_market_data_update)
|
||
|
||
# 窗口关闭时取消订阅
|
||
def on_window_close():
|
||
if eBus.MarketDataUpdate in eBus.event_bus.listeners and on_market_data_update in eBus.event_bus.listeners[eBus.MarketDataUpdate]:
|
||
eBus.event_bus.listeners[eBus.MarketDataUpdate].remove(on_market_data_update)
|
||
correction_window.destroy()
|
||
|
||
correction_window.protocol("WM_DELETE_WINDOW", on_window_close)
|
||
|
||
# 初始化当前价格
|
||
if target.market_price is not None:
|
||
current_price_var.set(f"{target.market_price:.3f}")
|
||
|
||
def update_position_status(self, current_position: int, required_position: int, status_label: ttk.Label):
|
||
"""更新持仓量状态提示"""
|
||
if current_position >= required_position:
|
||
status_label.config(text="持仓量充足", foreground="green")
|
||
else:
|
||
shortage = required_position - current_position
|
||
status_label.config(text=f"还需补充 {shortage} 手仓位", foreground="red")
|
||
|
||
def save_grid_correction(self, window, target: SFGridTradeTarget, new_grid_index: int):
|
||
"""保存网格修正"""
|
||
# 更新网格序号
|
||
setattr(target, 'grid_index', new_grid_index)
|
||
|
||
# 重新计算需求持仓量
|
||
required_position = new_grid_index * target.grid_volume
|
||
|
||
# 检查持仓量是否满足要求
|
||
current_position = getattr(target, 'current_position')
|
||
if current_position < required_position:
|
||
shortage = required_position - current_position
|
||
result = messagebox.askyesno(
|
||
"持仓量不足",
|
||
f"当前持仓量({current_position}手)小于需求持仓量({required_position}手)\n"
|
||
f"还需补充 {shortage} 手仓位,是否继续保存?"
|
||
)
|
||
if not result:
|
||
return
|
||
|
||
# 关闭窗口
|
||
window.destroy()
|
||
|
||
# 添加日志
|
||
PrintLog(LogLevel.INFO, f"网格修正已保存: {target.stock_code} - {target.stock_name}, 网格序号: {new_grid_index}")
|
||
|
||
|
||
def update_required_position_and_status(self, grid_index: int, target: SFGridTradeTarget, required_position_label: ttk.Label, position_status_label: ttk.Label):
|
||
"""更新需求持仓量和持仓状态"""
|
||
# 计算需求持仓量
|
||
required_position:int = grid_index * target.grid_volume # type: ignore
|
||
required_position_label.config(text=str(required_position))
|
||
|
||
# 更新持仓量状态
|
||
current_position = getattr(target, 'current_position')
|
||
self.update_position_status(current_position, required_position, position_status_label)
|
||
|
||
|
||
# 交易池管理
|
||
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)
|
||
if existing_target:
|
||
PrintLog(LogLevel.INFO, f'交易标的 {stock_code} {stock_name} 已存在')
|
||
return
|
||
|
||
# 刷新标的持仓
|
||
pos = qmtv.getStockPosition(stock_code) # type: ignore
|
||
new_target = SFGridTradeTarget.create(
|
||
stock_name=stock_name,
|
||
stock_code=stock_code,
|
||
market_price=0.0,
|
||
current_position=pos,
|
||
grid_index=gridIndex,
|
||
last_trade_price=0.0,
|
||
plan_buy_price=0.0,
|
||
plan_sell_price=0.0,
|
||
current_order_price=0.0,
|
||
current_order_no='',
|
||
current_order_type=''
|
||
)
|
||
new_target.save()
|
||
# 更新标的池
|
||
self.updateTradeTarget(new_target)
|
||
|
||
except Exception as e:
|
||
PrintLog(LogLevel.ERROR, f'新增交易标的失败 {stock_code} {e}')
|
||
|
||
# def update_trade_target_grid(self, data: GridFixData):
|
||
# """更新交易标的网格信息"""
|
||
# try:
|
||
# target = data.tradeTarget
|
||
# grid_index = data.grid_index
|
||
|
||
# # 更新数据库中的网格索引
|
||
# target.grid_index = grid_index
|
||
# target.save()
|
||
|
||
# PrintLog(LogLevel.INFO, f"网格修正已应用: {target.stock_code} - {target.stock_name}, 网格索引: {grid_index}")
|
||
# except Exception as e:
|
||
# PrintLog(LogLevel.ERROR, f"网格修正更新失败: {str(e)}")
|
||
|
||
# button handlers =============================================================================================
|
||
def btnHandlerGridCorrect(self):
|
||
|
||
target = self.get_selected_target()
|
||
if not target:
|
||
return
|
||
self.create_grid_correction_window(target)
|
||
|
||
|
||
def btnHandlerToggleMarketMonitor(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 btnHandlerTradeSettings(self):
|
||
"""网格配置功能"""
|
||
target = self.get_selected_target()
|
||
if not target:
|
||
return
|
||
|
||
# 检查标的的状态,status为1时仅可查看
|
||
if target.status == 1:
|
||
# 创建只读的网格配置查看窗口
|
||
self.create_grid_view_window(target)
|
||
else:
|
||
# 创建可编辑的网格配置窗口
|
||
self.create_grid_config_window(target)
|
||
|
||
def btnHandlerStartSelectedTrade(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:
|
||
PrintLog(LogLevel.INFO, f'启动标的交易 {target.targetName()}')
|
||
target.enabled = True # type: ignore
|
||
|
||
id = target.get_id()
|
||
if id in self.strategy_ctrl:
|
||
tradeController: SFGridStrategy = self.strategy_ctrl[target.get_id()]
|
||
tradeTarget = tradeController.enabledTrading(True)
|
||
self.tradeTargetData[id] = tradeTarget
|
||
else:
|
||
PrintLog(LogLevel.INFO, f"\t创建标的交易控制器 {target.targetName()}")
|
||
|
||
def btnHandlerStopSelectedTrade(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:
|
||
PrintLog(LogLevel.INFO, f'暂停标的交易 {target.targetName()}')
|
||
id = target.get_id()
|
||
if id in self.strategy_ctrl:
|
||
tradeController: SFGridStrategy = self.strategy_ctrl[target.get_id()]
|
||
tradeTarget = tradeController.enabledTrading(False)
|
||
orders = qmtv.queryPendingOrder(target.stock_code, tradeController.getName()) # type: ignore
|
||
for order in orders:
|
||
qmtv.xttrader.cancel_order_stock_async(qmtv.account, order.order_id)
|
||
print(f'取消未成交订单 {len(orders)}')
|
||
self.tradeTargetData[id] = tradeTarget
|
||
else:
|
||
print(f"标的交易控制器不存在 {target.stock_code} {target.stock_name}\n")
|
||
# self.add_log("INFO", f"已暂停交易: {target.stock_code} - {target.stock_name}")
|
||
# messagebox.showinfo("暂停成功", f"已暂停 {target.stock_code} ({target.stock_name}) 的交易")
|
||
|
||
def btnHandlerDelSelectedTradeTarget(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:
|
||
id = target.get_id()
|
||
try:
|
||
|
||
del self.tradeTargetData[id]
|
||
del self.strategy_ctrl[id]
|
||
del self.stockCodeIdMap[target.stock_code] # type: ignore
|
||
|
||
# 从数据库中删除
|
||
target.delete_instance()
|
||
# 添加日志
|
||
PrintLog(LogLevel.INFO, f"交易标的已删除,ID: {id} {target.targetName()}")
|
||
except Exception as e:
|
||
PrintLog(LogLevel.ERROR, f"删除交易标的失败 ID {id}: {str(e)}")
|
||
PrintLog(LogLevel.INFO, f"已发送删除请求: {target.stock_code} - {target.stock_name}")
|
||
|
||
def btnHandlerAddTradeTarget(self):
|
||
"""添加新的交易标的"""
|
||
# 获取顶层窗口
|
||
root = self.winfo_toplevel()
|
||
|
||
# 创建顶层窗口
|
||
add_window = tk.Toplevel(root)
|
||
add_window.title("添加交易标的")
|
||
add_window.geometry("400x150")
|
||
add_window.resizable(False, False)
|
||
|
||
# 设置窗口模态
|
||
add_window.transient(root)
|
||
add_window.grab_set()
|
||
|
||
# 居中显示
|
||
root.update_idletasks()
|
||
x = root.winfo_x() + (root.winfo_width() // 2) - 200
|
||
y = root.winfo_y() + (root.winfo_height() // 2) - 75
|
||
add_window.geometry(f"400x150+{x}+{y}")
|
||
|
||
# 创建输入框架
|
||
input_frame = ttk.Frame(add_window, padding=20)
|
||
input_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 股票代码输入
|
||
ttk.Label(input_frame, text="股票代码:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||
stock_code_entry = ttk.Entry(input_frame, width=30)
|
||
stock_code_entry.grid(row=0, column=1, pady=5, padx=(10, 0))
|
||
stock_code_entry.focus()
|
||
|
||
# 按钮框架
|
||
button_frame = ttk.Frame(input_frame)
|
||
button_frame.grid(row=1, column=0, columnspan=2, pady=20)
|
||
|
||
def confirm_add():
|
||
stock_code = stock_code_entry.get().strip()
|
||
if not stock_code:
|
||
messagebox.showwarning("输入错误", "请输入股票代码")
|
||
return
|
||
|
||
# 发布事件通知主控制器添加标的
|
||
self.addTradeTarget(stock_code)
|
||
add_window.destroy()
|
||
|
||
def cancel_add():
|
||
add_window.destroy()
|
||
|
||
# 确认和取消按钮
|
||
ttk.Button(button_frame, text="确认", command=confirm_add, width=10).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(button_frame, text="取消", command=cancel_add, width=10).pack(side=tk.LEFT, padx=5)
|
||
|
||
# 绑定回车键确认
|
||
stock_code_entry.bind('<Return>', lambda event: confirm_add())
|
||
|
||
PrintLog(LogLevel.INFO, "点击添加交易标的按钮") |