市场数据跟踪

This commit is contained in:
2025-11-10 14:43:14 +08:00
parent 7733d6df32
commit 3a137b6aee
4 changed files with 192 additions and 41 deletions
+3
View File
@@ -10,10 +10,13 @@ ActionDisableMarketData = "disable_market_data"
MarketDataEnabled = "market_data_enabled" MarketDataEnabled = "market_data_enabled"
MarketDataDisabled = "market_data_disabled" MarketDataDisabled = "market_data_disabled"
# 删除交易标的事件 # 删除交易标的事件
EventTradeTargetUpdate = "trade_target_update"
ActionEventAddTradeTarget = "add_trade_target" ActionEventAddTradeTarget = "add_trade_target"
ResultEventTradeTargetAdded = "trade_target_added" ResultEventTradeTargetAdded = "trade_target_added"
ActionEventDeleteTradeTarget = "delete_trade_target" ActionEventDeleteTradeTarget = "delete_trade_target"
ResultEventTradeTargetDeleted = "trade_target_deleted" ResultEventTradeTargetDeleted = "trade_target_deleted"
# 网格修正事件
ActionEventGridFix = "grid_fix"
# Pring Log # Pring Log
EventPrintLog = "print_log" EventPrintLog = "print_log"
+48 -17
View File
@@ -1,6 +1,6 @@
# coding:utf-8 # coding:utf-8
from core.strategy_db import TradeTarget 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 from xtquant.xttrader import XtQuantTrader
import time import time
from peewee import ModelSelect from peewee import ModelSelect
@@ -16,6 +16,7 @@ from xtquant.xttrader import XtQuantTraderCallback
import datetime import datetime
import core.ui as ui import core.ui as ui
from core.logger import PrintLog, LogLevel from core.logger import PrintLog, LogLevel
from core.objects import GridFixData
# 量化核心控制对象 # 量化核心控制对象
class SFGridController(XtQuantTraderCallback): class SFGridController(XtQuantTraderCallback):
@@ -49,6 +50,7 @@ class SFGridController(XtQuantTraderCallback):
else: else:
self.inited = False self.inited = False
return return
self.listening_stock = []
self.stock_trade_ctrl = {} self.stock_trade_ctrl = {}
self.init_instrument_pool(self.xt_trader, self.account) # type: ignore 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(ActionDisableMarketData, self.onMarketDataDisabled)
event_bus.subscribe(ActionEventAddTradeTarget, self.onAddTradeTarget) event_bus.subscribe(ActionEventAddTradeTarget, self.onAddTradeTarget)
event_bus.subscribe(ActionEventDeleteTradeTarget, self.onDeleteTradeTarget) event_bus.subscribe(ActionEventDeleteTradeTarget, self.onDeleteTradeTarget)
event_bus.subscribe(ActionEventGridFix, self.onGridFix)
def onDeleteTradeTarget(self, id: int): def onDeleteTradeTarget(self, id: int):
"""处理删除交易标的事件""" """处理删除交易标的事件"""
@@ -88,6 +91,33 @@ class SFGridController(XtQuantTraderCallback):
def onDisableTrade(self, id: int): def onDisableTrade(self, id: int):
self.pause_stock_trade(id) 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): def hold(self):
self.appUi.run() self.appUi.run()
@@ -193,7 +223,7 @@ class SFGridController(XtQuantTraderCallback):
PrintLog(LogLevel.INFO, f' |- 同步[{target.stock_code}-{target.stock_name}]持仓信息[{'成功' if result == 1 else '失败'}]') PrintLog(LogLevel.INFO, f' |- 同步[{target.stock_code}-{target.stock_name}]持仓信息[{'成功' if result == 1 else '失败'}]')
stockTradeController = SFGridStrategy(tradeTarget, self.xt_trader, self.account) # type: ignore stockTradeController = SFGridStrategy(tradeTarget, self.xt_trader, self.account) # type: ignore
self.stock_trade_ctrl[tradeTarget.stock_code] = stockTradeController 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)} 个标的') PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.instrument_pool)} 个标的')
@@ -280,22 +310,23 @@ class SFGridController(XtQuantTraderCallback):
# ====== 市场回调方法 -- 以下方法由XtQuantData调用 ====== # ====== 市场回调方法 -- 以下方法由XtQuantData调用 ======
def onDataUpdate(self, data): def onDataUpdate(self, data):
if sfgrid_constants.max_enabled_targets <= 0: # 全推 # 收集所有市场数据用于市场监控
for stock_code, tickData in data.items(): for stock_code, tickData in data.items():
lastPrice = tickData['lastPrice'] if stock_code in self.stock_trade_ctrl:
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
stock_controller: SFGridStrategy = self.stock_trade_ctrl[stock_code] stock_controller: SFGridStrategy = self.stock_trade_ctrl[stock_code]
stock_controller.onDataUpdate(data) 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调用 ====== # ====== 市场回调方法 -- 以下方法由XtQuantTrader调用 ======
@@ -368,4 +399,4 @@ class SFGridController(XtQuantTraderCallback):
:param response: XtAccountStatus 对象 :param response: XtAccountStatus 对象
:return: :return:
""" """
print(datetime.datetime.now(), status) print(datetime.datetime.now(), status)
+8 -3
View File
@@ -2,7 +2,7 @@ from peewee import IntegerField
from core import strategy_db 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.strategy_db import OrderTypeBuy, OrderTypeInit, OrderTypeSell, TradeTarget
from core.util import queryPendingOrder, is_trading_time from core.util import queryPendingOrder, is_trading_time
@@ -20,9 +20,14 @@ class SFGridStrategy:
self.account:StockAccount = account self.account:StockAccount = account
self.enabledTrading(bool(tradeTarget.enabled)) # 修复类型兼容性问题 self.enabledTrading(bool(tradeTarget.enabled)) # 修复类型兼容性问题
event_bus.publish(MarketDataUpdate, self.tradeTarget) event_bus.publish(EventTradeTargetUpdate, self.tradeTarget)
self.dataUpdateLock = threading.Lock() 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: def enabledTrading(self, enabled: bool) -> TradeTarget:
self.tradeTarget.enabled = enabled # type: ignore self.tradeTarget.enabled = enabled # type: ignore
@@ -216,5 +221,5 @@ class SFGridStrategy:
def saveProxy(self): def saveProxy(self):
rc = self.tradeTarget.save() rc = self.tradeTarget.save()
event_bus.publish(MarketDataUpdate, self.tradeTarget) event_bus.publish(EventTradeTargetUpdate, self.tradeTarget)
return rc return rc
+133 -21
View File
@@ -1,3 +1,6 @@
from typing import Any
import tkinter as tk import tkinter as tk
from tkinter import ttk, messagebox, filedialog from tkinter import ttk, messagebox, filedialog
from datetime import datetime from datetime import datetime
@@ -8,11 +11,13 @@ from core.logger import LogData, LogLevel
from core.strategy_db import TradeTarget from core.strategy_db import TradeTarget
import configparser import configparser
import sfgrid_constants import sfgrid_constants
from core.objects import GridFixData
from core.util import getInstrumentName
class TradeTargetUI: class TradeTargetUI:
def __init__(self): def __init__(self):
self.data:dict[int, TradeTarget] = {} self.tradeTargetData:dict[int, TradeTarget] = {}
self.market_data_enabled = False # 添加市场数据监听状态变量 self.market_data_enabled = False # 添加市场数据监听状态变量
self.ui_refresh_enabled = False # 添加UI刷新线程状态变量 self.ui_refresh_enabled = False # 添加UI刷新线程状态变量
self.registerEventHandler() self.registerEventHandler()
@@ -20,16 +25,20 @@ class TradeTargetUI:
# 创建刷新线程标志 # 创建刷新线程标志
self.refresh_thread_running = False # 默认不启动刷新线程 self.refresh_thread_running = False # 默认不启动刷新线程
# 市场监控数据
self.marketData: dict[str, Any] = {} # 存储市场数据 {stock_code: {stock_name, last_price, time}}
self.root = tk.Tk() self.root = tk.Tk()
self.root.title("三疯交易系统") self.root.title("三疯交易系统")
self.root.geometry("1200x700") self.root.geometry("1400x700")
# 创建界面 # 创建界面
self.create_ui() self.create_ui()
# 不再自动启动刷新线程,由市场数据开关控制 # 不再自动启动刷新线程,由市场数据开关控制
def registerEventHandler(self): 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.ResultEventTradeEnabled, self.onTradeEnabled)
eBus.event_bus.subscribe(eBus.ResultEventTradeDisabled, self.onTradeDisabled) eBus.event_bus.subscribe(eBus.ResultEventTradeDisabled, self.onTradeDisabled)
eBus.event_bus.subscribe(eBus.MarketDataEnabled, self.onMarketDataToggled) eBus.event_bus.subscribe(eBus.MarketDataEnabled, self.onMarketDataToggled)
@@ -58,8 +67,8 @@ class TradeTargetUI:
def onTradeTargetDeleted(self, id: int): def onTradeTargetDeleted(self, id: int):
"""处理交易标的删除完成事件""" """处理交易标的删除完成事件"""
# 从本地数据中删除 # 从本地数据中删除
if id in self.data: if id in self.tradeTargetData:
del self.data[id] del self.tradeTargetData[id]
# 添加日志 # 添加日志
self.add_log(LogLevel.INFO, f"交易标的已删除,ID: {id}") self.add_log(LogLevel.INFO, f"交易标的已删除,ID: {id}")
@@ -81,8 +90,17 @@ class TradeTargetUI:
def onTradeTargetUpdated(self, target: TradeTarget): 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): def create_ui(self):
"""创建UI界面""" """创建UI界面"""
@@ -183,13 +201,24 @@ class TradeTargetUI:
def create_tables_area(self, parent): def create_tables_area(self, parent):
"""创建表格区域""" """创建表格区域"""
# 上方交易标的区域 # 创建主表格框架(水平排列)
trade_frame = ttk.LabelFrame(parent, text="交易标的详情", padding=10) tables_frame = ttk.Frame(parent)
trade_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5)) 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.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) self.log_frame = ttk.LabelFrame(parent, text="操作日志", padding=10)
# 默认不显示,通过工具栏按钮控制 # 默认不显示,通过工具栏按钮控制
@@ -246,6 +275,85 @@ class TradeTargetUI:
# 绑定双击事件 # 绑定双击事件
self.trade_table.bind("<Double-1>", self.on_table_double_click) 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 = {
"时间": (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("<Double-1>", 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: def get_status_indicator(self, target: TradeTarget) -> str:
"""获取状态指示器(带颜色色块的文本)""" """获取状态指示器(带颜色色块的文本)"""
if target.status == 1: if target.status == 1:
@@ -266,8 +374,8 @@ class TradeTargetUI:
def populate_trade_table(self): def populate_trade_table(self):
"""填充交易标的表格数据""" """填充交易标的表格数据"""
for temp in self.data: for temp in self.tradeTargetData:
target: TradeTarget = self.data[temp] target: TradeTarget = self.tradeTargetData[temp]
values = [ values = [
target.id, # type: ignore target.id, # type: ignore
target.stock_code, target.stock_code,
@@ -275,10 +383,10 @@ class TradeTargetUI:
"-" if target.market_price is None else f"{target.market_price:.3f}", "-" if target.market_price is None else f"{target.market_price:.3f}",
target.current_position, target.current_position,
target.grid_index, target.grid_index,
f"{target.last_trade_price:.3f}", '-' if target.last_trade_price is None else f"{target.last_trade_price:.3f}",
f"{target.plan_buy_price:.3f}", '-' if target.plan_buy_price is None else f"{target.plan_buy_price:.3f}",
f"{target.plan_sell_price:.3f}", '-' if target.plan_sell_price is None else f"{target.plan_sell_price:.3f}",
f"{target.current_order_price:.3f}", '-' if target.current_order_price is None else f"{target.current_order_price:.3f}",
target.current_order_no, target.current_order_no,
target.current_order_type, target.current_order_type,
self.get_status_indicator(target), self.get_status_indicator(target),
@@ -348,9 +456,9 @@ class TradeTargetUI:
target_id = values[0] target_id = values[0]
# 从列表中找到对应的target对象 # 从列表中找到对应的target对象
for id in self.data: for id in self.tradeTargetData:
if int(target_id) == id: # type: ignore if int(target_id) == id: # type: ignore
return self.data[id] return self.tradeTargetData[id]
return None return None
@@ -513,6 +621,9 @@ class TradeTargetUI:
values = self.trade_table.item(item)['values'] values = self.trade_table.item(item)['values']
if values and values[0] in selected_values: if values and values[0] in selected_values:
self.trade_table.selection_add(item) self.trade_table.selection_add(item)
# 刷新市场监控表格
self.populate_market_table()
def onLog(self, data:LogData): def onLog(self, data:LogData):
self.add_log(data.level, data.message) self.add_log(data.level, data.message)
@@ -990,8 +1101,9 @@ class TradeTargetUI:
if not result: if not result:
return return
# 保存到数据库(这里需要发布事件让控制器处理) # 发布网格修正事件,传递GridFixData对象
# TODO: 发布保存事件到控制器 grid_fix_data = GridFixData(new_grid_index, target)
eBus.event_bus.publish(eBus.ActionEventGridFix, grid_fix_data)
# 关闭窗口 # 关闭窗口
window.destroy() window.destroy()
@@ -1027,4 +1139,4 @@ class TradeTargetUI:
# 更新持仓量状态 # 更新持仓量状态
current_position = getattr(target, 'current_position') 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)