From 3a137b6aee9d47c6f582f47560b4eebfc136ec3d Mon Sep 17 00:00:00 2001 From: "GDP\\solonot" Date: Mon, 10 Nov 2025 14:43:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B8=82=E5=9C=BA=E6=95=B0=E6=8D=AE=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/eventbus.py | 3 + core/main_controller.py | 65 ++++++++++++----- core/sfgrid_strategy.py | 11 ++- core/ui.py | 154 ++++++++++++++++++++++++++++++++++------ 4 files changed, 192 insertions(+), 41 deletions(-) diff --git a/core/eventbus.py b/core/eventbus.py index 7f400ff..713c16b 100644 --- a/core/eventbus.py +++ b/core/eventbus.py @@ -10,10 +10,13 @@ ActionDisableMarketData = "disable_market_data" MarketDataEnabled = "market_data_enabled" MarketDataDisabled = "market_data_disabled" # 删除交易标的事件 +EventTradeTargetUpdate = "trade_target_update" ActionEventAddTradeTarget = "add_trade_target" ResultEventTradeTargetAdded = "trade_target_added" ActionEventDeleteTradeTarget = "delete_trade_target" ResultEventTradeTargetDeleted = "trade_target_deleted" +# 网格修正事件 +ActionEventGridFix = "grid_fix" # Pring Log EventPrintLog = "print_log" diff --git a/core/main_controller.py b/core/main_controller.py index 2c2e1c8..048473d 100644 --- a/core/main_controller.py +++ b/core/main_controller.py @@ -1,6 +1,6 @@ # coding:utf-8 from core.strategy_db import TradeTarget -from core.eventbus import ActionDisableMarketData, ActionEnableMarketData, ActionEventAddTradeTarget, ActionEventDeleteTradeTarget, ActionEventDisableTrade, ActionEventEnableTrade, MarketDataUpdate, MarketDataEnabled, MarketDataDisabled, ResultEventTradeDisabled, ResultEventTradeEnabled, ResultEventTradeTargetDeleted, event_bus +from core.eventbus import ActionDisableMarketData, ActionEnableMarketData, ActionEventAddTradeTarget, ActionEventDeleteTradeTarget, ActionEventDisableTrade, ActionEventEnableTrade, EventTradeTargetUpdate, MarketDataUpdate, MarketDataEnabled, MarketDataDisabled, ResultEventTradeDisabled, ResultEventTradeEnabled, ResultEventTradeTargetDeleted, ActionEventGridFix, event_bus from xtquant.xttrader import XtQuantTrader import time from peewee import ModelSelect @@ -16,6 +16,7 @@ from xtquant.xttrader import XtQuantTraderCallback import datetime import core.ui as ui from core.logger import PrintLog, LogLevel +from core.objects import GridFixData # 量化核心控制对象 class SFGridController(XtQuantTraderCallback): @@ -49,6 +50,7 @@ class SFGridController(XtQuantTraderCallback): else: self.inited = False return + self.listening_stock = [] self.stock_trade_ctrl = {} self.init_instrument_pool(self.xt_trader, self.account) # type: ignore @@ -63,6 +65,7 @@ class SFGridController(XtQuantTraderCallback): event_bus.subscribe(ActionDisableMarketData, self.onMarketDataDisabled) event_bus.subscribe(ActionEventAddTradeTarget, self.onAddTradeTarget) event_bus.subscribe(ActionEventDeleteTradeTarget, self.onDeleteTradeTarget) + event_bus.subscribe(ActionEventGridFix, self.onGridFix) def onDeleteTradeTarget(self, id: int): """处理删除交易标的事件""" @@ -88,6 +91,33 @@ class SFGridController(XtQuantTraderCallback): def onDisableTrade(self, id: int): self.pause_stock_trade(id) + def onGridFix(self, data: GridFixData): + """处理网格修正事件""" + self.update_trade_target_grid(data) + + def update_trade_target_grid(self, data: GridFixData): + """更新交易标的网格信息""" + try: + target = data.tradeTarget + grid_index = data.grid_index + + # 更新数据库中的网格索引 + target.grid_index = grid_index + target.save() + + # 更新内存中的交易标的 + if target.get_id() in self.instrument_pool: + self.instrument_pool[target.get_id()] = target + + # 更新交易控制器中的网格信息 + if target.stock_code in self.stock_trade_ctrl: + trade_controller: SFGridStrategy = self.stock_trade_ctrl[target.stock_code] + trade_controller.updateGridIndex(grid_index) # type: ignore + + PrintLog(LogLevel.INFO, f"网格修正已应用: {target.stock_code} - {target.stock_name}, 网格索引: {grid_index}") + except Exception as e: + PrintLog(LogLevel.ERROR, f"网格修正更新失败: {str(e)}") + def hold(self): self.appUi.run() @@ -193,7 +223,7 @@ class SFGridController(XtQuantTraderCallback): PrintLog(LogLevel.INFO, f' |- 同步[{target.stock_code}-{target.stock_name}]持仓信息[{'成功' if result == 1 else '失败'}]') stockTradeController = SFGridStrategy(tradeTarget, self.xt_trader, self.account) # type: ignore self.stock_trade_ctrl[tradeTarget.stock_code] = stockTradeController - event_bus.publish(MarketDataUpdate, tradeTarget) + event_bus.publish(EventTradeTargetUpdate, tradeTarget) PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.instrument_pool)} 个标的') @@ -280,22 +310,23 @@ class SFGridController(XtQuantTraderCallback): # ====== 市场回调方法 -- 以下方法由XtQuantData调用 ====== def onDataUpdate(self, data): - if sfgrid_constants.max_enabled_targets <= 0: # 全推 - for stock_code, tickData in data.items(): - lastPrice = tickData['lastPrice'] - if lastPrice == 10.0 and stock_code not in self.stock_trade_ctrl: - print(f'New trade target = {stock_code} - {getInstrumentName(stock_code)} {tickData['lastPrice']}') - self.add_trade_target(stock_code) - self.stock_trade_ctrl[stock_code].enabledTrading(True) - else: # 指定目标 当前主要使用这种模式 - for id in self.instrument_pool: - stock_code = self.instrument_pool[id].stock_code - # 如果存在对应的StockTradeController,则调用其onDataUpdate方法 - if stock_code not in self.stock_trade_ctrl or stock_code not in data: - # print(f"股票代码 {stock_code} 未在交易控制器中找到,跳过处理。\n") - continue + # 收集所有市场数据用于市场监控 + for stock_code, tickData in data.items(): + if stock_code in self.stock_trade_ctrl: stock_controller: SFGridStrategy = self.stock_trade_ctrl[stock_code] stock_controller.onDataUpdate(data) + else: + # 非目标交易,发布市场数据更新事件用于市场监控 + lastPrice = tickData['lastPrice'] + if lastPrice == 10 or stock_code in self.listening_stock: + # 发布市场数据更新事件用于市场监控 + market_target = TradeTarget() + market_target.stock_code = stock_code + market_target.stock_name = getInstrumentName(stock_code) # type: ignore + market_target.market_price = lastPrice # type: ignore + event_bus.publish(MarketDataUpdate, market_target) + if stock_code not in self.listening_stock: + self.listening_stock.append(stock_code) # ====== 市场回调方法 -- 以下方法由XtQuantTrader调用 ====== @@ -368,4 +399,4 @@ class SFGridController(XtQuantTraderCallback): :param response: XtAccountStatus 对象 :return: """ - print(datetime.datetime.now(), status) + print(datetime.datetime.now(), status) \ No newline at end of file diff --git a/core/sfgrid_strategy.py b/core/sfgrid_strategy.py index fe53e1c..9f52835 100644 --- a/core/sfgrid_strategy.py +++ b/core/sfgrid_strategy.py @@ -2,7 +2,7 @@ from peewee import IntegerField from core import strategy_db -from core.eventbus import MarketDataUpdate, event_bus +from core.eventbus import EventTradeTargetUpdate, event_bus from core.strategy_db import OrderTypeBuy, OrderTypeInit, OrderTypeSell, TradeTarget from core.util import queryPendingOrder, is_trading_time @@ -20,9 +20,14 @@ class SFGridStrategy: self.account:StockAccount = account self.enabledTrading(bool(tradeTarget.enabled)) # 修复类型兼容性问题 - event_bus.publish(MarketDataUpdate, self.tradeTarget) + event_bus.publish(EventTradeTargetUpdate, self.tradeTarget) self.dataUpdateLock = threading.Lock() + def updateGridIndex(self, grid_index: int): + """更新网格索引""" + self.tradeTarget.grid_index = grid_index # type: ignore + self.refreshPlanPrice() + self.saveProxy() def enabledTrading(self, enabled: bool) -> TradeTarget: self.tradeTarget.enabled = enabled # type: ignore @@ -216,5 +221,5 @@ class SFGridStrategy: def saveProxy(self): rc = self.tradeTarget.save() - event_bus.publish(MarketDataUpdate, self.tradeTarget) + event_bus.publish(EventTradeTargetUpdate, self.tradeTarget) return rc \ No newline at end of file diff --git a/core/ui.py b/core/ui.py index 4f2812b..82a33a6 100644 --- a/core/ui.py +++ b/core/ui.py @@ -1,3 +1,6 @@ +from typing import Any + + import tkinter as tk from tkinter import ttk, messagebox, filedialog from datetime import datetime @@ -8,11 +11,13 @@ from core.logger import LogData, LogLevel from core.strategy_db import TradeTarget import configparser import sfgrid_constants +from core.objects import GridFixData +from core.util import getInstrumentName class TradeTargetUI: def __init__(self): - self.data:dict[int, TradeTarget] = {} + self.tradeTargetData:dict[int, TradeTarget] = {} self.market_data_enabled = False # 添加市场数据监听状态变量 self.ui_refresh_enabled = False # 添加UI刷新线程状态变量 self.registerEventHandler() @@ -20,16 +25,20 @@ class TradeTargetUI: # 创建刷新线程标志 self.refresh_thread_running = False # 默认不启动刷新线程 + # 市场监控数据 + self.marketData: dict[str, Any] = {} # 存储市场数据 {stock_code: {stock_name, last_price, time}} + self.root = tk.Tk() self.root.title("三疯交易系统") - self.root.geometry("1200x700") + self.root.geometry("1400x700") # 创建界面 self.create_ui() # 不再自动启动刷新线程,由市场数据开关控制 def registerEventHandler(self): - eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onTradeTargetUpdated) + eBus.event_bus.subscribe(eBus.EventTradeTargetUpdate, self.onTradeTargetUpdated) + eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onMarketDataUpdated) eBus.event_bus.subscribe(eBus.ResultEventTradeEnabled, self.onTradeEnabled) eBus.event_bus.subscribe(eBus.ResultEventTradeDisabled, self.onTradeDisabled) eBus.event_bus.subscribe(eBus.MarketDataEnabled, self.onMarketDataToggled) @@ -58,8 +67,8 @@ class TradeTargetUI: def onTradeTargetDeleted(self, id: int): """处理交易标的删除完成事件""" # 从本地数据中删除 - if id in self.data: - del self.data[id] + if id in self.tradeTargetData: + del self.tradeTargetData[id] # 添加日志 self.add_log(LogLevel.INFO, f"交易标的已删除,ID: {id}") @@ -81,8 +90,17 @@ class TradeTargetUI: def onTradeTargetUpdated(self, target: TradeTarget): # 更新或添加数据到本地缓存 - self.data[target.get_id()] = target - # 不再直接刷新表格,由刷新线程统一处理 + self.tradeTargetData[target.get_id()] = target + + + def onMarketDataUpdated(self, target: TradeTarget): + # 更新市场监控数据 + current_time = datetime.now().strftime("%H:%M:%S") + self.marketData[str(target.stock_code)] = { + 'stock_name': target.stock_name, + 'last_price': target.market_price if target.market_price is not None else 0.0, + 'time': current_time + } def create_ui(self): """创建UI界面""" @@ -183,13 +201,24 @@ class TradeTargetUI: def create_tables_area(self, parent): """创建表格区域""" - # 上方交易标的区域 - trade_frame = ttk.LabelFrame(parent, text="交易标的详情", padding=10) - trade_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5)) + # 创建主表格框架(水平排列) + 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) + # 右侧市场监控区域 + market_frame = ttk.LabelFrame(tables_frame, text="市场监控", padding=10) + market_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0)) + + # 创建市场监控表格 + self.create_market_monitor_table(market_frame) + # 下方操作日志区域(默认隐藏) self.log_frame = ttk.LabelFrame(parent, text="操作日志", padding=10) # 默认不显示,通过工具栏按钮控制 @@ -246,6 +275,85 @@ class TradeTargetUI: # 绑定双击事件 self.trade_table.bind("", 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 = { + "时间": (120, "center"), + "股票代码": (90, "center"), + "股票名称": (80, "center"), + "最新价格": (80, "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("", self.on_market_table_double_click) + + # 填充初始数据 + self.populate_market_table() + + def populate_market_table(self): + """填充市场监控表格数据""" + pass + # 清空现有数据 + for item in self.market_table.get_children(): + self.market_table.delete(item) + + # 填充市场数据 + tmp = self.marketData.copy() + for stock_code, data in tmp.items(): + values = [ + data['time'], + stock_code, + data['stock_name'], + f"{data['last_price']:.3f}" + ] + self.market_table.insert('', tk.END, values=values) + + 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'] + stock_code = values[1] + stock_name = values[2] + last_price = 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: + # 发布事件通知主控制器添加标的 + eBus.event_bus.publish(eBus.ActionEventAddTradeTarget, stock_code) + self.add_log(LogLevel.INFO, f"已发送添加请求: {stock_code} - {stock_name}") + def get_status_indicator(self, target: TradeTarget) -> str: """获取状态指示器(带颜色色块的文本)""" if target.status == 1: @@ -266,8 +374,8 @@ class TradeTargetUI: def populate_trade_table(self): """填充交易标的表格数据""" - for temp in self.data: - target: TradeTarget = self.data[temp] + for temp in self.tradeTargetData: + target: TradeTarget = self.tradeTargetData[temp] values = [ target.id, # type: ignore target.stock_code, @@ -275,10 +383,10 @@ class TradeTargetUI: "-" if target.market_price is None else f"{target.market_price:.3f}", target.current_position, target.grid_index, - f"{target.last_trade_price:.3f}", - f"{target.plan_buy_price:.3f}", - f"{target.plan_sell_price:.3f}", - f"{target.current_order_price:.3f}", + '-' 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, target.current_order_type, self.get_status_indicator(target), @@ -348,9 +456,9 @@ class TradeTargetUI: target_id = values[0] # 从列表中找到对应的target对象 - for id in self.data: + for id in self.tradeTargetData: if int(target_id) == id: # type: ignore - return self.data[id] + return self.tradeTargetData[id] return None @@ -513,6 +621,9 @@ class TradeTargetUI: 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 onLog(self, data:LogData): self.add_log(data.level, data.message) @@ -990,8 +1101,9 @@ class TradeTargetUI: if not result: return - # 保存到数据库(这里需要发布事件让控制器处理) - # TODO: 发布保存事件到控制器 + # 发布网格修正事件,传递GridFixData对象 + grid_fix_data = GridFixData(new_grid_index, target) + eBus.event_bus.publish(eBus.ActionEventGridFix, grid_fix_data) # 关闭窗口 window.destroy() @@ -1027,4 +1139,4 @@ class TradeTargetUI: # 更新持仓量状态 current_position = getattr(target, 'current_position') - self.update_position_status(current_position, required_position, position_status_label) + self.update_position_status(current_position, required_position, position_status_label) \ No newline at end of file