市场数据跟踪

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"
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"
+48 -17
View File
@@ -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)
+8 -3
View File
@@ -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
+133 -21
View File
@@ -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("<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:
"""获取状态指示器(带颜色色块的文本)"""
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)