diff --git a/core/eventbus.py b/core/eventbus.py index e74c868..7f400ff 100644 --- a/core/eventbus.py +++ b/core/eventbus.py @@ -10,8 +10,12 @@ ActionDisableMarketData = "disable_market_data" MarketDataEnabled = "market_data_enabled" MarketDataDisabled = "market_data_disabled" # 删除交易标的事件 +ActionEventAddTradeTarget = "add_trade_target" +ResultEventTradeTargetAdded = "trade_target_added" ActionEventDeleteTradeTarget = "delete_trade_target" ResultEventTradeTargetDeleted = "trade_target_deleted" +# Pring Log +EventPrintLog = "print_log" class EventBus: diff --git a/core/logger.py b/core/logger.py new file mode 100644 index 0000000..9938f81 --- /dev/null +++ b/core/logger.py @@ -0,0 +1,19 @@ +from enum import Enum + +from core.eventbus import EventPrintLog, event_bus + +class LogLevel(Enum): + DEBUG = "DEBUG" + INFO = "INFO" + WARNING = "WARNING" + ERROR = "ERROR" + CRITICAL = "CRITICAL" + +class LogData: + def __init__(self, level:LogLevel, message:str): + self.level = level + self.message = message + +def PrintLog(level:LogLevel, message:str): + data = LogData(level, message) + event_bus.publish(EventPrintLog, data) diff --git a/core/main_controller.py b/core/main_controller.py index 9938695..2c2e1c8 100644 --- a/core/main_controller.py +++ b/core/main_controller.py @@ -1,6 +1,6 @@ # coding:utf-8 from core.strategy_db import TradeTarget -from core.eventbus import ActionDisableMarketData, ActionEnableMarketData, ActionEventDeleteTradeTarget, ActionEventDisableTrade, ActionEventEnableTrade, MarketDataUpdate, MarketDataEnabled, MarketDataDisabled, ResultEventTradeDisabled, ResultEventTradeEnabled, ResultEventTradeTargetDeleted, event_bus +from core.eventbus import ActionDisableMarketData, ActionEnableMarketData, ActionEventAddTradeTarget, ActionEventDeleteTradeTarget, ActionEventDisableTrade, ActionEventEnableTrade, MarketDataUpdate, MarketDataEnabled, MarketDataDisabled, ResultEventTradeDisabled, ResultEventTradeEnabled, ResultEventTradeTargetDeleted, event_bus from xtquant.xttrader import XtQuantTrader import time from peewee import ModelSelect @@ -15,6 +15,7 @@ from xtquant import xtdata from xtquant.xttrader import XtQuantTraderCallback import datetime import core.ui as ui +from core.logger import PrintLog, LogLevel # 量化核心控制对象 class SFGridController(XtQuantTraderCallback): @@ -31,8 +32,8 @@ class SFGridController(XtQuantTraderCallback): self.xt_trader: XtQuantTrader = XtQuantTrader(miniQmtPath, session_id) self.xt_trader.register_callback(self) self.xt_trader.start() - connect_result = self.xt_trader.connect() - print(f'- [{'成功' if self.xt_trader.connected else '失败'}]市场交易连接{connect_result}--: {miniQmtPath}') + self.xt_trader.connect() + PrintLog(LogLevel.INFO, f'- [{'成功' if self.xt_trader.connected else '失败'}]市场交易连接: {miniQmtPath}') if self.xt_trader.connected == False: self.inited: bool = False return @@ -40,9 +41,9 @@ class SFGridController(XtQuantTraderCallback): self.inited = True self.account= StockAccount(account_no, 'STOCK') - print(f'- [成功]交易账号对象初始化完成, 账号: {self.account.account_id}') # pyright: ignore[reportAttributeAccessIssue] + PrintLog(LogLevel.INFO, f'- [成功]交易账号对象初始化完成, 账号: {self.account.account_id}') # pyright: ignore[reportAttributeAccessIssue] subscribe_result = self.xt_trader.subscribe(self.account) - print(f'- [{'成功' if subscribe_result == 0 else '失败'}:{subscribe_result}]交易状态订阅') + PrintLog(LogLevel.INFO, f'- [{'成功' if subscribe_result == 0 else '失败'}:{subscribe_result}]交易状态订阅') if subscribe_result == 0: self.inited = True else: @@ -52,15 +53,15 @@ class SFGridController(XtQuantTraderCallback): self.init_instrument_pool(self.xt_trader, self.account) # type: ignore self.seq = None - print('- [成功]三疯交易系统初始化完成') - # self.startMarketData() + PrintLog(LogLevel.INFO, '- [成功]三疯交易系统初始化完成') + 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) + event_bus.subscribe(ActionEventAddTradeTarget, self.onAddTradeTarget) event_bus.subscribe(ActionEventDeleteTradeTarget, self.onDeleteTradeTarget) def onDeleteTradeTarget(self, id: int): @@ -91,17 +92,20 @@ class SFGridController(XtQuantTraderCallback): self.appUi.run() def startMarketData(self): - print('- 启动市场数据订阅') + PrintLog(LogLevel.INFO, '- 启动市场数据订阅') + self.seq = xtdata.subscribe_whole_quote(['SH', 'SZ'], callback=self.onDataUpdate) if self.seq == -1: - print('- 市场数据订阅失败') + PrintLog(LogLevel.ERROR, '- 市场数据订阅失败') else: event_bus.publish(MarketDataEnabled, True) - print(f'- 市场数据订阅成功, 订阅号={self.seq}') + PrintLog(LogLevel.INFO, f'- 市场数据订阅成功, 订阅号={self.seq}') + def stopMarketData(self): - print('- 停止市场数据订阅') + PrintLog(LogLevel.INFO, '- 停止市场数据订阅') + if self.seq is not None and self.seq > 0: xtdata.unsubscribe_quote(self.seq) event_bus.publish(MarketDataDisabled, False) @@ -111,13 +115,13 @@ class SFGridController(XtQuantTraderCallback): try: stock_name = getInstrumentName(stock_code) if not stock_name: - print(f'无法获取股票代码 {stock_code} 的名称,请检查代码是否正确') + PrintLog(LogLevel.ERROR, 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} 已存在') + PrintLog(LogLevel.INFO, f'交易标的 {stock_code} {stock_name} 已存在') return new_target = strategy_db.TradeTarget.create( @@ -134,7 +138,7 @@ class SFGridController(XtQuantTraderCallback): current_order_type='' ) new_target.save() - print(f'新增交易标的 {stock_code} {stock_name}, {new_target.id}') + PrintLog(LogLevel.INFO, f'新增交易标的 {stock_code} {stock_name}, {new_target.id}') # 刷新标的持仓 pos = getStockPosition(stock_code, self.xt_trader, self.account) # type: ignore strategy_db.TradeTarget.update(current_position=pos).where(strategy_db.TradeTarget.stock_code == stock_code).execute() @@ -145,14 +149,14 @@ class SFGridController(XtQuantTraderCallback): self.stock_trade_ctrl[stock_code] = stockTradeController except Exception as e: - print(f'新增交易标的失败 {stock_code} {e}') + PrintLog(LogLevel.ERROR, f'新增交易标的失败 {stock_code} {e}') def del_trade_target(self, id:int): try: # 检查标的是否存在 if id not in self.instrument_pool: - print(f"交易标的 ID {id} 不存在") + PrintLog(LogLevel.ERROR, f"交易标的 ID {id} 不存在") return target: strategy_db.TradeTarget = self.instrument_pool[id] @@ -171,23 +175,27 @@ class SFGridController(XtQuantTraderCallback): # 刷新标的池 self.refresh_targets() - print(f"已删除交易标的: {target.stock_code} - {target.stock_name}") + PrintLog(LogLevel.INFO, f"已删除交易标的: {target.stock_code} - {target.stock_name}") except Exception as e: - print(f"删除交易标的失败 ID {id}: {str(e)}") + PrintLog(LogLevel.ERROR, f"删除交易标的失败 ID {id}: {str(e)}") def init_instrument_pool(self, xtTrader:XtQuantTrader, account:StockAccount): self.refresh_targets() for id in self.instrument_pool: + target:TradeTarget = self.instrument_pool[id] + status = "新建" if target.status == 0 else "已建初始仓" + PrintLog(LogLevel.INFO, 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 + 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}') + 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) - print(f'- [成功]交易标的信息初始化, 共 {len(self.instrument_pool)} 个标的') + PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.instrument_pool)} 个标的') def refresh_targets(self): @@ -197,55 +205,47 @@ class SFGridController(XtQuantTraderCallback): 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 id in self.instrument_pool: - target: strategy_db.TradeTarget = self.instrument_pool[id] - status = "新建" if target.status == 0 else "已建初始仓" - 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) if positions: - print("\n- 持仓信息") + PrintLog(LogLevel.INFO, "\n- 持仓信息") for temp in positions: pos : XtPosition = temp if pos.volume <=0: continue - print(f"股票代码: {pos.stock_code}-{getInstrumentName(pos.stock_code)}") - print(f"总持仓: {pos.volume}") - print(f"可用持仓: {pos.can_use_volume}") - print(f"持仓成本: {pos.avg_price}") - print("---") + PrintLog(LogLevel.INFO, f"股票代码: {pos.stock_code}-{getInstrumentName(pos.stock_code)}") + PrintLog(LogLevel.INFO, f"总持仓: {pos.volume}") + PrintLog(LogLevel.INFO, f"可用持仓: {pos.can_use_volume}") + PrintLog(LogLevel.INFO, f"持仓成本: {pos.avg_price}") + PrintLog(LogLevel.INFO, "---") else: - print("\n当前无持仓") + PrintLog(LogLevel.INFO, "\n当前无持仓") def print_account_info(self): temp = self.xt_trader.query_stock_asset(self.account) asset: XtAsset = temp # type: ignore - print(f"=== 账户信息 {self.account.account_id} ===") # type: ignore - print(f"可用资金: {asset.cash}") - print(f"总资产: {asset.total_asset}") - print(f"证券市值: {asset.market_value}") + PrintLog(LogLevel.INFO, f"=== 账户信息 {self.account.account_id} ===") # type: ignore + PrintLog(LogLevel.INFO, f"可用资金: {asset.cash}") + PrintLog(LogLevel.INFO, f"总资产: {asset.total_asset}") + PrintLog(LogLevel.INFO, f"证券市值: {asset.market_value}") def print_stock_orders(self): orders = self.xt_trader.query_stock_orders(self.account, cancelable_only=True) if orders: - print("\n=== 委托信息 ===") + PrintLog(LogLevel.INFO, "\n=== 委托信息 ===") for order in orders: - print(f"委托编号: {order.order_id}") - print(f"股票代码: {order.stock_code} {getInstrumentName(order.stock_code)}") - print(f"委托方向: {order.offset_flag} ") - print(f"委托价格: {order.price}") - print(f"委托数量: {order.order_volume}") - print(f"已成交数量: {order.traded_volume}") - print(f"委托状态: {order.order_status} ") - print("---") + PrintLog(LogLevel.INFO, f"委托编号: {order.order_id}") + PrintLog(LogLevel.INFO, f"股票代码: {order.stock_code} {getInstrumentName(order.stock_code)}") + PrintLog(LogLevel.INFO, f"委托方向: {order.offset_flag} ") + PrintLog(LogLevel.INFO, f"委托价格: {order.price}") + PrintLog(LogLevel.INFO, f"委托数量: {order.order_volume}") + PrintLog(LogLevel.INFO, f"已成交数量: {order.traded_volume}") + PrintLog(LogLevel.INFO, f"委托状态: {order.order_status} ") + PrintLog(LogLevel.INFO, "---") else: - print("\n当前无委托记录") + PrintLog(LogLevel.INFO, "\n当前无委托记录") # 初始化指定标的交易控制器 @@ -259,7 +259,7 @@ class SFGridController(XtQuantTraderCallback): self.instrument_pool[id] = tradeTarget event_bus.publish(ResultEventTradeEnabled, tradeTarget) else: - print(f"\t创建标的交易控制器 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}") + PrintLog(LogLevel.INFO, f"\t创建标的交易控制器 {tradeTarget.stock_code} {getInstrumentName(tradeTarget.stock_code)}") def pause_stock_trade(self, id: int): diff --git a/core/sfgrid_strategy.py b/core/sfgrid_strategy.py index adbc786..f4439c7 100644 --- a/core/sfgrid_strategy.py +++ b/core/sfgrid_strategy.py @@ -1,10 +1,13 @@ +from peewee import IntegerField + + from core import strategy_db from core.eventbus import MarketDataUpdate, event_bus -from core.strategy_db import TradeTarget +from core.strategy_db import OrderTypeBuy, OrderTypeInit, OrderTypeSell, TradeTarget from core.util import queryPendingOrder from xtquant import xttrader, xtconstant -from xtquant.xttype import StockAccount, XtOrder, XtOrderResponse, XtTrade +from xtquant.xttype import StockAccount, XtOrder, XtTrade import sfgrid_constants import threading @@ -16,8 +19,9 @@ class SFGridStrategy: self.xt_trader: xttrader.XtQuantTrader = xt_trader self.account:StockAccount = account self.enabledTrading(bool(tradeTarget.enabled)) # 修复类型兼容性问题 + + event_bus.publish(MarketDataUpdate, self.tradeTarget) self.dataUpdateLock = threading.Lock() - print(f'标的{self.tradeTarget.targetName()}交易启动状态 {self.tradeTarget.enabled}') def enabledTrading(self, enabled: bool) -> TradeTarget: @@ -25,26 +29,11 @@ class SFGridStrategy: self.saveProxy() if enabled: + self.refreshPlanPrice() print(f" |- 标的{self.tradeTarget.targetName()}交易启动, 持仓量:{self.tradeTarget.current_position}") if self.tradeTarget.status == 0: # 未建仓 - print(f" |- 标的{self.tradeTarget.targetName()}初始状态,检查订单") - 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.tradeTarget.grid_index = 1 # pyright: ignore[reportAttributeAccessIssue] - self.tradeTarget.current_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, - sfgrid_constants.grid_price[int(self.tradeTarget.grid_index)], # type: ignore - self.getName(), strategy_db.OrderTypeInit) - print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 建初始仓 买单已发出 InitBuyOrderSeq: {self.tradeTarget.current_order_no} Price: {sfgrid_constants.grid_price[int(self.tradeTarget.grid_index)]} Volume: {sfgrid_constants.grid_volume}\n") # type: ignore + print(f" |- 标的{self.tradeTarget.targetName()}初始状态, 设置网格序号 1") + self.tradeTarget.grid_index = 1 # pyright: ignore[reportAttributeAccessIssue] else: # 已建仓 # 交易阶段,检查仓位,检查现有订单 print(f" |- 标的{self.tradeTarget.targetName()}已有仓位或非初始状态 无需建初始仓 当前仓位: {self.tradeTarget.current_position} 状态: {self.tradeTarget.status}") @@ -52,7 +41,9 @@ class SFGridStrategy: if minRequirePosition <= int(self.tradeTarget.current_position): # type: ignore print(f' |- 仓位检查: 持仓需求充足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}') else: - print(f' |- 仓位检查: 持仓需求不足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}') + print(f' |- 仓位检查: 持仓需求不足, (gridVolume*gridIndex)={minRequirePosition}, 当前持仓:{self.tradeTarget.current_position}, 交易启动失败') + self.tradeTarget.enabled = False # type: ignore + self.saveProxy() else: print(f" |- 标的{self.tradeTarget.targetName()}交易监控暂停") return self.tradeTarget @@ -68,61 +59,72 @@ class SFGridStrategy: self.tradeTarget.market_price = lastPrice # type: ignore self.saveProxy() - if not(self.tradeTarget.enabled and self.tradeTarget.status == 1): # 已建仓,常规自动交易中 + if not self.tradeTarget.enabled: # 未建仓,自动交易暂停 print(f"|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - 未建仓或交易监控暂停,不进行自动交易") return - + 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: # 下下方多单 + try: + orderPrice:float = -1 + orderType = -1 + index: int = self.tradeTarget.grid_index # pyright: ignore[reportAssignmentType] + orderRemark= "" + + gridBasePrice = -1 if index>=len(sfgrid_constants.grid_price) or index < 0 else sfgrid_constants.grid_price[int(index)] # pyright: ignore[reportArgumentType] + + if self.tradeTarget.enabled and self.tradeTarget.status == 0: # 已启用,未建仓,建仓 + orderPrice = sfgrid_constants.grid_price[index] + orderType = xtconstant.STOCK_BUY + orderRemark = OrderTypeInit + + if self.tradeTarget.enabled and self.tradeTarget.status == 1: # 已启用,已建仓,网格单 + 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[index - 1] + + if lastPrice <= lowPrice: # 下下方多单 + orderPrice = lowPrice + orderType = xtconstant.STOCK_BUY + orderRemark = OrderTypeBuy + + elif lastPrice >= highPrice: # 下上方空单 + orderPrice = highPrice + orderType = xtconstant.STOCK_SELL + orderRemark = OrderTypeSell + + if orderType != -1: 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: + if len([order for order in orders if order.order_type == orderType and order.price == orderPrice]) > 0: # 已存在未交易的多单 - print(f' |- 已存在未交易的多单,不重复下单') + print(f' |- 已存在未交易的{"多单" if orderType == xtconstant.STOCK_BUY else "空单"},不重复下单') else: - print(f' |- 下网格多单') + print(f' |- 下网格{"多单" if orderType == xtconstant.STOCK_BUY else "空单"}') self.tradeTarget.current_order_no = self.xt_trader.order_stock_async( self.account, str(self.tradeTarget.stock_code), - xtconstant.STOCK_BUY, + orderType, sfgrid_constants.grid_volume, xtconstant.FIX_PRICE, - lowPrice, + orderPrice, self.getName(), # strategy_name - strategy_db.OrderTypeBuy # remark # type: ignore + orderRemark # remark # type: ignore ) - self.tradeTarget.plan_buy_price = float(lowPrice) # type: ignore - print(f' |- 下网格多单号 {self.tradeTarget.current_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_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(), - strategy_db.OrderTypeBuy) # type: ignore - self.tradeTarget.plan_sell_price = float(highPrice) # type: ignore - print(f' |- 下网格空单号 {self.tradeTarget.current_order_no}, 网格基准价 {price}, 下单价 {highPrice}, 下单量 {sfgrid_constants.grid_volume}') + orderTypeName = "" + if orderRemark == OrderTypeBuy: + orderTypeName = "多单" + elif orderRemark == OrderTypeSell: + orderTypeName = "空单" + elif orderRemark == OrderTypeInit: + orderTypeName = "建仓单" + print(f' |- {orderTypeName}委托, 单号 {self.tradeTarget.current_order_no}, 网格基准价 {gridBasePrice}, 下单价 {orderPrice}, 下单量 {sfgrid_constants.grid_volume}') finally: print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - release lock') self.saveProxy() self.dataUpdateLock.release() print(f'|- 市价更新[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}] - END') + def onAsyncOrderResponse(self, order:XtOrder): print(f' |- 委托回调[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{order.order_id}]:START') self.dataUpdateLock.acquire() @@ -153,6 +155,8 @@ 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 = 1 # type: ignore + self.tradeTarget.plan_buy_price = float(sfgrid_constants.grid_price[2]) # type: ignore + self.tradeTarget.plan_sell_price = float(sfgrid_constants.grid_price[0]) # type: ignore self.tradeTarget.status = 1 # type: ignore self.saveProxy() print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 建初始仓订单ID: {self.tradeTarget.current_order_no}已成交 ") @@ -164,7 +168,6 @@ 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.saveProxy() print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 上涨 卖单已成交 订单ID: {self.tradeTarget.current_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}') @@ -174,7 +177,6 @@ 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.saveProxy() print(f"|- 标的{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} 下跌 买单已成交 订单ID: {self.tradeTarget.current_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}') @@ -183,10 +185,27 @@ class SFGridStrategy: # 打印订单信息和订单状态 print(f'|- 非策略内部订单,或订单状态不满足监控条件 {trade.order_id} {trade.stock_code}-{trade.instrument_name} {trade.commission}') finally: + self.refreshPlanPrice() print(f' |- 委托成交通知[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{trade.order_id}]:release lock') self.dataUpdateLock.release() print(f' |- 委托成交通知[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name}-{trade.order_id}]:END') + def refreshPlanPrice(self): + if self.tradeTarget.status == 1: + buyIdx: int = self.tradeTarget.grid_index + 1 # pyright: ignore[reportAssignmentType] + sellIdx: int = self.tradeTarget.grid_index - 1 + if self.tradeTarget.grid_index > 0: # 可以下空单 + self.tradeTarget.plan_sell_price = float(sfgrid_constants.grid_price[sellIdx]) # pyright: ignore[reportAttributeAccessIssue] + else: + self.tradeTarget.plan_sell_price = -1.0 # type: ignore + if self.tradeTarget.grid_index < len(sfgrid_constants.grid_price) - 1: + self.tradeTarget.plan_buy_price = float(sfgrid_constants.grid_price[buyIdx]) # pyright: ignore[reportAttributeAccessIssue] + else: + self.tradeTarget.plan_buy_price = -1.0 # pyright: ignore[reportAttributeAccessIssue] + else: + self.tradeTarget.plan_buy_price = 10.0 # type: ignore + self.tradeTarget.plan_sell_price = -1.0 # type: ignore + self.saveProxy() def getName(self): return "SFGRID" diff --git a/core/strategy_db.py b/core/strategy_db.py index c413949..97f2bed 100644 --- a/core/strategy_db.py +++ b/core/strategy_db.py @@ -13,7 +13,7 @@ class BaseModel(Model): OrderTypeBuy = f'{xtconstant.STOCK_BUY}' # 买 OrderTypeSell = f'{xtconstant.STOCK_SELL}' # 卖 OrderTypeInit = "0" # 建仓 -OrderTypeNone = "None" # 建仓 +OrderTypeNone = "None" # 定义Target类,对应targets表 class TradeTarget(BaseModel): diff --git a/core/ui.py b/core/ui.py index f95ce91..54fcbb1 100644 --- a/core/ui.py +++ b/core/ui.py @@ -3,7 +3,8 @@ from tkinter import ttk, messagebox, filedialog from datetime import datetime import threading import time -from core.eventbus import ActionDisableMarketData, ActionEnableMarketData, ActionEventDeleteTradeTarget, ActionEventDisableTrade, ActionEventEnableTrade, MarketDataUpdate, MarketDataEnabled, MarketDataDisabled, ResultEventTradeDisabled, ResultEventTradeEnabled, ResultEventTradeTargetDeleted, event_bus +import core.eventbus as eBus +from core.logger import LogData, LogLevel from core.strategy_db import TradeTarget import configparser import sfgrid_constants @@ -28,12 +29,13 @@ class TradeTargetUI: # 不再自动启动刷新线程,由市场数据开关控制 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) - event_bus.subscribe(ResultEventTradeTargetDeleted, self.onTradeTargetDeleted) + eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onTradeTargetUpdated) + eBus.event_bus.subscribe(eBus.ResultEventTradeEnabled, self.onTradeEnabled) + eBus.event_bus.subscribe(eBus.ResultEventTradeDisabled, self.onTradeDisabled) + eBus.event_bus.subscribe(eBus.MarketDataEnabled, self.onMarketDataToggled) + eBus.event_bus.subscribe(eBus.MarketDataDisabled, self.onMarketDataToggled) + eBus.event_bus.subscribe(eBus.ResultEventTradeTargetDeleted, self.onTradeTargetDeleted) + eBus.event_bus.subscribe(eBus.EventPrintLog, self.onLog) def start_refresh_thread(self): """启动刷新线程""" @@ -59,11 +61,11 @@ class TradeTargetUI: if id in self.data: del self.data[id] # 添加日志 - self.add_log("INFO", f"交易标的已删除,ID: {id}") + self.add_log(LogLevel.INFO, f"交易标的已删除,ID: {id}") def onMarketDataToggled(self, data:bool): self.market_data_enabled = self.market_data_switch_var.get() - self.add_log("INFO", "市场数据监听已" + ("启用" if data else "禁用")) + self.add_log(LogLevel.INFO, "市场数据监听已" + ("启用" if data else "禁用")) # 同步UI刷新线程状态 if data: self.start_ui_refresh() @@ -71,10 +73,10 @@ class TradeTargetUI: self.stop_ui_refresh() def onTradeEnabled(self, target:TradeTarget): - self.add_log("INFO", f"交易启用: {target.stock_code} - {target.stock_name}") + self.add_log(LogLevel.INFO, f"交易启用: {target.stock_code} - {target.stock_name}") def onTradeDisabled(self, target:TradeTarget): - self.add_log("INFO", f"交易禁用: {target.stock_code} - {target.stock_name}") + self.add_log(LogLevel.INFO, f"交易禁用: {target.stock_code} - {target.stock_name}") def onTradeTargetUpdated(self, target: TradeTarget): @@ -137,11 +139,11 @@ class TradeTargetUI: print(f'市场数据监听开关') self.market_data_enabled = self.market_data_switch_var.get() if self.market_data_enabled: - event_bus.publish(ActionEnableMarketData, True) + eBus.event_bus.publish(eBus.ActionEnableMarketData, True) # 同步开启UI刷新线程 self.start_ui_refresh() else: - event_bus.publish(ActionDisableMarketData, True) + eBus.event_bus.publish(eBus.ActionDisableMarketData, True) # 同步关闭UI刷新线程 self.stop_ui_refresh() @@ -150,14 +152,14 @@ class TradeTargetUI: if not self.refresh_thread_running: self.refresh_thread_running = True self.start_refresh_thread() - self.add_log("INFO", "UI刷新线程已启动") + self.add_log(LogLevel.INFO, "UI刷新线程已启动") def stop_ui_refresh(self): """停止UI刷新线程""" if self.refresh_thread_running: self.stop_refresh_thread() self.refresh_thread_running = False - self.add_log("INFO", "UI刷新线程已停止") + self.add_log(LogLevel.INFO, "UI刷新线程已停止") def create_menu_bar(self): """创建菜单栏""" @@ -330,7 +332,7 @@ class TradeTargetUI: if selected: item = selected[0] values = self.trade_table.item(item)['values'] - self.add_log("DEBUG", f"双击查看详情: {values[0]} - {values[1]}") + self.add_log(LogLevel.DEBUG, f"双击查看详情: {values[0]} - {values[1]}") def get_selected_target(self): """获取选中的交易标的""" @@ -351,10 +353,6 @@ class TradeTargetUI: return None - def onLog(self, level: str, message: str): - """接收外部日志消息并显示在日志组件中""" - self.add_log(level, message) - def start_selected_trade(self): """启动选中的交易""" target = self.get_selected_target() @@ -374,12 +372,12 @@ class TradeTargetUI: if result: target.enabled = True # type: ignore - event_bus.publish(ActionEventEnableTrade, target.get_id()) + eBus.event_bus.publish(eBus.ActionEventEnableTrade, target.get_id()) # self.add_log("INFO", f"已启动交易: {target.stock_code} - {target.stock_name}") # messagebox.showinfo("启动成功", f"已启动 {target.stock_code} ({target.stock_name}) 的交易") def on_trade_enabled(self, target: TradeTarget): - event_bus.publish(ActionEventEnableTrade, target) + eBus.event_bus.publish(eBus.ActionEventEnableTrade, target) def pause_selected_trade(self): """暂停选中的交易""" @@ -400,7 +398,7 @@ class TradeTargetUI: if result: target.enabled = False # type: ignore - event_bus.publish(ActionEventDisableTrade, target.get_id()) + eBus.event_bus.publish(eBus.ActionEventDisableTrade, target.get_id()) # self.add_log("INFO", f"已暂停交易: {target.stock_code} - {target.stock_name}") # messagebox.showinfo("暂停成功", f"已暂停 {target.stock_code} ({target.stock_name}) 的交易") @@ -421,8 +419,8 @@ class TradeTargetUI: if result: # 通过事件总线发出删除动作 - event_bus.publish(ActionEventDeleteTradeTarget, target.get_id()) - self.add_log("INFO", f"已发送删除请求: {target.stock_code} - {target.stock_name}") + eBus.event_bus.publish(eBus.ActionEventDeleteTradeTarget, target.get_id()) + self.add_log(LogLevel.INFO, f"已发送删除请求: {target.stock_code} - {target.stock_name}") def add_trade_target(self): """添加新的交易标的""" @@ -463,7 +461,7 @@ class TradeTargetUI: return # 发布事件通知主控制器添加标的 - event_bus.publish("add_trade_target", stock_code) + eBus.event_bus.publish(eBus.ActionEventAddTradeTarget, stock_code) add_window.destroy() def cancel_add(): @@ -476,7 +474,7 @@ class TradeTargetUI: # 绑定回车键确认 stock_code_entry.bind('', lambda event: confirm_add()) - self.add_log("INFO", "点击添加交易标的按钮") + self.add_log(LogLevel.INFO, "点击添加交易标的按钮") def toggle_log_panel(self): """切换日志面板的显示/隐藏""" @@ -514,18 +512,21 @@ class TradeTargetUI: values = self.trade_table.item(item)['values'] if values and values[0] in selected_values: self.trade_table.selection_add(item) - - def add_log(self, level, message): + + def onLog(self, data:LogData): + self.add_log(data.level, data.message) + + def add_log(self, level:LogLevel, message): """添加日志记录""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.log_table.insert('', 0, values=(timestamp, level, message)) + self.log_table.insert('', 0, values=(timestamp, level.value, message)) def clear_logs(self): """清空日志记录""" # 删除所有日志项 for item in self.log_table.get_children(): self.log_table.delete(item) - self.add_log("INFO", "日志已清空") + self.add_log(LogLevel.DEBUG, "日志已清空") def system_settings(self): """系统设置""" @@ -813,12 +814,12 @@ class TradeTargetUI: sfgrid_constants.initConfig() messagebox.showinfo("成功", f"配置已保存!\n网格价格序列: {grid_price_str}\n部分配置可能需要重启程序后生效。") - self.add_log("INFO", f"系统配置已更新 - 网格数量: {len(grid_prices)}") + self.add_log(LogLevel.INFO, f"系统配置已更新 - 网格数量: {len(grid_prices)}") settings_window.destroy() except Exception as e: messagebox.showerror("错误", f"保存配置失败:{str(e)}") - self.add_log("ERROR", f"保存配置失败: {str(e)}") + self.add_log(LogLevel.ERROR, f"保存配置失败: {str(e)}") def cancel_settings(): """取消设置""" diff --git a/example.db b/example.db index 4b95f6d..7432cba 100644 Binary files a/example.db and b/example.db differ diff --git a/sfgrid_constants.py b/sfgrid_constants.py index 5135219..8464e6c 100644 --- a/sfgrid_constants.py +++ b/sfgrid_constants.py @@ -16,12 +16,7 @@ def initConfig(): config = configparser.ConfigParser() config.read('config.ini') miniQMTPath = config.get('config','miniQMTPath') - print(f'QMTPath: {miniQMTPath}') str_list = config.get('config','grid_price').split(',') grid_price = [float(item) for item in str_list] - print(f'网格设置:{grid_price}') grid_volume = config.getint('config','grid_volume') - # account_no = config.get('config','account_no') - print(f'账号: {account_no}') max_enabled_targets = config.getint('config','max_enabled_targets') - print(f'最大启用目标数: {max_enabled_targets}') diff --git a/starter.py b/starter.py index b870d67..be7e0b3 100644 --- a/starter.py +++ b/starter.py @@ -1,6 +1,7 @@ # coding:utf-8 from core import strategy_db from core.main_controller import SFGridController +from core.logger import LogLevel, PrintLog import sfgrid_constants as sdConstants def startTrade(index: int): @@ -13,9 +14,7 @@ if __name__ == '__main__': sdConstants.initConfig() strategy_db.db.connect() strategy_db.db.create_tables([strategy_db.TradeTarget]) - print('- [成功]数据库模块初始化') - - print(f'{sdConstants.account_no} : {sdConstants.miniQMTPath}') + PrintLog(LogLevel.INFO, '- [成功]数据库模块初始化') ctrl: SFGridController = SFGridController(sdConstants.account_no, sdConstants.miniQMTPath) ctrl.hold()