This commit is contained in:
2025-11-13 17:32:10 +08:00
parent c03a4adb53
commit 550126d060
4 changed files with 327 additions and 302 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
[config] [config]
miniqmtpath = D:/Programs/DTQMT_MN/userdata_mini miniqmtpath = D:/Programs/DTQMT/userdata_mini
; account_no = 99082560 account_no = 99082560
account_no = 89009170 ; account_no = 89009170
+2 -1
View File
@@ -165,7 +165,8 @@ class QmtV(XtQuantTraderCallback):
:param order: XtOrder对象 :param order: XtOrder对象
:return: :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 # stockCode = order.stock_code
# ctrl:SFGridStrategy = self.stock_trade_ctrl[stockCode] # ctrl:SFGridStrategy = self.stock_trade_ctrl[stockCode]
# # 如果存在对应的StockTradeController,则调用其onDataUpdate方法 # # 如果存在对应的StockTradeController,则调用其onDataUpdate方法
+44 -42
View File
@@ -16,27 +16,29 @@ class SFGridStrategy:
def __init__(self, tradeTarget: model.SFGridTradeTarget): def __init__(self, tradeTarget: model.SFGridTradeTarget):
self.tradeTarget:model.SFGridTradeTarget = tradeTarget self.tradeTarget:model.SFGridTradeTarget = tradeTarget
self.enabledTrading(bool(tradeTarget.enabled)) # 修复类型兼容性问题 self.enabledTrading(tradeTarget.enabled) # type: ignore
event_bus.publish(EventTradeTargetUpdate, self.tradeTarget)
self.dataUpdateLock = threading.Lock() self.dataUpdateLock = threading.Lock()
def updateGridIndex(self, grid_index: int): def updateTradeTarget(self, inTradeTarget:model.SFGridTradeTarget):
"""更新网格索引""" print(f'|- 标的{self.tradeTarget.targetName()}信息更新: START')
self.tradeTarget.grid_index = grid_index # type: ignore self.dataUpdateLock.acquire()
self.refreshPlanPrice() print(f'|- 标的{self.tradeTarget.targetName()}信息更新: LOCKED')
self.saveProxy() 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: def enabledTrading(self, enabled: bool) -> model.SFGridTradeTarget:
self.tradeTarget.enabled = enabled # type: ignore self.tradeTarget.enabled = enabled # type: ignore
self.saveProxy()
if enabled: if enabled:
self.refreshPlanPrice()
print(f" |- 标的{self.tradeTarget.targetName()}交易启动, 持仓量:{self.tradeTarget.current_position}") print(f" |- 标的{self.tradeTarget.targetName()}交易启动, 持仓量:{self.tradeTarget.current_position}")
if self.tradeTarget.status == 0: # 未建仓 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.tradeTarget.grid_index = 1 # pyright: ignore[reportAttributeAccessIssue]
self.refreshPlanPrice()
else: # 已建仓 else: # 已建仓
# 交易阶段,检查仓位,检查现有订单 # 交易阶段,检查仓位,检查现有订单
print(f" |- 标的{self.tradeTarget.targetName()}已有仓位或非初始状态 无需建初始仓 当前仓位: {self.tradeTarget.current_position} 状态: {self.tradeTarget.status}") print(f" |- 标的{self.tradeTarget.targetName()}已有仓位或非初始状态 无需建初始仓 当前仓位: {self.tradeTarget.current_position} 状态: {self.tradeTarget.status}")
@@ -46,9 +48,10 @@ class SFGridStrategy:
else: else:
print(f' |- 仓位检查: 持仓需求不足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}, 交易启动失败') print(f' |- 仓位检查: 持仓需求不足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}, 交易启动失败')
self.tradeTarget.enabled = False # type: ignore self.tradeTarget.enabled = False # type: ignore
self.saveProxy()
else: else:
print(f" |- 标的{self.tradeTarget.targetName()}交易监控暂停") print(f" |- 标的{self.tradeTarget.targetName()}交易监控暂停")
self.saveProxy()
return self.tradeTarget return self.tradeTarget
def isEnabled(self) -> bool: 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] 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] orderPrice = inTradeTarget.getPriceGrid()[index]
orderType = xtconstant.STOCK_BUY orderType = xtconstant.STOCK_BUY
orderRemark = OrderTypeInit orderRemark = OrderTypeInit
@@ -104,7 +107,7 @@ class SFGridStrategy:
print(f' |- 下网格{"多单" if orderType == xtconstant.STOCK_BUY else "空单"}') print(f' |- 下网格{"多单" if orderType == xtconstant.STOCK_BUY else "空单"}')
self.tradeTarget.current_order_no = qmtv.orderAsync( self.tradeTarget.current_order_no = qmtv.orderAsync(
str(self.tradeTarget.stock_code), str(self.tradeTarget.stock_code),
config.grid_volume, self.tradeTarget.grid_volume,
orderType, orderType,
orderPrice, orderPrice,
xtconstant.FIX_PRICE, xtconstant.FIX_PRICE,
@@ -118,7 +121,7 @@ class SFGridStrategy:
orderTypeName = "空单" orderTypeName = "空单"
elif orderRemark == OrderTypeInit: elif orderRemark == OrderTypeInit:
orderTypeName = "建仓单" 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: finally:
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - release lock') print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - release lock')
self.saveProxy() self.saveProxy()
@@ -126,21 +129,21 @@ class SFGridStrategy:
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - END') print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - END')
def onAsyncOrderResponse(self, order:XtOrder): # def onAsyncOrderResponse(self, order:XtOrder):
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:START') # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:START')
self.dataUpdateLock.acquire() # self.dataUpdateLock.acquire()
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:LOCKED') # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:LOCKED')
try: # try:
if order.strategy_name == self.getName(): # if order.strategy_name == self.getName():
self.tradeTarget.current_order_no = order.order_id # self.tradeTarget.current_order_no = order.order_id
self.tradeTarget.current_order_type = order.order_remark # self.tradeTarget.current_order_type = order.order_remark
self.saveProxy() # self.saveProxy()
else: # else:
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]: 不在策略监控范围内{order.strategy_name}') # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]: 不在策略监控范围内{order.strategy_name}')
finally: # finally:
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:release lock') # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:release lock')
self.dataUpdateLock.release() # self.dataUpdateLock.release()
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:END') # print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:END')
def onOrderTrade(self, trade:XtTrade): def onOrderTrade(self, trade:XtTrade):
print(f' |- 委托成交通知[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{trade.order_id}]:START, {trade.order_id}') 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') print(f' |- 委托成交通知[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{trade.order_id}]:END')
def refreshPlanPrice(self): def refreshPlanPrice(self):
if self.tradeTarget.status == 1: if self.tradeTarget.status == 0:
buyIdx: int = self.tradeTarget.grid_index + 1 # pyright: ignore[reportAssignmentType] self.tradeTarget.grid_index = 1 # type: ignore
sellIdx: int = self.tradeTarget.grid_index - 1
if self.tradeTarget.grid_index > 0: # 可以下空单 buyIdx: int = self.tradeTarget.grid_index + 1 # pyright: ignore[reportAssignmentType]
self.tradeTarget.plan_sell_price = float(self.tradeTarget.getPriceGrid()[sellIdx]) # pyright: ignore[reportAttributeAccessIssue] sellIdx: int = self.tradeTarget.grid_index - 1
else: if self.tradeTarget.grid_index > 0: # 可以下空单
self.tradeTarget.plan_sell_price = -1.0 # type: ignore self.tradeTarget.plan_sell_price = float(self.tradeTarget.getPriceGrid()[sellIdx]) # pyright: ignore[reportAttributeAccessIssue]
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]
else: else:
self.tradeTarget.plan_buy_price = 10.0 # type: ignore
self.tradeTarget.plan_sell_price = -1.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() self.saveProxy()
def getName(self): def getName(self):
+274 -252
View File
@@ -8,6 +8,7 @@ import time
from core import constants from core import constants
import core.eventbus as eBus import core.eventbus as eBus
from core.logger import LogLevel, PrintLog from core.logger import LogLevel, PrintLog
from core.sfgrid import bus_events
from core.sfgrid.model import SFGridTradeTarget from core.sfgrid.model import SFGridTradeTarget
import configparser import configparser
import config import config
@@ -25,6 +26,7 @@ class TradeTargetUI(ttk.Frame):
self.listening_stock = [] self.listening_stock = []
self.init_trade_target_pool() self.init_trade_target_pool()
eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onMarketDataUpdated) 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.marketData: dict[str, Any] = {} # 存储市场数据 {stock_code: {stock_name, last_price, time}}
@@ -35,24 +37,45 @@ class TradeTargetUI(ttk.Frame):
# 创建界面 # 创建界面
self.create_ui() 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): def onMarketDataUpdated(self, data):
# 收集所有市场数据用于市场监控
for stock_code, tickData in data.items(): for stock_code, tickData in data.items():
if stock_code in self.stockCodeIdMap: if stock_code in self.stockCodeIdMap:
id:int = self.stockCodeIdMap[stock_code] id:int = self.stockCodeIdMap[stock_code]
PrintLog(LogLevel.INFO, f'股票代码: {stock_code} in trade pool, 市场数据更新 {tickData["lastPrice"]}')
tradeTarget = self.tradeTargetData[id] tradeTarget = self.tradeTargetData[id]
lastPrice = float("{:.3f}".format(tickData['lastPrice'])) lastPrice = float("{:.3f}".format(tickData['lastPrice']))
PrintLog(LogLevel.INFO, f'股票代码: {stock_code} {id}, 市场数据更新 {lastPrice}')
tradeTarget.market_price = lastPrice # type: ignore tradeTarget.market_price = lastPrice # type: ignore
self.updateTradeTarget(tradeTarget) self.updateTradeTarget(tradeTarget)
stock_controller: SFGridStrategy = self.strategy_ctrl[id] stock_controller = self.strategy_ctrl[id]
stock_controller.onDataUpdate(tradeTarget) stock_controller.onDataUpdate(tradeTarget)
else: else:
# 非目标交易,发布市场数据更新事件用于市场监控 # 非目标交易,发布市场数据更新事件用于市场监控
lastPrice = tickData['lastPrice'] lastPrice = tickData['lastPrice']
if lastPrice == 10 or stock_code in self.listening_stock: if lastPrice == 10 or stock_code in self.listening_stock:
PrintLog(LogLevel.INFO, f'股票代码: {stock_code} 监听中, 市场数据更新 {tickData["lastPrice"]}')
# 发布市场数据更新事件用于市场监控 # 发布市场数据更新事件用于市场监控
market_target = SFGridTradeTarget() market_target = SFGridTradeTarget()
market_target.stock_code = stock_code market_target.stock_code = stock_code
@@ -69,40 +92,25 @@ class TradeTargetUI(ttk.Frame):
} }
def init_trade_target_pool(self): # 来自策略的数据更新
results = SFGridTradeTarget.select() def onStrategyUpdate(self, target: SFGridTradeTarget):
for temp in results: PrintLog(LogLevel.INFO, f'策略更新: {target.stock_code}-{target.stock_name}')
tradeTarget :SFGridTradeTarget = temp self.updateTradeTarget(target)
id = tradeTarget.get_id()
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): 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.tradeTargetData[id] = target
self.stockCodeIdMap[target.stock_code] = target.get_id() # type: ignore
def start_refresh_thread(self): if id not in self.strategy_ctrl:
"""启动刷新线程""" self.stockCodeIdMap[target.stock_code] = id # type: ignore
self.refresh_thread = threading.Thread(target=self.refresh_loop, daemon=True) self.strategy_ctrl[id] = SFGridStrategy(target) # pyright: ignore[reportArgumentType]
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): def create_ui(self):
"""创建UI界面""" """创建UI界面"""
# 主框架(使用self作为父容器) # 主框架(使用self作为父容器)
@@ -114,30 +122,36 @@ class TradeTargetUI(ttk.Frame):
toolbar_frame.pack(fill=tk.X, pady=(0, 10)) 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=" 添加标的", 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="🗑 删除标的", 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="🛠 交易设置", 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="🛠 网格修正", 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="▣ 实时监控", 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.create_tables_area(main_frame)
def grid_correct(self): # 启动刷新线程
self.refresh_thread = threading.Thread(target=self.refresh_loop, daemon=True)
self.refresh_thread.start()
PrintLog(LogLevel.INFO, "UI刷新线程已启动")
target = self.get_selected_target()
if not target: def refresh_loop(self):
return """刷新循环"""
self.create_grid_correction_window(target) 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): def create_tables_area(self, parent):
@@ -161,18 +175,6 @@ class TradeTargetUI(ttk.Frame):
self.create_market_monitor_table(self.market_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): def create_trade_target_table(self, parent):
"""创建交易标的表格""" """创建交易标的表格"""
@@ -359,8 +361,6 @@ class TradeTargetUI(ttk.Frame):
self.get_trade_enabled_indicator(target.enabled) # type: ignore self.get_trade_enabled_indicator(target.enabled) # type: ignore
] ]
self.trade_table.insert('', tk.END, values=values) self.trade_table.insert('', tk.END, values=values)
@@ -399,155 +399,6 @@ class TradeTargetUI(ttk.Frame):
return None 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('<Return>', lambda event: confirm_add())
PrintLog(LogLevel.INFO, "点击添加交易标的按钮")
def refresh_table(self): def refresh_table(self):
"""刷新表格数据""" """刷新表格数据"""
# 保存当前选中的项 # 保存当前选中的项
@@ -575,20 +426,6 @@ class TradeTargetUI(ttk.Frame):
# 刷新市场监控表格 # 刷新市场监控表格
self.populate_market_table() 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): 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=save_config).pack(side=tk.RIGHT, padx=5)
ttk.Button(button_frame, text="取消", command=config_window.destroy).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): def create_grid_correction_window(self, target: SFGridTradeTarget):
"""创建网格修正窗口""" """创建网格修正窗口"""
# 获取顶层窗口 # 获取顶层窗口
@@ -1043,23 +897,6 @@ class TradeTargetUI(ttk.Frame):
PrintLog(LogLevel.INFO, f"网格修正已保存: {target.stock_code} - {target.stock_name}, 网格序号: {new_grid_index}") 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): 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) 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: try:
stock_name = qmtv.getInstrumentName(stock_code) stock_name = qmtv.getInstrumentName(stock_code)
@@ -1108,16 +946,200 @@ class TradeTargetUI(ttk.Frame):
except Exception as e: except Exception as e:
PrintLog(LogLevel.ERROR, f'新增交易标的失败 {stock_code} {e}') PrintLog(LogLevel.ERROR, f'新增交易标的失败 {stock_code} {e}')
def update_trade_target_grid(self, data: GridFixData): # def update_trade_target_grid(self, data: GridFixData):
"""更新交易标的网格信息""" # """更新交易标的网格信息"""
try: # try:
target = data.tradeTarget # target = data.tradeTarget
grid_index = data.grid_index # grid_index = data.grid_index
# 更新数据库中的网格索引 # # 更新数据库中的网格索引
target.grid_index = grid_index # target.grid_index = grid_index
target.save() # target.save()
PrintLog(LogLevel.INFO, f"网格修正已应用: {target.stock_code} - {target.stock_name}, 网格索引: {grid_index}") # PrintLog(LogLevel.INFO, f"网格修正已应用: {target.stock_code} - {target.stock_name}, 网格索引: {grid_index}")
except Exception as e: # except Exception as e:
PrintLog(LogLevel.ERROR, f"网格修正更新失败: {str(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, "点击添加交易标的按钮")