This commit is contained in:
2025-11-12 17:57:45 +08:00
parent 1193dc2f69
commit 81d0131a7b
5 changed files with 345 additions and 334 deletions
+3 -4
View File
@@ -1,6 +1,5 @@
[config] [config]
miniqmtpath = D:/Programs/DTQMT/userdata_mini miniqmtpath = D:/Programs/DTQMT_MN/userdata_mini
grid_price = 11.0,10.0,9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0 ; account_no = 99082560
grid_volume = 100 account_no = 89009170
account_no = 99082560
+4
View File
@@ -0,0 +1,4 @@
import xtquant.xtconstant as xtconstant
HeatTypeUpStop = "UpStop" # 涨停
HeatTypeDragonTiger = "DragonTiger" # 龙虎榜
+11
View File
@@ -0,0 +1,11 @@
from peewee import CharField, DateField
from core.database import BaseModel, db
class HeatStock(BaseModel):
stock_code = CharField(unique=True)
stock_name = CharField()
heat_type = CharField()
date = DateField()
db.create_tables([HeatStock])
+45 -8
View File
@@ -1,6 +1,6 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from core.logger import LogLevel, PrintLog from core.logger import LogLevel
from core.sfgrid.sfgrid_ui import TradeTargetUI from core.sfgrid.sfgrid_ui import TradeTargetUI
@@ -35,16 +35,20 @@ class MainWindow:
tab_bar_frame = ttk.Frame(content_area) tab_bar_frame = ttk.Frame(content_area)
tab_bar_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) tab_bar_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
# 创建自定义样式
self.create_custom_styles()
# 创建Tab按钮(垂直排列,文字垂直显示) # 创建Tab按钮(垂直排列,文字垂直显示)
self.tab_buttons = [] self.tab_buttons = []
strategy_names = ["蒙派\n策略", "涨停\n分析"] strategy_names = ["蒙派\n策略", "涨停\n复盘"]
for idx, name in enumerate(strategy_names): for idx, name in enumerate(strategy_names):
btn = ttk.Button( btn = ttk.Button(
tab_bar_frame, tab_bar_frame,
text=name, text=name,
command=lambda i=idx: self.switch_strategy_tab(i), command=lambda i=idx: self.switch_strategy_tab(i),
width=4 width=4,
style='Bookmark.TButton' # 使用自定义书签样式
) )
btn.pack(side=tk.TOP, pady=2, fill=tk.X) btn.pack(side=tk.TOP, pady=2, fill=tk.X)
self.tab_buttons.append(btn) self.tab_buttons.append(btn)
@@ -98,6 +102,38 @@ class MainWindow:
# 默认显示第一个策略 # 默认显示第一个策略
self.switch_strategy_tab(0) self.switch_strategy_tab(0)
def create_custom_styles(self):
"""创建自定义样式"""
style = ttk.Style()
# 创建书签样式
style.configure(
'Bookmark.TButton',
relief='flat',
borderwidth=1,
padding=(5, 10),
foreground='black',
background='#FFE599', # 浅黄色背景,类似便签纸
font=('Arial', 10, 'bold')
)
# 设置焦点样式(选中状态)
style.map(
'Bookmark.TButton',
background=[('active', '#F1C232'), ('pressed', '#F1C232')],
relief=[('pressed', 'sunken')]
)
# 创建选中状态的书签样式
style.configure(
'SelectedBookmark.TButton',
relief='flat',
borderwidth=1,
padding=(5, 10),
background='#3D85C6', # 蓝色背景表示选中状态
font=('Arial', 10, 'bold')
)
def create_global_log_panel(self, parent): def create_global_log_panel(self, parent):
"""创建全局日志面板""" """创建全局日志面板"""
# 日志区域(默认隐藏) # 日志区域(默认隐藏)
@@ -143,7 +179,6 @@ class MainWindow:
# 删除所有日志项 # 删除所有日志项
for item in self.log_table.get_children(): for item in self.log_table.get_children():
self.log_table.delete(item) self.log_table.delete(item)
self.add_log(LogLevel.DEBUG, "日志已清空")
def create_strategy_frames(self, strategy_names): def create_strategy_frames(self, strategy_names):
"""创建各个策略的Frame""" """创建各个策略的Frame"""
@@ -184,9 +219,12 @@ class MainWindow:
def update_tab_button_styles(self): def update_tab_button_styles(self):
"""更新Tab按钮的样式以显示选中状态""" """更新Tab按钮的样式以显示选中状态"""
# 注意:ttk.Button的样式需要通过ttk.Style来设置 # 重置所有按钮为普通书签样式
# 这里简化处理,仅作为接口预留 for i, btn in enumerate(self.tab_buttons):
pass if i == self.current_strategy_index:
btn.configure(style='SelectedBookmark.TButton') # 选中状态
else:
btn.configure(style='Bookmark.TButton') # 普通状态
def toggle_log_panel(self): def toggle_log_panel(self):
"""切换日志面板的显示/隐藏""" """切换日志面板的显示/隐藏"""
@@ -211,4 +249,3 @@ class MainWindow:
def run(self): def run(self):
"""运行程序""" """运行程序"""
self.root.mainloop() self.root.mainloop()
+278 -318
View File
@@ -8,7 +8,6 @@ import time
from core import constants from core import constants
import core.eventbus as eBus import core.eventbus as eBus
from core.logger import LogLevel, PrintLog from core.logger import LogLevel, PrintLog
from core.sfgrid.bus_events import ActionEventAddTradeTarget
from core.sfgrid.model import SFGridTradeTarget from core.sfgrid.model import SFGridTradeTarget
import configparser import configparser
import config import config
@@ -27,9 +26,6 @@ class TradeTargetUI(ttk.Frame):
self.init_trade_target_pool() self.init_trade_target_pool()
eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onMarketDataUpdated) eBus.event_bus.subscribe(eBus.MarketDataUpdate, self.onMarketDataUpdated)
# 创建刷新线程标志
self.refresh_thread_running = False # 默认不启动刷新线程
# 市场监控数据 # 市场监控数据
self.marketData: dict[str, Any] = {} # 存储市场数据 {stock_code: {stock_name, last_price, time}} self.marketData: dict[str, Any] = {} # 存储市场数据 {stock_code: {stock_name, last_price, time}}
@@ -39,7 +35,7 @@ class TradeTargetUI(ttk.Frame):
# 创建界面 # 创建界面
self.create_ui() self.create_ui()
self.start_ui_refresh() self.start_refresh_thread()
def onMarketDataUpdated(self, data): def onMarketDataUpdated(self, data):
# 收集所有市场数据用于市场监控 # 收集所有市场数据用于市场监控
@@ -48,7 +44,7 @@ class TradeTargetUI(ttk.Frame):
id:int = self.stockCodeIdMap[stock_code] id:int = self.stockCodeIdMap[stock_code]
tradeTarget = self.tradeTargetData[id] tradeTarget = self.tradeTargetData[id]
lastPrice = float("{:.3f}".format(tickData['lastPrice'])) lastPrice = float("{:.3f}".format(tickData['lastPrice']))
print(f'股票代码: {stock_code} {id}, 市场数据更新 {lastPrice}') PrintLog(LogLevel.INFO, f'股票代码: {stock_code} {id}, 市场数据更新 {lastPrice}')
tradeTarget.market_price = lastPrice # type: ignore tradeTarget.market_price = lastPrice # type: ignore
self.updateTradeTarget(tradeTarget) self.updateTradeTarget(tradeTarget)
stock_controller: SFGridStrategy = self.strategy_ctrl[id] stock_controller: SFGridStrategy = self.strategy_ctrl[id]
@@ -72,61 +68,41 @@ class TradeTargetUI(ttk.Frame):
'time': current_time 'time': current_time
} }
def refresh_targets(self):
# 更新标的池
results = SFGridTradeTarget.select()
for temp in results:
result :SFGridTradeTarget = temp
self.tradeTargetData[result.get_id()] = result
self.stockCodeIdMap[str(result.stock_code)] = result.get_id()
def init_trade_target_pool(self): def init_trade_target_pool(self):
self.refresh_targets() results = SFGridTradeTarget.select()
for temp in results:
tradeTarget :SFGridTradeTarget = temp
id = tradeTarget.get_id()
for id, tradeTarget in self.tradeTargetData.items():
status = "新建" if tradeTarget.status == 0 else "已建初始仓" status = "新建" if tradeTarget.status == 0 else "已建初始仓"
PrintLog(LogLevel.INFO, f' [序号-{id}] 股票代码: {tradeTarget.stock_code}-{tradeTarget.stock_name} 当前持仓: {qmtv.getStockPosition(tradeTarget.stock_code)} 网格索引: {tradeTarget.grid_index} 基准价格 {tradeTarget.getPriceGrid()[tradeTarget.grid_index]} 状态: {status} 启用交易线程: {'自动交易中' if tradeTarget.enabled else '交易已停止'}') # type: ignore
tradeTarget.current_position = qmtv.getStockPosition(tradeTarget.stock_code) # type: ignore tradeTarget.current_position = qmtv.getStockPosition(tradeTarget.stock_code) # type: ignore
result = tradeTarget.save() tradeTarget.save()
PrintLog(LogLevel.INFO, f' |- 同步[{tradeTarget.stock_code}-{tradeTarget.stock_name}]持仓信息[{'成功' if result == 1 else '失败'}]') PrintLog(LogLevel.INFO, f' [序号-{id}] 股票代码: {tradeTarget.stock_code}-{tradeTarget.stock_name} 当前持仓: {qmtv.getStockPosition(tradeTarget.stock_code)} 网格索引: {tradeTarget.grid_index} 基准价格 {tradeTarget.getPriceGrid()[tradeTarget.grid_index]} 状态: {status} 启用交易线程: {'自动交易中' if tradeTarget.enabled else '交易已停止'}') # type: ignore
stockTradeController = SFGridStrategy(tradeTarget) # type: ignore stockTradeController = SFGridStrategy(tradeTarget) # type: ignore
self.strategy_ctrl[id] = stockTradeController # pyright: ignore[reportArgumentType] self.strategy_ctrl[id] = stockTradeController # pyright: ignore[reportArgumentType]
self.updateTradeTarget(tradeTarget) self.updateTradeTarget(tradeTarget)
PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.tradeTargetData)} 个标的') PrintLog(LogLevel.INFO, f'- [成功]交易标的信息初始化, 共 {len(self.tradeTargetData)} 个标的')
def start_refresh_thread(self):
"""启动刷新线程"""
if not hasattr(self, 'refresh_thread') or not self.refresh_thread.is_alive():
self.refresh_thread = threading.Thread(target=self.refresh_loop, daemon=True)
self.refresh_thread.start()
def refresh_loop(self):
"""刷新循环"""
while self.refresh_thread_running:
# 在主线程中更新UI
self.after(0, self.refresh_table)
self.after(0, self.populate_market_table)
time.sleep(0.2) # 每0.5秒刷新一次
def stop_refresh_thread(self):
"""停止刷新线程"""
self.refresh_thread_running = False
def onTradeEnabled(self, target:SFGridTradeTarget):
PrintLog(LogLevel.INFO, f"交易启用: {target.stock_code} - {target.stock_name}")
def onTradeDisabled(self, target:SFGridTradeTarget):
PrintLog(LogLevel.INFO, f"交易禁用: {target.stock_code} - {target.stock_name}")
def updateTradeTarget(self, target: SFGridTradeTarget): def updateTradeTarget(self, target: SFGridTradeTarget):
# 更新或添加数据到本地缓存 # 更新或添加数据到本地缓存
self.tradeTargetData[target.get_id()] = target self.tradeTargetData[target.get_id()] = target
self.stockCodeIdMap[target.stock_code] = target.get_id() # type: ignore self.stockCodeIdMap[target.stock_code] = target.get_id() # type: ignore
def start_refresh_thread(self):
"""启动刷新线程"""
self.refresh_thread = threading.Thread(target=self.refresh_loop, daemon=True)
self.refresh_thread.start()
PrintLog(LogLevel.INFO, "UI刷新线程已启动")
def refresh_loop(self):
"""刷新循环"""
while True:
self.after(0, self.refresh_table)
self.after(0, self.populate_market_table)
time.sleep(0.2) # 每0.5秒刷新一次
def create_ui(self): def create_ui(self):
"""创建UI界面""" """创建UI界面"""
# 主框架(使用self作为父容器) # 主框架(使用self作为父容器)
@@ -144,33 +120,16 @@ class TradeTargetUI(ttk.Frame):
command=self.pause_selected_trade, width=12).pack(side=tk.LEFT, padx=2) command=self.pause_selected_trade, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text=" 添加标的", ttk.Button(toolbar_frame, text=" 添加标的",
command=self.add_trade_target, width=12).pack(side=tk.LEFT, padx=2) command=self.add_trade_target, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text="🛠 网格配置",
command=self.grid_settings, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text="🗑 删除标的", ttk.Button(toolbar_frame, text="🗑 删除标的",
command=self.delete_selected_trade, width=12).pack(side=tk.LEFT, padx=2) command=self.delete_selected_trade, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text="🛠 交易设置",
# 添加抽屉按钮到工具栏最右侧 command=self.trade_settings, width=12).pack(side=tk.LEFT, padx=2)
self.toggle_market_monitor_btn = ttk.Button(toolbar_frame, text="◀▶", ttk.Button(toolbar_frame, text="▣ 实时监控",
command=self.toggle_market_monitor, width=3) command=self.toggle_market_monitor, width=12).pack(side=tk.RIGHT, padx=2)
self.toggle_market_monitor_btn.pack(side=tk.RIGHT, padx=2)
# 表格区域 # 表格区域
self.create_tables_area(main_frame) self.create_tables_area(main_frame)
def start_ui_refresh(self):
"""启动UI刷新线程"""
if not self.refresh_thread_running:
self.refresh_thread_running = True
self.start_refresh_thread()
PrintLog(LogLevel.INFO, "UI刷新线程已启动")
def stop_ui_refresh(self):
"""停止UI刷新线程"""
if self.refresh_thread_running:
self.stop_refresh_thread()
self.refresh_thread_running = False
PrintLog(LogLevel.INFO, "UI刷新线程已停止")
def create_tables_area(self, parent): def create_tables_area(self, parent):
"""创建表格区域""" """创建表格区域"""
@@ -192,6 +151,19 @@ class TradeTargetUI(ttk.Frame):
# 创建市场监控表格 # 创建市场监控表格
self.create_market_monitor_table(self.market_frame) self.create_market_monitor_table(self.market_frame)
def toggle_market_monitor(self):
"""切换市场监控窗口显示/隐藏"""
if self.market_monitor_visible:
# 隐藏市场监控窗口
self.market_frame.pack_forget()
self.market_monitor_visible = False
else:
# 显示市场监控窗口
self.market_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
self.market_monitor_visible = True
def create_trade_target_table(self, parent): def create_trade_target_table(self, parent):
"""创建交易标的表格""" """创建交易标的表格"""
@@ -216,7 +188,7 @@ class TradeTargetUI(ttk.Frame):
"当前订单价": (90, tk.E), "当前订单价": (90, tk.E),
"当前订单号": (90, tk.E), "当前订单号": (90, tk.E),
"当前订单类型": (90, tk.E), "当前订单类型": (90, tk.E),
"交易状态": (80, tk.E) "交易状态": (80, tk.CENTER)
} }
for col in columns: for col in columns:
@@ -343,14 +315,14 @@ class TradeTargetUI(ttk.Frame):
if result: if result:
# 发布事件通知主控制器添加标的 # 发布事件通知主控制器添加标的
self.onAddTradeTarget(stock_code) self.addTradeTarget(stock_code)
def get_trade_enabled_indicator(self, enabled: bool) -> str: def get_trade_enabled_indicator(self, enabled: bool) -> str:
"""获取交易状态指示器""" """获取交易状态指示器"""
if enabled: if enabled:
return "🟢 策略运行" return "运行"
else: else:
return "🟡 策略暂停" return "⏸ 已停止"
def populate_trade_table(self): def populate_trade_table(self):
"""填充交易标的表格数据""" """填充交易标的表格数据"""
@@ -495,9 +467,8 @@ class TradeTargetUI(ttk.Frame):
icon='warning' icon='warning'
) )
id = target.get_id()
if result: if result:
# 通过事件总线发出删除动作 id = target.get_id()
try: try:
# 从数据库中删除 # 从数据库中删除
target.delete_instance() target.delete_instance()
@@ -553,7 +524,7 @@ class TradeTargetUI(ttk.Frame):
return return
# 发布事件通知主控制器添加标的 # 发布事件通知主控制器添加标的
eBus.event_bus.publish(ActionEventAddTradeTarget, stock_code) self.addTradeTarget(stock_code)
add_window.destroy() add_window.destroy()
def cancel_add(): def cancel_add():
@@ -595,143 +566,221 @@ class TradeTargetUI(ttk.Frame):
# 刷新市场监控表格 # 刷新市场监控表格
self.populate_market_table() self.populate_market_table()
def system_settings(self): def trade_settings(self):
"""系统设置""" """网格配置功能"""
target = self.get_selected_target()
if not target:
return
# 获取顶层窗口 # 获取顶层窗口
root = self.winfo_toplevel() root = self.winfo_toplevel()
settings_window = tk.Toplevel(root) # 创建顶层窗口
settings_window.title("网格交易系统配置") view_window = tk.Toplevel(root)
view_window.title(f"网格配置查看 - {target.stock_code} ({target.stock_name})")
view_window.geometry("500x450")
view_window.resizable(False, False)
# 设置窗口大小 # 检查标的的状态,status为1时仅可查看
window_width = 700 # if target.status == 1:
window_height = 550 # # 创建只读的网格配置查看窗口
# self.create_grid_view_window(target)
# else:
# # 创建可编辑的网格配置窗口
# self.create_grid_config_window(target)
# 先设置为模态窗口 def create_grid_view_window(self, target: SFGridTradeTarget):
settings_window.transient(root) """创建网格配置查看窗口(只读)"""
# 获取顶层窗口
root = self.winfo_toplevel()
# 确保主窗口完全初始化 # 创建顶层窗口
view_window = tk.Toplevel(root)
view_window.title(f"网格配置查看 - {target.stock_code} ({target.stock_name})")
view_window.geometry("500x450")
view_window.resizable(False, False)
# 设置窗口模态
view_window.transient(root)
view_window.grab_set()
# 居中显示
root.update_idletasks() root.update_idletasks()
x = root.winfo_x() + (root.winfo_width() // 2) - 250
y = root.winfo_y() + (root.winfo_height() // 2) - 225
view_window.geometry(f"500x450+{x}+{y}")
# 获取主窗口的实际大小(包括边框) # 创建主框架
# 使用winfo_rootx/rooty获取窗口在屏幕上的绝对位置 main_frame = ttk.Frame(view_window, padding=20)
main_x = root.winfo_rootx() main_frame.pack(fill=tk.BOTH, expand=True)
main_y = root.winfo_rooty()
main_width = root.winfo_width()
main_height = root.winfo_height()
# 计算设置窗口相对于主窗口的居中位置 # 显示股票信息
x = main_x + (main_width - window_width) // 2 info_frame = ttk.LabelFrame(main_frame, text="标的详情", padding=10)
y = main_y + (main_height - window_height) // 2 info_frame.pack(fill=tk.X, pady=(0, 10))
# 设置窗口大小和位置 ttk.Label(info_frame, text=f"股票代码: {target.stock_code}").grid(row=0, column=0, sticky=tk.W, pady=2)
settings_window.geometry(f"{window_width}x{window_height}+{x}+{y}") ttk.Label(info_frame, text=f"股票名称: {target.stock_name}").grid(row=0, column=1, sticky=tk.W, padx=(20, 0), pady=2)
settings_window.resizable(False, False) ttk.Label(info_frame, text=f"状态: 已建初始仓(仅查看模式)").grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=2)
# 设置为模态窗口 # 创建网格配置查看框架
settings_window.grab_set() config_frame = ttk.LabelFrame(main_frame, text="网格配置", padding=10)
config_frame.pack(fill=tk.X, pady=(0, 10))
# 添加底部按钮区域(先创建,确保固定在底部) # 基准价格
button_frame = ttk.Frame(settings_window) base_price_frame = ttk.Frame(config_frame)
button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=10) base_price_frame.pack(fill=tk.X, pady=5)
ttk.Label(base_price_frame, text="基准价格:", width=15).pack(side=tk.LEFT)
ttk.Label(base_price_frame, text=f"{target.grid_start_price:.3f}", width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5)
ttk.Label(base_price_frame, text="", foreground='gray').pack(side=tk.LEFT)
# 创建选项卡(在按钮之后创建,填充剩余空间) # 网格大小
notebook = ttk.Notebook(settings_window) grid_size_frame = ttk.Frame(config_frame)
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0)) grid_size_frame.pack(fill=tk.X, pady=5)
ttk.Label(grid_size_frame, text="网格大小:", width=15).pack(side=tk.LEFT)
ttk.Label(grid_size_frame, text=f"{target.grid_size:.3f}", width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5)
ttk.Label(grid_size_frame, text="", foreground='gray').pack(side=tk.LEFT)
# 基础设置 # 网格交易量
basic_frame = ttk.Frame(notebook) grid_volume_frame = ttk.Frame(config_frame)
notebook.add(basic_frame, text="基础设置") grid_volume_frame.pack(fill=tk.X, pady=5)
ttk.Label(grid_volume_frame, text="网格交易量:", width=15).pack(side=tk.LEFT)
ttk.Label(grid_volume_frame, text=str(target.grid_volume), width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5)
ttk.Label(grid_volume_frame, text="", foreground='gray').pack(side=tk.LEFT)
# 高级设置 # 上方网格数量
advanced_frame = ttk.Frame(notebook) upper_count_frame = ttk.Frame(config_frame)
notebook.add(advanced_frame, text="高级设置") upper_count_frame.pack(fill=tk.X, pady=5)
ttk.Label(upper_count_frame, text="上方网格数量:", width=15).pack(side=tk.LEFT)
ttk.Label(upper_count_frame, text=str(target.grid_upper_count), width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5)
ttk.Label(upper_count_frame, text="", foreground='gray').pack(side=tk.LEFT)
# 读取当前配置 # 下方网格数量
config = configparser.ConfigParser() lower_count_frame = ttk.Frame(config_frame)
config_path = config.get('config', 'config_path') lower_count_frame.pack(fill=tk.X, pady=5)
config.read(config_path, encoding='utf-8') ttk.Label(lower_count_frame, text="下方网格数量:", width=15).pack(side=tk.LEFT)
ttk.Label(lower_count_frame, text=str(target.grid_lower_count), width=15, anchor=tk.W).pack(side=tk.LEFT, padx=5)
ttk.Label(lower_count_frame, text="", foreground='gray').pack(side=tk.LEFT)
# 生成网格价格序列
price_grid_frame = ttk.LabelFrame(main_frame, text="网格价格序列", padding=10)
price_grid_frame.pack(fill=tk.X, pady=(0, 10))
# 计算并显示网格价格序列
price_list = target.getPriceGrid()
price_text = ", ".join([f"{price:.3f}" for price in price_list])
# 创建文本框显示网格价格序列
text_frame = ttk.Frame(price_grid_frame)
text_frame.pack(fill=tk.BOTH, expand=True)
text_widget = tk.Text(text_frame, height=4, wrap=tk.WORD)
text_widget.insert(tk.END, price_text)
text_widget.config(state=tk.DISABLED) # 只读
scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=text_widget.yview)
text_widget.configure(yscrollcommand=scrollbar.set)
text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 关闭按钮
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=(10, 0))
ttk.Button(button_frame, text="关闭", command=view_window.destroy).pack(side=tk.RIGHT, padx=5)
def create_grid_config_window(self, target: SFGridTradeTarget):
"""创建网格配置窗口(可编辑)"""
# 获取顶层窗口
root = self.winfo_toplevel()
# 创建顶层窗口
config_window = tk.Toplevel(root)
config_window.title(f"网格配置 - {target.stock_code} ({target.stock_name})")
config_window.geometry("550x550")
config_window.resizable(False, False)
# 设置窗口模态
config_window.transient(root)
config_window.grab_set()
# 居中显示
root.update_idletasks()
x = root.winfo_x() + (root.winfo_width() // 2) - 275
y = root.winfo_y() + (root.winfo_height() // 2) - 275
config_window.geometry(f"550x550+{x}+{y}")
# 创建主框架
main_frame = ttk.Frame(config_window, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 显示股票信息
info_frame = ttk.LabelFrame(main_frame, text="标的详情", padding=10)
info_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(info_frame, text=f"股票代码: {target.stock_code}").grid(row=0, column=0, sticky=tk.W, pady=2)
ttk.Label(info_frame, text=f"股票名称: {target.stock_name}").grid(row=0, column=1, sticky=tk.W, padx=(20, 0), pady=2)
ttk.Label(info_frame, text=f"状态: 新标的(可配置模式)").grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=2)
# 创建网格配置框架
config_frame = ttk.LabelFrame(main_frame, text="网格配置", padding=15)
config_frame.pack(fill=tk.X, pady=(0, 10))
# 创建输入框字典用于保存引用 # 创建输入框字典用于保存引用
entries = {} entries = {}
# 网格价格计算参数
grid_params = {}
# 添加网格价格设置(特殊处理)
grid_price_frame = ttk.LabelFrame(basic_frame, text="网格价格设置", padding=15)
grid_price_frame.pack(fill=tk.X, padx=20, pady=10)
# 基准价格 # 基准价格
base_price_row = ttk.Frame(grid_price_frame) base_price_frame = ttk.Frame(config_frame)
base_price_row.pack(fill=tk.X, pady=5) base_price_frame.pack(fill=tk.X, pady=5)
ttk.Label(base_price_row, text="基准价格:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) ttk.Label(base_price_frame, text="基准价格:", width=15).pack(side=tk.LEFT)
base_price_entry = ttk.Entry(base_price_row, width=15, font=('Arial', 10)) base_price_entry = ttk.Entry(base_price_frame, width=15)
base_price_entry.insert(0, "10.0") base_price_entry.insert(0, str(target.grid_start_price))
base_price_entry.pack(side=tk.LEFT, padx=5) base_price_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(base_price_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT) ttk.Label(base_price_frame, text="", foreground='gray').pack(side=tk.LEFT)
grid_params['base_price'] = base_price_entry entries['grid_start_price'] = base_price_entry
# 网格类型
grid_type_row = ttk.Frame(grid_price_frame)
grid_type_row.pack(fill=tk.X, pady=5)
ttk.Label(grid_type_row, text="网格类型:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
grid_type_var = tk.StringVar(value="金额差")
ttk.Radiobutton(grid_type_row, text="百分比", variable=grid_type_var,
value="百分比", command=lambda: on_grid_type_change()).pack(side=tk.LEFT, padx=5)
ttk.Radiobutton(grid_type_row, text="金额差", variable=grid_type_var,
value="金额差", command=lambda: on_grid_type_change()).pack(side=tk.LEFT, padx=5)
grid_params['grid_type'] = grid_type_var
# 网格大小 # 网格大小
grid_size_row = ttk.Frame(grid_price_frame) grid_size_frame = ttk.Frame(config_frame)
grid_size_row.pack(fill=tk.X, pady=5) grid_size_frame.pack(fill=tk.X, pady=5)
ttk.Label(grid_size_row, text="网格大小:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) ttk.Label(grid_size_frame, text="网格大小:", width=15).pack(side=tk.LEFT)
grid_size_entry = ttk.Entry(grid_size_row, width=15, font=('Arial', 10)) grid_size_entry = ttk.Entry(grid_size_frame, width=15)
grid_size_entry.insert(0, "1.0") grid_size_entry.insert(0, str(target.grid_size))
grid_size_entry.pack(side=tk.LEFT, padx=5) grid_size_entry.pack(side=tk.LEFT, padx=5)
grid_size_unit_label = ttk.Label(grid_size_row, text="", foreground='gray', font=('Arial', 9)) ttk.Label(grid_size_frame, text="", foreground='gray').pack(side=tk.LEFT)
grid_size_unit_label.pack(side=tk.LEFT) entries['grid_size'] = grid_size_entry
grid_params['grid_size'] = grid_size_entry
grid_params['grid_size_unit_label'] = grid_size_unit_label
# 网格类型改变时更新单位 # 网格交易量
def on_grid_type_change(): grid_volume_frame = ttk.Frame(config_frame)
if grid_type_var.get() == "百分比": grid_volume_frame.pack(fill=tk.X, pady=5)
grid_size_unit_label.config(text="%") ttk.Label(grid_volume_frame, text="网格交易量:", width=15).pack(side=tk.LEFT)
grid_size_entry.delete(0, tk.END) grid_volume_entry = ttk.Entry(grid_volume_frame, width=15)
grid_size_entry.insert(0, "1.0") grid_volume_entry.insert(0, str(target.grid_volume))
else: grid_volume_entry.pack(side=tk.LEFT, padx=5)
grid_size_unit_label.config(text="") ttk.Label(grid_volume_frame, text="", foreground='gray').pack(side=tk.LEFT)
grid_size_entry.delete(0, tk.END) entries['grid_volume'] = grid_volume_entry
grid_size_entry.insert(0, "1.0")
update_preview()
# 上方网格数量 # 上方网格数量
upper_grid_row = ttk.Frame(grid_price_frame) upper_count_frame = ttk.Frame(config_frame)
upper_grid_row.pack(fill=tk.X, pady=5) upper_count_frame.pack(fill=tk.X, pady=5)
ttk.Label(upper_grid_row, text="上方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) ttk.Label(upper_count_frame, text="上方网格数量:", width=15).pack(side=tk.LEFT)
upper_grid_entry = ttk.Entry(upper_grid_row, width=15, font=('Arial', 10)) upper_count_entry = ttk.Entry(upper_count_frame, width=15)
upper_grid_entry.insert(0, "1") upper_count_entry.insert(0, str(target.grid_upper_count))
upper_grid_entry.pack(side=tk.LEFT, padx=5) upper_count_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(upper_grid_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT) ttk.Label(upper_count_frame, text="", foreground='gray').pack(side=tk.LEFT)
grid_params['upper_count'] = upper_grid_entry entries['grid_upper_count'] = upper_count_entry
# 下方网格数量 # 下方网格数量
lower_grid_row = ttk.Frame(grid_price_frame) lower_count_frame = ttk.Frame(config_frame)
lower_grid_row.pack(fill=tk.X, pady=5) lower_count_frame.pack(fill=tk.X, pady=5)
ttk.Label(lower_grid_row, text="下方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT) ttk.Label(lower_count_frame, text="下方网格数量:", width=15).pack(side=tk.LEFT)
lower_grid_entry = ttk.Entry(lower_grid_row, width=15, font=('Arial', 10)) lower_count_entry = ttk.Entry(lower_count_frame, width=15)
lower_grid_entry.insert(0, "10") lower_count_entry.insert(0, str(target.grid_lower_count))
lower_grid_entry.pack(side=tk.LEFT, padx=5) lower_count_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(lower_grid_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT) ttk.Label(lower_count_frame, text="", foreground='gray').pack(side=tk.LEFT)
grid_params['lower_count'] = lower_grid_entry entries['grid_lower_count'] = lower_count_entry
# 预览按钮和结果显示 # 预览按钮和结果显示
preview_row = ttk.Frame(grid_price_frame) preview_frame = ttk.LabelFrame(main_frame, text="网格价格序列预览", padding=10)
preview_row.pack(fill=tk.X, pady=10) preview_frame.pack(fill=tk.X, pady=(0, 10))
preview_result = tk.StringVar(value="点击'预览'查看生成的网格价格序列") preview_result = tk.StringVar(value="点击'预览'查看生成的网格价格序列")
@@ -740,17 +789,13 @@ class TradeTargetUI(ttk.Frame):
try: try:
base_price = float(base_price_entry.get()) base_price = float(base_price_entry.get())
grid_size = float(grid_size_entry.get()) grid_size = float(grid_size_entry.get())
upper_count = int(upper_grid_entry.get()) upper_count = int(upper_count_entry.get())
lower_count = int(lower_grid_entry.get()) lower_count = int(lower_count_entry.get())
grid_type = grid_type_var.get()
prices = [] prices = []
# 计算上方网格价格 # 计算上方网格价格
for i in range(upper_count, 0, -1): for i in range(upper_count, 0, -1):
if grid_type == "百分比":
price = base_price * (1 + grid_size / 100 * i)
else: # 金额差
price = base_price + grid_size * i price = base_price + grid_size * i
prices.append(round(price, 3)) prices.append(round(price, 3))
@@ -759,9 +804,6 @@ class TradeTargetUI(ttk.Frame):
# 计算下方网格价格 # 计算下方网格价格
for i in range(1, lower_count + 1): for i in range(1, lower_count + 1):
if grid_type == "百分比":
price = base_price * (1 - grid_size / 100 * i)
else: # 金额差
price = base_price - grid_size * i price = base_price - grid_size * i
# 确保价格不为负 # 确保价格不为负
if price >= 0: if price >= 0:
@@ -770,145 +812,75 @@ class TradeTargetUI(ttk.Frame):
break break
return prices return prices
except ValueError as e: except ValueError:
return None return None
def update_preview(): def update_preview():
"""自动更新网格价格序列预览""" """更新网格价格序列预览"""
prices = calculate_grid_prices() prices = calculate_grid_prices()
if prices: if prices:
price_str = ", ".join([str(p) for p in prices]) price_str = ", ".join([str(p) for p in prices])
preview_result.set(f"网格价格序列: {price_str}") preview_result.set(f"网格价格序列: {price_str}")
else: else:
preview_result.set("参数错误,请检查!") preview_result.set("参数错误,请检查输入")
# 绑定输入变化自动预览 # 绑定输入变化自动预览
for entry_widget in (base_price_entry, grid_size_entry, upper_grid_entry, lower_grid_entry): for entry_widget in entries.values():
entry_widget.bind("<KeyRelease>", lambda e: update_preview()) entry_widget.bind("<KeyRelease>", lambda e: update_preview())
entry_widget.bind("<FocusOut>", lambda e: update_preview()) entry_widget.bind("<FocusOut>", lambda e: update_preview())
# 预览按钮
preview_button_frame = ttk.Frame(preview_frame)
preview_button_frame.pack(fill=tk.X, pady=5)
# ttk.Button(preview_button_frame, text="预览", command=update_preview).pack(side=tk.LEFT)
# 预览结果显示
preview_label = ttk.Label(preview_button_frame, textvariable=preview_result, foreground='blue')
preview_label.pack(side=tk.LEFT, padx=10)
# 初始预览 # 初始预览
update_preview() update_preview()
ttk.Label(preview_row, textvariable=preview_result,
font=('Arial', 10)).pack(side=tk.LEFT, padx=10)
# 添加其他基础配置选项 # 按钮框架
other_basic_frame = ttk.LabelFrame(basic_frame, text="交易设置", padding=15) button_frame = ttk.Frame(main_frame)
other_basic_frame.pack(fill=tk.X, padx=20, pady=10) button_frame.pack(fill=tk.X, pady=(10, 0))
other_basic_settings = [ def save_config():
("网格交易手数", "grid_volume", config.get('config', 'grid_volume'), "每个网格的交易手数")
]
for i, (label, key, default, tooltip) in enumerate(other_basic_settings):
frame = ttk.Frame(other_basic_frame)
frame.pack(fill=tk.X, pady=5)
label_widget = ttk.Label(frame, text=label + ":", width=15, font=('Arial', 10))
label_widget.pack(side=tk.LEFT)
entry = ttk.Entry(frame, width=15, font=('Arial', 10))
entry.insert(0, default)
entry.pack(side=tk.LEFT, padx=5)
entries[key] = entry
# 添加提示信息
tip_label = ttk.Label(frame, text=tooltip, font=('Arial', 9), foreground='gray')
tip_label.pack(side=tk.LEFT, padx=5)
# 添加高级设置选项
account_frame = ttk.LabelFrame(advanced_frame, text="账号设置", padding=15)
account_frame.pack(fill=tk.X, padx=20, pady=10)
# 交易账号
account_row = ttk.Frame(account_frame)
account_row.pack(fill=tk.X, pady=5)
ttk.Label(account_row, text="交易账号:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
account_entry = ttk.Entry(account_row, width=15, font=('Arial', 10))
account_entry.insert(0, config.get('config', 'account_no'))
account_entry.pack(side=tk.LEFT, padx=5)
entries['account_no'] = account_entry
ttk.Label(account_row, text="QMT交易账号", font=('Arial', 9), foreground='gray').pack(side=tk.LEFT, padx=5)
# QMT路径特殊处理 - 使用文件浏览器
qmt_path_frame = ttk.LabelFrame(advanced_frame, text="软件路径", padding=15)
qmt_path_frame.pack(fill=tk.X, padx=20, pady=10)
qmt_row = ttk.Frame(qmt_path_frame)
qmt_row.pack(fill=tk.X, pady=5)
ttk.Label(qmt_row, text="QMT路径:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
qmt_entry = ttk.Entry(qmt_row, width=30, font=('Arial', 10))
qmt_entry.insert(0, config.get('config', 'miniQMTPath'))
qmt_entry.pack(side=tk.LEFT, padx=5)
entries['miniQMTPath'] = qmt_entry
def browse_qmt_path():
"""打开文件夹浏览器选择QMT路径"""
initial_dir = qmt_entry.get() if qmt_entry.get() else "/"
folder_path = filedialog.askdirectory(
title="选择miniQMT安装路径",
initialdir=initial_dir
)
if folder_path:
qmt_entry.delete(0, tk.END)
qmt_entry.insert(0, folder_path)
ttk.Button(qmt_row, text="📁 浏览...", command=browse_qmt_path, width=10).pack(side=tk.LEFT, padx=5)
ttk.Label(qmt_row, text="miniQMT软件安装路径", font=('Arial', 9), foreground='gray').pack(side=tk.LEFT, padx=5)
# 定义保存和取消按钮的功能(button_frame已在上方创建)
def save_settings():
"""保存配置""" """保存配置"""
# try: try:
# # 计算网格价格序列 # 获取输入值
# grid_prices = calculate_grid_prices() grid_start_price = float(base_price_entry.get())
# if not grid_prices: grid_size = float(grid_size_entry.get())
# messagebox.showerror("错误", "网格价格参数有误,请检查输入!") grid_volume = int(grid_volume_entry.get())
# return grid_upper_count = int(upper_count_entry.get())
grid_lower_count = int(lower_count_entry.get())
# grid_price_str = ",".join([str(p) for p in grid_prices]) # 更新target对象(使用setattr来正确设置Peewee字段的值)
setattr(target, 'grid_start_price', grid_start_price)
setattr(target, 'grid_size', grid_size)
setattr(target, 'grid_volume', grid_volume)
setattr(target, 'grid_upper_count', grid_upper_count)
setattr(target, 'grid_lower_count', grid_lower_count)
# # 更新配置对象 # 保存到数据库
# config.set('config', 'miniQMTPath', entries['miniQMTPath'].get()) target.save()
# config.set('config', 'grid_price', grid_price_str)
# config.set('config', 'grid_volume', entries['grid_volume'].get())
# config.set('config', 'account_no', entries['account_no'].get())
# # 写入配置文件 # 关闭窗口
# config_path = config.get_config_path() config_window.destroy()
# with open(config_path, 'w', encoding='utf-8') as configfile:
# config.write(configfile)
# # 重新加载配置到内存中 # 添加日志
# config.initConfig() PrintLog(LogLevel.INFO, f"网格配置已保存: {target.stock_code} - {target.stock_name}")
messagebox.showinfo("成功", "网格配置已保存!")
# messagebox.showinfo("成功", f"配置已保存!\n网格价格序列: {grid_price_str}\n部分配置可能需要重启程序后生效。") except ValueError:
# self.add_log(LogLevel.INFO, f"系统配置已更新 - 网格数量: {len(grid_prices)}") messagebox.showerror("错误", "输入参数有误,请检查!")
# settings_window.destroy() except Exception as e:
messagebox.showerror("错误", f"保存配置失败:{str(e)}")
PrintLog(LogLevel.ERROR, f"保存网格配置失败: {str(e)}")
# except Exception as e: # 保存和取消按钮
# messagebox.showerror("错误", f"保存配置失败:{str(e)}") ttk.Button(button_frame, text="保存", command=save_config).pack(side=tk.RIGHT, padx=5)
# self.add_log(LogLevel.ERROR, f"保存配置失败: {str(e)}") ttk.Button(button_frame, text="取消", command=config_window.destroy).pack(side=tk.RIGHT, padx=5)
pass
def cancel_settings():
"""取消设置"""
# settings_window.destroy()
pass
# 在button_frame中添加按钮
ttk.Button(button_frame, text="💾 保存配置", command=save_settings, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="❌ 取消", command=cancel_settings, width=15).pack(side=tk.LEFT, padx=5)
def grid_settings(self):
"""网格修正功能"""
target = self.get_selected_target()
if not target:
return
# 创建网格修正窗口
self.create_grid_correction_window(target)
def create_grid_correction_window(self, target: SFGridTradeTarget): def create_grid_correction_window(self, target: SFGridTradeTarget):
"""创建网格修正窗口""" """创建网格修正窗口"""
@@ -1099,13 +1071,14 @@ class TradeTargetUI(ttk.Frame):
self.update_position_status(current_position, required_position, position_status_label) self.update_position_status(current_position, required_position, position_status_label)
def onAddTradeTarget(self, stock_code: str): def addTradeTarget(self, stock_code: str, gridIndex: int = 1):
"""处理添加交易标的事件""" """处理添加交易标的事件"""
try: try:
stock_name = qmtv.getInstrumentName(stock_code) stock_name = qmtv.getInstrumentName(stock_code)
if not stock_name: if not stock_name:
PrintLog(LogLevel.ERROR, f'无法获取股票代码 {stock_code} 的名称,请检查代码是否正确') PrintLog(LogLevel.ERROR, f'无法获取股票代码 {stock_code} 的名称,请检查代码是否正确')
return return
PrintLog(LogLevel.DEBUG, f'添加交易标的: {stock_code} {stock_name}')
# 检查是否已存在该标的 # 检查是否已存在该标的
existing_target = SFGridTradeTarget.get_or_none(SFGridTradeTarget.stock_code == stock_code) existing_target = SFGridTradeTarget.get_or_none(SFGridTradeTarget.stock_code == stock_code)
@@ -1120,7 +1093,7 @@ class TradeTargetUI(ttk.Frame):
stock_code=stock_code, stock_code=stock_code,
market_price=0.0, market_price=0.0,
current_position=pos, current_position=pos,
grid_index=0, grid_index=gridIndex,
last_trade_price=0.0, last_trade_price=0.0,
plan_buy_price=0.0, plan_buy_price=0.0,
plan_sell_price=0.0, plan_sell_price=0.0,
@@ -1148,16 +1121,3 @@ class TradeTargetUI(ttk.Frame):
PrintLog(LogLevel.INFO, f"网格修正已应用: {target.stock_code} - {target.stock_name}, 网格索引: {grid_index}") PrintLog(LogLevel.INFO, f"网格修正已应用: {target.stock_code} - {target.stock_name}, 网格索引: {grid_index}")
except Exception as e: except Exception as e:
PrintLog(LogLevel.ERROR, f"网格修正更新失败: {str(e)}") PrintLog(LogLevel.ERROR, f"网格修正更新失败: {str(e)}")
def toggle_market_monitor(self):
"""切换市场监控窗口显示/隐藏"""
if self.market_monitor_visible:
# 隐藏市场监控窗口
self.market_frame.pack_forget()
self.toggle_market_monitor_btn.config(text="")
self.market_monitor_visible = False
else:
# 显示市场监控窗口
self.market_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
self.toggle_market_monitor_btn.config(text="◀▶")
self.market_monitor_visible = True