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
+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])
+46 -9
View File
@@ -1,6 +1,6 @@
import tkinter as tk
from tkinter import ttk
from core.logger import LogLevel, PrintLog
from core.logger import LogLevel
from core.sfgrid.sfgrid_ui import TradeTargetUI
@@ -35,16 +35,20 @@ class MainWindow:
tab_bar_frame = ttk.Frame(content_area)
tab_bar_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
# 创建自定义样式
self.create_custom_styles()
# 创建Tab按钮(垂直排列,文字垂直显示)
self.tab_buttons = []
strategy_names = ["蒙派\n策略", "涨停\n分析"]
strategy_names = ["蒙派\n策略", "涨停\n复盘"]
for idx, name in enumerate(strategy_names):
btn = ttk.Button(
tab_bar_frame,
text=name,
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)
self.tab_buttons.append(btn)
@@ -98,6 +102,38 @@ class MainWindow:
# 默认显示第一个策略
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):
"""创建全局日志面板"""
# 日志区域(默认隐藏)
@@ -143,7 +179,6 @@ class MainWindow:
# 删除所有日志项
for item in self.log_table.get_children():
self.log_table.delete(item)
self.add_log(LogLevel.DEBUG, "日志已清空")
def create_strategy_frames(self, strategy_names):
"""创建各个策略的Frame"""
@@ -184,9 +219,12 @@ class MainWindow:
def update_tab_button_styles(self):
"""更新Tab按钮的样式以显示选中状态"""
# 注意:ttk.Button的样式需要通过ttk.Style来设置
# 这里简化处理,仅作为接口预留
pass
# 重置所有按钮为普通书签样式
for i, btn in enumerate(self.tab_buttons):
if i == self.current_strategy_index:
btn.configure(style='SelectedBookmark.TButton') # 选中状态
else:
btn.configure(style='Bookmark.TButton') # 普通状态
def toggle_log_panel(self):
"""切换日志面板的显示/隐藏"""
@@ -210,5 +248,4 @@ class MainWindow:
def run(self):
"""运行程序"""
self.root.mainloop()
self.root.mainloop()
+281 -321
View File
@@ -8,7 +8,6 @@ import time
from core import constants
import core.eventbus as eBus
from core.logger import LogLevel, PrintLog
from core.sfgrid.bus_events import ActionEventAddTradeTarget
from core.sfgrid.model import SFGridTradeTarget
import configparser
import config
@@ -27,9 +26,6 @@ class TradeTargetUI(ttk.Frame):
self.init_trade_target_pool()
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}}
@@ -39,7 +35,7 @@ class TradeTargetUI(ttk.Frame):
# 创建界面
self.create_ui()
self.start_ui_refresh()
self.start_refresh_thread()
def onMarketDataUpdated(self, data):
# 收集所有市场数据用于市场监控
@@ -48,7 +44,7 @@ class TradeTargetUI(ttk.Frame):
id:int = self.stockCodeIdMap[stock_code]
tradeTarget = self.tradeTargetData[id]
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
self.updateTradeTarget(tradeTarget)
stock_controller: SFGridStrategy = self.strategy_ctrl[id]
@@ -71,61 +67,41 @@ class TradeTargetUI(ttk.Frame):
'last_price': tickData['lastPrice'],
'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):
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 "已建初始仓"
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
result = tradeTarget.save()
PrintLog(LogLevel.INFO, f' |- 同步[{tradeTarget.stock_code}-{tradeTarget.stock_name}]持仓信息[{'成功' if result == 1 else '失败'}]')
tradeTarget.save()
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
self.strategy_ctrl[id] = stockTradeController # pyright: ignore[reportArgumentType]
self.updateTradeTarget(tradeTarget)
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):
# 更新或添加数据到本地缓存
self.tradeTargetData[target.get_id()] = target
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):
"""创建UI界面"""
@@ -144,33 +120,16 @@ class TradeTargetUI(ttk.Frame):
command=self.pause_selected_trade, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text=" 添加标的",
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="🗑 删除标的",
command=self.delete_selected_trade, width=12).pack(side=tk.LEFT, padx=2)
# 添加抽屉按钮到工具栏最右侧
self.toggle_market_monitor_btn = ttk.Button(toolbar_frame, text="◀▶",
command=self.toggle_market_monitor, width=3)
self.toggle_market_monitor_btn.pack(side=tk.RIGHT, padx=2)
ttk.Button(toolbar_frame, text="🛠 交易设置",
command=self.trade_settings, width=12).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar_frame, text="▣ 实时监控",
command=self.toggle_market_monitor, width=12).pack(side=tk.RIGHT, padx=2)
# 表格区域
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):
"""创建表格区域"""
@@ -191,6 +150,19 @@ class TradeTargetUI(ttk.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):
"""创建交易标的表格"""
@@ -216,7 +188,7 @@ class TradeTargetUI(ttk.Frame):
"当前订单价": (90, tk.E),
"当前订单号": (90, tk.E),
"当前订单类型": (90, tk.E),
"交易状态": (80, tk.E)
"交易状态": (80, tk.CENTER)
}
for col in columns:
@@ -343,14 +315,14 @@ class TradeTargetUI(ttk.Frame):
if result:
# 发布事件通知主控制器添加标的
self.onAddTradeTarget(stock_code)
self.addTradeTarget(stock_code)
def get_trade_enabled_indicator(self, enabled: bool) -> str:
"""获取交易状态指示器"""
if enabled:
return "🟢 策略运行"
return "运行"
else:
return "🟡 策略暂停"
return "⏸ 已停止"
def populate_trade_table(self):
"""填充交易标的表格数据"""
@@ -495,9 +467,8 @@ class TradeTargetUI(ttk.Frame):
icon='warning'
)
id = target.get_id()
if result:
# 通过事件总线发出删除动作
id = target.get_id()
try:
# 从数据库中删除
target.delete_instance()
@@ -553,7 +524,7 @@ class TradeTargetUI(ttk.Frame):
return
# 发布事件通知主控制器添加标的
eBus.event_bus.publish(ActionEventAddTradeTarget, stock_code)
self.addTradeTarget(stock_code)
add_window.destroy()
def cancel_add():
@@ -595,143 +566,221 @@ class TradeTargetUI(ttk.Frame):
# 刷新市场监控表格
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()
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)
# 设置窗口大小
window_width = 700
window_height = 550
# 检查标的的状态,status为1时仅可查看
# if target.status == 1:
# # 创建只读的网格配置查看窗口
# self.create_grid_view_window(target)
# else:
# # 创建可编辑的网格配置窗口
# self.create_grid_config_window(target)
def create_grid_view_window(self, target: SFGridTradeTarget):
"""创建网格配置查看窗口(只读)"""
# 获取顶层窗口
root = self.winfo_toplevel()
# 先设置为模态窗口
settings_window.transient(root)
# 创建顶层窗口
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()
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_x = root.winfo_rootx()
main_y = root.winfo_rooty()
main_width = root.winfo_width()
main_height = root.winfo_height()
# 创建主框架
main_frame = ttk.Frame(view_window, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 计算设置窗口相对于主窗口的居中位置
x = main_x + (main_width - window_width) // 2
y = main_y + (main_height - window_height) // 2
# 显示股票信息
info_frame = ttk.LabelFrame(main_frame, text="标的详情", padding=10)
info_frame.pack(fill=tk.X, pady=(0, 10))
# 设置窗口大小和位置
settings_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
settings_window.resizable(False, False)
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)
# 设置为模态窗口
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)
button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=10)
# 基准价格
base_price_frame = ttk.Frame(config_frame)
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)
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0))
# 网格大小
grid_size_frame = ttk.Frame(config_frame)
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)
notebook.add(basic_frame, text="基础设置")
# 网格交易量
grid_volume_frame = ttk.Frame(config_frame)
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)
notebook.add(advanced_frame, text="高级设置")
# 上方网格数量
upper_count_frame = ttk.Frame(config_frame)
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()
config_path = config.get('config', 'config_path')
config.read(config_path, encoding='utf-8')
# 下方网格数量
lower_count_frame = ttk.Frame(config_frame)
lower_count_frame.pack(fill=tk.X, pady=5)
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 = {}
# 网格价格计算参数
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_row.pack(fill=tk.X, pady=5)
ttk.Label(base_price_row, text="基准价格:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
base_price_entry = ttk.Entry(base_price_row, width=15, font=('Arial', 10))
base_price_entry.insert(0, "10.0")
base_price_frame = ttk.Frame(config_frame)
base_price_frame.pack(fill=tk.X, pady=5)
ttk.Label(base_price_frame, text="基准价格:", width=15).pack(side=tk.LEFT)
base_price_entry = ttk.Entry(base_price_frame, width=15)
base_price_entry.insert(0, str(target.grid_start_price))
base_price_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(base_price_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT)
grid_params['base_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
ttk.Label(base_price_frame, text="", foreground='gray').pack(side=tk.LEFT)
entries['grid_start_price'] = base_price_entry
# 网格大小
grid_size_row = ttk.Frame(grid_price_frame)
grid_size_row.pack(fill=tk.X, pady=5)
ttk.Label(grid_size_row, text="网格大小:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
grid_size_entry = ttk.Entry(grid_size_row, width=15, font=('Arial', 10))
grid_size_entry.insert(0, "1.0")
grid_size_frame = ttk.Frame(config_frame)
grid_size_frame.pack(fill=tk.X, pady=5)
ttk.Label(grid_size_frame, text="网格大小:", width=15).pack(side=tk.LEFT)
grid_size_entry = ttk.Entry(grid_size_frame, width=15)
grid_size_entry.insert(0, str(target.grid_size))
grid_size_entry.pack(side=tk.LEFT, padx=5)
grid_size_unit_label = ttk.Label(grid_size_row, text="", foreground='gray', font=('Arial', 9))
grid_size_unit_label.pack(side=tk.LEFT)
grid_params['grid_size'] = grid_size_entry
grid_params['grid_size_unit_label'] = grid_size_unit_label
ttk.Label(grid_size_frame, text="", foreground='gray').pack(side=tk.LEFT)
entries['grid_size'] = grid_size_entry
# 网格类型改变时更新单位
def on_grid_type_change():
if grid_type_var.get() == "百分比":
grid_size_unit_label.config(text="%")
grid_size_entry.delete(0, tk.END)
grid_size_entry.insert(0, "1.0")
else:
grid_size_unit_label.config(text="")
grid_size_entry.delete(0, tk.END)
grid_size_entry.insert(0, "1.0")
update_preview()
# 网格交易量
grid_volume_frame = ttk.Frame(config_frame)
grid_volume_frame.pack(fill=tk.X, pady=5)
ttk.Label(grid_volume_frame, text="网格交易量:", width=15).pack(side=tk.LEFT)
grid_volume_entry = ttk.Entry(grid_volume_frame, width=15)
grid_volume_entry.insert(0, str(target.grid_volume))
grid_volume_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(grid_volume_frame, text="", foreground='gray').pack(side=tk.LEFT)
entries['grid_volume'] = grid_volume_entry
# 上方网格数量
upper_grid_row = ttk.Frame(grid_price_frame)
upper_grid_row.pack(fill=tk.X, pady=5)
ttk.Label(upper_grid_row, text="上方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
upper_grid_entry = ttk.Entry(upper_grid_row, width=15, font=('Arial', 10))
upper_grid_entry.insert(0, "1")
upper_grid_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(upper_grid_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT)
grid_params['upper_count'] = upper_grid_entry
upper_count_frame = ttk.Frame(config_frame)
upper_count_frame.pack(fill=tk.X, pady=5)
ttk.Label(upper_count_frame, text="上方网格数量:", width=15).pack(side=tk.LEFT)
upper_count_entry = ttk.Entry(upper_count_frame, width=15)
upper_count_entry.insert(0, str(target.grid_upper_count))
upper_count_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(upper_count_frame, text="", foreground='gray').pack(side=tk.LEFT)
entries['grid_upper_count'] = upper_count_entry
# 下方网格数量
lower_grid_row = ttk.Frame(grid_price_frame)
lower_grid_row.pack(fill=tk.X, pady=5)
ttk.Label(lower_grid_row, text="下方网格数量:", width=15, font=('Arial', 10)).pack(side=tk.LEFT)
lower_grid_entry = ttk.Entry(lower_grid_row, width=15, font=('Arial', 10))
lower_grid_entry.insert(0, "10")
lower_grid_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(lower_grid_row, text="", foreground='gray', font=('Arial', 9)).pack(side=tk.LEFT)
grid_params['lower_count'] = lower_grid_entry
lower_count_frame = ttk.Frame(config_frame)
lower_count_frame.pack(fill=tk.X, pady=5)
ttk.Label(lower_count_frame, text="下方网格数量:", width=15).pack(side=tk.LEFT)
lower_count_entry = ttk.Entry(lower_count_frame, width=15)
lower_count_entry.insert(0, str(target.grid_lower_count))
lower_count_entry.pack(side=tk.LEFT, padx=5)
ttk.Label(lower_count_frame, text="", foreground='gray').pack(side=tk.LEFT)
entries['grid_lower_count'] = lower_count_entry
# 预览按钮和结果显示
preview_row = ttk.Frame(grid_price_frame)
preview_row.pack(fill=tk.X, pady=10)
preview_frame = ttk.LabelFrame(main_frame, text="网格价格序列预览", padding=10)
preview_frame.pack(fill=tk.X, pady=(0, 10))
preview_result = tk.StringVar(value="点击'预览'查看生成的网格价格序列")
@@ -740,18 +789,14 @@ class TradeTargetUI(ttk.Frame):
try:
base_price = float(base_price_entry.get())
grid_size = float(grid_size_entry.get())
upper_count = int(upper_grid_entry.get())
lower_count = int(lower_grid_entry.get())
grid_type = grid_type_var.get()
upper_count = int(upper_count_entry.get())
lower_count = int(lower_count_entry.get())
prices = []
# 计算上方网格价格
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))
# 添加基准价格
@@ -759,10 +804,7 @@ class TradeTargetUI(ttk.Frame):
# 计算下方网格价格
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:
prices.append(round(price, 3))
@@ -770,145 +812,75 @@ class TradeTargetUI(ttk.Frame):
break
return prices
except ValueError as e:
except ValueError:
return None
def update_preview():
"""自动更新网格价格序列预览"""
"""更新网格价格序列预览"""
prices = calculate_grid_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}")
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("<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()
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)
other_basic_frame.pack(fill=tk.X, padx=20, pady=10)
# 按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=(10, 0))
other_basic_settings = [
("网格交易手数", "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():
def save_config():
"""保存配置"""
# try:
# # 计算网格价格序列
# grid_prices = calculate_grid_prices()
# if not grid_prices:
# messagebox.showerror("错误", "网格价格参数有误,请检查输入!")
# return
try:
# 获取输入值
grid_start_price = float(base_price_entry.get())
grid_size = float(grid_size_entry.get())
grid_volume = int(grid_volume_entry.get())
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())
# 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())
# 保存到数据库
target.save()
# # 写入配置文件
# config_path = config.get_config_path()
# with open(config_path, 'w', encoding='utf-8') as configfile:
# config.write(configfile)
# 关闭窗口
config_window.destroy()
# # 重新加载配置到内存中
# config.initConfig()
# 添加日志
PrintLog(LogLevel.INFO, f"网格配置已保存: {target.stock_code} - {target.stock_name}")
messagebox.showinfo("成功", "网格配置已保存!")
# messagebox.showinfo("成功", f"配置已保存!\n网格价格序列: {grid_price_str}\n部分配置可能需要重启程序后生效。")
# self.add_log(LogLevel.INFO, f"系统配置已更新 - 网格数量: {len(grid_prices)}")
# settings_window.destroy()
# except Exception as e:
# messagebox.showerror("错误", f"保存配置失败:{str(e)}")
# self.add_log(LogLevel.ERROR, f"保存配置失败: {str(e)}")
pass
except ValueError:
messagebox.showerror("错误", "输入参数有误,请检查!")
except Exception as e:
messagebox.showerror("错误", f"保存配置失败:{str(e)}")
PrintLog(LogLevel.ERROR, f"保存网格配置失败: {str(e)}")
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)
# 保存和取消按钮
ttk.Button(button_frame, text="保存", command=save_config).pack(side=tk.RIGHT, padx=5)
ttk.Button(button_frame, text="取消", command=config_window.destroy).pack(side=tk.RIGHT, padx=5)
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)
def onAddTradeTarget(self, stock_code: str):
def addTradeTarget(self, stock_code: str, gridIndex: int = 1):
"""处理添加交易标的事件"""
try:
stock_name = qmtv.getInstrumentName(stock_code)
if not stock_name:
PrintLog(LogLevel.ERROR, f'无法获取股票代码 {stock_code} 的名称,请检查代码是否正确')
return
PrintLog(LogLevel.DEBUG, f'添加交易标的: {stock_code} {stock_name}')
# 检查是否已存在该标的
existing_target = SFGridTradeTarget.get_or_none(SFGridTradeTarget.stock_code == stock_code)
@@ -1120,7 +1093,7 @@ class TradeTargetUI(ttk.Frame):
stock_code=stock_code,
market_price=0.0,
current_position=pos,
grid_index=0,
grid_index=gridIndex,
last_trade_price=0.0,
plan_buy_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}")
except Exception as 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