admin
2025-06-10 568c763084b926a6f2d632b7ac65b9ec8280752f
main.py
@@ -1,1581 +1,616 @@
import base64
import ctypes
import hashlib
import json
import os
import socket
import sys
import time
from multiprocessing import freeze_support
import cv2
import torch
import win32con
import win32gui
from matplotlib.lines import Line2D
import ocr_util
import opencv_util
import setting
import ths_util
import win32_util
freeze_support()
import logging
import multiprocessing
import queue
import threading
import time
import sys
from functools import partial
from multiprocessing import Pipe, Process, freeze_support
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import torch
import win32api
import win32con
import win32gui
from PyQt5.QtGui import QFont, QPalette, QColor, QTextOption
import wx
import wx.html2 as webview
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings, QWebEnginePage
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMessageBox, QLabel
import code_data_manager
import juejin_core
import tool
import requests
from PyQt5.QtCore import Qt, pyqtSlot, QObject, pyqtSignal, QTimer, QUrl, QPoint
APP_TITLE = "卖票看板"
APP_ICON = ""
SERVER_HOST = "192.168.3.252"
SOCKET_PORT = 9001
HTTP_PORT = 9004
import constant
import gui_wx
from utils import network_util, xgb_api, ths_util, ths_ocr_util
import setting
from utils import tool
import win32_util
from kpl.kpl_data_manager import KPLLimitUpDataManager
from utils.network_delegate_manager import LocalKanPanNetworkDelegate
URL_MSG_LIST = f"http://{constant.WEB_HOST}/kp/msg_list.html"
window_msg_queue = queue.Queue()
def show_warning(content, click):
    toastone = wx.MessageDialog(None, content, "提示", wx.YES_DEFAULT | wx.ICON_WARNING)
    if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
        if click is not None:
            click()
        toastone.Destroy()
class BaseBridgeClass(QObject):
    signal_request = pyqtSignal(str, str, str)
    def __request_result_callback(self, method, key, result):
        base64_str = base64.b64encode(result.encode('utf-8')).decode('utf-8')
        self.__webview.page().runJavaScript(f"{method}('{key}','{base64_str}')")
def show_info(content, click):
    toastone = wx.MessageDialog(None, content, "提示", wx.YES_DEFAULT | wx.ICON_INFORMATION)
    if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
        click()
        toastone.Destroy()
    def __init__(self, webview):
        super().__init__()
        self.__webview = webview
        self.signal_request.connect(self.__request_result_callback)
def show_sure(content, callback):
    toastone = wx.MessageDialog(None, content, "提示", wx.YES_NO | wx.ICON_INFORMATION)
    result = toastone.ShowModal()
    if result == wx.ID_YES:  # 如果点击了提示框的确定按钮
        callback(True)
        toastone.Destroy()
    elif result == wx.ID_NO:
        callback(False)
        toastone.Destroy()
class JueJinSettingFrame(wx.Frame):
    def __init__(self, position):
        wx.Frame.__init__(self, None, -1, "掘金参数设置", style=wx.SYSTEM_MENU ^ wx.CLOSE_BOX ^ wx.CAPTION ^ wx.STAY_ON_TOP,
                          size=(450, 200))
        self.SetBackgroundColour(wx.Colour(224, 224, 224))
        self.SetPosition(wx.Point(position[0] - self.GetSize()[0] / 2, position[1] - self.GetSize()[1] / 2))
        boxsier = wx.BoxSizer()
        flex = wx.FlexGridSizer(rows=3, cols=2, vgap=10, hgap=10)
        # 策略
        label = wx.StaticText(self, label="策略ID:")
        flex.Add(label, flag=wx.ALIGN_RIGHT)
        self.edit_celue = wx.TextCtrl(self, size=(300, -1))
        flex.Add(self.edit_celue, flag=wx.EXPAND)
        # token
        label = wx.StaticText(self, label="Token:")
        flex.Add(label)
        self.edit_token = wx.TextCtrl(self, size=(300, -1), style=wx.TE_MULTILINE)
        flex.Add(self.edit_token)
        # 占位
        flex.Add(wx.StaticText(self, label=""))
        # 确定按钮
        ID_SURE = wx.NewId()
        self.btn_sure = wx.Button(self, label='确定', id=ID_SURE)
        self.btn_sure.Bind(wx.EVT_BUTTON, self.__sure_btn)
        flex.Add(self.btn_sure, 1, wx.TOP | wx.LEFT, 20)
        boxsier.Add(flex, 1, wx.TOP | wx.LEFT, 20)
        self.SetSizer(boxsier)
        # 初始化数据
        self.__init_data()
    def __init_data(self):
        strategy_id, token = setting.get_juejin_params()
        self.edit_celue.SetLabelText(strategy_id)
        self.edit_token.SetLabelText(token)
        pass
    def __sure_btn(self, event):
        strategy_id = self.edit_celue.GetValue()
        token = self.edit_token.GetValue()
        setting.set_juejin_params(strategy_id, token)
        toastone = wx.MessageDialog(None, "更改成功", "提示", wx.YES_DEFAULT | wx.ICON_INFORMATION)
        if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
            self.Close()
            toastone.Destroy()
class CodesSettingFrame(wx.Frame):
    def __init__(self, position, callback):
        wx.Frame.__init__(self, None, -1, "代码设置", style=wx.SYSTEM_MENU ^ wx.CLOSE_BOX ^ wx.CAPTION ^ wx.STAY_ON_TOP,
                          size=(170, 300))
        self.SetBackgroundColour(wx.Colour(224, 224, 224))
        self.SetPosition(wx.Point(position[0] - self.GetSize()[0] / 2, position[1] - self.GetSize()[1] / 2))
        boxsier = wx.BoxSizer(wx.VERTICAL)
        # 代码
        label = wx.StaticText(self, label="目标代码:")
        boxsier.Add(label)
        self.edit_codes = wx.TextCtrl(self, size=(150, 200), style=wx.TE_MULTILINE)
        boxsier.Add(self.edit_codes)
        # 确定按钮
        ID_SURE = wx.NewId()
        self.btn_sure = wx.Button(self, label='确定', id=ID_SURE)
        self.btn_sure.Bind(wx.EVT_BUTTON, self.__sure_btn)
        boxsier.Add(self.btn_sure)
        root_boxsier = wx.BoxSizer(wx.HORIZONTAL)
        root_boxsier.Add(boxsier, 1, wx.TOP | wx.TOP, 10)
        self.SetSizer(root_boxsier)
        # 初始化数据
        self.__init_data()
        self.callback = callback
    def __init_data(self):
        codes = juejin_core.GPCodeManager().get_codes()
        self.edit_codes.SetValue("\n".join(codes))
        pass
    def __sure_btn(self, event):
        codes_str = self.edit_codes.GetValue()
        codes = codes_str.split("\n")
        codes_result = []
        for code in codes:
            if code.strip():
                codes_result.append(code.strip())
        juejin_core.GPCodeManager().set_codes(codes_result)
        # 重新订阅
        self.callback()
        toastone = wx.MessageDialog(None, "更改成功", "提示", wx.YES_DEFAULT | wx.ICON_INFORMATION)
        if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
            self.Close()
            toastone.Destroy()
class FobiddenCodesFrame(wx.Frame):
    def __init__(self, position):
        wx.Frame.__init__(self, None, -1, "添加禁止交易代码", style=wx.SYSTEM_MENU ^ wx.CLOSE_BOX ^ wx.CAPTION ^ wx.STAY_ON_TOP,
                          size=(170, 200))
        self.SetBackgroundColour(wx.Colour(224, 224, 224))
        self.SetPosition(wx.Point(position[0] - self.GetSize()[0] / 2, position[1] - self.GetSize()[1] / 2))
        boxsier = wx.BoxSizer(wx.VERTICAL)
        # 代码
        label = wx.StaticText(self, label="代码:")
        boxsier.Add(label, flag=wx.ALIGN_LEFT, border=10)
        self.edit_codes = wx.TextCtrl(self, size=(150, 100), style=wx.TE_MULTILINE)
        boxsier.Add(self.edit_codes)
        # 确定按钮
        ID_SURE = wx.NewId()
        self.btn_sure = wx.Button(self, label='确定', id=ID_SURE)
        self.btn_sure.Bind(wx.EVT_BUTTON, self.__sure_btn)
        boxsier.Add(self.btn_sure)
        root_boxsier = wx.BoxSizer(wx.HORIZONTAL)
        root_boxsier.Add(boxsier, 1, wx.TOP | wx.TOP, 10)
        self.SetSizer(root_boxsier)
    def __request(self, codes):
        client = socket.socket()  # 生成socket,连接server
        ip_port = (SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        data = {"type": 201, "data": {"codes": codes}}
        client.send(json.dumps(data).encode("utf-8"))
        client.close()
    def __sure_btn(self, event):
        codes_str = self.edit_codes.GetValue()
        if codes_str:
            codes_str = codes_str.strip()
        codes = codes_str.split("\n")
        codes_result = []
        for code in codes:
            if code.strip() and len(code.strip()) == 6:
                codes_result.append(code.strip())
        if len(codes_result) == 0:
            show_warning("请填写正确的代码!", self.Close)
            return
    def __http_request(self, url, callback_info):
        try:
            self.__request(codes_result)
            # 代理请求结果
            result, need_delegate = LocalKanPanNetworkDelegate.http_delegate_request(url)
            if not need_delegate:
                result = network_util.http_get(url)
            print(url, "请求结果:", result)
            self.signal_request.emit(callback_info[0], callback_info[1], result)
            return result
        except Exception as e:
            show_warning("添加出错:" + str(e), None)
            return
            logging.exception(e)
        toastone = wx.MessageDialog(None, "添加成功", "提示", wx.YES_DEFAULT | wx.ICON_INFORMATION)
        if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
            self.Close()
            toastone.Destroy()
class THSPositionSettingFrame(wx.Frame):
    def __init__(self, position):
        wx.Frame.__init__(self, None, -1, "同花顺坐标设置", style=wx.SYSTEM_MENU ^ wx.CLOSE_BOX ^ wx.CAPTION ^ wx.STAY_ON_TOP,
                          size=(170, 400))
        self.SetBackgroundColour(wx.Colour(224, 224, 224))
        self.SetPosition(wx.Point(position[0] - self.GetSize()[0] / 2, position[1] - self.GetSize()[1] / 2))
        boxsier = wx.BoxSizer(wx.VERTICAL)
        label = wx.StaticText(self, label="交易刷新事件间隔(ms):")
        boxsier.Add(label)
        self.edit_refresh_time_space = wx.TextCtrl(self, size=(150, -1))
        boxsier.Add(self.edit_refresh_time_space)
        label = wx.StaticText(self, label="点击时间间隔(ms):")
        boxsier.Add(label)
        self.edit_time_space = wx.TextCtrl(self, size=(150, -1))
        boxsier.Add(self.edit_time_space)
        # 代码
        label = wx.StaticText(self, label="坐标:")
        boxsier.Add(label)
        self.edit_codes = wx.TextCtrl(self, size=(150, 200), style=wx.TE_MULTILINE)
        boxsier.Add(self.edit_codes)
        # 确定按钮
        ID_SURE = wx.NewId()
        self.btn_sure = wx.Button(self, label='确定', id=ID_SURE)
        self.btn_sure.Bind(wx.EVT_BUTTON, self.__sure_btn)
        boxsier.Add(self.btn_sure)
        root_boxsier = wx.BoxSizer(wx.HORIZONTAL)
        root_boxsier.Add(boxsier, 1, wx.TOP | wx.TOP, 10)
        self.SetSizer(root_boxsier)
        # 初始化数据
        self.__init_data()
    def __init_data(self):
        pos = setting.get_ths_auto_click_positions()
        self.edit_codes.SetValue("\n".join(pos))
        space = setting.get_ths_auto_click_time_space()
        if space is None:
            space = 500
        self.edit_time_space.SetValue(f"{space}")
        space = setting.get_ths_auto_refresh_time_space()
        if space is None:
            space = 500
        self.edit_refresh_time_space.SetValue(f"{space}")
    def __sure_btn(self, event):
        codes_str = self.edit_codes.GetValue()
        ps = codes_str.split("\n")
        result = []
        for p in ps:
            if p.strip():
                result.append(p.strip())
        setting.set_ths_auto_click_positions(result)
        time_space = self.edit_time_space.GetValue()
        if len(time_space) == 0:
            show_warning("点击时间间隔不正确", None)
            return
        refresh_time_space = self.edit_refresh_time_space.GetValue()
        if len(refresh_time_space) == 0:
            show_warning("刷新时间间隔不正确", None)
            return
        setting.set_ths_auto_click_time_space(time_space)
        setting.set_ths_auto_refresh_time_space(refresh_time_space)
        toastone = wx.MessageDialog(None, "更改成功", "提示", wx.YES_DEFAULT | wx.ICON_INFORMATION)
        if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
            self.Close()
            toastone.Destroy()
last_ocr_code = ["000000"]
import log
def ocr_ths_code():
    hwnd = ths_util.get_ths_main_content_hwnd()
    if not hwnd:
        raise Exception("看盘页面句柄未获取到")
    # 句柄截图
    rect = win32gui.GetWindowRect(hwnd)
    # hwnd_width = (rect[2] - rect[0]) * 15 // 10
    rect_ = setting.get_ths_auto_code_rect()
    img = win32_util.window_capture(hwnd, (0, rect_[0], rect_[1], rect_[0] + rect_[2]))
    clip_img, details = opencv_util.clip_ths_code_area(img)
    start_time = time.time()
    code = ocr_util.recognize_code(clip_img)
    use_time =round( (time.time()-start_time)*1000)
    if code is None:
        code = ocr_util.recognize_code(img)
    if code != last_ocr_code[0]:
        print("保存图片", code)
        log.debug_logger.info(f"代码识别结果:{code} 识别时间:{use_time}ms")
        last_ocr_code[0] = code
        cv2.imwrite(f"datas/test/{code}.png", opencv_util.gray_img(img))
    return code
# 悬浮框
class FloatFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "悬浮盯盘", style=wx.CAPTION ^ wx.MINIMIZE_BOX ^ wx.CLOSE_BOX ^ wx.STAY_ON_TOP,
                          size=(320, 195))
        self.SetBackgroundColour(wx.Colour(224, 224, 224))
        self.SetTransparent(230)
        self.Bind(wx.EVT_CLOSE, self.OnExit)
        # 读取配置信息
        window_info = setting.get_float_watch_window_info()
        if window_info:
            self.SetPosition(wx.Point(window_info[0], window_info[1]))
            self.Size = wx.Size(window_info[2], window_info[3])
        boxsier = wx.FlexGridSizer(5, 2, 2, 5)
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        self.btn_remove_black = wx.Button(self, label="移除黑名单", size=(70, 30))
        self.btn_close_buy = wx.Button(self, label="关闭交易", size=(70, 30))
        self.btn_close_buy.SetForegroundColour("#FF3232")
        bs1.Add(self.btn_remove_black)
        bs1.Add(self.btn_close_buy)
        boxsier.Add(bs1, 0, wx.TOP, 5)
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        self.btn_remove_white = wx.Button(self, label="移除白名单", size=(70, 30))
        self.btn_open_buy = wx.Button(self, label="开启交易", size=(70, 30), )
        self.btn_open_buy.SetForegroundColour("#00e600")
        bs1.Add(self.btn_open_buy, 0, wx.TOP, 5)
        bs1.Add(self.btn_remove_white, 0, wx.TOP, 5)
        boxsier.Add(bs1, 0, wx.LEFT, 0)
        label = wx.StaticText(self, label="交易刷新:")
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        bs1.Add(label)
        self.check_auto_refresh = wx.CheckBox(self, size=(-1, -1))
        bs1.Add(self.check_auto_refresh, 0, wx.LEFT, 10)
        boxsier.Add(bs1, 0, wx.TOP, 5)
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        self.want_list = wx.Button(self, label="想买单", size=(45, 20))
        bs1.Add(self.want_list, 0, wx.TOP, 2)
        self.btn_want_buy = wx.Button(self, label="加入想买单", size=(70, 25))
        bs1.Add(self.btn_want_buy)
        boxsier.Add(bs1, 0, wx.LEFT, 25)
        label = wx.StaticText(self, label="分组刷新:")
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        bs1.Add(label)
        self.check_auto_click = wx.CheckBox(self, size=(-1, -1))
        bs1.Add(self.check_auto_click, 0, wx.LEFT, 10)
        boxsier.Add(bs1)
        self.btn_want_buy_remove = wx.Button(self, label="移除想买单", size=(70, 25))
        boxsier.Add(self.btn_want_buy_remove, 0, wx.LEFT, 70)
        self.edit_code = wx.TextCtrl(self, size=(80, -1))
        boxsier.Add(self.edit_code)
        self.notify_text = wx.StaticText(self, label="", size=(80, -1))
        boxsier.Add(self.notify_text, 0, wx.LEFT, -45)
        # 代码
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        self.btn_black = wx.Button(self, label="加入黑名单", size=(70, 30))
        bs1.Add(self.btn_black)
        self.black_list = wx.Button(self, label="黑名单", size=(45, 20))
        self.black_list.SetForegroundColour("#00e600")
        bs1.Add(self.black_list, 0, wx.CENTER | wx.ALL, 0)
        boxsier.Add(bs1, 0, wx.LEFT, 0)
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        self.btn_white = wx.Button(self, label="加入白名单", size=(70, 30))
        self.white_list = wx.Button(self, label="白名单", size=(45, 20))
        self.white_list.SetForegroundColour("#FF3232")
        bs1.Add(self.white_list, 0, wx.CENTER | wx.ALL, 0)
        bs1.Add(self.btn_white, 0, wx.LEFT, 2)
        boxsier.Add(bs1, 0, wx.LEFT, 22)
        # 绑定
        self.btn_open_buy.Bind(wx.EVT_BUTTON, self.__open_buy)
        self.btn_close_buy.Bind(wx.EVT_BUTTON, self.__close_buy)
        self.btn_black.Bind(wx.EVT_BUTTON, self.add_black)
        self.btn_white.Bind(wx.EVT_BUTTON, self.add_white)
        self.btn_remove_black.Bind(wx.EVT_BUTTON, self.remove_from_black)
        self.btn_remove_white.Bind(wx.EVT_BUTTON, self.remove_from_white)
        self.btn_want_buy.Bind(wx.EVT_BUTTON, self.add_want)
        self.btn_want_buy_remove.Bind(wx.EVT_BUTTON, self.remove_from_want)
        self.check_auto_click.Bind(wx.EVT_CHECKBOX, self.__auto_click_check)
        self.check_auto_refresh.Bind(wx.EVT_CHECKBOX, self.__auto_refresh_check)
        self.white_list.Bind(wx.EVT_BUTTON, lambda e: self.show_list(e, "白名单列表", 302))
        self.black_list.Bind(wx.EVT_BUTTON, lambda e: self.show_list(e, "黑名单列表", 301))
        self.want_list.Bind(wx.EVT_BUTTON, lambda e: self.show_list(e, "想要买列表", 403))
        root_boxsier = wx.BoxSizer(wx.HORIZONTAL)
        root_boxsier.Add(boxsier, 1, wx.LEFT, 10)
        self.SetSizer(root_boxsier)
        self.__bind_hot_keys()
        # 初始化数据
        self.__init_data()
        self.timer = wx.Timer(self)  # 创建定时器
        self.Bind(wx.EVT_TIMER, self.clear_msg, self.timer)
    def clear_msg(self, event):
        self.notify_text.SetLabelText("")
    def __ocr_code(self):
        code = self.edit_code.GetValue()
        if code is not None and len(code.strip()) == 0:
            code = None
        if code is not None:
            if len(code) != 6:
                self.show_warning("请填写正确的代码")
                return
    def __socket_request(self, text, callback_info, port=None):
        try:
            print("socket請求:", text)
            if port:
                result = network_util.socket_request(text, port=port)
            else:
                return
        code = ocr_ths_code()
        if code is None:
            raise Exception("代码识别出错")
        self.edit_code.SetValue(code)
    def show_warning(self, content):
        self.notify_text.SetLabel(content)
        self.notify_text.SetForegroundColour("#FF7F27")
        self.timer.Stop()
        self.timer.StartOnce(20000)
    def show_info(self, content):
        self.notify_text.SetLabel(content)
        self.notify_text.SetForegroundColour("#008000")
        self.timer.Stop()
        self.timer.StartOnce(20000)
    def __get_code(self):
        self.__ocr_code()
        code = self.edit_code.GetValue()
        if code is None or len(code) != 6:
            raise Exception("请填写正确的代码")
        return code
    def add_black(self, event):
        try:
            code = self.__get_code()
            print("加入黑名单", code)
            self.__request([code], 201)
            self.show_info(f"{code}加入黑名单成功")
            self.edit_code.SetValue("")
                result = network_util.socket_request(text)
            print("请求结果:", result)
            self.signal_request.emit(callback_info[0], callback_info[1], result)
            return result
        except Exception as e:
            self.show_warning(str(e))
            return
            logging.exception(e)
    def add_white(self, event):
    @pyqtSlot(str, str, str)
    def http_request(self, path, params, callback_info):
        url = path + "?"
        if params:
            params = json.loads(params)
            url += "&".join([f"{key}={params[key]}" for key in params])
        print("http请求", url)
        callback_info = json.loads(callback_info)
        t1 = threading.Thread(target=lambda: self.__http_request(url, callback_info))
        t1.setDaemon(True)
        t1.start()
    @pyqtSlot(str, str)
    def socket_request(self, text, callback_info):
        print("socket_request", text)
        try:
            code = self.__get_code()
            print("加入白名单", code)
            result = self.__request([code], 202)
            result = json.loads(result)
            if result["code"] == 0:
                self.show_info(f"{code}加入白名单成功")
            else:
                self.show_warning(f"加入失败:{result['msg']}")
            self.edit_code.SetValue("")
        except Exception as e:
            self.show_warning(str(e))
            return
    def add_want(self, event):
        try:
            code = self.__get_code()
            print("加入想要买", code)
            self.__request([code], 401)
            self.show_info(f"{code}加入想要买名单成功")
            self.edit_code.SetValue("")
        except Exception as e:
            self.show_warning(str(e))
            return
    def remove_from_black(self, event):
        try:
            code = self.__get_code()
            print("移除黑名单", code)
            self.__request([code], 203)
            self.show_info(f"{code}移除黑名单成功")
            self.edit_code.SetValue("")
        except Exception as e:
            self.show_warning(str(e))
            return
    def remove_from_white(self, event):
        try:
            code = self.__get_code()
            print("移除白名单", code)
            self.__request([code], 204)
            self.show_info(f"{code}移除白名单成功")
            self.edit_code.SetValue("")
        except Exception as e:
            self.show_warning(str(e))
            return
    def remove_from_want(self, event):
        try:
            code = self.__get_code()
            print("移除想要买名单", code)
            self.__request([code], 402)
            self.show_info(f"{code}移除想要买名单成功")
            self.edit_code.SetValue("")
        except Exception as e:
            self.show_warning(str(e))
            return
    def show_list(self, event, title, type):
        try:
            result = self.__request_list(type)
            result = json.loads(result)
            self.__show_list(title, result["data"])
        except Exception as e:
            show_warning(str(e), None)
    def __show_list(self, title, datas):
        st = ""
        for i in range(0, len(datas)):
            st += datas[i]
            if i % 2 == 1 and i != len(datas) - 1:
                st += "\n"
            elif i != len(datas) - 1:
                st += " , "
        toastone = wx.MessageDialog(None, st, title)
        if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
            toastone.Destroy()
    def __bind_hot_keys(self):
        # 快捷键
        setting_ = wx.Menu()
        m_black = wx.MenuItem(setting_, id=101, text='&E', kind=wx.ITEM_NORMAL)
        m_white = wx.MenuItem(setting_, id=102, text='&E', kind=wx.ITEM_NORMAL)
        self.Bind(wx.EVT_MENU, self.add_black, m_black)
        self.Bind(wx.EVT_MENU, self.add_white, m_white)
        entries = [wx.AcceleratorEntry() for i in range(2)]
        entries[0].Set(wx.ACCEL_CTRL, wx.WXK_F4, 101)
        entries[1].Set(wx.ACCEL_CTRL, wx.WXK_F5, 102)
        accel = wx.AcceleratorTable(entries)
        self.SetAcceleratorTable(accel)
    def __init_data(self):
        auto_click = setting.is_ths_auto_click()
        if auto_click:
            self.check_auto_click.SetValue(True)
        else:
            self.check_auto_click.SetValue(False)
        auto_refresh = setting.is_ths_trade_auto_refresh()
        if auto_refresh:
            self.check_auto_refresh.SetValue(True)
        else:
            self.check_auto_refresh.SetValue(False)
        self.__init_trade_state()
    def __init_trade_state(self):
        # 获取交易状态
        try:
            result = self.__request_buy_state()
            result = json.loads(result)
            if result["code"] == 0:
                if result["data"]["can_buy"]:
                    self.btn_open_buy.SetLabelText("开启交易*")
                    self.btn_close_buy.SetLabelText("关闭交易")
            text_json = json.loads(text)
            params = []
            for k in text_json:
                if k == "sign":
                    continue
                if type(text_json[k]) == dict or type(text_json[k]) == list:
                    params.append(f"{k}={json.dumps(text_json[k], separators=(',', ':'))}")
                else:
                    self.btn_open_buy.SetLabelText("开启交易")
                    self.btn_close_buy.SetLabelText("关闭交易*")
                    params.append(f"{k}={text_json[k]}")
            params.sort()
            params.append("%Yeshi2014@#.")
            params_str = "&".join(params)
            md5 = hashlib.md5()
            md5.update(params_str.encode("utf-8"))
            md5_hash = md5.hexdigest()
            text_json["sign"] = md5_hash
            text = json.dumps(text_json)
        except:
            pass
    def __auto_click_check(self, event):
        if event.Selection:
            setting.set_ths_auto_click(True)
        else:
            setting.set_ths_auto_click(False)
    def __auto_refresh_check(self, event):
        if event.Selection:
            setting.set_ths_trade_auto_refresh(True)
        else:
            setting.set_ths_trade_auto_refresh(False)
    def __request(self, codes, type):
        client = socket.socket()  # 生成socket,连接server
        ip_port = (SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        data = {"type": type, "data": {"codes": codes}}
        client.send(json.dumps(data).encode("utf-8"))
        result = client.recv(1024)
        client.close()
        return result.decode("gbk")
    def __request_list(self, type):
        client = socket.socket()  # 生成socket,连接server
        ip_port = (SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        data = {"type": type, "data": {}}
        client.send(json.dumps(data).encode("utf-8"))
        # 读取内容
        result = client.recv(10240)
        client.close()
        return result.decode("gbk")
    def __request_buy(self, is_open):
        client = socket.socket()  # 生成socket,连接server
        ip_port = (SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        data = {"type": 501, "data": {"open": is_open}}
        client.send(json.dumps(data).encode("utf-8"))
        # 读取内容
        result = client.recv(10240)
        client.close()
        return result.decode("gbk")
    # 获取买入状态
    def __request_buy_state(self):
        client = socket.socket()  # 生成socket,连接server
        ip_port = (SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        data = {"type": 502, "data": {}}
        client.send(json.dumps(data).encode("utf-8"))
        # 读取内容
        result = client.recv(1024)
        client.close()
        return result.decode("gbk")
    def __open_buy(self, event):
        def open_buy(sure):
            if sure:
                try:
                    result = self.__request_buy(True)
                    msg = json.loads(result)["msg"]
                    show_info(msg, None)
                    self.__init_trade_state()
                except Exception as e:
                    show_warning(str(e), None)
        show_sure("是否开启交易", open_buy)
    def __close_buy(self, event):
        def close_buy(sure):
            if sure:
                try:
                    result = self.__request_buy(False)
                    msg = json.loads(result)["msg"]
                    show_info(msg, None)
                    self.__init_trade_state()
                except Exception as e:
                    show_warning(str(e), None)
        show_sure("是否关闭交易", close_buy)
    def OnExit(self, e):
        try:
            setting.set_float_watch_window_info((self.Position[0], self.Position[1], self.Size[0], self.Size[1]))
        except Exception as e:
            print("")
        self.Hide()
# 代码数据
class CodeDataFrame(wx.Frame):
    def __init__(self, set_codes_success_callback, show_float_callback, show_main_callback):
        # style=wx.CAPTION |
        wx.Frame.__init__(self, None, -1, "数据",
                          style=wx.CAPTION | wx.STAY_ON_TOP | wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.CLOSE_BOX | wx.RESIZE_BORDER,
                          size=(320, 195))
        self.set_codes_success = set_codes_success_callback
        self.SetBackgroundColour(wx.Colour(224, 224, 224))
        # self.SetTransparent(230)
        boxsier = wx.BoxSizer(wx.VERTICAL)
        # ---配置浏览器----
        webview.WebView.MSWSetEmulationLevel()
        backend = None
        backends = [
            (webview.WebViewBackendEdge, 'WebViewBackendEdge'),
            (webview.WebViewBackendIE, 'WebViewBackendIE'),
            (webview.WebViewBackendWebKit, 'WebViewBackendWebKit'),
            (webview.WebViewBackendDefault, 'WebViewBackendDefault'),
        ]
        for id, name in backends:
            available = webview.WebView.IsBackendAvailable(id)
            if available and backend is None:
                backend = id
        self.wv = webview.WebView.New(self, size=wx.Size(self.Size[0], self.Size[
            1]), backend=backend)
        self.wv.SetZoomType(wx.html2.WEBVIEW_ZOOM_TYPE_TEXT)
        self.Bind(webview.EVT_WEBVIEW_NAVIGATING, self.OnWebViewNavigating, self.wv)
        self.Bind(webview.EVT_WEBVIEW_LOADED, self.OnWebViewLoaded, self.wv)
        # 执行定时更新
        self.timer = wx.Timer(self)  # 创建定时器
        self.Bind(wx.EVT_TIMER, self.__update_kpl, self.timer)
        self.timer.Start(3000)
        self.count = 0
        self.wv.SetBackgroundColour(wx.Colour(224, 224, 224))
        if "gtk2" in wx.PlatformInfo:
            self.wv.SetStandardFonts()
        boxsier.Add(self.wv, 1, wx.EXPAND)
        self.latest_code = ''
        self.SetSizer(boxsier)
        window_info = setting.get_xgb_window_info()
        if window_info:
            self.SetPosition(wx.Point(window_info[0], window_info[1]))
            self.Size = wx.Size(window_info[2], window_info[3])
        self.Bind(wx.EVT_CLOSE, self.OnExit)
        self.Bind(wx.EVT_SIZE, self.OnResize)
        self.wv.SetPage("", "")
        self.__menu()
        self.show_float_callback = show_float_callback
        self.show_main_callback = show_main_callback
    def OnWebViewNavigating(self, evt):
        # this event happens prior to trying to get a resource
        if evt.GetURL().find("cancel_order://") >= 0:
            code__ = evt.GetURL().split("cancel_order://")[1]
            # if wx.MessageBox("是否要撤单?",
            #                  style=wx.YES_NO | wx.ICON_QUESTION) == wx.YES:
            self.__cancel_order(code__)
            evt.Veto()
    def OnWebViewLoaded(self, evt):
        # pass
        # 加载开盘啦数据
        self.__update_kpl(None)
    # 更新开盘啦数据
    def __update_kpl(self, evt):
        response = requests.get(f"http://{SERVER_HOST}:{HTTP_PORT}/get_kpl_data")
        if response.status_code == 200:
            print(response.text)
            with open("res/kpl.js", mode="r", encoding="utf-8") as f:
                lines = f.readlines()
                script = "\n".join(lines)
                script = script.replace("{}", response.text)
                self.wv.RunScript(script)
    def __cancel_order(self, code):
        client = socket.socket()  # 生成socket,连接server
        ip_port = (SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
        try:
            client.connect(ip_port)
            data = {"type": 80, "data": {"code": code}}
            client.send(json.dumps(data).encode("utf-8"))
            result = client.recv(1024)
            result = result.decode("utf-8")
            result = json.loads(result)
            if result["code"] == 0:
                # show_info("撤单处理成功", None)
                # 刷新数据
                self.__init_data(self.latest_code)
            else:
                show_warning(result["msg"], None)
            client.close()
        except Exception as e:
            show_warning(str(e), None)
    def OnWebViewError(self, evt):
        # The full document has loaded
        print("网页出错")
    def handleMessage(self, event):
        print("数据回调")
    def set_data(self, code, data):
        self.wv.SetPage(data, "")
    # 设置代码
    def set_code(self, code):
        if self.latest_code == code:
            return
        self.latest_code = code
        t1 = threading.Thread(target=lambda: self.__init_data(code))
        callback_info = json.loads(callback_info)
        t1 = threading.Thread(target=lambda: self.__socket_request(text, callback_info))
        t1.setDaemon(True)
        t1.start()
    def __init_data(self, code):
        if code:
            # 请求网络
            result_data = self.__request(code)
            if result_data:
                result_json = json.loads(result_data)
                if result_json['code'] == 0:
                    code = result_json['data']['code']
                    data = result_json['data']['data']
                    wx.CallAfter(self.set_data, code, data)
    def __request(self, code, type=71):
        client = socket.socket()  # 生成socket,连接server
        ip_port = (SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        data = {"type": type, "data": {"code": code}}
        client.send(json.dumps(data).encode("utf-8"))
        # 读取内容
        result = client.recv(102400)
        client.close()
        return result.decode("gbk")
    def OnExit(self, e):
    @pyqtSlot(str, str)
    def ls_socket_request(self, text, callback_info):
        print("ls_socket_request", text)
        try:
            setting.set_xgb_window_info((self.Position[0], self.Position[1], self.Size[0], self.Size[1]))
        except Exception as e:
            print("")
        jueJinProcess.terminate()
        sys.exit(0)
            text_json = json.loads(text)
            params = []
            for k in text_json:
                if k == "sign":
                    continue
                if type(text_json[k]) == dict or type(text_json[k]) == list:
                    params.append(f"{k}={json.dumps(text_json[k], separators=(',', ':'))}")
                else:
                    params.append(f"{k}={text_json[k]}")
            params.sort()
            params.append("%Yeshi2014@#.")
            params_str = "&".join(params)
            md5 = hashlib.md5()
            md5.update(params_str.encode("utf-8"))
            md5_hash = md5.hexdigest()
            text_json["sign"] = md5_hash
            text = json.dumps(text_json)
        except:
            pass
        callback_info = json.loads(callback_info)
        t1 = threading.Thread(target=lambda: self.__socket_request(text, callback_info, port=14008))
        t1.setDaemon(True)
        t1.start()
    def OnResize(self, e):
        print("变化后的尺寸", e.Size)
        self.wv.Size = e.Size
    # 获取客户端ID
    @pyqtSlot(result=str)
    def get_client(self):
        return setting.get_client()
    def __menu(self):
        self.mainmenu = wx.MenuBar()
        setting_ = wx.Menu()
        # setting_.Append(104, '&钉住', '', kind=wx.ITEM_CHECK)
        # setting_.Check(104, setting.is_stay_on_top())
        setting_.AppendSeparator()
        setting_.Append(101, '&掘金参数配置', 'Open a new document')
        setting_.AppendSeparator()
        setting_.Append(103, '&代码管理', 'Open a new document')
        setting_.Append(102, '&下载涨停代码', 'Open a new document')
        setting_.AppendSeparator()
        setting_.Append(104, '&GPU检测', 'Open a new document')
        self.mainmenu.Append(setting_, '&设置')
        auto_help = wx.Menu()
        auto_help.Append(202, '&同花顺设置', '')
        self.mainmenu.Append(auto_help, '&自动化')
        view = wx.Menu()
        view.Append(301, '&打开悬浮盯盘', '')
        view.Append(302, '&打开分时看盘', '')
        self.mainmenu.Append(view, '&视图')
        # 设置事件
        self.Bind(wx.EVT_MENU, self.__set_juejin_params, id=101)
        self.Bind(wx.EVT_MENU, self.__download_codes, id=102)
        self.Bind(wx.EVT_MENU, self.__manage_code, id=103)
        self.Bind(wx.EVT_MENU, self.__gpu_is_avaiable, id=104)
        self.Bind(wx.EVT_MENU, self.__manage_ths_pos, id=202)
        self.Bind(wx.EVT_MENU, self.__show_float_callback, id=301)
        self.Bind(wx.EVT_MENU, self.__show_main_callback, id=302)
        self.SetMenuBar(self.mainmenu)
    def __gpu_is_avaiable(self,event):
        if torch.cuda.is_available():
            show_info("GPU可用", None)
        else:
            show_warning("GPU不可用", None)
    def __set_juejin_params(self, event):
        ps = self.GetPosition()
        size = self.GetSize()
        JueJinSettingFrame((ps[0] + size[0] / 2, ps[1] + size[1] / 2)).Show()
    def __download_codes(self, event):
        try:
            result = self.__request("", 72)
            result = json.loads(result)
            if result['code'] == 0:
                codes_dict = result['data']
                if codes_dict:
                    for key in codes_dict:
                        # 将代码保存到桌面
                        path = f"C:\\Users\\Administrator\\Desktop\\涨停代码{key}.txt"
                        with open(path, mode='w') as f:
                            for c in codes_dict[key]:
                                f.write(c)
                                f.write("\n")
                    show_info("下载成功", None)
            else:
                show_warning(result['msg'], None)
        except Exception as e:
            show_warning(str(e), None)
    def __manage_code(self, event):
        ps = self.GetPosition()
        size = self.GetSize()
        CodesSettingFrame((ps[0] + size[0] / 2, ps[1] + size[1] / 2), self.set_codes_success).Show()
    def __manage_ths_pos(self, event):
        ps = self.GetPosition()
        size = self.GetSize()
        THSPositionSettingFrame((ps[0] + size[0] / 2, ps[1] + size[1] / 2)).Show()
    def __show_float_callback(self, event):
        self.show_float_callback()
    def __show_main_callback(self, event):
        self.show_main_callback()
    @pyqtSlot(str)
    def add_code_to_ths(self, code):
        # 添加到同花顺
        threading.Thread(target=lambda: ths_util.add_code_to_zixuan(code), daemon=True).start()
class TickFrame(wx.Frame):
class SecondWindowBridgeClass(BaseBridgeClass):
    @pyqtSlot(str)
    def set_target_code(self, code):
        # 设置目标代码
        window_msg_queue.put_nowait({"type": "set_target_code", "data": {"code": code}})
class JSBridgeClass(BaseBridgeClass):
    def __init__(self, window, webview):
        super().__init__(webview)
        self.window = window
        self.webview = webview
    @pyqtSlot(str)
    def show_info(self, msg):
        QMessageBox.information(self.window, "提示", msg, QMessageBox.Yes)
    @pyqtSlot(str, str, str, str, str, str)
    def set_trade_info(self, code, name, trade_data, trade_record, initiative_buy_codes, passive_buy_codes):
        self.window.set_trade_data(code, name, trade_data, trade_record, initiative_buy_codes, passive_buy_codes)
class MessageWindow(QMainWindow):
    msgChange = pyqtSignal(str)
    def __setMsg(self, msg):
        print("收到信息:", msg)
        palette = QPalette()
        if msg.find("撤单") > -1:
            palette.setColor(QPalette.WindowText, QColor(0, 128, 0))  # 设置字体颜色为绿色
        elif msg.find("下单") > -1:
            palette.setColor(QPalette.WindowText, QColor(34, 26, 178))  # 设置字体颜色为黄色
        elif msg.find("成交") > -1:
            palette.setColor(QPalette.WindowText, QColor(255, 0, 0))  # 设置字体颜色为红色
        self.label.setPalette(palette)
        self.label.setText(msg)
        self.show()
        self.timer.stop()
        self.timer.start(5000)
    # 设置信息
    def setMsg(self, msg):
        # TODO 测试
        self.msgChange.emit(msg)
    def __init__(self):
        '''构造函数'''
        wx.Frame.__init__(self, None, -1, APP_TITLE, style=wx.DEFAULT_FRAME_STYLE,
                          size=(800, 500))
        # ^ wx.RESIZE_BORDER ^ wx.STAY_ON_TOP
        # 默认style是下列项的组合:wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN
        self.SetBackgroundColour(wx.Colour(0, 0, 0))
        win_info = setting.get_tick_window_info()
        if win_info:
            self.SetPosition(wx.Point(win_info[0], win_info[1]))
            self.Size = wx.Size(win_info[2], win_info[3])
        else:
            self.Center()
        super().__init__()
        window_height = 80
        padding = 10
        self.resize(300, 50)
        # hwnds = win32_util.search_window("悬浮盯盘", is_one_result=True)
        # if hwnds:
        #     rect = win32gui.GetWindowRect(hwnds[0])
        #     self.move(rect[0] + padding - 3, rect[1] - window_height - 30)
        #     if rect[2] - rect[0] > 0:
        #         self.resize((rect[2] - rect[0]) - (padding - 2) * 2, window_height)
        self.setWindowTitle("消息提示")
        self.setWindowFlag(Qt.WindowStaysOnTopHint, True)
        self.setWindowOpacity(0.9)
        # 去掉标题栏
        # self.setWindowFlags(Qt.FramelessWindowHint)
        # 删除最大最小化按钮
        # self.setWindowFlags(self.windowFlags() & ~Qt.WindowMaximizeButtonHint & ~Qt.WindowMinimizeButtonHint)
        # 以下代码处理图标
        # if hasattr(sys, "frozen") and getattr(sys, "frozen") == "windows_exe":
        #     exeName = win32api.GetModuleFileName(win32api.GetModuleHandle(None))
        #     icon = wx.Icon(exeName, wx.BITMAP_TYPE_ICO)
        # else:
        #     icon = wx.Icon(APP_ICON, wx.BITMAP_TYPE_ICO)
        # self.SetIcon(icon)
        # 定义窗口关闭
        self.Bind(wx.EVT_CLOSE, self.OnExit)
        self.Bind(wx.EVT_SIZE, self.OnResize)
        self.panels = []
        self.scroll = None
        self.mark_lines = {}
        self.col = 1
        self.ratio = 0.63
        self.__re_draw()
        font = QFont('微软雅黑', 12)  # 设置字体为微软雅黑,字号为12
        palette = QPalette()  # 创建一个调色板
        palette.setColor(QPalette.WindowText, QColor(255, 0, 0))  # 设置字体颜色为红色
        self.timer = wx.Timer(self)  # 创建定时器
        self.Bind(wx.EVT_TIMER, self.post_redraw, self.timer)  # 绑定一个定时器事件
        self.last_size = (self.Size[0], self.Size[1])
        self.label = QLabel(self)
        self.label.setStyleSheet(f"padding: {padding}px;")
        self.label.setFont(font)
        self.label.setPalette(palette)  # 将该调色板应用到QLabel上
        self.label.setAlignment(Qt.AlignTop)
        self.label.resize(self.width(), self.height())
        # 设置多行显示
        self.label.setWordWrap(True)  # 设置Label的自动换行
        # self.scroll.Layout()
        # self.boxsier.Fit(self.scroll)
        self.timer = QTimer()
        self.timer.timeout.connect(self.close)
        # 只运行1次定时器
        self.timer.setSingleShot(True)
        self.msgChange.connect(self.__setMsg)
        # boxsier.Add(mainBoxsier, 1, wx.EXPAND | wx.ALL, 5)
        # self.SetSizer(boxsier)
        # mainBoxsier.Fit(self)
        if setting.is_stay_on_top():
            self.WindowStyle = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN | wx.STAY_ON_TOP
    def scrollTo(self, pos):
        self.scroll.Scroll(0, pos)
class MsgListWindow(QMainWindow):
    def __re_draw(self):
        codes_name = juejin_core.GPCodeManager().get_codes_with_names()
        rows = len(codes_name)
        if rows % self.col == 0:
            rows = rows / self.col
        else:
            rows = rows / self.col + 1
        space = 0
        if self.scroll is None:
            self.scroll = wx.ScrolledWindow(self, -1, size=(800, 1000))
            self.boxsier = wx.FlexGridSizer(rows, self.col, space, space)
            self.scroll.SetSizer(self.boxsier)
        self.scroll.EnableScrolling(False, True)
        if self.panels:
            for p in self.panels:
                p.Destroy()
        self.boxsier.Clear()
    def __init__(self):
        super().__init__()
        self.setWindowTitle('历史消息')
        # 窗口置顶
        self.setWindowFlag(Qt.WindowStaysOnTopHint, True)
        self.resize(500, 800)
        self.webview = QWebEngineView()
        self.webview.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)
        self.webview.page().setZoomFactor(1)
        self.setCentralWidget(self.webview)
        pannel_height = round((self.Size[0] - (self.col - 1) * space) / self.col * self.ratio)
        # JS桥设置
        channel = QWebChannel(self.webview.page())
        self.webview.page().setWebChannel(channel)
        self.python_bridge = BaseBridgeClass(self.webview)
        channel.registerObject("Bridge", self.python_bridge)
        self.scroll.SetScrollbars(1, 1, 0, pannel_height * rows)
        self.scroll.SetScrollRate(0, pannel_height)
        global drawManager
        axes_list = []
        self.panels = []
        for i in range(0, len(codes_name)):
            # pos=(0, i * pannel_height)
            pannel = wx.Panel(self.scroll, size=(-1, pannel_height))
            pannel.BackgroundColour = wx.Colour(0, 0, 0)
            self.panels.append(pannel)
            self.boxsier.Add(pannel)
            axes = self.__create_canvas(pannel, "{}({})".format(codes_name[i][0], codes_name[i][1]), codes_name[i][1],
                                        codes_name[i][2])
            axes_list.append(axes)
    def loadUrl(self, url):
        self.webview.load(QUrl(url))
        self.scroll.Layout()
        # self.boxsier.Fit(self.scroll)
        #
        # 初始化数据
        drawManager = DrawManager(axes_list, codes_name)
        t1 = threading.Thread(target=lambda: drawManager.init_code_datas())
        # 后台运行
        t1.setDaemon(True)
        t1.start()
    def __create_canvas(self, pannel, title, name, price, close_callback=None):
        def show_mouse_line(event):
            # 删除之前的线
            if title in self.mark_lines:
                if self.mark_lines.get(title):
                    line = self.mark_lines.get(title).get("mouse")
                    if line is not None:
                        line.remove()
                        self.mark_lines.get(title).pop("mouse")
            else:
                self.mark_lines[title] = {}
class SecondWindow(QMainWindow):
    signal_update_kpl = pyqtSignal(str)
    def update_kpl_func(self):
        while True:
            time.sleep(3)
            try:
                line = axes2.axhline(event.ydata, linestyle='-', color='white', lw=0.5)
                self.mark_lines[title]["mouse"] = line
                axes2.figure.canvas.draw()
                self.__update_kpl()
            except:
                pass
        def clear_mouse_line(event):
            print("clear_mouse_line")
            if title in self.mark_lines:
                if self.mark_lines.get(title):
                    line = self.mark_lines.get(title).get("mouse")
                    if line is not None:
                        line.remove()
                        self.mark_lines.get(title).pop("mouse")
                        axes2.figure.canvas.draw()
        def close_canvas(event):
            print("关闭", title)
            close_callback(title)
        dpi = 100
        width_dpi = self.Size[0] / (dpi * self.col)
        figure_score = Figure(figsize=(width_dpi, round(width_dpi * (self.ratio), 2)), dpi=dpi)
        # 设置外边距
        right_padding_px = 85
        right = round((self.Size[0] - right_padding_px) / self.Size[0], 4)
        figure_score.subplots_adjust(left=0.01, bottom=0.15, top=0.92,
                                     right=right)
        # 设置字体颜色
        plt.rcParams["text.color"] = "red"
        plt.rcParams["axes.labelcolor"] = "red"
        # 设置坐标轴数字颜色
        plt.rcParams["xtick.color"] = "white"
        plt.rcParams["ytick.color"] = "white"
        # 设置坐标轴颜色
        plt.rcParams["axes.edgecolor"] = "firebrick"
        # 解决中文乱码问题
        plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置字体
        plt.rcParams["font.serif"] = ["SimHei"]
        plt.rcParams["axes.unicode_minus"] = False  # 该语句解决图像中的“-”负号的乱码问题
        # 测试
        buttonaxe = plt.axes([0.94, 0.5, 0.1, 0.1])
        button1 = Button(buttonaxe, '关闭', color='white', hovercolor='yellow')
        axes = figure_score.add_subplot(1, 1, 1)
        axes.autoscale(True)
        # axes_score.plot(t_score, s_score, 'ro', t_score, s_score, 'k')
        axes.set_title(title)
        axes.grid(color='firebrick', ls='-', lw=0.5)
        axes.set_xlabel(f'时间({name})')
        axes.dist = 0
        # axes.set_ylabel(u'价格')
        # 获取平开价
        extra = 0  # (tool.get_limit_up_price(price)-decimal.Decimal(price))*decimal.Decimal(0.02)
        axes.set_ylim(tool.get_limit_down_price(price) - extra, tool.get_limit_up_price(price) + extra)
        axes.patch.set_facecolor('black')
        figure_score.patch.set_facecolor('black')
        axes2 = axes.twinx()
        # axes2.grid(color='firebrick', ls='-', lw=0.5)
        # axes2.grid(color='firebrick', ls='-', lw=0.5)
        axes2.grid(False)
        # 鼠标在画布移动
        axes2.figure.canvas.mpl_connect('motion_notify_event', show_mouse_line)
        # 鼠标离开画布
        axes2.figure.canvas.mpl_connect('axes_leave_event', clear_mouse_line)
        # 设置纵坐标轴
        limit_up_price = float(tool.get_limit_up_price(price))
        max_rate = round((limit_up_price - price) / price, 4) * 100
        print("涨停最大比例", max_rate)
        yticks2 = []
        for i in range(0, 41):
            if i >= 20:
                yticks2.append(0 - round(max_rate * (20 - i) / 20, 4))
            else:
                yticks2.append(round(max_rate * (i - 20) / 20, 4))
        yticks2_labels = []
        yticks = []
        yticks_labels = []
        for i in range(0, len(yticks2)):
            if i % 2 == 0:
                yticks2_labels.append("{}%".format(abs(round(yticks2[i], 2))))
            else:
                yticks2_labels.append("")
            price_ = round((1 + yticks2[i] / 100) * price, 2)
            yticks.append(price_)
            if i % 2 == 0:
                yticks_labels.append("")
                # yticks_labels.append(round(yticks[i], 2))
                if i == 20:
                    axes2.axhline(yticks2[i], linestyle='-', color='firebrick', lw=2)
                else:
                    axes2.axhline(yticks2[i], linestyle='-', color='firebrick', lw=1.2)
            else:
                # axes2.axhline(yticks2[i], linestyle='-', color='firebrick', lw=0.5)
                yticks_labels.append("")
        # 加粗中轴线
        axes2.set_ylabel(u'')
        # 设置纵轴的值的范围
        axes2.set_ylim(0 - max_rate * (1), max_rate * (1))
        axes2.set_yticks(yticks2)
        axes2.set_yticklabels(yticks2_labels)
        axes.set_yticks(yticks)
        axes.set_yticklabels(yticks_labels)
        # 设置纵坐标数值颜色
        for i in range(0, 19):
            axes.get_yticklabels()[i].set_color("green")
            axes2.get_yticklabels()[i].set_color("green")
        for i in range(20, 20):
            axes.get_yticklabels()[i].set_color("white")
            axes2.get_yticklabels()[i].set_color("white")
        for i in range(21, 41):
            axes.get_yticklabels()[i].set_color("red")
            axes2.get_yticklabels()[i].set_color("red")
        line = axes2.plot([], [], color='white', linewidth=1)
        average_line = axes2.plot([], [], color='green', linewidth=1, linestyle='-')
        average_line_1m = axes2.plot([], [], color='yellow', linewidth=1, linestyle='-')
        # axes2.legend(loc='upper left')
        cannvas = FigureCanvas(pannel, -1, figure_score)
        axes2.text(1, 11.5, r'现价:0.0 涨幅:0.00% \n留格局:0%', fontsize=10, color='white')
        axes2.text(-1, -11.5, r'现价:0.0 涨幅:0.00% \n留格局:0%', fontsize=10, color='white')
        axes2.spines['top'].set_visible(False)
        axes.spines['top'].set_visible(False)
        axes2.spines['bottom'].set_visible(False)
        axes.spines['bottom'].set_visible(False)
        return (axes2, line, average_line_1m, average_line)
    def __show_top(self, event):
        if event.Selection:
            setting.set_stay_on_top(1)
            self.WindowStyle = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN | wx.STAY_ON_TOP
    def __init__(self, parent=None):
        super(SecondWindow, self).__init__(parent)
        self.setWindowTitle('看盘副屏')
        window_info = setting.get_kp_second_window_info()
        if window_info:
            self.move(window_info[0], window_info[1])
            self.resize(window_info[2], window_info[3])
        else:
            setting.set_stay_on_top(0)
            self.WindowStyle = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN
            self.resize(500, 800)
    def OnExit(self, e):
        self.webview = QWebEngineView()
        self.webview.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)
        self.webview.page().setZoomFactor(1)
        self.setCentralWidget(self.webview)
        # JS桥设置
        channel = QWebChannel(self.webview.page())
        self.webview.page().setWebChannel(channel)
        self.python_bridge = SecondWindowBridgeClass(self.webview)
        channel.registerObject("Bridge", self.python_bridge)
        # win32gui.SetWindowLong(self.winId(), win32con.GWL_WNDPROC, self.handleCustomMessage)
        # self.signal_update_kpl.connect(self.set_kpl_data)
        # t1 = threading.Thread(target=self.update_kpl_func)
        # t1.setDaemon(True)
        # t1.start()
    # 自定义消息处理函数
    def handleCustomMessage(self, hwnd, msg, wparam, lparam):
        if msg == win32con.WM_USER + 1024:
            # 解析数据
            # try:
            #     code = ctypes.cast(lparam, ctypes.py_object).value
            #     print(code)
            # except:
            #     pass
            pass
            # 处理数据
            # self.dataReceived.emit(intValue, stringValue)
        return win32gui.DefWindowProc(hwnd, msg, wparam, lparam)
    def loadUrl(self, url):
        self.webview.load(QUrl(url))
    # 设置交易数据
    def set_trade_data(self, code, code_name, trade_data, trade_record, initiative_buy_codes, passive_buy_codes):
        script_str = "try{"
        script_str += "var trade_data = " + trade_data + ";"
        script_str += "var trade_record = " + trade_record + ";"
        script_str += "app.set_trade_info(\"{}\",\"{}\",trade_data,trade_record,'{}','{}');".format(code, code_name,
                                                                                                    initiative_buy_codes,
                                                                                                    passive_buy_codes)
        script_str += "} catch(e){console.log(e)}"
        self.webview.page().runJavaScript(script_str)
    def set_kpl_data(self, data):
        self.webview.page().runJavaScript(f"fill_kpl_data('{data}')")
    # 更新开盘啦数据
    def __update_kpl(self):
        datas = network_util.http_get("/get_kpl_data")
        self.signal_update_kpl.emit(datas)
    def set_target_code(self, code):
        print("set_target_code", code)
        # 测试
        self.webview.page().runJavaScript(f"app.set_target_code('{code}')")
    def closeEvent(self, event):
        try:
            setting.set_tick_window_info((self.Position[0], self.Position[1], self.Size[0], self.Size[1]))
            setting.set_kp_second_window_info(
                (self.pos().x(), self.pos().y(), self.size().width(), self.size().height()))
        except Exception as e:
            print("")
        self.Hide()
    def post_redraw(self, evt):
        if abs(self.last_size[0] - self.Size[0]) > 20:
            print("--------post_redraw--------")
            self.last_size = (self.Size[0], self.Size[1])
            self.__re_draw()
    def OnResize(self, e):
        print("变化后的尺寸", e.Size)
        # 留出滚动条,留出上边距
        if self.scroll:
            self.scroll.Size = (e.Size[0] - 15, e.Size[1] - 60)
            for p in self.panels:
                p_height = round(e.Size[0] * (450 / 800))
                p.Size = (e.Size[0], p_height)
        self.timer.Stop()
        self.timer.StartOnce(1000)
        # 降低重绘频率
        # self.__re_draw()
    def set_codes_success(self):
        print("设置代码成功回调")
        p2.send("resub")
        self.__re_draw()
# 绘图管理器
class DrawManager:
    X_RANGE_MINIUTES = 60
    X_DATA_MINIUTES = 56
class WebEnginePage(QWebEnginePage):
    def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
        """处理 JavaScript 控制台消息"""
        print(f"JavaScript Console Error: {message} at line {lineNumber} of {sourceID}")
    def __load_lack_datas(self, code, time_ranges):
        codeDataManager = code_data_manager.CodeDataManager()
        day = tool.get_now_date_str()
        for time_range in time_ranges:
            results = juejin_core.GPCodeManager().get_history_tick(code, day + " " + time_range[0],
                                                                   day + " " + time_range[1])
            datas = []
            for data in results:
                datas.append(juejin_core.parse_tick(data))
            # 保存数据
            for data in datas:
                # 09:25:00之前的数据不保存
                created_at = data["created_at"].strftime("%Y-%m-%d %H:%M:%S")
                time_ = created_at[-8:]
                if tool.trade_time_sub(time_, "09:25:00") < 0:
                    continue
                if tool.trade_time_sub(time_, "15:00:00") > 0:
                    continue
                if tool.trade_time_sub(time_, "11:30:00") > 0 and tool.trade_time_sub(time_, "13:00:00") < 0:
                    continue
                # 不是今天的数据不保存
                if day != created_at[:10]:
                    continue
                codeDataManager.save_data(data)
    def init_code_datas(self):
        global code_datas
        global max_min_prices
        codeDataManager = code_data_manager.CodeDataManager()
        gpCodeManager = juejin_core.GPCodeManager()
        code_datas = {}
        max_min_prices = {}
        codes = gpCodeManager.get_codes()
        if codes:
            # 获取当日的最高价最低价
            res = juejin_core.GPCodeManager().get_min_and_max_price(codes)
            for data in res:
                max_min_prices[data[0]] = (data[1], data[2])
            for code in codes:
                # 加载历史数据
                code_datas[code] = []
                old_datas = codeDataManager.get_datas(code)
                # 获取缺失的数据
                min_time = tool.get_now_time_str()
                if int(min_time.replace(":", "")) > int("150000"):
                    min_time = "15:00:00"
                elif int("113000") < int(min_time.replace(":", "")) < int("130000"):
                    min_time = "11:30:00"
                min_time = tool.trade_time_add_second(min_time, 0 - DrawManager.X_DATA_MINIUTES * 60)
                ranges = codeDataManager.get_lack_datas_time_range(old_datas, min_time)
                if len(ranges) > 0:
                    self.__load_lack_datas(code, ranges)
                    old_datas = codeDataManager.get_datas(code)
                if old_datas:
                    code_datas[code].extend(old_datas)
                    # self.update(code, code_datas[code])
                    wx.CallAfter(self.update, code, code_datas[code])
    # 更新数据
    def __update_data(self, code, axes, datas, min_rate, max_rate):
        def get_time_as_seconds(created_at):
            time_ = created_at[-8:]
            if tool.get_time_as_second("13:00:00") > tool.get_time_as_second(time_) > tool.get_time_as_second(
                    "11:30:00"):
                time_ = "11:30:00"
            time_s = int(time_.split(":")[0]) * 3600 + int(time_.split(":")[1]) * 60 + int(
                time_.split(":")[2]) - 9 * 3600 - 60 * 30
            if int(time_.replace(":", "")) > int("11:30:00".replace(":", "")):
                time_s -= 90 * 60
            return time_s
        def seconds_2_time_str(seconds):
            seconds += 9 * 3600 + 60 * 30
            if seconds > 11 * 3600 + 60 * 30:
                seconds += 90 * 60
            h = seconds // 3600
            m = seconds % 3600 // 60
            s = seconds % 60
            return "{0:0>2}:{1:0>2}:{2:0>2}".format(h, m, s)
        # 删除9:30以前的数据
        for i in range(0, len(datas)):
            time_ = datas[i]["created_at"][-8:]
            if int(time_.replace(":", "")) >= int("092600"):
                datas = datas[i:]
                break
        # 取最近14分钟的数据
        for i in range(len(datas) - 1, -1, -1):
            time_ = datas[i]["created_at"][-8:]
            if tool.trade_time_sub(datas[-1]["created_at"][-8:], time_) >= self.X_DATA_MINIUTES * 60:
                datas = datas[i:]
                break
        xs = []
        ys_rate = []
        ys_average_rate_1m = []
        ys_average_rate = []
        for data in datas:
            xs.append(get_time_as_seconds(data["created_at"]))
            ys_rate.append(data["rate"] * 100)
            ys_average_rate_1m.append(data["average_rate"] * 100 + 1.5)
            ys_average_rate.append(data["average_rate"] * 100)
        xticks = []
        xticklabels = []
        # 设置X轴范围为09:30:00 到15:00:00
        # x轴范围为0-15分钟
        end_x = "0000-00-00 " + tool.trade_time_add_second(datas[0]["created_at"][-8:], self.X_RANGE_MINIUTES * 60)
        axes[0].set_xlim(get_time_as_seconds(datas[0]["created_at"]), get_time_as_seconds(end_x))
        # if len(xs) < 2 or xs[-1] - xs[0] < 30 * 60:
        #     axes[0].set_xlim(xs[0], xs[0] + 30 * 60)
        # else:
        #     axes[0].set_xlim(xs[0], xs[-1])
        xms = axes[0].get_xlim()
        yms = axes[0].get_ylim()
        step = (int(xms[1]) - int(xms[0])) // 30
        for i in range(int(xms[0]), int(xms[1] + 1), step):
            xticks.append(i)
            xticklabels.append("")
        axes[0].set_xticks(xticks)
        axes[0].set_xticklabels(xticklabels)
        axes[1][0].set_data(xs, ys_rate)
        axes[2][0].set_data(xs, ys_average_rate_1m)
        if axes[3]:
            axes[3][0].set_data(xs, ys_average_rate)
        texts = axes[0].texts
        texts[0].set_text("{}% \n留格局:0%".format(round(datas[-1]["rate"] * 100, 2)))
        texts[1].set_text("{}".format(datas[-1]["created_at"].split(" ")[1]))
        texts[0].set_x(xms[1] - 80)
        texts[0].set_y(yms[1] + 0.5)
        texts[1].set_x(xms[0])
        texts[1].set_y(yms[0] - 1.5)
        # 删除之前
        if code in self.lines:
            for key in self.lines[code]:
                line = self.lines[code][key]
                line.remove()
            self.lines.pop(code)
        # 绘制最大最小坐标
        line_min = axes[0].axhline(min_rate, linestyle='--', color='yellow', lw=0.5)
        line_max = axes[0].axhline(max_rate, linestyle='--', color='yellow', lw=0.5)
        self.lines[code] = {"min": line_min, "max": line_max}
        axes[0].figure.canvas.draw()
        axes[0].figure.canvas.flush_events()
    def __init__(self, axes_list, codes_info):
        self.axes_list = axes_list
        self.codes_info = codes_info
        self.lines = {}
    def update(self, code, datas):
        __start_time = time.time()
        # 获取前高和前低
        max_rate = None
        min_rate = None
        if code in max_min_prices:
            pre_price = juejin_core.GPCodeManager().get_pre_prices(code)
            min_rate = round((max_min_prices[code][0] - pre_price) / pre_price, 4)
            max_rate = round((max_min_prices[code][1] - pre_price) / pre_price, 4)
        for data in datas:
            rate = data["rate"] * 100
            created_at = data["created_at"][-8:]
            if tool.get_time_as_second("15:00:00") >= tool.get_time_as_second(created_at) >= tool.get_time_as_second(
                    "09:30:00"):
                if max_rate is None:
                    max_rate = rate
                if min_rate is None:
                    min_rate = rate
                if rate > max_rate:
                    max_rate = rate
                if rate < min_rate:
                    min_rate = rate
        # 展示最近的600个
        datas = datas[0 - self.X_DATA_MINIUTES * 20:]
        # 修正量数据
        last_info = None
        for i in range(1, len(datas)):
            # 如果在
            if not last_info:
                last_info = datas[i]
            if int(datas[i]["created_at"][-2:]) - int(datas[i - 1]["created_at"][-2:]) < 0:
                last_info = datas[i]
            datas[i]['average_rate_1m'] = last_info['average_rate']
        datas[0]['average_rate_1m'] = datas[0]['average_rate']
        for i in range(0, len(self.codes_info)):
            if self.codes_info[i][0] == code:
                try:
                    self.__update_data(code, self.axes_list[i], datas, min_rate, max_rate)
                except Exception as e:
                    logging.exception(e)
        print("绘图花费时间:", time.time() - __start_time)
    def onLoadFailed(self, errorCode, failingUrl, errorDescription):
        """处理加载失败的情况"""
        print(f"Failed to load URL: {failingUrl}, Error: {errorDescription}, Code: {errorCode}")
class mainApp(wx.App):
class MainWindow(QMainWindow):
    signal_update_code = pyqtSignal(str)
    def __refresh(self):
        codes = juejin_core.GPCodeManager().get_codes()
        last_time = round(time.time())
    def show_info(self, msg):
        QMessageBox.information(self, "提示", msg, QMessageBox.Yes)
    def show_warning(self, msg):
        QMessageBox.warning(self, "提示", msg, QMessageBox.Yes)
    def set_trade_data(self, code, code_name, trade_data, trade_record, initiative_buy_codes, passive_buy_codes):
        self.secondWindow.set_trade_data(code, code_name, trade_data, trade_record, initiative_buy_codes,
                                         passive_buy_codes)
    # 设置目标代码
    def set_target_code(self, code):
        print("set_target_code")
        self.wx_pipe.send(json.dumps({"type": "set_code", "code": code}))
        self.webview.page().runJavaScript(f"app.set_target_code('{code}')")
        self.secondWindow.set_target_code(code)
    def read_window_msg(self):
        while True:
            try:
                code = ocr_ths_code()
                if not code:
                    time.sleep(0.1)
                    continue
                self.xgb.set_code(code)
                # 1s更新一次
                if round(time.time()) - last_time > 5:
                    codes = juejin_core.GPCodeManager().get_codes()
                    last_time = round(time.time())
                for index in range(0, len(codes)):
                    if codes[index] == code:
                        self.frame.scrollTo(index)
                        break
            except Exception as e:
                # print(str(e))
                data = window_msg_queue.get()
                if data["type"] == "set_target_code":
                    code = data["data"]["code"]
                    self.signal_update_code.emit(code)
            except:
                pass
            time.sleep(0.005)
            finally:
                pass
    def __show_float_frame(self):
        self.floatFrame.Show()
    # 菜单及菜单点击事件
    def __menu(self):
        def __gpu_is_avaiable():
            if torch.cuda.is_available():
                self.show_info("GPU可用")
            else:
                self.show_warning("GPU不可用")
    def __show_main_frame(self):
        self.frame.Show()
        def __set_juejin_params():
            ps = (self.x(), self.y())
            size = (self.width(), self.height())
            self.wx_pipe.send(json.dumps({"type": "juejin_setting", "pos": (ps[0] + size[0] / 2, ps[1] + size[1] / 2)}))
    def OnInit(self):
        self.SetAppName(APP_TITLE)
        self.frame = TickFrame()
        self.floatFrame = FloatFrame()
        # 传递代码设置成功的回调
        self.xgb = CodeDataFrame(self.frame.set_codes_success, self.__show_float_frame, self.__show_main_frame)
        self.xgb.Show()
        t1 = threading.Thread(target=lambda: self.__refresh())
        # 后台运行
        t1.setDaemon(True)
        t1.start()
        def __juejin_tick_download():
            self.wx_pipe.send(json.dumps({"type": "juejin_tick_download"}))
        return True
        def __ths_ocr_code():
            try:
                code = ths_ocr_util.ocr_ths_code(always_save=True)
                self.show_info(f"识别到的代码:{code}")
            except Exception as e:
                self.show_warning(f"识别出错:{str(e)}")
        def __download_codes():
            try:
                result = self.__request("", 72)
                result = json.loads(result)
                if result['code'] == 0:
                    codes_dict = result['data']
                    if codes_dict:
                        for key in codes_dict:
                            # 将代码保存到桌面
                            path = f"C:\\Users\\Administrator\\Desktop\\涨停代码{key}.txt"
                            with open(path, mode='w') as f:
                                for c in codes_dict[key]:
                                    f.write(c)
                                    f.write("\n")
                        self.show_info("下载成功")
                else:
                    self.show_warning(result['msg'])
            except Exception as e:
                self.show_warning(str(e))
def recieve_tick(pipe):
    codeDataManager = code_data_manager.CodeDataManager()
    while True:
        data = pipe.recv()
        if data:
            type = data["type"]
            if type == 0:
                # tick数据
                data = data["data"]
                code = data["code"]
                if code not in code_datas:
                    code_datas[code] = []
                code_datas[code].append(data)
                codeDataManager.save_data(data)
                # 更新数据
        def __manage_code():
            ps = (self.x(), self.y())
            size = (self.width(), self.height())
            self.wx_pipe.send(json.dumps({"type": "codes_setting", "pos": (ps[0] + size[0] / 2, ps[1] + size[1] / 2)}))
        def __show_dead_hwnds():
            self.wx_pipe.send(json.dumps({"type": "show_dead_hwnds"}))
        def __manage_ths_pos():
            ps = (self.x(), self.y())
            size = (self.width(), self.height())
            self.wx_pipe.send(json.dumps({"type": "manage_ths_pos", "pos": (ps[0] + size[0] / 2, ps[1] + size[1] / 2)}))
        def __show_second_window():
            self.secondWindow.show()
        def __show_his_msg_window():
            self.msgListWindow.loadUrl(URL_MSG_LIST)
            self.msgListWindow.show()
        # 清除浏览器缓存
        def __clear_webview_cache():
            self.webview.page().profile().clearHttpCache()
        def __show_ths_flash_trade():
            hwnds = ths_util.get_flash_trade_hwnds()
            for hwnd in hwnds:
                if not win32gui.IsWindowVisible(hwnd):
                    win32gui.BringWindowToTop(hwnd)
                win32_util.move_window(hwnd, 0, 0)
        menubar = self.menuBar()
        setting_ = menubar.addMenu('&设置')
        action = QAction("&GPU检测", self)
        action.triggered.connect(__gpu_is_avaiable)
        setting_.addAction(action)
        action = QAction("&清除浏览器缓存", self)
        action.triggered.connect(__clear_webview_cache)
        setting_.addAction(action)
        action = QAction("&同花顺代码识别", self)
        action.triggered.connect(__ths_ocr_code)
        setting_.addAction(action)
        action = QAction("&死句柄管理", self)
        action.triggered.connect(__show_dead_hwnds)
        setting_.addAction(action)
        view_ = menubar.addMenu('&视图')
        action = QAction("&打开副屏", self)
        action.triggered.connect(__show_second_window)
        view_.addAction(action)
        action = QAction("&打开历史消息", self)
        action.triggered.connect(__show_his_msg_window)
        view_.addAction(action)
        view_ = menubar.addMenu('&显示一键卖出')
        view_.aboutToShow.connect(__show_ths_flash_trade)
    def __init__(self, wx_pipe):
        super().__init__()
        self.wx_pipe = wx_pipe
        self.setWindowTitle('看盘页面')
        window_info = setting.get_kp_window_info()
        if window_info:
            self.move(window_info[0], window_info[1])
            self.resize(window_info[2], window_info[3])
        else:
            self.resize(1100, 1000)
            self.center()
        self.setWindowFlag(Qt.WindowStaysOnTopHint, True)
        self.webview = QWebEngineView()
        self.webview.setPage(WebEnginePage())
        self.webview.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)
        self.__menu()
        # JS桥设置
        channel = QWebChannel(self.webview.page())
        self.webview.page().setWebChannel(channel)
        self.python_bridge = JSBridgeClass(self, self.webview)
        channel.registerObject("Bridge", self.python_bridge)
        # 设置副屏
        self.secondWindow = SecondWindow(self)
        self.setCentralWidget(self.webview)
        self.show()
        if not constant.IS_TEST:
            self.webview.load(QUrl(f"http://{constant.WEB_HOST}/kp/index23-05-04.html"))
        else:
            self.webview.load(QUrl("http://127.0.0.1:8848/kp/index23-05-04.html"))
        if not setting.is_only_convertible_bonds():
            self.secondWindow.show()
            if not constant.IS_TEST:
                self.secondWindow.loadUrl(f"http://{constant.WEB_HOST}/kp/codes_list.html")
            else:
                self.secondWindow.loadUrl("http://127.0.0.1:8848/kp/codes_list.html")
        # 绑定槽函数
        self.signal_update_code.connect(self.set_target_code)
        # self.statusBar().showMessage("这是条测试数据额", 10000)
        self.messageWindow = MessageWindow()
        self.msgListWindow = MsgListWindow()
        threading.Thread(target=KPLLimitUpDataManager().run, daemon=True).start()
        threading.Thread(target=xgb_api.run, daemon=True).start()
        threading.Thread(target=self.read_window_msg, daemon=True).start()
    def closeEvent(self, event):
        event.accept()
        try:
            setting.set_kp_window_info((self.pos().x(), self.pos().y(), self.size().width(), self.size().height()))
        except Exception as e:
            print("")
        self.wx_pipe.send(json.dumps({"type": "exit"}))
        wxGuiProcess.terminate()
        wxGuiProcess.join()
        sys.exit(0)
    # 读取消息
    def read_msg(self):
        client = setting.get_client()
        if not client:
            client = 'hxh'
        while True:
            if tool.is_trade_time():
                try:
                    drawManager.update(code, code_datas[code])
                    print("接受到的tick数据:", data)
                    res = network_util.http_get(f"/pull_kp_client_msg?client=" + client.strip())
                    if res:
                        res = json.loads(res)
                        if res["code"] == 0:
                            print("拉取到消息", res)
                            self.messageWindow.setMsg(res["data"])
                except:
                    pass
            time.sleep(0.5)
def ths_auto_click():
    hwnd = ths_util.get_ths_second_screen_menu_hwnd()
def recieve_code(pipe, mainWindow):
    latest_code = ''
    while True:
        try:
            if hwnd is None or not win32gui.IsWindowVisible(hwnd):
                # print("未找到同花顺副屏句柄")
                hwnd = ths_util.get_ths_second_screen_menu_hwnd()
            if hwnd is None:
                continue
            if not setting.is_ths_auto_click():
                continue
            ps = setting.get_ths_auto_click_positions()
            if not ps:
                continue
            ps_new = []
            for p in ps:
                p = eval(p)
                ps_new.append(p)
            space = setting.get_ths_auto_click_time_space()
            if space is None:
                space = 500
            ths_util.betch_click(hwnd, ps_new, round(space / 1000, 4))
            data = pipe.recv()
            if data:
                data = json.loads(data)
                if data["type"] == "code":
                    if latest_code != data["code"]:
                        latest_code = data["code"]
                        mainWindow.signal_update_code.emit(latest_code)
        except Exception as e:
            pass
        finally:
            time.sleep(0.02)
            logging.exception(e)
def ths_auto_refresh():
    hwnd = ths_util.get_trade_refesh_hwnd()
    while True:
        try:
            if hwnd is None or not win32gui.IsWindowVisible(hwnd):
                # print("未找到同花顺交易刷新句柄")
                hwnd = ths_util.get_trade_refesh_hwnd()
            if hwnd is None:
                continue
            if not setting.is_ths_trade_auto_refresh():
                continue
            rect = win32gui.GetWindowRect(hwnd)
            win32_util.visual_click(hwnd, (160, (rect[3] - rect[1]) // 2))
            time_space = setting.get_ths_auto_refresh_time_space()
            if time_space is None:
                time_space = 500
            time.sleep(round(time_space / 1000, 4))
        except Exception as e:
            pass
        finally:
            time.sleep(0.02)
if __name__ == "__main__1":
    print(webview.WebView.IsBackendAvailable(webview.WebViewBackendEdge))
# 打包命令
# cd D:\workspace\GP\trade_desk
# D:\workspace\GP\trade_desk\dist\env\pk_env\Scripts\pyinstaller.exe main.spec
# 为了不出现意外的bug,运行时请将目录放在英文路径
if __name__ == "__main__":
    print(pow(3, 1))
    global p2
    freeze_support()
    p1, p2 = multiprocessing.Pipe()
    global jueJinProcess
    jueJinProcess = multiprocessing.Process(target=juejin_core.run, args=(p1,))
    jueJinProcess.start()
    global wxGuiProcess
    wxGuiProcess = Process(target=gui_wx.run, args=(p1,))
    wxGuiProcess.start()
    t1 = threading.Thread(target=lambda: recieve_tick(p2))
    app = QApplication(sys.argv)
    browser = MainWindow(p2)
    t1 = threading.Thread(target=lambda: recieve_code(p2, browser))
    # 后台运行
    t1.setDaemon(True)
    t1.start()
    t2 = threading.Thread(target=lambda: ths_auto_click())
    # 后台运行
    t2.setDaemon(True)
    t2.start()
    t3 = threading.Thread(target=lambda: ths_auto_refresh())
    # 后台运行
    t3.setDaemon(True)
    t3.start()
    app = mainApp(redirect=False)
    app.MainLoop()
    sys.exit(app.exec_())