diff --git a/config.ini b/config.ini index 8d72d38..8900e3b 100644 --- a/config.ini +++ b/config.ini @@ -1,5 +1,5 @@ [config] -miniqmtpath = D:/Programs/DTQMT_MN/userdata_mini -; account_no = 99082560 -account_no = 89009170 +miniqmtpath = D:/Programs/DTQMT/userdata_mini +account_no = 99082560 +; account_no = 89009170 diff --git a/core/qmt.py b/core/qmt.py index 7119736..5cec9b1 100644 --- a/core/qmt.py +++ b/core/qmt.py @@ -165,7 +165,8 @@ class QmtV(XtQuantTraderCallback): :param order: XtOrder对象 :return: """ - print(f'orderd {order.strategy_name}-{order.stock_code} {order.order_id} {order.order_volume}-{order.order_status}') + pass + # print(f'orderd {order.strategy_name}-{order.stock_code} {order.order_id} {order.order_volume}-{order.order_status}') # stockCode = order.stock_code # ctrl:SFGridStrategy = self.stock_trade_ctrl[stockCode] # # 如果存在对应的StockTradeController,则调用其onDataUpdate方法 diff --git a/core/sfgrid/sfgrid_strategy.py b/core/sfgrid/sfgrid_strategy.py index c48cd12..f9b110e 100644 --- a/core/sfgrid/sfgrid_strategy.py +++ b/core/sfgrid/sfgrid_strategy.py @@ -16,27 +16,29 @@ class SFGridStrategy: def __init__(self, tradeTarget: model.SFGridTradeTarget): self.tradeTarget:model.SFGridTradeTarget = tradeTarget - self.enabledTrading(bool(tradeTarget.enabled)) # 修复类型兼容性问题 - - event_bus.publish(EventTradeTargetUpdate, self.tradeTarget) + self.enabledTrading(tradeTarget.enabled) # type: ignore self.dataUpdateLock = threading.Lock() - - def updateGridIndex(self, grid_index: int): - """更新网格索引""" - self.tradeTarget.grid_index = grid_index # type: ignore - self.refreshPlanPrice() - self.saveProxy() + + def updateTradeTarget(self, inTradeTarget:model.SFGridTradeTarget): + print(f'|- 标的{self.tradeTarget.targetName()}信息更新: START') + self.dataUpdateLock.acquire() + print(f'|- 标的{self.tradeTarget.targetName()}信息更新: LOCKED') + try: + self.tradeTarget = inTradeTarget + finally: + print(f'|- 标的{self.tradeTarget.targetName()}信息更新: UNLOCKED') + self.dataUpdateLock.release() + print(f'|- 标的{self.tradeTarget.targetName()}信息更新: END') def enabledTrading(self, enabled: bool) -> model.SFGridTradeTarget: self.tradeTarget.enabled = enabled # type: ignore - self.saveProxy() if enabled: - self.refreshPlanPrice() print(f" |- 标的{self.tradeTarget.targetName()}交易启动, 持仓量:{self.tradeTarget.current_position}") if self.tradeTarget.status == 0: # 未建仓 - print(f" |- 标的{self.tradeTarget.targetName()}初始状态, 设置网格序号 1") + print(f" |- 标的{self.tradeTarget.targetName()}初始状态, 设置网格序号 1,") self.tradeTarget.grid_index = 1 # pyright: ignore[reportAttributeAccessIssue] + self.refreshPlanPrice() else: # 已建仓 # 交易阶段,检查仓位,检查现有订单 print(f" |- 标的{self.tradeTarget.targetName()}已有仓位或非初始状态 无需建初始仓 当前仓位: {self.tradeTarget.current_position} 状态: {self.tradeTarget.status}") @@ -46,9 +48,10 @@ class SFGridStrategy: else: print(f' |- 仓位检查: 持仓需求不足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}, 交易启动失败') self.tradeTarget.enabled = False # type: ignore - self.saveProxy() else: print(f" |- 标的{self.tradeTarget.targetName()}交易监控暂停") + + self.saveProxy() return self.tradeTarget def isEnabled(self) -> bool: @@ -76,7 +79,7 @@ class SFGridStrategy: gridBasePrice = -1 if index>=len(inTradeTarget.getPriceGrid()) or index < 0 else inTradeTarget.getPriceGrid()[int(index)] # pyright: ignore[reportArgumentType] - if self.tradeTarget.enabled and self.tradeTarget.status == 0 and lastPrice <= config.grid_price[1]: # 已启用,未建仓,建仓 + if self.tradeTarget.enabled and self.tradeTarget.status == 0 and lastPrice <= inTradeTarget.getPriceGrid()[1]: # 已启用,未建仓,建仓 orderPrice = inTradeTarget.getPriceGrid()[index] orderType = xtconstant.STOCK_BUY orderRemark = OrderTypeInit @@ -104,7 +107,7 @@ class SFGridStrategy: print(f' |- 下网格{"多单" if orderType == xtconstant.STOCK_BUY else "空单"}') self.tradeTarget.current_order_no = qmtv.orderAsync( str(self.tradeTarget.stock_code), - config.grid_volume, + self.tradeTarget.grid_volume, orderType, orderPrice, xtconstant.FIX_PRICE, @@ -118,7 +121,7 @@ class SFGridStrategy: orderTypeName = "空单" elif orderRemark == OrderTypeInit: orderTypeName = "建仓单" - print(f' |- {orderTypeName}委托, 单号 {self.tradeTarget.current_order_no}, 网格基准价 {gridBasePrice}, 下单价 {orderPrice}, 下单量 {config.grid_volume}') + print(f' |- {orderTypeName}委托, 单号 {self.tradeTarget.current_order_no}, 网格基准价 {gridBasePrice}, 下单价 {orderPrice}, 下单量 {self.tradeTarget.grid_volume}') finally: print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - release lock') self.saveProxy() @@ -126,21 +129,21 @@ class SFGridStrategy: print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - END') - def onAsyncOrderResponse(self, order:XtOrder): - print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:START') - self.dataUpdateLock.acquire() - print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:LOCKED') - try: - if order.strategy_name == self.getName(): - self.tradeTarget.current_order_no = order.order_id - self.tradeTarget.current_order_type = order.order_remark - self.saveProxy() - else: - print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]: 不在策略监控范围内{order.strategy_name}') - finally: - print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:release lock') - self.dataUpdateLock.release() - print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:END') + # def onAsyncOrderResponse(self, order:XtOrder): + # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:START') + # self.dataUpdateLock.acquire() + # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:LOCKED') + # try: + # if order.strategy_name == self.getName(): + # self.tradeTarget.current_order_no = order.order_id + # self.tradeTarget.current_order_type = order.order_remark + # self.saveProxy() + # else: + # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]: 不在策略监控范围内{order.strategy_name}') + # finally: + # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:release lock') + # self.dataUpdateLock.release() + # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:END') def onOrderTrade(self, trade:XtTrade): print(f' |- 委托成交通知[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{trade.order_id}]:START, {trade.order_id}') @@ -192,20 +195,19 @@ class SFGridStrategy: print(f' |- 委托成交通知[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{trade.order_id}]:END') def refreshPlanPrice(self): - if self.tradeTarget.status == 1: - buyIdx: int = self.tradeTarget.grid_index + 1 # pyright: ignore[reportAssignmentType] - sellIdx: int = self.tradeTarget.grid_index - 1 - if self.tradeTarget.grid_index > 0: # 可以下空单 - self.tradeTarget.plan_sell_price = float(self.tradeTarget.getPriceGrid()[sellIdx]) # pyright: ignore[reportAttributeAccessIssue] - else: - self.tradeTarget.plan_sell_price = -1.0 # type: ignore - if self.tradeTarget.grid_index < len(self.tradeTarget.getPriceGrid()) - 1: - self.tradeTarget.plan_buy_price = float(self.tradeTarget.getPriceGrid()[buyIdx]) # pyright: ignore[reportAttributeAccessIssue] - else: - self.tradeTarget.plan_buy_price = -1.0 # pyright: ignore[reportAttributeAccessIssue] + if self.tradeTarget.status == 0: + self.tradeTarget.grid_index = 1 # type: ignore + + buyIdx: int = self.tradeTarget.grid_index + 1 # pyright: ignore[reportAssignmentType] + sellIdx: int = self.tradeTarget.grid_index - 1 + if self.tradeTarget.grid_index > 0: # 可以下空单 + self.tradeTarget.plan_sell_price = float(self.tradeTarget.getPriceGrid()[sellIdx]) # pyright: ignore[reportAttributeAccessIssue] else: - self.tradeTarget.plan_buy_price = 10.0 # type: ignore self.tradeTarget.plan_sell_price = -1.0 # type: ignore + if self.tradeTarget.grid_index < len(self.tradeTarget.getPriceGrid()) - 1: + self.tradeTarget.plan_buy_price = float(self.tradeTarget.getPriceGrid()[buyIdx]) # pyright: ignore[reportAttributeAccessIssue] + else: + self.tradeTarget.plan_buy_price = -1.0 # pyright: ignore[reportAttributeAccessIssue] self.saveProxy() def getName(self): diff --git a/core/sfgrid/sfgrid_ui.py b/core/sfgrid/sfgrid_ui.py index 609cdd0..905b443 100644 --- a/core/sfgrid/sfgrid_ui.py +++ b/core/sfgrid/sfgrid_ui.py @@ -8,6 +8,7 @@ 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 @@ -25,6 +26,7 @@ class TradeTargetUI(ttk.Frame): 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}} @@ -34,25 +36,46 @@ class TradeTargetUI(ttk.Frame): # 创建界面 self.create_ui() - - self.start_refresh_thread() + + 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'])) - PrintLog(LogLevel.INFO, f'股票代码: {stock_code} {id}, 市场数据更新 {lastPrice}') tradeTarget.market_price = lastPrice # type: ignore self.updateTradeTarget(tradeTarget) - stock_controller: SFGridStrategy = self.strategy_ctrl[id] + 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 @@ -69,40 +92,25 @@ class TradeTargetUI(ttk.Frame): } - def init_trade_target_pool(self): - results = SFGridTradeTarget.select() - for temp in results: - tradeTarget :SFGridTradeTarget = temp - id = tradeTarget.get_id() + # 来自策略的数据更新 + def onStrategyUpdate(self, target: SFGridTradeTarget): + PrintLog(LogLevel.INFO, f'策略更新: {target.stock_code}-{target.stock_name}') + self.updateTradeTarget(target) - status = "新建" if tradeTarget.status == 0 else "已建初始仓" - tradeTarget.current_position = qmtv.getStockPosition(tradeTarget.stock_code) # 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 - stockTradeController = SFGridStrategy(tradeTarget) # type: ignore - self.strategy_ctrl[id] = stockTradeController # pyright: ignore[reportArgumentType] - self.updateTradeTarget(tradeTarget) - PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.tradeTargetData)} 个标的') - 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[target.get_id()] = target - self.stockCodeIdMap[target.stock_code] = target.get_id() # 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] - def start_refresh_thread(self): - """启动刷新线程""" - self.refresh_thread = threading.Thread(target=self.refresh_loop, daemon=True) - self.refresh_thread.start() - PrintLog(LogLevel.INFO, "UI刷新线程已启动") - - def refresh_loop(self): - """刷新循环""" - while True: - self.after(0, self.refresh_table) - self.after(0, self.populate_market_table) - time.sleep(0.2) # 每0.5秒刷新一次 - + # UI CREATE def create_ui(self): """创建UI界面""" # 主框架(使用self作为父容器) @@ -114,31 +122,37 @@ class TradeTargetUI(ttk.Frame): 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) + command=self.btnHandlerAddTradeTarget, 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) + 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.trade_settings, width=12).pack(side=tk.LEFT, padx=2) + command=self.btnHandlerTradeSettings, width=12).pack(side=tk.LEFT, padx=2) ttk.Button(toolbar_frame, text="🛠 网格修正", - command=self.grid_correct, width=12).pack(side=tk.LEFT, padx=2) + command=self.btnHandlerGridCorrect, width=12).pack(side=tk.LEFT, padx=2) ttk.Button(toolbar_frame, text="▣ 实时监控", - command=self.toggle_market_monitor, width=12).pack(side=tk.RIGHT, padx=2) + 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 grid_correct(self): - - target = self.get_selected_target() - if not target: - return - self.create_grid_correction_window(target) + 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): """创建表格区域""" @@ -160,18 +174,6 @@ class TradeTargetUI(ttk.Frame): # 创建市场监控表格 self.create_market_monitor_table(self.market_frame) - - def toggle_market_monitor(self): - """切换市场监控窗口显示/隐藏""" - if self.market_monitor_visible: - # 隐藏市场监控窗口 - self.market_frame.pack_forget() - self.market_monitor_visible = False - else: - # 显示市场监控窗口 - self.market_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0)) - self.market_monitor_visible = True - def create_trade_target_table(self, parent): """创建交易标的表格""" @@ -358,8 +360,6 @@ class TradeTargetUI(ttk.Frame): orderTypeStr, self.get_trade_enabled_indicator(target.enabled) # type: ignore ] - - self.trade_table.insert('', tk.END, values=values) @@ -398,155 +398,6 @@ class TradeTargetUI(ttk.Frame): return self.tradeTargetData[id] 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: - 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 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: - 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 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: - id = target.get_id() - try: - # 从数据库中删除 - target.delete_instance() - - del self.tradeTargetData[id] - del self.strategy_ctrl[id] - del self.stockCodeIdMap[target.stock_code] # type: ignore - # 添加日志 - 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 add_trade_target(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('', lambda event: confirm_add()) - - PrintLog(LogLevel.INFO, "点击添加交易标的按钮") def refresh_table(self): """刷新表格数据""" @@ -574,20 +425,6 @@ class TradeTargetUI(ttk.Frame): # 刷新市场监控表格 self.populate_market_table() - - def trade_settings(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 create_grid_view_window(self, target: SFGridTradeTarget): """创建网格配置查看窗口(只读)""" @@ -882,6 +719,23 @@ class TradeTargetUI(ttk.Frame): 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): """创建网格修正窗口""" # 获取顶层窗口 @@ -1041,24 +895,7 @@ class TradeTargetUI(ttk.Frame): # 添加日志 PrintLog(LogLevel.INFO, f"网格修正已保存: {target.stock_code} - {target.stock_name}, 网格序号: {new_grid_index}") - - - 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 update_required_position_and_status(self, grid_index: int, target: SFGridTradeTarget, required_position_label: ttk.Label, position_status_label: ttk.Label): """更新需求持仓量和持仓状态""" @@ -1071,7 +908,8 @@ class TradeTargetUI(ttk.Frame): self.update_position_status(current_position, required_position, position_status_label) - def addTradeTarget(self, stock_code: str, gridIndex: int = 1): + # 交易池管理 + def addTradeTarget(self, stock_code: str, gridIndex: int = 1): # 新增 """处理添加交易标的事件""" try: stock_name = qmtv.getInstrumentName(stock_code) @@ -1108,16 +946,200 @@ class TradeTargetUI(ttk.Frame): 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 + # def update_trade_target_grid(self, data: GridFixData): + # """更新交易标的网格信息""" + # try: + # target = data.tradeTarget + # grid_index = data.grid_index - # 更新数据库中的网格索引 - target.grid_index = grid_index - target.save() + # # 更新数据库中的网格索引 + # 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)}") + # 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('', lambda event: confirm_add()) + + PrintLog(LogLevel.INFO, "点击添加交易标的按钮") \ No newline at end of file