Compare commits
6 Commits
6b3b1a1f76
...
0b916b5c44
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b916b5c44 | |||
| 5a26f5f7b3 | |||
| 66768cb359 | |||
| 988947aa1a | |||
| b435f12c49 | |||
| c59d29d52e |
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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'})
|
||||
@@ -0,0 +1,7 @@
|
||||
from .eventbus import EventBus
|
||||
|
||||
# Pring Log
|
||||
EventPrintLog = "print_log" # 打印日志
|
||||
|
||||
# 创建事件总线实例
|
||||
loggerEBus = EventBus()
|
||||
@@ -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
@@ -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}')
|
||||
|
||||
@@ -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()
|
||||
@@ -0,0 +1,8 @@
|
||||
from qmt import QmtV
|
||||
from eventbus import marketDataEventBus
|
||||
|
||||
qmtv:QmtV = None
|
||||
|
||||
def init_qmtv():
|
||||
global qmtv
|
||||
qmtv = QmtV()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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: 应用核心程序目录
|
||||
@@ -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
@@ -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()
|
||||
Reference in New Issue
Block a user