添加编译文件、打包瘦身、UI逻辑调整

This commit is contained in:
2025-11-05 18:04:52 +08:00
parent c77ff1c0ae
commit df0e9ecb22
9 changed files with 434 additions and 199 deletions
+3 -5
View File
@@ -1,9 +1,7 @@
[config]
; miniqmtpath = /Users/gao/Workspace/quant
miniQMTPath=D:\\Programs\\DTQMT\\userdata_mini
; grid_price = 10.9,10.0,9.1,8.2,7.3,6.4,5.5,4.6,3.7,2.8,1.9,1.0
grid_price=1.665,1.660,1.655,1.650,1.645,1.640,1.635,1.630,1.625,1.620,1.615,1.610
grid_volume = 200
miniqmtpath = D:/Programs/DTQMT/userdata_mini
grid_price = 11.0,10.0,9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0
grid_volume = 100
account_no = 99082560
max_enabled_targets = 10
+37
View File
@@ -0,0 +1,37 @@
# 定义事件处理函数
MarketDataUpdate = "market_data_update"
ActionEventEnableTrade = "enable_trade"
ResultEventTradeEnabled = "trade_enabled"
ActionEventDisableTrade = "disable_trade"
ResultEventTradeDisabled = "trade_disabled"
# 市场数据监听控制事件
ActionEnableMarketData = "enable_market_data"
ActionDisableMarketData = "disable_market_data"
MarketDataEnabled = "market_data_enabled"
MarketDataDisabled = "market_data_disabled"
class EventBus:
def __init__(self):
self.listeners = {} # 管理各种event的订阅情况
def subscribe(self, event_type, listener):
if event_type not in self.listeners:
self.listeners[event_type] = []
self.listeners[event_type].append(listener)
def publish(self, event_type, data):
if event_type in self.listeners:
for listener in self.listeners[event_type]:
listener(data)
# # 订阅事件
# event_bus.subscribe('my_event', handle_event)
# # 发布事件
# event_bus.publish('my_event', {'key': 'value'})
# 创建事件总线实例
event_bus = EventBus()
+82 -38
View File
@@ -1,10 +1,12 @@
# coding:utf-8
from core.strategy_db import TradeTarget
from typing import Any
from core.eventbus import ActionDisableMarketData, ActionEnableMarketData, ActionEventDisableTrade, ActionEventEnableTrade, MarketDataUpdate, MarketDataEnabled, MarketDataDisabled, ResultEventTradeDisabled, ResultEventTradeEnabled, event_bus
from xtquant.xttrader import XtQuantTrader
import time, sys
import time
from peewee import ModelSelect
import xtquant.xtconstant as xtconstant
sys.stdout.reconfigure(encoding='utf-8') # 设置标准输出编码为UTF-8 # type: ignore
import core.strategy_db as strategy_db
import sfgrid_constants
from core.sfgrid_strategy import SFGridStrategy
@@ -14,12 +16,16 @@ from xtquant.xttype import StockAccount, XtAsset, XtOrder, XtOrderResponse, XtPo
from xtquant import xtdata
from xtquant.xttrader import XtQuantTraderCallback
import datetime
import core.ui as ui
# 量化核心控制对象
class SFGridController(XtQuantTraderCallback):
def __init__(self, account_no: str, miniQmtPath: str):
super().__init__()
self.registerEventHandler()
self.appUi = ui.TradeTargetUI()
xtdata.enable_hello = False
session_id = int(time.time())
@@ -49,8 +55,35 @@ class SFGridController(XtQuantTraderCallback):
self.seq = None
print('- [成功]三疯交易系统初始化完成')
# self.startMarketData()
def registerEventHandler(self):
event_bus.subscribe(ActionEventEnableTrade, self.onEnableTrade)
event_bus.subscribe(ActionEventDisableTrade, self.onDisableTrade)
event_bus.subscribe(ActionEnableMarketData, self.onMarketDataEnabled)
event_bus.subscribe(ActionDisableMarketData, self.onMarketDataDisabled)
event_bus.subscribe("add_trade_target", self.onAddTradeTarget)
def onAddTradeTarget(self, stock_code: str):
"""处理添加交易标的事件"""
self.add_trade_target(stock_code)
def onMarketDataEnabled(self, data):
"""处理市场数据监听启用事件"""
self.startMarketData()
def onMarketDataDisabled(self, data):
"""处理市场数据监听禁用事件"""
self.stopMarketData()
def onEnableTrade(self, id: int):
self.start_stock_trade(id)
def onDisableTrade(self, id: int):
self.pause_stock_trade(id)
def hold(self):
self.appUi.run()
def startMarketData(self):
print('- 启动市场数据订阅')
@@ -58,6 +91,7 @@ class SFGridController(XtQuantTraderCallback):
if self.seq == -1:
print('- 市场数据订阅失败')
else:
event_bus.publish(MarketDataEnabled, True)
print(f'- 市场数据订阅成功, 订阅号={self.seq}')
@@ -65,11 +99,22 @@ class SFGridController(XtQuantTraderCallback):
print('- 停止市场数据订阅')
if self.seq is not None and self.seq > 0:
xtdata.unsubscribe_quote(self.seq)
event_bus.publish(MarketDataDisabled, False)
def add_trade_target(self, stock_code: str):
try:
stock_name = getInstrumentName(stock_code)
if not stock_name:
print(f'无法获取股票代码 {stock_code} 的名称,请检查代码是否正确')
return
# 检查是否已存在该标的
existing_target = strategy_db.TradeTarget.get_or_none(strategy_db.TradeTarget.stock_code == stock_code)
if existing_target:
print(f'交易标的 {stock_code} {stock_name} 已存在')
return
new_target = strategy_db.TradeTarget.create(
stock_name=stock_name,
stock_code=stock_code,
@@ -96,8 +141,8 @@ class SFGridController(XtQuantTraderCallback):
print(f'新增交易标的失败 {stock_code} {e}')
def del_trade_target(self, index:int):
target: strategy_db.TradeTarget = self.instrument_pool[index]
def del_trade_target(self, id:int):
target: strategy_db.TradeTarget = self.instrument_pool[id]
# self.stock_trade_ctrl.
del self.stock_trade_ctrl[target.stock_code]
target.delete_instance()
@@ -107,28 +152,33 @@ class SFGridController(XtQuantTraderCallback):
def init_instrument_pool(self, xtTrader:XtQuantTrader, account:StockAccount):
self.refresh_targets()
for temp in self.instrument_pool:
tradeTarget:strategy_db.TradeTarget = temp
for id in self.instrument_pool:
tradeTarget:strategy_db.TradeTarget = self.instrument_pool[id]
tradeTarget.current_position = getStockPosition(tradeTarget.stock_code, xtTrader, account) # type: ignore
result = tradeTarget.save()
print(f' |- 同步当前持仓信息 {tradeTarget.stock_code}, {tradeTarget.current_position}, result = {result}')
stockTradeController = SFGridStrategy(tradeTarget, self.xt_trader, self.account, tradeTarget.enabled) # type: ignore
stockTradeController = SFGridStrategy(tradeTarget, self.xt_trader, self.account) # type: ignore
self.stock_trade_ctrl[tradeTarget.stock_code] = stockTradeController
event_bus.publish(MarketDataUpdate, tradeTarget)
print(f'- [成功]交易标的信息初始化, 共 {len(self.instrument_pool)} 个标的')
def refresh_targets(self):
# 更新标的池
self.instrument_pool:ModelSelect = strategy_db.TradeTarget.select()
results:ModelSelect = strategy_db.TradeTarget.select()
self.instrument_pool: dict[int, strategy_db.TradeTarget] = {}
for temp in results:
result :strategy_db.TradeTarget = temp
self.instrument_pool[result.get_id()] = result
self.print_pool()
def print_pool(self):
print("- [信息]标的池信息")
for i in range(len(self.instrument_pool)):
target: strategy_db.TradeTarget = self.instrument_pool[i]
for id in self.instrument_pool:
target: strategy_db.TradeTarget = self.instrument_pool[id]
status = "新建" if target.status == 0 else "已建初始仓"
print(f' [序号-{i}] 股票代码: {target.stock_code}-{target.stock_name} 当前持仓: {getStockPosition(target.stock_code, self.xt_trader, self.account)} 网格索引: {target.grid_index} 基准价格 {sfgrid_constants.grid_price[target.grid_index]} 状态: {status} 启用交易线程: {'自动交易中' if target.enabled else '交易已停止'}') # type: ignore
print(f' [序号-{id}] 股票代码: {target.stock_code}-{target.stock_name} 当前持仓: {getStockPosition(target.stock_code, self.xt_trader, self.account)} 网格索引: {target.grid_index} 基准价格 {sfgrid_constants.grid_price[target.grid_index]} 状态: {status} 启用交易线程: {'自动交易中' if target.enabled else '交易已停止'}') # type: ignore
def print_position_info(self):
positions:list[XtPosition] = self.xt_trader.query_stock_positions(self.account)
@@ -173,34 +223,29 @@ class SFGridController(XtQuantTraderCallback):
# 初始化指定标的交易控制器
def start_stock_trade(self, index: int):
tradeTarget = self.instrument_pool[index]
def start_stock_trade(self, id: int):
tradeTarget: TradeTarget = self.instrument_pool[id]
# check existing thread
if tradeTarget.stock_code in self.stock_trade_ctrl:
tradeController: SFGridStrategy = self.stock_trade_ctrl[tradeTarget.stock_code]
if tradeController.isEnabled():
print(f"标的交易控制器已存在且正在运行 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}\n")
else:
print(f"标的交易控制器已存在但未运行,重新启动 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}\n")
tradeController.enabledTrading(True)
tradeTarget = tradeController.enabledTrading(True)
self.instrument_pool[id] = tradeTarget
event_bus.publish(ResultEventTradeEnabled, tradeTarget)
else:
stockTradeController = SFGridStrategy(tradeTarget, self.xt_trader, self.account, tradeTarget.enabled) # type: ignore
self.stock_trade_ctrl[tradeTarget.stock_code] = stockTradeController
print(f"\t创建标的交易控制器 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}")
def pause_stock_trade(self, index: int):
tradeTarget = self.instrument_pool[index]
if tradeTarget.stock_code in self.stock_trade_ctrl:
tradeController: SFGridStrategy = self.stock_trade_ctrl[tradeTarget.stock_code]
if tradeController.isEnabled():
print(f"暂停标的交易 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}\n")
tradeController.enabledTrading(False)
else:
print(f"标的交易已暂停 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}\n")
def pause_stock_trade(self, id: int):
localTarget: strategy_db.TradeTarget = self.instrument_pool[id]
print(f'暂停标的交易 {localTarget.stock_code} - enabled {localTarget.enabled}')
if localTarget.stock_code in self.stock_trade_ctrl:
tradeController: SFGridStrategy = self.stock_trade_ctrl[localTarget.stock_code]
tradeTarget = tradeController.enabledTrading(False)
self.instrument_pool[id] = tradeTarget
event_bus.publish(ResultEventTradeDisabled, tradeTarget)
else:
print(f"标的交易控制器不存在 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}\n")
print(f"标的交易控制器不存在 {localTarget.stock_code} {localTarget.stock_name}\n")
# ====== 市场回调方法 -- 以下方法由XtQuantData调用 ======
@@ -213,8 +258,8 @@ class SFGridController(XtQuantTraderCallback):
self.add_trade_target(stock_code)
self.stock_trade_ctrl[stock_code].enabledTrading(True)
else: # 指定目标 当前主要使用这种模式
for target in self.instrument_pool:
stock_code = target.stock_code
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")
@@ -251,8 +296,8 @@ class SFGridController(XtQuantTraderCallback):
else:
print(f"委托下单回调 投资备注 orderId: {order.order_sysid} [{order.stock_code}-{order.instrument_name}] volume: {order.order_volume} 订单策略: '{order.strategy_name}'<-->'{ctrl.getName()}'")
def test_sim_trade(self, index: int, orderType: int):
tradeTarget:strategy_db.TradeTarget = self.instrument_pool[index]
def test_sim_trade(self, id: int, orderType: int):
tradeTarget:strategy_db.TradeTarget = self.instrument_pool[id]
ctrl:SFGridStrategy = self.stock_trade_ctrl[tradeTarget.stock_code]
trade: XtTrade = None # type: ignore
if orderType == xtconstant.STOCK_BUY:
@@ -260,7 +305,7 @@ class SFGridController(XtQuantTraderCallback):
sfgrid_constants.account_no,
'300083.SZ',
xtconstant.STOCK_BUY,
1, 1, tradeTarget.current_buy_price, sfgrid_constants.grid_volume, 1000,
1, 1, tradeTarget.plan_buy_price, sfgrid_constants.grid_volume, 1000,
tradeTarget.current_buy_order_no,
None, ctrl.getName(), None, None, None, None, None, tradeTarget.stock_name)
else:
@@ -306,5 +351,4 @@ class SFGridController(XtQuantTraderCallback):
:param response: XtAccountStatus 对象
:return:
"""
print(datetime.datetime.now(), sys._getframe().f_code.co_name)
print(datetime.datetime.now(), status)
+92 -76
View File
@@ -1,5 +1,4 @@
from re import L
from core import util
from core.eventbus import MarketDataUpdate, event_bus
from core.strategy_db import TradeTarget
from core.util import queryPendingOrder
@@ -11,42 +10,49 @@ import threading
class SFGridStrategy:
def __init__(self, tradeTarget: TradeTarget, xt_trader: xttrader.XtQuantTrader, account: StockAccount, enabled: bool = False):
def __init__(self, tradeTarget: TradeTarget, xt_trader: xttrader.XtQuantTrader, account: StockAccount):
self.tradeTarget:TradeTarget = tradeTarget
self.xt_trader: xttrader.XtQuantTrader = xt_trader
self.account:StockAccount = account
self.enabledTrading(enabled)
self.enabledTrading(bool(tradeTarget.enabled)) # 修复类型兼容性问题
self.dataUpdateLock = threading.Lock()
print(f'标的{self.tradeTarget.targetName()}交易启动状态 {self.tradeTarget.enabled}')
def getName(self):
return "SFGRID"
def saveProxy(self):
rc = self.tradeTarget.save()
event_bus.publish(MarketDataUpdate, self.tradeTarget)
return rc
def enabledTrading(self, enabled: bool):
def enabledTrading(self, enabled: bool) -> TradeTarget:
self.tradeTarget.enabled = enabled # type: ignore
self.tradeTarget.save()
pendingOrders = queryPendingOrder(str(self.tradeTarget.stock_code),self.getName(), self.xt_trader,self.account)
if len(pendingOrders) > 0:
print(f' |- 已存在{len(pendingOrders)}订单,全部取消,按需要重下。')
for order in pendingOrders:
self.xt_trader.cancel_order_stock(self.account, order.order_id)
self.saveProxy()
if enabled:
print(f" |- 标的{self.tradeTarget.targetName()}交易启动, position {self.tradeTarget.current_position}")
# 建仓状态检查
if int(self.tradeTarget.current_position) == 0 and int(self.tradeTarget.status) == 0: # type: ignore
self.tradeTarget.grid_index = 1 # type: ignore
self.tradeTarget.save()
self.initBuyOrderId = self.xt_trader.order_stock_async(
self.account,
str(self.tradeTarget.stock_code),
xtconstant.STOCK_BUY,
sfgrid_constants.grid_volume,
xtconstant.FIX_PRICE,
sfgrid_constants.grid_price[int(self.tradeTarget.grid_index)], # type: ignore
'sf_grid', f'{self.tradeTarget.stock_code}_init_buy')
print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 建初始仓 买单已发出 InitBuyOrderSeq: {self.initBuyOrderId} Price: {sfgrid_constants.grid_price[int(self.tradeTarget.grid_index)]} Volume: {sfgrid_constants.grid_volume}\n") # type: ignore
self.saveProxy()
orders = queryPendingOrder(str(self.tradeTarget.stock_code),self.getName(), self.xt_trader,self.account)
if len([order for order in orders if order.order_type == xtconstant.STOCK_BUY and order.price == sfgrid_constants.grid_price[1]]) > 0:
# 已存在未交易的多单
order = [order for order in orders if order.order_type == xtconstant.STOCK_BUY and order.price == sfgrid_constants.grid_price[1]][0]
print(f' |- 已存在未交易的建仓单,不重复下单:{order.order_id}')
else:
self.initBuyOrderId = self.xt_trader.order_stock_async(
self.account,
str(self.tradeTarget.stock_code),
xtconstant.STOCK_BUY,
sfgrid_constants.grid_volume,
xtconstant.FIX_PRICE,
sfgrid_constants.grid_price[int(self.tradeTarget.grid_index)], # type: ignore
self.getName(), f'{self.tradeTarget.stock_code}_init_buy')
print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 建初始仓 买单已发出 InitBuyOrderSeq: {self.initBuyOrderId} Price: {sfgrid_constants.grid_price[int(self.tradeTarget.grid_index)]} Volume: {sfgrid_constants.grid_volume}\n") # type: ignore
else:
# 交易阶段,检查仓位,检查现有订单
print(f" |- 标的{self.tradeTarget.targetName()}已有仓位或非初始状态 无需建初始仓 当前仓位: {self.tradeTarget.current_position} 状态: {self.tradeTarget.status}")
@@ -55,67 +61,77 @@ class SFGridStrategy:
print(f' |- 仓位检查: 持仓需求充足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}')
else:
print(f' |- 仓位检查: 持仓需求不足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}')
else:
print(f" |- 标的{self.tradeTarget.targetName()}交易监控暂停")
return self.tradeTarget
def isEnabled(self) -> bool:
return bool(self.tradeTarget.enabled)
print(f'|- 检查交易状态[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - {self.tradeTarget.enabled}')
return bool(self.tradeTarget.enabled) # 修复返回类型问题
def onDataUpdate(self, data):
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - START')
self.dataUpdateLock.acquire()
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - LOCKED')
try:
index = self.tradeTarget.grid_index
price = -1 if index>=len(sfgrid_constants.grid_price) else sfgrid_constants.grid_price[int(index)] # pyright: ignore[reportArgumentType]
lowPrice = -1 if index+1>=len(sfgrid_constants.grid_price) else sfgrid_constants.grid_price[int(index) + 1] # pyright: ignore[reportArgumentType]
highPrice = sfgrid_constants.grid_price[int(index) - 1] # pyright: ignore[reportArgumentType]
lastPrice = float("{:.3f}".format(data[self.tradeTarget.stock_code]['lastPrice']))
print(f"|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - 价格: {lastPrice}, 网格序号: {index}, 网格价格: {price}, 计划多单价: {lowPrice}, 计划空单价: {highPrice}")
if self.tradeTarget.enabled and self.tradeTarget.status == 1: # 交易中
self.dataUpdateLock.acquire()
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - LOCKED')
try:
index = self.tradeTarget.grid_index
price = -1 if index>=len(sfgrid_constants.grid_price) else sfgrid_constants.grid_price[int(index)] # pyright: ignore[reportArgumentType]
lowPrice = -1 if index+1>=len(sfgrid_constants.grid_price) else sfgrid_constants.grid_price[int(index) + 1] # pyright: ignore[reportArgumentType]
highPrice = sfgrid_constants.grid_price[int(index) - 1] # pyright: ignore[reportArgumentType]
if lastPrice <= lowPrice: # 下下方多单
orders = queryPendingOrder(str(self.tradeTarget.stock_code), self.getName(), self.xt_trader, self.account)
if len([order for order in orders if order.order_type == xtconstant.STOCK_BUY and order.price == lowPrice]) > 0:
# 已存在未交易的多单
print(f' |- 已存在未交易的多单,不重复下单')
else:
print(f' |- 下网格多单')
self.tradeTarget.current_buy_order_no = self.xt_trader.order_stock_async(
self.account,
str(self.tradeTarget.stock_code),
xtconstant.STOCK_BUY,
sfgrid_constants.grid_volume,
xtconstant.FIX_PRICE,
lowPrice,
self.getName(), # strategy_name
self.tradeTarget.stock_code # remark # type: ignore
)
self.tradeTarget.current_buy_price = float(lowPrice) # type: ignore
print(f' |- 下网格多单号 {self.tradeTarget.current_buy_order_no}, 网格基准价 {price}, 下单价 {lowPrice}, 下单量 {sfgrid_constants.grid_volume}')
elif lastPrice == highPrice: # 下上方空单
orders = queryPendingOrder(str(self.tradeTarget.stock_code), self.getName(), self.xt_trader, self.account)
if len([order for order in orders if order.order_type == xtconstant.STOCK_SELL and order.price == highPrice]) > 0:
# 已存在未交易的空单
print(f' |- 已存在未交易的空单,不重复下单')
else:
print(f' |- 下网格空单')
self.tradeTarget.current_sell_order_no = self.xt_trader.order_stock_async(
lastPrice = float("{:.3f}".format(data[self.tradeTarget.stock_code]['lastPrice']))
self.tradeTarget.market_price = lastPrice # type: ignore
print(f"|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - 价格: {lastPrice}, 网格序号: {index}, 网格价格: {price}, 计划多单价: {lowPrice}, 计划空单价: {highPrice}")
if lastPrice <= lowPrice: # 下下方多单
orders = queryPendingOrder(str(self.tradeTarget.stock_code), self.getName(), self.xt_trader, self.account)
if len([order for order in orders if order.order_type == xtconstant.STOCK_BUY and order.price == lowPrice]) > 0:
# 已存在未交易的多单
print(f' |- 已存在未交易的多单,不重复下单')
else:
print(f' |- 下网格多单')
self.tradeTarget.current_buy_order_no = self.xt_trader.order_stock_async(
self.account,
str(self.tradeTarget.stock_code),
xtconstant.STOCK_SELL,
xtconstant.STOCK_BUY,
sfgrid_constants.grid_volume,
xtconstant.FIX_PRICE,
highPrice,
self.getName(),
self.tradeTarget.stock_code) # type: ignore
self.tradeTarget.current_sell_price = float(highPrice) # type: ignore
print(f' |- 下网格空单号 {self.tradeTarget.current_sell_order_no}, 网格基准价 {price}, 下单价 {highPrice}, 下单量 {sfgrid_constants.grid_volume}')
self.tradeTarget.save()
finally:
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - release lock')
self.dataUpdateLock.release()
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - END')
lowPrice,
self.getName(), # strategy_name
self.tradeTarget.stock_code # remark # type: ignore
)
self.tradeTarget.plan_buy_price = float(lowPrice) # type: ignore
print(f' |- 下网格多单号 {self.tradeTarget.current_buy_order_no}, 网格基准价 {price}, 下单价 {lowPrice}, 下单量 {sfgrid_constants.grid_volume}')
elif lastPrice == highPrice: # 下上方空单
orders = queryPendingOrder(str(self.tradeTarget.stock_code), self.getName(), self.xt_trader, self.account)
if len([order for order in orders if order.order_type == xtconstant.STOCK_SELL and order.price == highPrice]) > 0:
# 已存在未交易的空单
print(f' |- 已存在未交易的空单,不重复下单')
else:
print(f' |- 下网格空单')
self.tradeTarget.current_sell_order_no = self.xt_trader.order_stock_async(
self.account,
str(self.tradeTarget.stock_code),
xtconstant.STOCK_SELL,
sfgrid_constants.grid_volume,
xtconstant.FIX_PRICE,
highPrice,
self.getName(),
self.tradeTarget.stock_code) # type: ignore
self.tradeTarget.plan_sell_price = float(highPrice) # type: ignore
print(f' |- 下网格空单号 {self.tradeTarget.current_sell_order_no}, 网格基准价 {price}, 下单价 {highPrice}, 下单量 {sfgrid_constants.grid_volume}')
finally:
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - release lock')
event_bus.publish('market_data', self.tradeTarget)
self.saveProxy()
self.dataUpdateLock.release()
print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - END')
elif self.tradeTarget.enabled and self.tradeTarget.status == 0: # 交易中
print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 交易中但未建初始仓,仅更新市场价")
self.tradeTarget.market_price = float("{:.3f}".format(data[self.tradeTarget.stock_code]['lastPrice'])) # type: ignore
self.saveProxy()
def onAsyncOrderResponse(self, order:XtOrderResponse):
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:START')
@@ -131,7 +147,7 @@ class SFGridStrategy:
self.tradeTarget.current_sell_order_no = order.order_id
else:
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]: 不在策略监控范围内')
rc = self.tradeTarget.save()
rc = self.saveProxy()
finally:
print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:release lock')
self.dataUpdateLock.release()
@@ -148,7 +164,7 @@ class SFGridStrategy:
self.tradeTarget.last_trade_price = float(trade.traded_price) # type: ignore
self.tradeTarget.grid_index = 1 # type: ignore
self.tradeTarget.status = 1 # type: ignore
self.tradeTarget.save()
self.saveProxy()
print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 建初始仓订单ID: {self.initBuyOrderId}已成交 ")
print(f' 成交价: {trade.traded_price} 成交量: {trade.traded_volume}')
print(f' 当前持仓: {self.tradeTarget.current_position}')
@@ -158,7 +174,7 @@ class SFGridStrategy:
self.tradeTarget.current_position = int(self.tradeTarget.current_position) - trade.traded_volume # type: ignore
self.tradeTarget.last_trade_price = float(trade.traded_price) # type: ignore
self.tradeTarget.grid_index = int(self.tradeTarget.grid_index) - 1 # type: ignore
self.tradeTarget.save()
self.saveProxy()
print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 上涨 卖单已成交 订单ID: {self.tradeTarget.current_sell_order_no} Price: {sfgrid_constants.grid_price[int(self.tradeTarget.grid_index)]} Volume: {sfgrid_constants.grid_volume} 手续费: {trade.commission}\n") # type: ignore
print(f' 成交价: {trade.traded_price} 成交量: {trade.traded_volume}')
print(f' 当前持仓: {self.tradeTarget.current_position}')
@@ -168,7 +184,7 @@ class SFGridStrategy:
self.tradeTarget.current_position = int(self.tradeTarget.current_position) + trade.traded_volume # type: ignore
self.tradeTarget.last_trade_price = float(trade.traded_price) # type: ignore
self.tradeTarget.grid_index = int(self.tradeTarget.grid_index) + 1 # type: ignore
self.tradeTarget.save()
self.saveProxy()
print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 下跌 买单已成交 订单ID: {self.tradeTarget.current_buy_order_no} Price: {trade.traded_price} Volume: {sfgrid_constants.grid_volume} 手续费: {trade.commission}")
print(f' 成交价: {trade.traded_price} 成交量: {trade.traded_volume}')
print(f' 当前持仓: {self.tradeTarget.current_position}')
+3 -2
View File
@@ -14,10 +14,11 @@ class TradeTarget(BaseModel):
stock_name = CharField()
current_position = IntegerField()
grid_index = IntegerField()
market_price = FloatField()
last_trade_price = FloatField()
current_buy_price = FloatField()
plan_buy_price = FloatField()
current_buy_order_no = CharField(default='')
current_sell_price = FloatField()
plan_sell_price = FloatField()
current_sell_order_no = CharField(default='')
status = IntegerField(default=0) # 0表示新标的,1表示已建初始仓,正常交易中
enabled = BooleanField(default=False) # 是否启动交易线程
+167 -64
View File
@@ -1,26 +1,48 @@
import random
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from typing import List, Optional
from datetime import datetime
from core.eventbus import ActionDisableMarketData, ActionEnableMarketData, ActionEventDisableTrade, ActionEventEnableTrade, MarketDataUpdate, MarketDataEnabled, MarketDataDisabled, ResultEventTradeDisabled, ResultEventTradeEnabled, event_bus
from core.strategy_db import TradeTarget
import configparser
import sfgrid_constants
class TradeTargetUI:
def __init__(self, trade_targets: List[TradeTarget]):
self.data:dict[str, TradeTarget] = {}
for temp in trade_targets:
target:TradeTarget = temp
self.data[str(target.get_id())] = target
def __init__(self):
self.data:dict[int, TradeTarget] = {}
self.market_data_enabled = True # 添加市场数据监听状态变量
self.registerEventHandler()
self.root = tk.Tk()
self.root.title("三疯交易系统")
self.root.geometry("1200x700")
# 创建界面
self.create_ui()
def registerEventHandler(self):
event_bus.subscribe(MarketDataUpdate, self.onTradeTargetUpdated)
event_bus.subscribe(ResultEventTradeEnabled, self.onTradeEnabled)
event_bus.subscribe(ResultEventTradeDisabled, self.onTradeDisabled)
event_bus.subscribe(MarketDataEnabled, self.onMarketDataToggled)
event_bus.subscribe(MarketDataDisabled, self.onMarketDataToggled)
def onMarketDataToggled(self, data:bool):
self.market_data_enabled = self.market_data_switch_var.get()
self.add_log("INFO", "市场数据监听已" + ("启用" if data else "禁用"))
def onTradeEnabled(self, target:TradeTarget):
self.add_log("INFO", f"交易启用: {target.stock_code} - {target.stock_name}")
def onTradeDisabled(self, target:TradeTarget):
self.add_log("INFO", f"交易禁用: {target.stock_code} - {target.stock_name}")
def onTradeTargetUpdated(self, target:TradeTarget):
# 更新或添加数据到本地缓存
self.data[target.get_id()] = target
# 刷新表格显示
self.refresh_table()
def create_ui(self):
"""创建UI界面"""
# 创建菜单栏
@@ -30,9 +52,55 @@ class TradeTargetUI:
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建工具栏
toolbar_frame = ttk.Frame(main_frame)
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=" 添加标的",
command=self.add_trade_target, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text="🗑 删除标的",
command=self.delete_selected_trade, width=12).pack(side=tk.LEFT, padx=2)
# 添加分隔符
ttk.Separator(toolbar_frame, orient='vertical').pack(side=tk.LEFT, fill=tk.Y, padx=10)
# 市场数据监听开关
self.market_data_switch_var = tk.BooleanVar(value=True)
self.market_data_switch = ttk.Checkbutton(
toolbar_frame,
text="📊 市场数据",
variable=self.market_data_switch_var,
command=self.toggle_market_data,
width=12
)
self.market_data_switch.pack(side=tk.LEFT, padx=2)
# 日志显示/隐藏按钮
self.log_toggle_btn = ttk.Button(toolbar_frame, text="📋 显示日志",
command=self.toggle_log_panel, width=12)
self.log_toggle_btn.pack(side=tk.LEFT, padx=2)
# 添加清空日志按钮
ttk.Button(toolbar_frame, text="🗑 清空日志",
command=self.clear_logs, width=12).pack(side=tk.LEFT, padx=2)
# 表格区域
self.create_tables_area(main_frame)
def toggle_market_data(self):
"""切换市场数据监听状态"""
print(f'市场数据监听开关')
self.market_data_enabled = self.market_data_switch_var.get()
if self.market_data_enabled:
event_bus.publish(ActionEnableMarketData, True)
else:
event_bus.publish(ActionDisableMarketData, True)
def create_menu_bar(self):
"""创建菜单栏"""
menubar = tk.Menu(self.root)
@@ -65,33 +133,9 @@ class TradeTargetUI:
def create_trade_target_table(self, parent):
"""创建交易标的表格"""
# 创建工具栏
toolbar_frame = ttk.Frame(parent)
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=" 添加标的",
command=self.add_trade_target, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text="🗑 删除标的",
command=self.delete_selected_trade, width=12).pack(side=tk.LEFT, padx=2)
# 添加分隔符
ttk.Separator(toolbar_frame, orient='vertical').pack(side=tk.LEFT, fill=tk.Y, padx=10)
# 日志显示/隐藏按钮
self.log_toggle_btn = ttk.Button(toolbar_frame, text="📋 显示日志",
command=self.toggle_log_panel, width=12)
self.log_toggle_btn.pack(side=tk.LEFT, padx=2)
# 添加分隔线
ttk.Separator(parent, orient='horizontal').pack(fill=tk.X, pady=5)
columns = ("ID",
"股票代码", "股票名称", "持仓数量", "网格索引",
"股票代码", "股票名称", "市场价", "持仓数量", "网格索引",
"最新成交价", "计划买入价", "买入订单号", "计划卖出价", "卖出订单号",
"启用状态", "交易状态"
)
@@ -103,15 +147,16 @@ class TradeTargetUI:
"ID": (50, tk.CENTER),
"股票代码": (90, tk.CENTER),
"股票名称": (100, tk.CENTER),
"市场价": (60, tk.CENTER),
"持仓数量": (90, tk.CENTER),
"网格索引": (80, tk.CENTER),
"最新成交价": (100, tk.CENTER),
"计划买入价": (100, tk.CENTER),
"网格索引": (50, tk.CENTER),
"最新成交价": (60, tk.CENTER),
"计划买入价": (60, tk.CENTER),
"买入订单号": (100, tk.CENTER),
"计划卖出价": (100, tk.CENTER),
"计划卖出价": (60, tk.CENTER),
"卖出订单号": (100, tk.CENTER),
"启用状态": (80, tk.CENTER),
"交易状态": (80, tk.CENTER)
"启用状态": (100, tk.CENTER),
"交易状态": (100, tk.CENTER)
}
for col in columns:
@@ -143,9 +188,9 @@ class TradeTargetUI:
else:
return "🔴 错误状态"
def get_trade_status_indicator(self, status: int) -> str:
def get_trade_enabled_indicator(self, enabled: bool) -> str:
"""获取交易状态指示器"""
if status == 1:
if enabled:
return "🟢 策略运行"
else:
return "🟡 策略暂停"
@@ -158,15 +203,16 @@ class TradeTargetUI:
target.id, # type: ignore
target.stock_code,
target.stock_name,
f"{target.market_price:.3f}",
target.current_position,
target.grid_index,
f"{target.last_trade_price:.2f}",
f"{target.current_buy_price:.2f}",
f"{target.plan_buy_price:.2f}",
target.current_buy_order_no,
f"{target.current_sell_price:.2f}",
f"{target.plan_sell_price:.2f}",
target.current_sell_order_no,
self.get_status_indicator(target),
self.get_trade_status_indicator(target.status) # type: ignore
self.get_trade_enabled_indicator(target.enabled) # type: ignore
]
self.trade_table.insert('', tk.END, values=values)
@@ -178,9 +224,9 @@ class TradeTargetUI:
self.log_table = ttk.Treeview(parent, columns=columns, show='headings', height=8)
log_column_configs = {
"timestamp": ("时间", 120),
"level": ("级别", 60),
"message": ("消息", 200)
"timestamp": ("时间", 100),
"level": ("级别", 50),
"message": ("消息", 850)
}
for col in columns:
@@ -191,12 +237,6 @@ class TradeTargetUI:
# 填充示例日志
sample_logs = [
("2024-01-15 10:30:15", "INFO", "系统启动成功"),
("2024-01-15 10:31:22", "DEBUG", "加载交易标的: 5个"),
("2024-01-15 10:32:45", "INFO", "000001 - 网格交易线程启动"),
("2024-01-15 10:33:10", "WARNING", "601318 - 未启用交易"),
("2024-01-15 10:34:30", "ERROR", "300750 - 订单提交失败"),
("2024-01-15 10:35:18", "INFO", "600036 - 买入订单创建成功"),
("2024-01-15 10:36:05", "INFO", "数据刷新完成")
]
for log in sample_logs:
@@ -239,11 +279,15 @@ class TradeTargetUI:
# 从列表中找到对应的target对象
for id in self.data:
if target_id == id: # type: ignore
if int(target_id) == id: # type: ignore
return self.data[id]
return None
def onLog(self, level: str, message: str):
"""接收外部日志消息并显示在日志组件中"""
self.add_log(level, message)
def start_selected_trade(self):
"""启动选中的交易"""
target = self.get_selected_target()
@@ -263,9 +307,13 @@ class TradeTargetUI:
if result:
target.enabled = True # type: ignore
self.add_log("INFO", f"已启动交易: {target.stock_code} - {target.stock_name}")
self.refresh_table()
messagebox.showinfo("启动成功", f"已启动 {target.stock_code} ({target.stock_name}) 的交易")
event_bus.publish(ActionEventEnableTrade, target.get_id())
# self.add_log("INFO", f"已启动交易: {target.stock_code} - {target.stock_name}")
# self.refresh_table()
# messagebox.showinfo("启动成功", f"已启动 {target.stock_code} ({target.stock_name}) 的交易")
def on_trade_enabled(self, target: TradeTarget):
event_bus.publish(ActionEventEnableTrade, target)
def pause_selected_trade(self):
"""暂停选中的交易"""
@@ -286,9 +334,10 @@ class TradeTargetUI:
if result:
target.enabled = False # type: ignore
self.add_log("INFO", f"已暂停交易: {target.stock_code} - {target.stock_name}")
self.refresh_table()
messagebox.showinfo("暂停成功", f"已暂停 {target.stock_code} ({target.stock_name}) 的交易")
event_bus.publish(ActionEventDisableTrade, target.get_id())
# self.add_log("INFO", f"已暂停交易: {target.stock_code} - {target.stock_name}")
# self.refresh_table()
# messagebox.showinfo("暂停成功", f"已暂停 {target.stock_code} ({target.stock_name}) 的交易")
def delete_selected_trade(self):
"""删除选中的交易标的"""
@@ -317,8 +366,56 @@ class TradeTargetUI:
def add_trade_target(self):
"""添加新的交易标的"""
# TODO: 实现添加交易标的的对话框
messagebox.showinfo("提示", "添加交易标的功能待实现")
# 创建顶层窗口
add_window = tk.Toplevel(self.root)
add_window.title("添加交易标的")
add_window.geometry("400x150")
add_window.resizable(False, False)
# 设置窗口模态
add_window.transient(self.root)
add_window.grab_set()
# 居中显示
self.root.update_idletasks()
x = self.root.winfo_x() + (self.root.winfo_width() // 2) - 200
y = self.root.winfo_y() + (self.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
# 发布事件通知主控制器添加标的
event_bus.publish("add_trade_target", 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())
self.add_log("INFO", "点击添加交易标的按钮")
def toggle_log_panel(self):
@@ -348,6 +445,13 @@ class TradeTargetUI:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log_table.insert('', 0, values=(timestamp, level, message))
def clear_logs(self):
"""清空日志记录"""
# 删除所有日志项
for item in self.log_table.get_children():
self.log_table.delete(item)
self.add_log("INFO", "日志已清空")
def system_settings(self):
"""系统设置"""
settings_window = tk.Toplevel(self.root)
@@ -355,7 +459,7 @@ class TradeTargetUI:
# 设置窗口大小
window_width = 700
window_height = 600
window_height = 550
# 先设置为模态窗口
settings_window.transient(self.root)
@@ -652,4 +756,3 @@ class TradeTargetUI:
def run(self):
"""运行程序"""
self.root.mainloop()
+2 -3
View File
@@ -1,6 +1,3 @@
from typing import Any
import sfgrid_constants
import xtquant.xtconstant as xtconstant
from xtquant import xtdata, xttrader
@@ -43,6 +40,8 @@ def is_trading_time():
def getInstrumentName(stock_code):
# print(f"getInstrumentName: 获取标的名称 {stock_code}")
detail = xtdata.get_instrument_detail(stock_code, False)
if detail is None:
return "UnNamed"
return detail['InstrumentName']
+7 -7
View File
@@ -1,10 +1,13 @@
# coding:utf-8
import sys
sys.stdout.reconfigure(encoding='utf-8') # 设置标准输出编码为UTF-8 # type: ignore
from core import strategy_db
from core.main_controller import SFGridController
import sfgrid_constants as sdConstants
import ui
def startTrade(index: int):
ctrl.start_stock_trade(index)
def pauseTrade(index: int):
ctrl.pause_stock_trade(index)
if __name__ == '__main__':
sdConstants.initConfig()
@@ -12,10 +15,7 @@ if __name__ == '__main__':
strategy_db.db.create_tables([strategy_db.TradeTarget])
print('- [成功]数据库模块初始化')
targets = strategy_db.TradeTarget.select()
appUi = ui.TradeTargetUI(trade_targets=targets)
print(f'{sdConstants.account_no} : {sdConstants.miniQMTPath}')
ctrl: SFGridController = SFGridController(sdConstants.account_no, sdConstants.miniQMTPath)
appUi.run()
ctrl.hold()
+37
View File
@@ -0,0 +1,37 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['starter.py'],
pathex=[],
binaries=[],
datas=[('config.ini', '.')], # 明确包含配置文件
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['PyQt5', 'PyQt6', 'PySide2', 'PySide6', 'matplotlib', 'numpy', 'pandas', 'jupyter', 'notebook', 'ipython'], # 排除不必要的包
noarchive=False,
optimize=2, # 启用最高级别优化
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='starter',
debug=False,
bootloader_ignore_signals=False,
strip=True, # 去除调试符号
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)