6 Commits

Author SHA1 Message Date
kyugao 0b916b5c44 Merge branch 'new_structure' of ssh://git.gogao.top:2222/sfgrid into new_structure
# Conflicts:
#	config.py
2026-01-04 17:48:33 +08:00
kyugao 5a26f5f7b3 update 2026-01-04 17:46:48 +08:00
kyugao 66768cb359 update config 2025-12-08 18:08:43 +08:00
kyugao 988947aa1a 适配macos中对pytray支持不好的情况。使用系统菜单。 2025-12-06 00:12:44 +08:00
kyugao b435f12c49 update 2025-12-05 18:06:39 +08:00
kyugao c59d29d52e init new structure 2025-12-05 17:43:13 +08:00
21 changed files with 504 additions and 182 deletions
+6
View File
@@ -1,6 +1,7 @@
import configparser
from pathlib import Path
import sys
from typing import Any
miniQMTPath = r'D:\\Programs\\DTQMT\\userdata_mini' # miniQMT软件的安装路径
# miniQMTPath = ''
@@ -8,6 +9,8 @@ account_no:str = '99082560'
console_log = True
log_level = "INFO"
config : Any
def get_config_path() -> Path:
"""获取配置文件的正确路径(兼容开发环境和打包后的可执行文件)"""
if getattr(sys, 'frozen', False):
@@ -21,6 +24,9 @@ def get_config_path() -> Path:
return base_path / 'config.ini'
def get_config(section:str, key:str):
pass
def save_config(miniQmtPath:str, account_no:str):
"""创建默认配置文件"""
config = configparser.ConfigParser()
+94
View File
@@ -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
+11
View File
@@ -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)
+1 -2
View File
@@ -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):
+2 -17
View File
@@ -1,12 +1,3 @@
# 市场数据监听控制事件
EventMarketActiveSwitch = "market_active_switch" # 市场数据状态变更
MarketDataUpdate = "market_data_update" # 市价更新
MarketOrderCreated = "market_order_created" # 市价单创建
MarketOrderTraded = "market_order_traded" # 市价单成交
# Pring Log
EventPrintLog = "print_log" # 打印日志
class EventBus:
def __init__(self):
self.listeners = {} # 管理各种event的订阅情况
@@ -21,12 +12,6 @@ class EventBus:
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()
# event_bus.publish('my_event', {'key': 'value'})
+7
View File
@@ -0,0 +1,7 @@
from .eventbus import EventBus
# Pring Log
EventPrintLog = "print_log" # 打印日志
# 创建事件总线实例
loggerEBus = EventBus()
+10
View File
@@ -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()
+4 -4
View File
@@ -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}')
+179
View File
@@ -0,0 +1,179 @@
# coding:utf-8
# MainEntry 负责应用主窗口与菜单的统一构建:
# - 通过 build_menu_model 定义跨平台统一的菜单数据结构
# - 在 macOS 上使用 Tk 菜单栏;在非 macOS 上使用 pystray 系统托盘
# - 所有菜单项均绑定到同名处理函数,切换平台无需改动业务逻辑
import tkinter as tk
from core.logger import LogLevel, PrintLog
import threading
import sys
class MainEntry:
def __init__(self, master):
# 初始化 Tk 窗口属性与基础状态
self.master = master
self.master.title("Main Board")
self.master.geometry("800x600")
self.master.configure(bg="#f0f0f0")
self.master.resizable(False, False)
self.master.protocol("WM_DELETE_WINDOW", self.hide_window)
# QMT 开关状态用于动态更新菜单文案
self.qmt_enabled = False
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):
# 菜单模型统一描述所有菜单:
# - 每个分组包含 label 与 items
# - item 支持:label 文案、action 处理函数名、enabled 启用状态、default 默认项、separator 分隔符
# - 文案可根据状态动态生成(如 QMT 开关)
qmt_label = "QMT (已开启)" if self.qmt_enabled else "QMT (已关闭)"
return [
{
"label": "-- 交易大师 --",
"items": [
{"label": "交易复盘", "action": "handler", "enabled": True},
{"label": "市场数据", "action": "handler", "enabled": True},
{"label": "快速下单", "action": "handler", "enabled": True},
],
},
{
"label": "-- 策略交易 --",
"items": [
{"label": "交易看板", "action": "handler", "enabled": True},
{"label": "策略中心", "action": None, "enabled": False},
{"label": "策略定制", "action": None, "enabled": False},
],
},
{
"label": "-- 实时数据 --",
"items": [
{"label": qmt_label, "action": "marketDataSwitch", "enabled": True},
],
},
{
"label": "-- 系统 --",
"items": [
{"label": "控制台", "action": "show_window", "enabled": True, "default": True},
{"label": "设置", "action": "marketDataSwitch", "enabled": True},
{"separator": True},
{"label": "退出", "action": "quit_window", "enabled": True},
],
},
]
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()
if self.systray_supported:
# 非 macOS:延迟导入 pystray 与 PIL,避免在 macOS 上引入不兼容依赖
from PIL import Image
import pystray
image = Image.open("logo.png")
items = []
for group in model:
# 分组标题作为禁用的头部项
items.append(pystray.MenuItem(group["label"], None, enabled=False))
for it in group["items"]:
if it.get("separator"):
items.append(pystray.Menu.SEPARATOR)
else:
fn = getattr(self, it["action"]) if it.get("action") else None
items.append(pystray.MenuItem(it["label"], fn, default=it.get("default", False), enabled=it.get("enabled", True)))
menu = tuple(items)
if self.icon:
# 已存在托盘图标:更新菜单
self.icon.menu = menu
self.icon.update_menu()
else:
# 首次创建托盘图标并在后台线程运行
self.icon = pystray.Icon("name", image, "标题", menu)
self.trayThread = threading.Thread(target=self.icon.run, daemon=True)
self.trayThread.start()
else:
# macOS:使用 Tk 菜单栏
menu_bar = tk.Menu(self.master)
for group in model:
m = tk.Menu(menu_bar, tearoff=0)
for it in group["items"]:
if it.get("separator"):
m.add_separator()
else:
fn = getattr(self, it["action"]) if it.get("action") else None
if it.get("enabled", True) and fn:
m.add_command(label=it["label"], command=fn)
else:
m.add_command(label=it["label"], state="disabled")
menu_bar.add_cascade(label=group["label"], menu=m)
self.master.config(menu=menu_bar)
def marketDataSwitch(self):
# 切换 QMT 开关,并触发菜单重建以更新文案
if self.qmt_enabled:
self.qmt_enabled = False
PrintLog(LogLevel.INFO, "QMT 市场数据已关闭")
else:
self.qmt_enabled = True
PrintLog(LogLevel.INFO, "QMT 市场数据已开启")
self.create_menu()
self.create_dashboard()
def handler(self):
# 通用占位处理:当前仅记录点击行为,后续可替换为具体业务逻辑
PrintLog(LogLevel.INFO, f"点击了")
def hide_window(self):
# 关闭窗口事件:隐藏但不退出应用
PrintLog(LogLevel.INFO, "隐藏主窗口")
self.master.withdraw() # 隐藏主窗口
def show_window(self):
# 显示主窗口;在非 macOS 平台同步让托盘图标可见
if self.icon:
self.icon.visible = True
PrintLog(LogLevel.INFO, "显示主窗口")
self.master.deiconify() # 显示主窗口
def quit_window(self, icon=None):
# 退出应用;在非 macOS 平台时关闭托盘图标
if icon:
icon.stop()
PrintLog(LogLevel.INFO, "退出应用")
self.master.quit()
self.master.destroy()
def run(self):
# 主事件循环入口
self.master.mainloop()
+8
View File
@@ -0,0 +1,8 @@
from qmt import QmtV
from eventbus import marketDataEventBus
qmtv:QmtV = None
def init_qmtv():
global qmtv
qmtv = QmtV()
+7 -10
View File
@@ -9,7 +9,7 @@ from core.logger import LogLevel, PrintLog
from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from xtquant.xttype import StockAccount
from xtquant import xtconstant, xtdata
import core.eventbus as eBus
from eventbus import marketDataEventBus, EventMarketActiveSwitch, MarketDataUpdate, MarketOrderCreated, MarketOrderTraded
class QmtV(XtQuantTraderCallback):
def __init__(self) -> None:
@@ -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:
@@ -118,7 +117,7 @@ class QmtV(XtQuantTraderCallback):
# ====== 市场回调方法 -- 以下方法由XtQuantData调用 ======
def onDataUpdate(self, data):
# 收集所有市场数据用于市场监控
eBus.event_bus.publish(eBus.MarketDataUpdate, data)
marketDataEventBus.publish(marketDataEventBus.MarketDataUpdate, data)
now = time.time()
if now - self.lastMarketDataUpdateTimestamp < 5:
self.isMarketActive = True
@@ -133,7 +132,7 @@ class QmtV(XtQuantTraderCallback):
if tmpMarketStatus != self.isMarketActive and tmpTime - self.lastMarketDataUpdateTimestamp < 5:
tmpMarketStatus = self.isMarketActive
PrintLog(LogLevel.INFO, f'- [市场状态变更] {self.isMarketActive}')
eBus.event_bus.publish(eBus.EventMarketActiveSwitch, self.isMarketActive)
marketDataEventBus.publish(EventMarketActiveSwitch, self.isMarketActive)
if tmpMarketStatus and self.isMarketActive and tmpTime - self.lastMarketDataUpdateTimestamp > 10: # 上次更新市场状态已经超过10秒
self.isMarketActive = False
PrintLog(LogLevel.INFO, f'- [市场状态变更] {self.isMarketActive}')
@@ -171,7 +170,7 @@ class QmtV(XtQuantTraderCallback):
:param trade: XtTrade对象
:return:
"""
eBus.event_bus.publish(eBus.MarketOrderTraded, trade)
marketDataEventBus.publish(MarketOrderTraded, trade)
# stockCode = trade.stock_code
# ctrl:SFGridStrategy = self.stock_trade_ctrl[stockCode]
# # 如果存在对应的StockTradeController,则调用其onDataUpdate方法
@@ -182,7 +181,7 @@ class QmtV(XtQuantTraderCallback):
def on_order_stock_async_response(self, response:XtOrderResponse):
# print(f"委托回调 on_order_stock_async_response 投资备注 {response.order_id} {response.seq} {response.error_msg}{response.strategy_name} {response.order_remark}")
eBus.event_bus.publish(eBus.MarketOrderCreated, response)
marketDataEventBus.publish(MarketOrderCreated, response)
# stockCode = response.order_remark
# ctrl:SFGridStrategy = self.stock_trade_ctrl[stockCode]
@@ -208,5 +207,3 @@ class QmtV(XtQuantTraderCallback):
"""
print(datetime.datetime.now(), status)
qmtv = QmtV()
+7
View File
@@ -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()
+11
View File
@@ -0,0 +1,11 @@
# 软件介绍
软件名称:神之一手交易系统
软件介绍:面向个人的交易管理系统,提供交易记录、复盘工具、持仓管理、资产监控、策略交易等功能。
# 模块介绍
1. /core/daily_review: 每日复盘模块目录
2. /core/market_data: 市场数据模块目录
3. /core/quick_trade: 快速交易模块目录
4. /core/strategy/builder: 策略构建模块目录
5. /core/strategy/trade: 策略交易模块目录
6. /core: 应用核心程序目录
+149
View File
@@ -0,0 +1,149 @@
# coding:utf-8
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import configparser
from core.main_ui import MainWindow
import config as sdConstants
from core.qmt import qmtv
class ConfigWindow:
def __init__(self, root):
self.root = root
self.root.title("系统配置")
self.root.geometry("500x250")
self.root.resizable(False, False)
# 居中显示
self.root.withdraw() # 先隐藏窗口
self.root.update_idletasks()
x = (self.root.winfo_screenwidth() // 2) - (500 // 2)
y = (self.root.winfo_screenheight() // 2) - (250 // 2)
self.root.geometry(f"500x250+{x}+{y}")
self.root.deiconify() # 再显示窗口
self.miniQMTPath = tk.StringVar()
self.account_no = tk.StringVar()
self.create_widgets()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# miniQMT路径配置
path_frame = ttk.Frame(main_frame)
path_frame.pack(fill=tk.X, pady=5)
path_label = ttk.Label(path_frame, text="miniQMT路径:")
path_label.pack(side=tk.LEFT)
path_entry = ttk.Entry(path_frame, textvariable=self.miniQMTPath, width=40)
path_entry.pack(side=tk.LEFT, padx=(10, 5), fill=tk.X, expand=True)
browse_btn = ttk.Button(path_frame, text="浏览", command=self.browse_folder)
browse_btn.pack(side=tk.LEFT)
# 资金账号配置
account_frame = ttk.Frame(main_frame)
account_frame.pack(fill=tk.X, pady=5)
account_label = ttk.Label(account_frame, text="资金账号:")
account_label.pack(side=tk.LEFT)
account_entry = ttk.Entry(account_frame, textvariable=self.account_no, width=40)
account_entry.pack(side=tk.LEFT, padx=(10, 0))
# 说明文本
info_label = ttk.Label(
main_frame,
text="请配置miniQMT的userdata_mini路径和资金账号\n路径示例: D:/Programs/DTQMT/userdata_mini",
foreground="gray"
)
info_label.pack(pady=10)
# 按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=10)
save_btn = ttk.Button(button_frame, text="保存配置", command=self.save_config)
save_btn.pack(side=tk.RIGHT)
cancel_btn = ttk.Button(button_frame, text="取消", command=self.root.destroy)
cancel_btn.pack(side=tk.RIGHT, padx=(0, 10))
def browse_folder(self):
folder_selected = filedialog.askdirectory()
if folder_selected:
self.miniQMTPath.set(folder_selected)
def save_config(self):
mini_qmt_path = self.miniQMTPath.get().strip()
account_number = self.account_no.get().strip()
# 检查miniQMT路径
if not mini_qmt_path:
messagebox.showerror("错误", "请选择miniQMT路径")
return
if not os.path.exists(mini_qmt_path):
messagebox.showerror("错误", "miniQMT路径不存在")
return
# 检查账号
if not account_number:
messagebox.showerror("错误", "请输入资金账号")
return
# 保存配置
try:
sdConstants.save_config(mini_qmt_path.replace('\\', '/'), account_number)
messagebox.showinfo("成功", "配置已保存")
self.root.destroy()
except Exception as e:
messagebox.showerror("错误", f"保存配置失败: {str(e)}")
def check_and_create_config():
"""检查配置文件,如果不存在则打开配置窗口"""
root = tk.Tk()
config_window = ConfigWindow(root)
root.mainloop()
def initialize_system():
"""初始化系统"""
try:
while True:
# 初始化配置
if sdConstants.exist_config() and sdConstants.initConfig():
# 初始化qmtv
qmtv.init_qmtv()
connected = qmtv.connect()
if connected:
# 连接成功,启动主窗口
window = MainWindow(sdConstants.log_level)
window.run()
break
else:
option = messagebox.askokcancel("连接失败", "QMT连接失败,请检查")
if option:
check_and_create_config()
else:
break
else:
option = messagebox.askokcancel("错误", "请检查配置")
if option:
check_and_create_config()
else:
break
except Exception as e:
messagebox.showerror("错误", f"系统初始化失败: {str(e)}")
if __name__ == "__main__":
import tkinter as tk
root = tk.Tk()
app = MainBoardWindow(root)
app.run()
# initialize_system()
+8 -149
View File
@@ -1,153 +1,12 @@
# coding:utf-8
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import configparser
from core.main_ui import MainWindow
import config as sdConstants
from core.qmt import qmtv
from core.main_entry import MainEntry
class ConfigWindow:
def __init__(self, root):
self.root = root
self.root.title("系统配置")
self.root.geometry("500x250")
self.root.resizable(False, False)
# 居中显示
self.root.withdraw() # 先隐藏窗口
self.root.update_idletasks()
x = (self.root.winfo_screenwidth() // 2) - (500 // 2)
y = (self.root.winfo_screenheight() // 2) - (250 // 2)
self.root.geometry(f"500x250+{x}+{y}")
self.root.deiconify() # 再显示窗口
self.miniQMTPath = tk.StringVar()
self.account_no = tk.StringVar()
self.create_widgets()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# miniQMT路径配置
path_frame = ttk.Frame(main_frame)
path_frame.pack(fill=tk.X, pady=5)
path_label = ttk.Label(path_frame, text="miniQMT路径:")
path_label.pack(side=tk.LEFT)
path_entry = ttk.Entry(path_frame, textvariable=self.miniQMTPath, width=40)
path_entry.pack(side=tk.LEFT, padx=(10, 5), fill=tk.X, expand=True)
browse_btn = ttk.Button(path_frame, text="浏览", command=self.browse_folder)
browse_btn.pack(side=tk.LEFT)
# 资金账号配置
account_frame = ttk.Frame(main_frame)
account_frame.pack(fill=tk.X, pady=5)
account_label = ttk.Label(account_frame, text="资金账号:")
account_label.pack(side=tk.LEFT)
account_entry = ttk.Entry(account_frame, textvariable=self.account_no, width=40)
account_entry.pack(side=tk.LEFT, padx=(10, 0))
# 说明文本
info_label = ttk.Label(
main_frame,
text="请配置miniQMT的userdata_mini路径和资金账号\n路径示例: D:/Programs/DTQMT/userdata_mini",
foreground="gray"
)
info_label.pack(pady=10)
# 按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=10)
save_btn = ttk.Button(button_frame, text="保存配置", command=self.save_config)
save_btn.pack(side=tk.RIGHT)
cancel_btn = ttk.Button(button_frame, text="取消", command=self.root.destroy)
cancel_btn.pack(side=tk.RIGHT, padx=(0, 10))
def browse_folder(self):
folder_selected = filedialog.askdirectory()
if folder_selected:
self.miniQMTPath.set(folder_selected)
def save_config(self):
mini_qmt_path = self.miniQMTPath.get().strip()
account_number = self.account_no.get().strip()
# 检查miniQMT路径
if not mini_qmt_path:
messagebox.showerror("错误", "请选择miniQMT路径")
return
if not os.path.exists(mini_qmt_path):
messagebox.showerror("错误", "miniQMT路径不存在")
return
# 检查账号
if not account_number:
messagebox.showerror("错误", "请输入资金账号")
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)
messagebox.showinfo("成功", "配置已保存")
self.root.destroy()
except Exception as e:
messagebox.showerror("错误", f"保存配置失败: {str(e)}")
def check_and_create_config():
"""检查配置文件,如果不存在则打开配置窗口"""
# 这是应用的启动入口程序,负责初始化并启动主窗口。
# 它创建一个Tkinter根窗口,实例化主窗口类MainBoardWindow
# 并调用其run方法启动主事件循环。
if __name__ == "__main__":
import tkinter as tk
root = tk.Tk()
config_window = ConfigWindow(root)
root.mainloop()
def initialize_system():
"""初始化系统"""
try:
while True:
# 初始化配置
if sdConstants.exist_config() and sdConstants.initConfig():
# 初始化qmtv
qmtv.init_qmtv()
connected = qmtv.connect()
if connected:
# 连接成功,启动主窗口
window = MainWindow(sdConstants.log_level)
window.run()
break
else:
option = messagebox.askokcancel("连接失败", "QMT连接失败,请检查")
if option:
check_and_create_config()
else:
break
else:
option = messagebox.askokcancel("错误", "请检查配置")
if option:
check_and_create_config()
else:
break
except Exception as e:
messagebox.showerror("错误", f"系统初始化失败: {str(e)}")
if __name__ == '__main__':
initialize_system()
app = MainEntry(root)
app.run()