admin
2025-06-10 568c763084b926a6f2d632b7ac65b9ec8280752f
main.py
@@ -1,1247 +1,616 @@
import base64
import ctypes
import hashlib
import json
import socket
import sys
import time
from multiprocessing import freeze_support
import win32con
import win32gui
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
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 dateutil
from PyQt5.QtCore import Qt, pyqtSlot, QObject, pyqtSignal, QTimer, QUrl, QPoint
APP_TITLE = "卖票看板"
APP_ICON = ""
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)
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 = ("192.168.3.252", 9001)  # 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()
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]))
    code = ocr_util.recognize_code(opencv_util.clip_ths_code_area(img))
    return code
# 悬浮框
class FloatFrame(wx.Frame):
    def __init__(self, position):
        wx.Frame.__init__(self, None, -1, "悬浮盯盘", style=wx.CAPTION ^ wx.STAY_ON_TOP,
                          size=(320, 195))
        self.SetBackgroundColour(wx.Colour(224, 224, 224))
        self.SetTransparent(230)
        if position:
            self.SetPosition(wx.Point(position[0], position[1]))
        boxsier = wx.FlexGridSizer(5, 2, 2, 20)
        self.btn_remove_black = wx.Button(self, label="移除黑名单", size=(70, 30))
        boxsier.Add(self.btn_remove_black, 0, wx.TOP, 5)
        bs1 = wx.BoxSizer(wx.HORIZONTAL)
        self.btn_remove_white = wx.Button(self, label="移除白名单", size=(70, 30))
        bs1.Add(self.btn_remove_white, 0, wx.TOP, 5)
        boxsier.Add(bs1, 0, wx.LEFT, 80)
        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, 35)
        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, 80)
        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)
        # 代码
        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, 32)
        # 绑定
        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):
        try:
            code = self.__get_code()
            print("加入白名单", code)
            self.__request([code], 202)
            self.show_info(f"{code}加入白名单成功")
            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)
    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 = ("192.168.3.252", 9001)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        data = {"type": type, "data": {"codes": codes}}
        client.send(json.dumps(data).encode("utf-8"))
        client.close()
    def __request_list(self, type):
        client = socket.socket()  # 生成socket,连接server
        ip_port = ("192.168.3.252", 9001)  # 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")
class mainFrame(wx.Frame):
    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))
        self.Center()
        self.__menu()
        # 以下代码处理图标
        # 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.__re_draw()
        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.scroll.Layout()
        # self.boxsier.Fit(self.scroll)
        # boxsier.Add(mainBoxsier, 1, wx.EXPAND | wx.ALL, 5)
        # self.SetSizer(boxsier)
        # mainBoxsier.Fit(self)
        window_info = setting.get_window_info()
        float_pos = None
        if window_info:
            float_pos = window_info[1]
            self.SetPosition(wx.Point(window_info[0][0], window_info[0][1]))
            self.Size = wx.Size(window_info[0][2], window_info[0][3])
        self.floatFrame = FloatFrame(float_pos)
        self.floatFrame.Show()
        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)
    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()
        pannel_height = round((self.Size[0] - (self.col - 1) * space) / self.col * (450 / 800))
        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)
        self.scroll.Layout()
        # self.boxsier.Fit(self.scroll)
        #
        # 初始化数据
        drawManager = DrawManager(axes_list, codes_name)
        drawManager.init_code_datas()
    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] = {}
            try:
                line = axes2.axhline(event.ydata, linestyle='-', color='white', lw=0.5)
                self.mark_lines[title]["mouse"] = line
                axes2.figure.canvas.draw()
            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)
        width_dpi = self.Size[0] / (100 * self.col)
        figure_score = Figure(figsize=(width_dpi, round(width_dpi * (4.5 / 7.8), 2)))
        # 设置外边距
        figure_score.subplots_adjust(left=0.01, bottom=0.15, right=0.85)
        # 设置字体颜色
        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, 21):
            if i >= 10:
                yticks2.append(0 - round(max_rate * (10 - i) / 10, 4))
            else:
                yticks2.append(round(max_rate * (i - 10) / 10, 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 == 10:
                    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, 9):
            axes.get_yticklabels()[i].set_color("green")
            axes2.get_yticklabels()[i].set_color("green")
        for i in range(10, 10):
            axes.get_yticklabels()[i].set_color("white")
            axes2.get_yticklabels()[i].set_color("white")
        for i in range(11, 21):
            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='yellow', linewidth=1)
        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)
    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 __check_juejin(self, event):
        frame = wx.Frame(None, -1, '掘金参数设置', size=(400, 100))
        frame.Center()
        frame.Show()
    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 __add_forbidden_codes(self, event):
        ps = self.GetPosition()
        size = self.GetSize()
        FobiddenCodesFrame((ps[0] + size[0] / 2, ps[1] + size[1] / 2)).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 __ths_auto_click(self, event):
        if event.Selection:
            setting.set_ths_auto_click(1)
        else:
            setting.set_ths_auto_click(0)
    def set_codes_success(self):
        print("设置代码成功回调")
        p2.send("resub")
        self.__re_draw()
    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
        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
    def __menu(self):
        self.mainmenu = wx.MenuBar()
        setting_ = wx.Menu()
        setting_.Append(105, '&添加禁止代码', '')
        setting_.AppendSeparator()
        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_.Append(102, '&掘金检测', 'Open a new document')
        setting_.AppendSeparator()
        setting_.Append(103, '&代码管理', 'Open a new document')
        self.mainmenu.Append(setting_, '&设置')
        auto_help = wx.Menu()
        auto_help.Append(202, '&同花顺设置', '')
        auto_help.Append(201, '&自动点击', '', kind=wx.ITEM_CHECK)
        auto_help.Check(201, setting.is_ths_auto_click())
        self.mainmenu.Append(auto_help, '&自动化')
        # 设置事件
        self.Bind(wx.EVT_MENU, self.__set_juejin_params, id=101)
        self.Bind(wx.EVT_MENU, self.__check_juejin, id=102)
        self.Bind(wx.EVT_MENU, self.__manage_code, id=103)
        self.Bind(wx.EVT_MENU, self.__show_top, id=104)
        self.Bind(wx.EVT_MENU, self.__add_forbidden_codes, id=105)
        self.Bind(wx.EVT_MENU, self.__manage_ths_pos, id=202)
        self.Bind(wx.EVT_MENU, self.__ths_auto_click, id=201)
        self.SetMenuBar(self.mainmenu)
    def OnExit(self, e):
        try:
            self.floatFrame.Close(True)
            setting.set_window_info((self.Position[0], self.Position[1], self.Size[0], self.Size[1]), (
                self.floatFrame.Position[0], self.floatFrame.Position[1], self.floatFrame.Size[0],
                self.floatFrame.Size[1]))
        except Exception as e:
            print("")
        jueJinProcess.terminate()
        sys.exit(0)
    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()
# 绘图管理器
class DrawManager:
    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)
                # 获取缺失的数据
                ranges = codeDataManager.get_lack_datas_time_range(old_datas)
                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])
    # 更新数据
    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("093000"):
                datas = datas[i:]
                break
        # 取最近12分钟的数据
        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_) >= 14 * 60:
                datas = datas[i:]
                break
        xs = []
        ys_rate = []
        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.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:], 15 * 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)
        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):
        # 获取前高和前低
        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[-450:]
        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)
class mainApp(wx.App):
    def __refresh(self):
        codes = juejin_core.GPCodeManager().get_codes()
        last_time = round(time.time())
        while True:
            try:
                code = ocr_ths_code()
                if not code:
                    time.sleep(0.1)
                    continue
                # 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))
            time.sleep(0.005)
    def OnInit(self):
        self.SetAppName(APP_TITLE)
        self.Frame = mainFrame()
        # self.Frame = FloatFrame(None)
        self.Frame.Show()
        t1 = threading.Thread(target=lambda: self.__refresh())
        # 后台运行
    @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()
        return True
    @pyqtSlot(str, str)
    def socket_request(self, text, callback_info):
        print("socket_request", text)
        try:
            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))
        t1.setDaemon(True)
        t1.start()
    @pyqtSlot(str, str)
    def ls_socket_request(self, text, callback_info):
        print("ls_socket_request", text)
        try:
            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()
    # 获取客户端ID
    @pyqtSlot(result=str)
    def get_client(self):
        return setting.get_client()
    @pyqtSlot(str)
    def add_code_to_ths(self, code):
        # 添加到同花顺
        threading.Thread(target=lambda: ths_util.add_code_to_zixuan(code), daemon=True).start()
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)
                # 更新数据
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):
        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)
        font = QFont('微软雅黑', 12)  # 设置字体为微软雅黑,字号为12
        palette = QPalette()  # 创建一个调色板
        palette.setColor(QPalette.WindowText, QColor(255, 0, 0))  # 设置字体颜色为红色
        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.timer = QTimer()
        self.timer.timeout.connect(self.close)
        # 只运行1次定时器
        self.timer.setSingleShot(True)
        self.msgChange.connect(self.__setMsg)
class MsgListWindow(QMainWindow):
    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)
        # JS桥设置
        channel = QWebChannel(self.webview.page())
        self.webview.page().setWebChannel(channel)
        self.python_bridge = BaseBridgeClass(self.webview)
        channel.registerObject("Bridge", self.python_bridge)
    def loadUrl(self, url):
        self.webview.load(QUrl(url))
class SecondWindow(QMainWindow):
    signal_update_kpl = pyqtSignal(str)
    def update_kpl_func(self):
        while True:
            time.sleep(3)
            try:
                self.__update_kpl()
            except:
                pass
    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:
            self.resize(500, 800)
        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_kp_second_window_info(
                (self.pos().x(), self.pos().y(), self.size().width(), self.size().height()))
        except Exception as e:
            print("")
class WebEnginePage(QWebEnginePage):
    def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
        """处理 JavaScript 控制台消息"""
        print(f"JavaScript Console Error: {message} at line {lineNumber} of {sourceID}")
    def onLoadFailed(self, errorCode, failingUrl, errorDescription):
        """处理加载失败的情况"""
        print(f"Failed to load URL: {failingUrl}, Error: {errorDescription}, Code: {errorCode}")
class MainWindow(QMainWindow):
    signal_update_code = pyqtSignal(str)
    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:
                data = window_msg_queue.get()
                if data["type"] == "set_target_code":
                    code = data["data"]["code"]
                    self.signal_update_code.emit(code)
            except:
                pass
            finally:
                pass
    # 菜单及菜单点击事件
    def __menu(self):
        def __gpu_is_avaiable():
            if torch.cuda.is_available():
                self.show_info("GPU可用")
            else:
                self.show_warning("GPU不可用")
        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 __juejin_tick_download():
            self.wx_pipe.send(json.dumps({"type": "juejin_tick_download"}))
        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 __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":
    hwnd = ths_util.get_ths_main_content_hwnd()
    if not hwnd:
        raise Exception("看盘页面句柄未获取到")
    # 句柄截图
    rect = win32gui.GetWindowRect(hwnd)
    w = 150
    height = 45
    top = 38
    # img = win32_util.window_capture(hwnd, (rect[2] - rect[0] -100 , 25, rect[2] - rect[0], 55))
    # img = win32_util.window_capture(hwnd, rect)
    img = win32_util.window_capture(hwnd, (0, 0, (rect[2] - rect[0]) * 15 // 10, (rect[3] - rect[1]) * 15 // 10))
# 打包命令
# 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_())