release
This commit is contained in:
+15
-1
@@ -11,13 +11,27 @@ EventPrintLog = "print_log" # 打印日志
|
||||
class EventBus:
|
||||
def __init__(self):
|
||||
self.listeners = {} # 管理各种event的订阅情况
|
||||
self.last_events = {} # 存储每个事件的最后一次值,用于"重播"给新订阅者
|
||||
|
||||
def subscribe(self, event_type, listener):
|
||||
def subscribe(self, event_type, listener, replay=True):
|
||||
"""订阅事件
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
listener: 回调函数
|
||||
replay: 是否自动重播最近一次事件状态(默认True)
|
||||
"""
|
||||
if event_type not in self.listeners:
|
||||
self.listeners[event_type] = []
|
||||
self.listeners[event_type].append(listener)
|
||||
|
||||
# 新订阅者自动收到最近一次事件状态(如果存在)
|
||||
if replay and event_type in self.last_events:
|
||||
listener(self.last_events[event_type])
|
||||
|
||||
def publish(self, event_type, data):
|
||||
# 存储最后一次事件值
|
||||
self.last_events[event_type] = data
|
||||
if event_type in self.listeners:
|
||||
for listener in self.listeners[event_type]:
|
||||
listener(data)
|
||||
|
||||
+15
-8
@@ -545,11 +545,19 @@ class RealQmtV:
|
||||
PrintLog(LogLevel.ERROR, f'- [市场数据订阅失败-{e}]')
|
||||
|
||||
def _on_market_data(self, datas: dict):
|
||||
"""xtquant 行情回调 — 收到行情即标记市场活跃"""
|
||||
"""xtquant 行情回调 — 收到行情即标记市场活跃(但需满足 09:15 后才激活)"""
|
||||
self.lastMarketDataUpdateTimestamp = time.time()
|
||||
if not self.isMarketActive:
|
||||
self.isMarketActive = True
|
||||
eBus.event_bus.publish(eBus.EventMarketActiveSwitch, True)
|
||||
# 检查当前时间是否已过 09:15(集合竞价结束后才激活市场状态)
|
||||
import zoneinfo
|
||||
beijing_tz = zoneinfo.ZoneInfo("Asia/Shanghai")
|
||||
now = datetime.datetime.now(beijing_tz)
|
||||
t = now.time()
|
||||
activation_time = datetime.time(9, 15)
|
||||
if t >= activation_time:
|
||||
self.isMarketActive = True
|
||||
eBus.event_bus.publish(eBus.EventMarketActiveSwitch, True)
|
||||
PrintLog(LogLevel.INFO, f'- [行情] 市场激活 (时间 {t.strftime("%H:%M:%S")} >= 09:15)')
|
||||
eBus.event_bus.publish(eBus.MarketDataUpdate, datas)
|
||||
|
||||
def _is_trading_time(self) -> bool:
|
||||
@@ -567,17 +575,16 @@ class RealQmtV:
|
||||
return (morning_start <= t <= morning_end) or (afternoon_start <= t <= afternoon_end)
|
||||
|
||||
def _market_data_watchdog(self):
|
||||
"""行情活跃监控 — 超过 120 秒无行情 且 非交易时间 则标记市场不活跃"""
|
||||
"""行情活跃监控 — 超过 120 秒无行情 则标记市场不活跃(无论是否交易时间)"""
|
||||
while True:
|
||||
time.sleep(15)
|
||||
if self.isMarketActive:
|
||||
elapsed = time.time() - self.lastMarketDataUpdateTimestamp
|
||||
# 非交易时间直接标记休市;交易时间内才用超时判断
|
||||
if not self._is_trading_time() or elapsed > 120:
|
||||
# 只有超过 120 秒无行情才标记不活跃,不再区分交易时间
|
||||
if elapsed > 120:
|
||||
self.isMarketActive = False
|
||||
eBus.event_bus.publish(eBus.EventMarketActiveSwitch, False)
|
||||
if not self._is_trading_time():
|
||||
PrintLog(LogLevel.INFO, '- [行情] 非交易时间,市场标记为不活跃')
|
||||
PrintLog(LogLevel.INFO, f'- [行情] 超过 {elapsed:.0f} 秒无数据,市场标记为不活跃')
|
||||
|
||||
def stopMarketDataSubscription(self):
|
||||
"""停止市场数据订阅"""
|
||||
|
||||
@@ -54,6 +54,11 @@ class SFGridStrategy:
|
||||
"""
|
||||
self.tradeTarget: model.SFGridTradeTarget = tradeTarget
|
||||
|
||||
# 数据更新锁:保护 orderGrid 和 tradeTarget 的并发访问
|
||||
# QMT 回调在独立线程中触发,必须在可能触发回调的操作之前创建
|
||||
# 注意:这个锁必须在订阅事件之前创建,防止事件在初始化期间触发
|
||||
self.dataUpdateLock = threading.Lock()
|
||||
|
||||
# 订阅事件总线:监听订单创建、成交、失败三种事件
|
||||
event_bus.subscribe(eBus.MarketOrderCreated, self.onOrderCreateAsync)
|
||||
event_bus.subscribe(eBus.MarketOrderTraded, self.onOrderTrade)
|
||||
@@ -75,10 +80,6 @@ class SFGridStrategy:
|
||||
# 加载券商侧已存在的未成交订单,恢复到 orderGrid 中
|
||||
self.loadExistOrders()
|
||||
|
||||
# 数据更新锁:保护 orderGrid 和 tradeTarget 的并发访问
|
||||
# QMT 回调在独立线程中触发,必须在可能触发回调的操作之前创建
|
||||
self.dataUpdateLock = threading.Lock()
|
||||
|
||||
# 根据数据库中的 enabled 字段决定是否启动交易
|
||||
self.enabledTrading(tradeTarget.enabled) # type: ignore
|
||||
|
||||
@@ -122,6 +123,9 @@ class SFGridStrategy:
|
||||
|
||||
当市场数据从不可用变为可用时,如果策略已启用则刷新网格订单。
|
||||
"""
|
||||
PrintLog(LogLevel.INFO,
|
||||
f'|- [市场状态切换] 标的{self.tradeTarget.targetName()} '
|
||||
f'isActive={isActive}, enabled={self.tradeTarget.enabled}')
|
||||
if isActive and self.tradeTarget.enabled:
|
||||
self.refreshGridOrder()
|
||||
|
||||
@@ -300,7 +304,12 @@ class SFGridStrategy:
|
||||
self.tradeTarget.enabled = False # type: ignore
|
||||
|
||||
# 刷新网格订单(空仓只挂买单,有仓买卖对冲)
|
||||
self.refreshGridOrder()
|
||||
# 只有市场活跃时才下单,收盘后不再尝试下单
|
||||
if qmtv.isMarketActive:
|
||||
self.refreshGridOrder()
|
||||
else:
|
||||
PrintLog(LogLevel.INFO,
|
||||
f' |- 市场已休市,跳过刷新网格订单')
|
||||
|
||||
else:
|
||||
# ── 停用交易: 取消所有未成交订单 ──
|
||||
@@ -465,7 +474,12 @@ class SFGridStrategy:
|
||||
PrintLog(LogLevel.INFO,
|
||||
f' 手续费 : {trade.commission:.3f}')
|
||||
# 4. 刷新网格订单:在新的 grid_index 位置重新挂买卖单
|
||||
self.refreshGridOrder()
|
||||
# 只有市场活跃时才下单,收盘后不再尝试下单
|
||||
if qmtv.isMarketActive:
|
||||
self.refreshGridOrder()
|
||||
else:
|
||||
PrintLog(LogLevel.INFO,
|
||||
f'|- 成交后市场已休市,跳过刷新网格订单')
|
||||
|
||||
finally:
|
||||
self.dataUpdateLock.release()
|
||||
|
||||
@@ -1279,8 +1279,11 @@ class QmtApp:
|
||||
self._refresh_ui()
|
||||
|
||||
def _on_market_active(self, is_active: bool):
|
||||
from core.logger import PrintLog, LogLevel
|
||||
PrintLog(LogLevel.INFO, f'[UI] 收到市场状态切换事件 is_active={is_active}')
|
||||
self._update_market_status(is_active)
|
||||
self.page.update()
|
||||
PrintLog(LogLevel.INFO, f'[UI] 市场状态已更新,_market_label={self._market_label.value}')
|
||||
|
||||
def _update_market_status(self, active: bool):
|
||||
"""更新顶栏市场状态指示灯"""
|
||||
|
||||
Reference in New Issue
Block a user