添加编译文件、打包瘦身、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
+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,11 +16,15 @@ 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
@@ -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)
+94 -78
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,68 +61,78 @@ 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]
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]
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 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')
self.dataUpdateLock.acquire()
@@ -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) # 是否启动交易线程
+758
View File
@@ -0,0 +1,758 @@
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
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):
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界面"""
# 创建菜单栏
self.create_menu_bar()
# 主框架
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)
self.root.config(menu=menubar)
# 系统菜单
system_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="系统", menu=system_menu)
system_menu.add_command(label="系统设置", command=self.system_settings)
system_menu.add_separator()
system_menu.add_command(label="退出", command=self.root.destroy)
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))
# 创建交易标的表格
self.create_trade_target_table(trade_frame)
# 下方操作日志区域(默认隐藏)
self.log_frame = ttk.LabelFrame(parent, text="操作日志", padding=10)
# 默认不显示,通过工具栏按钮控制
# self.log_frame.pack(fill=tk.X, pady=(5, 0))
self.log_visible = False # 日志区域可见性标志
# 创建操作日志表格
self.create_log_table(self.log_frame)
def create_trade_target_table(self, parent):
"""创建交易标的表格"""
columns = ("ID",
"股票代码", "股票名称", "市场价", "持仓数量", "网格索引",
"最新成交价", "计划买入价", "买入订单号", "计划卖出价", "卖出订单号",
"启用状态", "交易状态"
)
self.trade_table = ttk.Treeview(parent, columns=columns, show='headings', height=15)
# 专业化的列配置
column_configs = {
"ID": (50, tk.CENTER),
"股票代码": (90, tk.CENTER),
"股票名称": (100, tk.CENTER),
"市场价": (60, tk.CENTER),
"持仓数量": (90, tk.CENTER),
"网格索引": (50, tk.CENTER),
"最新成交价": (60, tk.CENTER),
"计划买入价": (60, tk.CENTER),
"买入订单号": (100, tk.CENTER),
"计划卖出价": (60, tk.CENTER),
"卖出订单号": (100, tk.CENTER),
"启用状态": (100, tk.CENTER),
"交易状态": (100, tk.CENTER)
}
for col in columns:
width, anchor = column_configs[col]
self.trade_table.heading(col, text=col)
self.trade_table.column(col, width=width, anchor=anchor) # type: ignore
# 填充数据
self.populate_trade_table()
# 滚动条
scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.trade_table.yview)
self.trade_table.configure(yscrollcommand=scrollbar.set)
self.trade_table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 绑定双击事件
self.trade_table.bind("<Double-1>", self.on_table_double_click)
def get_status_indicator(self, target: TradeTarget) -> str:
"""获取状态指示器(带颜色色块的文本)"""
if target.status == 1:
# 绿色圆点表示交易中
return "🟢 已建仓"
elif target.status == 0:
# 黄色圆点表示暂停
return "🟡 未建仓"
else:
return "🔴 错误状态"
def get_trade_enabled_indicator(self, enabled: bool) -> str:
"""获取交易状态指示器"""
if enabled:
return "🟢 策略运行"
else:
return "🟡 策略暂停"
def populate_trade_table(self):
"""填充交易标的表格数据"""
for temp in self.data:
target: TradeTarget = self.data[temp]
values = [
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.plan_buy_price:.2f}",
target.current_buy_order_no,
f"{target.plan_sell_price:.2f}",
target.current_sell_order_no,
self.get_status_indicator(target),
self.get_trade_enabled_indicator(target.enabled) # type: ignore
]
self.trade_table.insert('', tk.END, values=values)
def create_log_table(self, parent):
"""创建操作日志表格"""
columns = ("timestamp", "level", "message")
self.log_table = ttk.Treeview(parent, columns=columns, show='headings', height=8)
log_column_configs = {
"timestamp": ("时间", 100),
"level": ("级别", 50),
"message": ("消息", 850)
}
for col in columns:
title, width = log_column_configs[col]
self.log_table.heading(col, text=title)
self.log_table.column(col, width=width, anchor=tk.W)
# 填充示例日志
sample_logs = [
("2024-01-15 10:30:15", "INFO", "系统启动成功"),
]
for log in sample_logs:
self.log_table.insert('', tk.END, values=log)
# 滚动条
scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.log_table.yview)
self.log_table.configure(yscrollcommand=scrollbar.set)
self.log_table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def get_status_text(self, status):
"""获取状态文本"""
status_map = {
0: "新标的",
1: "交易中"
}
return status_map.get(status, "未知")
def on_table_double_click(self, event):
"""表格双击事件"""
selected = self.trade_table.selection()
if selected:
item = selected[0]
values = self.trade_table.item(item)['values']
self.add_log("DEBUG", f"双击查看详情: {values[0]} - {values[1]}")
def get_selected_target(self):
"""获取选中的交易标的"""
selected = self.trade_table.selection()
if not selected:
messagebox.showwarning("未选中", "请先选择一个交易标的")
return None
# 获取选中行的ID
item = selected[0]
values = self.trade_table.item(item)['values']
target_id = values[0]
# 从列表中找到对应的target对象
for id in self.data:
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()
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:
target.enabled = True # type: ignore
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):
"""暂停选中的交易"""
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:
target.enabled = False # type: ignore
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):
"""删除选中的交易标的"""
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:
try:
del self.data[target.get_id()]
self.add_log("WARNING", f"已删除交易标的: {target.stock_code} - {target.stock_name}")
self.refresh_table()
messagebox.showinfo("删除成功", f"已删除 {target.stock_code} ({target.stock_name})")
except Exception as e:
self.add_log("ERROR", f"删除失败: {str(e)}")
messagebox.showerror("删除失败", f"删除交易标的时出错:{str(e)}")
def add_trade_target(self):
"""添加新的交易标的"""
# 创建顶层窗口
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):
"""切换日志面板的显示/隐藏"""
if self.log_visible:
# 隐藏日志面板
self.log_frame.pack_forget()
self.log_visible = False
self.log_toggle_btn.config(text="📋 显示日志")
else:
# 显示日志面板
self.log_frame.pack(fill=tk.X, pady=(5, 0))
self.log_visible = True
self.log_toggle_btn.config(text="📋 隐藏日志")
def refresh_table(self):
"""刷新表格数据"""
# 清空表格
for item in self.trade_table.get_children():
self.trade_table.delete(item)
# 重新填充
self.populate_trade_table()
def add_log(self, level, message):
"""添加日志记录"""
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)
settings_window.title("网格交易系统配置")
# 设置窗口大小
window_width = 700
window_height = 550
# 先设置为模态窗口
settings_window.transient(self.root)
# 确保主窗口完全初始化
self.root.update_idletasks()
# 获取主窗口的实际大小(包括边框)
# 使用winfo_rootx/rooty获取窗口在屏幕上的绝对位置
main_x = self.root.winfo_rootx()
main_y = self.root.winfo_rooty()
main_width = self.root.winfo_width()
main_height = self.root.winfo_height()
# 计算设置窗口相对于主窗口的居中位置
x = main_x + (main_width - window_width) // 2
y = main_y + (main_height - window_height) // 2
# 设置窗口大小和位置
settings_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
settings_window.resizable(False, False)
# 设置为模态窗口
settings_window.grab_set()
# 添加底部按钮区域(先创建,确保固定在底部)
button_frame = ttk.Frame(settings_window)
button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=10)
# 创建选项卡(在按钮之后创建,填充剩余空间)
notebook = ttk.Notebook(settings_window)
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0))
# 基础设置
basic_frame = ttk.Frame(notebook)
notebook.add(basic_frame, text="基础设置")
# 高级设置
advanced_frame = ttk.Frame(notebook)
notebook.add(advanced_frame, text="高级设置")
# 读取当前配置
config = configparser.ConfigParser()
config.read('config.ini')
# 创建输入框字典用于保存引用
entries = {}
# 网格价格计算参数
grid_params = {}
# 添加网格价格设置(特殊处理)
grid_price_frame = ttk.LabelFrame(basic_frame, text="网格价格设置", padding=15)
grid_price_frame.pack(fill=tk.X, padx=20, pady=10)
# 基准价格
base_price_row = ttk.Frame(grid_price_frame)
base_price_row.pack(fill=tk.X, pady=5)
ttk.Label(base_price_row, text="基准价格:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
base_price_entry = ttk.Entry(base_price_row, width=15, font=('Arial', 10))
base_price_entry.insert(0, "10.0")
base_price_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(base_price_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT)
grid_params['base_price'] = base_price_entry
# 网格类型
grid_type_row = ttk.Frame(grid_price_frame)
grid_type_row.pack(fill=tk.X, pady=5)
ttk.Label(grid_type_row, text="网格类型:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
grid_type_var = tk.StringVar(value="金额差")
ttk.Radiobutton(grid_type_row, text="百分比", variable=grid_type_var,
value="百分比", command=lambda: on_grid_type_change()).pack(side=tk.LEFT, padx=5)
ttk.Radiobutton(grid_type_row, text="金额差", variable=grid_type_var,
value="金额差", command=lambda: on_grid_type_change()).pack(side=tk.LEFT, padx=5)
grid_params['grid_type'] = grid_type_var
# 网格大小
grid_size_row = ttk.Frame(grid_price_frame)
grid_size_row.pack(fill=tk.X, pady=5)
ttk.Label(grid_size_row, text="网格大小:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
grid_size_entry = ttk.Entry(grid_size_row, width=15, font=('Arial', 10))
grid_size_entry.insert(0, "1.0")
grid_size_entry.pack(side=tk.LEFT, padx=5)
grid_size_unit_label = ttk.Label(grid_size_row, text="", foreground='gray', font=('Arial', 9))
grid_size_unit_label.pack(side=tk.LEFT)
grid_params['grid_size'] = grid_size_entry
grid_params['grid_size_unit_label'] = grid_size_unit_label
# 网格类型改变时更新单位
def on_grid_type_change():
if grid_type_var.get() == "百分比":
grid_size_unit_label.config(text="%")
grid_size_entry.delete(0, tk.END)
grid_size_entry.insert(0, "1.0")
else:
grid_size_unit_label.config(text="")
grid_size_entry.delete(0, tk.END)
grid_size_entry.insert(0, "1.0")
update_preview()
# 上方网格数量
upper_grid_row = ttk.Frame(grid_price_frame)
upper_grid_row.pack(fill=tk.X, pady=5)
ttk.Label(upper_grid_row, text="上方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
upper_grid_entry = ttk.Entry(upper_grid_row, width=15, font=('Arial', 10))
upper_grid_entry.insert(0, "1")
upper_grid_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(upper_grid_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT)
grid_params['upper_count'] = upper_grid_entry
# 下方网格数量
lower_grid_row = ttk.Frame(grid_price_frame)
lower_grid_row.pack(fill=tk.X, pady=5)
ttk.Label(lower_grid_row, text="下方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
lower_grid_entry = ttk.Entry(lower_grid_row, width=15, font=('Arial', 10))
lower_grid_entry.insert(0, "10")
lower_grid_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(lower_grid_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT)
grid_params['lower_count'] = lower_grid_entry
# 预览按钮和结果显示
preview_row = ttk.Frame(grid_price_frame)
preview_row.pack(fill=tk.X, pady=10)
preview_result = tk.StringVar(value="点击'预览'查看生成的网格价格序列")
def calculate_grid_prices():
"""计算网格价格序列"""
try:
base_price = float(base_price_entry.get())
grid_size = float(grid_size_entry.get())
upper_count = int(upper_grid_entry.get())
lower_count = int(lower_grid_entry.get())
grid_type = grid_type_var.get()
prices = []
# 计算上方网格价格
for i in range(upper_count, 0, -1):
if grid_type == "百分比":
price = base_price * (1 + grid_size / 100 * i)
else: # 金额差
price = base_price + grid_size * i
prices.append(round(price, 3))
# 添加基准价格
prices.append(base_price)
# 计算下方网格价格
for i in range(1, lower_count + 1):
if grid_type == "百分比":
price = base_price * (1 - grid_size / 100 * i)
else: # 金额差
price = base_price - grid_size * i
# 确保价格不为负
if price >= 0:
prices.append(round(price, 3))
else:
break
return prices
except ValueError as e:
return None
def update_preview():
"""自动更新网格价格序列预览"""
prices = calculate_grid_prices()
if prices:
price_str = ",".join([str(p) for p in prices])
preview_result.set(f"网格价格序列: {price_str}")
else:
preview_result.set("参数错误,请检查!")
# 绑定输入变化自动预览
for entry_widget in (base_price_entry, grid_size_entry, upper_grid_entry, lower_grid_entry):
entry_widget.bind("<KeyRelease>", lambda e: update_preview())
entry_widget.bind("<FocusOut>", lambda e: update_preview())
# 初始预览
update_preview()
ttk.Label(preview_row, textvariable=preview_result,
font=('Arial', 10)).pack(side=tk.LEFT, padx=10)
# 添加其他基础配置选项
other_basic_frame = ttk.LabelFrame(basic_frame, text="交易设置", padding=15)
other_basic_frame.pack(fill=tk.X, padx=20, pady=10)
other_basic_settings = [
("网格交易手数", "grid_volume", config.get('config', 'grid_volume'), "每个网格的交易手数"),
("最大启用目标数", "max_enabled_targets", config.get('config', 'max_enabled_targets'), "同时运行的最大标的数量")
]
for i, (label, key, default, tooltip) in enumerate(other_basic_settings):
frame = ttk.Frame(other_basic_frame)
frame.pack(fill=tk.X, pady=5)
label_widget = ttk.Label(frame, text=label + ":", width=15, font=('Arial', 10))
label_widget.pack(side=tk.LEFT)
entry = ttk.Entry(frame, width=15, font=('Arial', 10))
entry.insert(0, default)
entry.pack(side=tk.LEFT, padx=5)
entries[key] = entry
# 添加提示信息
tip_label = ttk.Label(frame, text=tooltip, font=('Arial', 9), foreground='gray')
tip_label.pack(side=tk.LEFT, padx=5)
# 添加高级设置选项
account_frame = ttk.LabelFrame(advanced_frame, text="账号设置", padding=15)
account_frame.pack(fill=tk.X, padx=20, pady=10)
# 交易账号
account_row = ttk.Frame(account_frame)
account_row.pack(fill=tk.X, pady=5)
ttk.Label(account_row, text="交易账号:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
account_entry = ttk.Entry(account_row, width=15, font=('Arial', 10))
account_entry.insert(0, config.get('config', 'account_no'))
account_entry.pack(side=tk.LEFT, padx=5)
entries['account_no'] = account_entry
ttk.Label(account_row, text="QMT交易账号", font=('Arial', 9), foreground='gray').pack(side=tk.LEFT, padx=5)
# QMT路径特殊处理 - 使用文件浏览器
qmt_path_frame = ttk.LabelFrame(advanced_frame, text="软件路径", padding=15)
qmt_path_frame.pack(fill=tk.X, padx=20, pady=10)
qmt_row = ttk.Frame(qmt_path_frame)
qmt_row.pack(fill=tk.X, pady=5)
ttk.Label(qmt_row, text="QMT路径:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
qmt_entry = ttk.Entry(qmt_row, width=30, font=('Arial', 10))
qmt_entry.insert(0, config.get('config', 'miniQMTPath'))
qmt_entry.pack(side=tk.LEFT, padx=5)
entries['miniQMTPath'] = qmt_entry
def browse_qmt_path():
"""打开文件夹浏览器选择QMT路径"""
initial_dir = qmt_entry.get() if qmt_entry.get() else "/"
folder_path = filedialog.askdirectory(
title="选择miniQMT安装路径",
initialdir=initial_dir
)
if folder_path:
qmt_entry.delete(0, tk.END)
qmt_entry.insert(0, folder_path)
ttk.Button(qmt_row, text="📁 浏览...", command=browse_qmt_path, width=10).pack(side=tk.LEFT, padx=5)
ttk.Label(qmt_row, text="miniQMT软件安装路径", font=('Arial', 9), foreground='gray').pack(side=tk.LEFT, padx=5)
# 定义保存和取消按钮的功能(button_frame已在上方创建)
def save_settings():
"""保存配置"""
try:
# 计算网格价格序列
grid_prices = calculate_grid_prices()
if not grid_prices:
messagebox.showerror("错误", "网格价格参数有误,请检查输入!")
return
grid_price_str = ",".join([str(p) for p in grid_prices])
# 更新配置对象
config.set('config', 'miniQMTPath', entries['miniQMTPath'].get())
config.set('config', 'grid_price', grid_price_str)
config.set('config', 'grid_volume', entries['grid_volume'].get())
config.set('config', 'account_no', entries['account_no'].get())
config.set('config', 'max_enabled_targets', entries['max_enabled_targets'].get())
# 写入配置文件
with open('config.ini', 'w') as configfile:
config.write(configfile)
# 重新加载配置到内存中
sfgrid_constants.initConfig()
messagebox.showinfo("成功", f"配置已保存!\n网格价格序列: {grid_price_str}\n部分配置可能需要重启程序后生效。")
self.add_log("INFO", f"系统配置已更新 - 网格数量: {len(grid_prices)}")
settings_window.destroy()
except Exception as e:
messagebox.showerror("错误", f"保存配置失败:{str(e)}")
self.add_log("ERROR", f"保存配置失败: {str(e)}")
def cancel_settings():
"""取消设置"""
settings_window.destroy()
# 在button_frame中添加按钮
ttk.Button(button_frame, text="💾 保存配置", command=save_settings, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="❌ 取消", command=cancel_settings, width=15).pack(side=tk.LEFT, padx=5)
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']