From 5a26f5f7b3bdead20649229f6de1897f38b40908 Mon Sep 17 00:00:00 2001 From: AdamGao Date: Sun, 4 Jan 2026 17:46:48 +0800 Subject: [PATCH] update --- config.py | 58 -------------------- core/config/config.py | 94 ++++++++++++++++++++++++++++++++ core/config/config_model.py | 11 ++++ core/database.py | 3 +- core/{ => ebus}/eventbus.py | 18 +----- core/ebus/logger_ebus.py | 7 +++ core/ebus/market_data_ebus.py | 10 ++++ core/logger.py | 8 +-- core/main_entry.py | 30 ++++++++++ core/market_data/event_bus.py | 2 - core/market_data/qmt.py | 5 +- core/models/stock_info.py | 7 +++ core/strategy/strategy_window.py | 11 +--- 13 files changed, 170 insertions(+), 94 deletions(-) delete mode 100644 config.py create mode 100644 core/config/config.py create mode 100644 core/config/config_model.py rename core/{ => ebus}/eventbus.py (54%) create mode 100644 core/ebus/logger_ebus.py create mode 100644 core/ebus/market_data_ebus.py delete mode 100644 core/market_data/event_bus.py create mode 100644 core/models/stock_info.py diff --git a/config.py b/config.py deleted file mode 100644 index 2e28300..0000000 --- a/config.py +++ /dev/null @@ -1,58 +0,0 @@ -import configparser -from pathlib import Path -import sys - -miniQMTPath = r'D:\\Programs\\DTQMT\\userdata_mini' # miniQMT软件的安装路径 -# miniQMTPath = '' -account_no:str = '99082560' -console_log = True -log_level = "INFO" - -def get_config_path() -> Path: - """获取配置文件的正确路径(兼容开发环境和打包后的可执行文件)""" - if getattr(sys, 'frozen', False): - # 打包后的可执行文件环境 - # sys._MEIPASS是PyInstaller解压临时文件的目录 - # 配置文件应该放在可执行文件同目录下 - base_path = Path(sys.executable).parent - else: - # 开发环境 - base_path = Path(__file__).resolve().parent - - return base_path / 'config.ini' - -def save_config(miniQmtPath:str, account_no:str): - """创建默认配置文件""" - config = configparser.ConfigParser() - config['config'] = { - 'miniQMTPath': miniQmtPath, - 'account_no': account_no - } - config_path = get_config_path() - with open(config_path, 'w') as configfile: - config.write(configfile) - print(f'已创建默认配置文件: {config_path}') - -def exist_config() -> bool: - """检查配置文件是否存在""" - config_path = get_config_path() - return config_path.exists() - -def initConfig() -> bool: - global miniQMTPath, account_no, log_level - - # 获取配置文件路径 - config_path = get_config_path() - - config = configparser.ConfigParser() - config.read(config_path, encoding='utf-8') - miniQMTPath = config.get('config','miniQMTPath') - account_no = config.get('config','account_no') - log_level = config.get('config','log_level') - - # 判断miniQMTPath是否为空,并且目录是否存在 - if not miniQMTPath or not Path(miniQMTPath).exists(): - print('请先配置miniQMTPath') - return False - else: - return True diff --git a/core/config/config.py b/core/config/config.py new file mode 100644 index 0000000..f139a21 --- /dev/null +++ b/core/config/config.py @@ -0,0 +1,94 @@ +# Global configuration variables +# Define these BEFORE imports to avoid circular dependency issues with logger +console_log = True +miniQMTPath = None +miniQMTAccount = None +log_level = "1" + +from pathlib import Path +from core.config.config_model import ConfigModel, CfgKeyLogLevel, CfgKeyMiniQmtPath, CfgKeyMiniQmtAccount, CfgKeyConsoleLog +from core.database import db + +def initConfig() -> bool: + """Initialize configuration from database""" + global miniQMTPath, miniQMTAccount, log_level, console_log + + # Ensure connection and tables + db.connect(reuse_if_open=True) + if not db.table_exists(ConfigModel._meta.table_name): + db.create_tables([ConfigModel]) + + # Check and initialize keys + _init_key(CfgKeyLogLevel, "1") + _init_key(CfgKeyConsoleLog, "True") + _init_key(CfgKeyMiniQmtPath, None) + _init_key(CfgKeyMiniQmtAccount, None) + + # Load values + try: + miniQMTPath = _get_value(CfgKeyMiniQmtPath) + miniQMTAccount = _get_value(CfgKeyMiniQmtAccount) + log_level = _get_value(CfgKeyLogLevel) or "1" + console_log = _get_value(CfgKeyConsoleLog) or "True" + console_log = console_log.lower() == "true" + + # console_log is not in DB currently, keeping default True or could add to DB + except Exception as e: + print(f"Error loading config: {e}") + return False + + # Validate path + if not miniQMTPath or not Path(miniQMTPath).exists(): + print('请先配置miniQMTPath') + return False + + return True + +def _init_key(key: str, default_value: str | None): + """Helper to initialize a key if it doesn't exist""" + try: + ConfigModel.get(ConfigModel.key == key) + except ConfigModel.DoesNotExist: + ConfigModel.create(key=key, value=default_value) + +def _get_value(key: str) -> str | None: + """Helper to get value safely""" + try: + return ConfigModel.get(ConfigModel.key == key).value + except ConfigModel.DoesNotExist: + return None + +def save_config(key: str, value: str): + """Save configuration to database""" + _update_key(key, value) + print(f'配置已更新: {key}={value}') + +def _update_key(key: str, value: str): + try: + record = ConfigModel.get(ConfigModel.key == key) + record.value = value + record.save() + except ConfigModel.DoesNotExist: + ConfigModel.create(key=key, value=value) + +def exist_config() -> bool: + """Check if essential config exists""" + path = _get_value(CfgKeyMiniQmtPath) + account = _get_value(CfgKeyMiniQmtAccount) + return bool(path and account) + +def getLogLevel() -> str: + """获取配置中的日志级别""" + return log_level + +def getConsoleLog() -> bool: + """获取配置中的控制台日志设置""" + return console_log + +def getMiniQMTPath() -> str | None: + """获取配置中的miniQMT路径""" + return miniQMTPath + +def getMiniQMTAccount() -> str | None: + """获取配置的miniQMT账号""" + return miniQMTAccount diff --git a/core/config/config_model.py b/core/config/config_model.py new file mode 100644 index 0000000..26a5bee --- /dev/null +++ b/core/config/config_model.py @@ -0,0 +1,11 @@ +from peewee import CharField +from core.database import BaseModel, db + +CfgKeyLogLevel = "log_level" +CfgKeyConsoleLog = "console_log" +CfgKeyMiniQmtPath = "miniQMTPath" +CfgKeyMiniQmtAccount = "miniQMTAccount" + +class ConfigModel(BaseModel): + key = CharField(unique=True) + value = CharField(null=True) \ No newline at end of file diff --git a/core/database.py b/core/database.py index 2631fa0..db1f60a 100644 --- a/core/database.py +++ b/core/database.py @@ -1,10 +1,9 @@ from peewee import SqliteDatabase, Model -from core.logger import LogLevel, PrintLog # 连接到SQLite数据库 db: SqliteDatabase = SqliteDatabase('example.db') db.connect() -PrintLog(LogLevel.INFO, '- [成功]数据库连接') +print("Database connected") # 定义基础模型类 class BaseModel(Model): diff --git a/core/eventbus.py b/core/ebus/eventbus.py similarity index 54% rename from core/eventbus.py rename to core/ebus/eventbus.py index baa07fc..6c47822 100644 --- a/core/eventbus.py +++ b/core/ebus/eventbus.py @@ -1,16 +1,3 @@ - -# 市场数据监听控制事件 -EventMarketActiveSwitch = "market_active_switch" # 市场数据状态变更 -MarketDataUpdate = "market_data_update" # 市价更新 -MarketOrderCreated = "market_order_created" # 市价单创建 -MarketOrderTraded = "market_order_traded" # 市价单成交 -# Pring Log -EventPrintLog = "print_log" # 打印日志 - -# 订阅与发布事件示例 -# event_bus.subscribe('my_event', handle_event) -# event_bus.publish('my_event', {'key': 'value'}) - class EventBus: def __init__(self): self.listeners = {} # 管理各种event的订阅情况 @@ -25,5 +12,6 @@ class EventBus: for listener in self.listeners[event_type]: listener(data) -# 创建事件总线实例 -event_bus = EventBus() \ No newline at end of file +# 订阅与发布事件示例 +# event_bus.subscribe('my_event', handle_event) +# event_bus.publish('my_event', {'key': 'value'}) \ No newline at end of file diff --git a/core/ebus/logger_ebus.py b/core/ebus/logger_ebus.py new file mode 100644 index 0000000..525d0a7 --- /dev/null +++ b/core/ebus/logger_ebus.py @@ -0,0 +1,7 @@ +from .eventbus import EventBus + +# Pring Log +EventPrintLog = "print_log" # 打印日志 + +# 创建事件总线实例 +loggerEBus = EventBus() \ No newline at end of file diff --git a/core/ebus/market_data_ebus.py b/core/ebus/market_data_ebus.py new file mode 100644 index 0000000..99d7c12 --- /dev/null +++ b/core/ebus/market_data_ebus.py @@ -0,0 +1,10 @@ +from eventbus import EventBus + +# 市场数据监听控制事件 +EventMarketActiveSwitch = "market_active_switch" # 市场数据状态变更 +MarketDataUpdate = "market_data_update" # 市价更新 +MarketOrderCreated = "market_order_created" # 市价单创建 +MarketOrderTraded = "market_order_traded" # 市价单成交 + +# 创建事件总线实例 +marketDataEventBus = EventBus() \ No newline at end of file diff --git a/core/logger.py b/core/logger.py index 019bc09..ee09e86 100644 --- a/core/logger.py +++ b/core/logger.py @@ -1,7 +1,7 @@ from enum import Enum -from core.eventbus import EventPrintLog, event_bus -import config +from core.ebus.logger_ebus import EventPrintLog, loggerEBus +from core.config import config as config class LogLevel(Enum): @@ -21,6 +21,6 @@ class LogData: def PrintLog(level:LogLevel, message:str): data = LogData(level, message) - event_bus.publish(EventPrintLog, data) - if config.console_log: + loggerEBus.publish(EventPrintLog, data) + if config.getConsoleLog(): print(f'{level.name} {message}') diff --git a/core/main_entry.py b/core/main_entry.py index 3422ee3..f55e330 100644 --- a/core/main_entry.py +++ b/core/main_entry.py @@ -22,8 +22,14 @@ class MainEntry: self.icon = None # 非 macOS 使用系统托盘(pystray);macOS 使用原生菜单栏 self.systray_supported = sys.platform != "darwin" + + # 主内容容器 + self.main_frame = tk.Frame(self.master, bg="#f0f0f0") + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) + # 首次进入根据平台构建菜单 self.create_menu() + self.create_dashboard() def build_menu_model(self): # 菜单模型统一描述所有菜单: @@ -65,6 +71,29 @@ class MainEntry: }, ] + def create_dashboard(self): + # 根据菜单模型构建主窗口按钮面板 + for widget in self.main_frame.winfo_children(): + widget.destroy() + + model = self.build_menu_model() + + for group in model: + # 为每个分组创建 LabelFrame + group_frame = tk.LabelFrame(self.main_frame, text=group["label"], bg="#f0f0f0", padx=10, pady=10) + group_frame.pack(fill=tk.X, pady=10, padx=10) + + for it in group["items"]: + if it.get("separator"): + continue + + fn = getattr(self, it["action"]) if it.get("action") else None + state = tk.NORMAL if it.get("enabled", True) else tk.DISABLED + + # 创建按钮 + btn = tk.Button(group_frame, text=it["label"], command=fn, state=state) + btn.pack(side=tk.LEFT, padx=5) + def create_menu(self): # 根据统一菜单模型与平台类型,渲染到系统托盘或 Tk 菜单栏 model = self.build_menu_model() @@ -119,6 +148,7 @@ class MainEntry: self.qmt_enabled = True PrintLog(LogLevel.INFO, "QMT 市场数据已开启") self.create_menu() + self.create_dashboard() def handler(self): # 通用占位处理:当前仅记录点击行为,后续可替换为具体业务逻辑 diff --git a/core/market_data/event_bus.py b/core/market_data/event_bus.py deleted file mode 100644 index bbc34f6..0000000 --- a/core/market_data/event_bus.py +++ /dev/null @@ -1,2 +0,0 @@ -# 创建事件总线实例 -marketDataEventBus = EventBus() diff --git a/core/market_data/qmt.py b/core/market_data/qmt.py index e25e0d4..9a0a05a 100644 --- a/core/market_data/qmt.py +++ b/core/market_data/qmt.py @@ -20,7 +20,6 @@ class QmtV(XtQuantTraderCallback): self.isMarketActive = False self.refresh_thread = threading.Thread(target=self.marketStatusNotifier, daemon=True) self.refresh_thread.start() - # time.sleep(3.1) def getTrader(self) -> XtQuantTrader: return self.xttrader @@ -42,8 +41,8 @@ class QmtV(XtQuantTraderCallback): else: self.inited = True - self.account = StockAccount(config.account_no, 'STOCK') # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue] - PrintLog(LogLevel.INFO, f'- [成功]交易账号对象初始化完成, 账号: {config.account_no}') # pyright: ignore[reportOptionalMemberAccess] + self.account = StockAccount(config.miniQMTAccount, 'STOCK') # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue] + PrintLog(LogLevel.INFO, f'- [成功]交易账号对象初始化完成, 账号: {config.miniQMTAccount}') # pyright: ignore[reportOptionalMemberAccess] subscribe_result = self.xttrader.subscribe(self.account) PrintLog(LogLevel.INFO, f'- [{'成功' if subscribe_result == 0 else '失败'}:{subscribe_result}]交易状态订阅') if subscribe_result != 0: diff --git a/core/models/stock_info.py b/core/models/stock_info.py new file mode 100644 index 0000000..841f3b8 --- /dev/null +++ b/core/models/stock_info.py @@ -0,0 +1,7 @@ +from peewee import CharField, DateField + +from core.database import BaseModel, db + +class StockInfo(BaseModel): + stock_code = CharField(unique=True, primary_key=True) + stock_name = CharField() \ No newline at end of file diff --git a/core/strategy/strategy_window.py b/core/strategy/strategy_window.py index e053614..8c5d18a 100644 --- a/core/strategy/strategy_window.py +++ b/core/strategy/strategy_window.py @@ -97,17 +97,8 @@ class ConfigWindow: return # 保存配置 - config = configparser.ConfigParser() - config['config'] = { - 'miniQMTPath': mini_qmt_path.replace('\\', '/'), - 'account_no': account_number, - 'log_level': 'INFO' - } - - config_path = sdConstants.get_config_path() try: - with open(config_path, 'w') as configfile: - config.write(configfile) + sdConstants.save_config(mini_qmt_path.replace('\\', '/'), account_number) messagebox.showinfo("成功", "配置已保存") self.root.destroy() except Exception as e: