admin
2023-12-05 7fd504ef9b54119629029495f51da5c83202cdf2
分时看盘调整
2个文件已添加
15个文件已修改
1个文件已删除
689 ■■■■■ 已修改文件
code_data_manager.py 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
constant.py 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gui_wx.py 379 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
juejin_core.py 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
kp_html/kp/codes_list.html 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
kp_html/kp/css/banshuping.css 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
kp_html/kp/index23-05-04.html 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
kp_html/kp/js/code_list.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
kpl/gui.py 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
main.py 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
network_util.py 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
res/codes.txt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
res/setting.conf 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sell_processor.py 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
test.py 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ths_util.py 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tool.py 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
utils/log_util.py 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
code_data_manager.py
@@ -4,36 +4,18 @@
import os
import tool
from utils import log_util
class CodeDataManager:
    def save_data(self, data):
        code = data["code"]
        create_at = data["created_at"]
        day = create_at.strftime("%Y%m%d")
        data["created_at"] = create_at.strftime("%Y-%m-%d %H:%M:%S")
        file_path = "datas/{}_{}.txt".format(day, code)
        if not os.path.exists(file_path):
            with open(file_path, 'w', encoding="utf-8") as f:
                pass
        with open(file_path, "a", encoding="utf-8") as f:
            f.write(json.dumps(data))
            f.write("\n")
        log_util.save_l1_data(code, data)
    def get_datas(self, code):
        day = datetime.datetime.now().strftime("%Y%m%d")
        file_path = "datas/{}_{}.txt".format(day, code)
        datas = []
        try:
            with open(file_path, "r") as f:
                while True:
                    line = f.readline()
                    if not line or len(line) == 0:
                        break
                    data = json.loads(line)
                    datas.append(data)
        except:
            pass
        datas = log_util.get_l1_datas(code)
        # 根据创建时间排序
        datas = sorted(datas, key=lambda tup: int(tup["created_at"].split(" ")[1].replace(":", "")))
        return datas
@@ -75,6 +57,9 @@
            if tool.trade_time_sub(time_, last_time) > 30:
                if len(ranges) == 0 or ranges[-1][0] != last_time:
                    if is_trade_time(last_time) and is_trade_time(time_):
                        ranges.append((tool.trade_time_add_second(last_time, 3), tool.trade_time_add_second(time_, -3)))
                        start_time = tool.trade_time_add_second(last_time, 3)
                        end_time = tool.trade_time_add_second(time_, -3)
                        if tool.trade_time_sub(end_time, start_time) > 0:
                            ranges.append((start_time, end_time))
            last_time = time_
        return ranges
constant.py
@@ -1,3 +1,4 @@
SERVER_HOST = "43.138.167.68"
# SERVER_HOST = "192.168.3.122"
WEB_HOST = "192.168.3.252"
IS_TEST = False
gui_wx.py
@@ -1,6 +1,8 @@
import array
import ctypes
import json
import queue
import random
import socket
import struct
import sys
@@ -9,6 +11,7 @@
import cv2
import win32con
import win32gui
from matplotlib.animation import FuncAnimation
import constant
import juejin_data_export
@@ -37,9 +40,11 @@
import tool
import requests
from sell_processor import TickDataProcess
APP_TITLE = "卖票看板"
APP_ICON = ""
SOCKET_PORT = 9001
SOCKET_PORT = 11008
HTTP_PORT = 9004
@@ -208,11 +213,17 @@
        # 代码
        label = wx.StaticText(self, label="目标代码:")
        boxsier.Add(label, 0, wx.LEFT | wx.RIGHT, 5)
        self.edit_codes = wx.TextCtrl(self, size=(150, 150), style=wx.TE_MULTILINE)
        # 自动填充按钮
        self.btn_position = wx.Button(self, label='填充持仓代码')
        self.btn_position.Bind(wx.EVT_BUTTON, self.__fill_position_codes)
        boxsier.Add(self.btn_position, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 5)
        self.edit_codes = wx.TextCtrl(self, size=(150, 120), style=wx.TE_MULTILINE)
        boxsier.Add(self.edit_codes, 0, wx.LEFT | wx.RIGHT, 5)
        # 确定按钮
        ID_SURE = wx.NewId()
        ID_SURE = random.randint(1000, 1200)
        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, 0, wx.LEFT | wx.TOP, 5 | 5)
@@ -253,6 +264,50 @@
        if toastone.ShowModal() == wx.ID_YES:  # 如果点击了提示框的确定按钮
            self.Close()
            toastone.Destroy()
    @classmethod
    def get_position_codes(cls):
        def request_position_codes():
            client = socket.socket()  # 生成socket,连接server
            ip_port = (constant.SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
            client.connect(ip_port)
            try:
                data = socket_util.encryp_client_params_sign(
                    {"type": "common", "data": {"ctype": "get_position_codes"}})
                client.send(socket_util.load_header(json.dumps(data).encode("utf-8")))
                result_str, header_str = socket_util.recv_data(client)
                return result_str
            finally:
                client.close()
        result_str = request_position_codes()
        result = json.loads(result_str)
        if result["code"] == 0:
            return result["data"]
        else:
            raise Exception(result["msg"])
    # 填充持仓代码
    def __fill_position_codes(self, event):
        def request_position_codes():
            client = socket.socket()  # 生成socket,连接server
            ip_port = (constant.SERVER_HOST, SOCKET_PORT)  # server地址和端口号(最好是10000以后)
            client.connect(ip_port)
            try:
                data = socket_util.encryp_client_params_sign(
                    {"type": "common", "data": {"ctype": "get_position_codes"}})
                client.send(socket_util.load_header(json.dumps(data).encode("utf-8")))
                result_str, header_str = socket_util.recv_data(client)
                return result_str
            finally:
                client.close()
        try:
            codes = self.get_position_codes()
            if codes:
                self.edit_codes.SetValue("\n".join(codes))
            else:
                raise Exception("无持仓")
        except Exception as e:
            show_warning(str(e), None)
class FobiddenCodesFrame(wx.Frame):
@@ -403,7 +458,8 @@
    # 格式为:(上边距,宽度,高度)
    rect_ = setting.get_ths_auto_code_rect()
    scale = 1.5
    # 测试
    scale = 1 if constant.IS_TEST else 1.5
    if len(rect_) > 3:
        scale = rect_[3]
    width = int((rect[2] - rect[0]) * scale)
@@ -1089,7 +1145,7 @@
    def __request(cls, data_json):
        socket_util.encryp_client_params_sign(data_json)
        client = socket.socket()  # 生成socket,连接server
        ip_port = (constant.SERVER_HOST, 10009)  # server地址和端口号(最好是10000以后)
        ip_port = (constant.SERVER_HOST, 11009)  # server地址和端口号(最好是10000以后)
        client.connect(ip_port)
        client.send(socket_util.load_header(json.dumps(data_json).encode("utf-8")))
        result_str, header = socket_util.recv_data(client)
@@ -1106,6 +1162,8 @@
class TickFrame(wx.Frame):
    mark_lines = {}
    def __init__(self):
        '''构造函数'''
        wx.Frame.__init__(self, None, -1, APP_TITLE, style=wx.DEFAULT_FRAME_STYLE,
@@ -1135,16 +1193,13 @@
        self.Bind(wx.EVT_SIZE, self.OnResize)
        self.panels = []
        self.scroll = None
        self.mark_lines = {}
        self.col = 1
        self.ratio = 0.63
        self.ratio = 0.55
        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)
@@ -1189,10 +1244,11 @@
            pannel.BackgroundColour = wx.Colour(0, 0, 0)
            self.panels.append(pannel)
            self.boxsier.Add(pannel)
            code =  codes_name[i][0]
            code = codes_name[i][0]
            axes = self.__create_canvas(pannel, code, "{}({})".format(codes_name[i][0], codes_name[i][1]),
                                        codes_name[i][1],
                                        codes_name[i][2])
            if code not in self.cost_price_threads:
                t1 = self.run_get_cost_price(codes_name[i][0])
                self.cost_price_threads[code] = t1
@@ -1208,33 +1264,58 @@
        t1.setDaemon(True)
        t1.start()
    @classmethod
    def set_mouse_data(cls, xdata, ydata, code, axes2):
        try:
            if code not in cls.mark_lines:
                cls.mark_lines[code] = {}
            if "mouse" not in cls.mark_lines[code]:
                line_h = axes2.axhline(ydata, linestyle='-', color='white', lw=0.5, zorder=3)
                line_v = axes2.axvline(xdata, linestyle='-', color='white', lw=0.5, zorder=3)
                text_h = axes2.text(axes2.get_xlim()[1], ydata, r'', fontsize=10, color='white',
                                    verticalalignment="center", bbox=dict(facecolor='white', alpha=0.9), zorder=3)
                text_v = axes2.text(xdata, axes2.get_ylim()[0], r'', fontsize=10, color='red',
                                    verticalalignment='top', horizontalalignment='center',
                                    bbox=dict(facecolor='white', alpha=0.9), zorder=3)
                cls.mark_lines[code]["mouse"] = (line_h, line_v, text_h, text_v)
            line = cls.mark_lines.get(code).get("mouse")
            x = round(xdata)
            y = round(ydata, 2)
            line[0].set_ydata(ydata)
            line[1].set_xdata(xdata)
            line[2].set_text(f"{y}%")
            line[3].set_text(f"{tool.trade_time_add_second('09:30:00', x)}")
            line[2].set_x(axes2.get_xlim()[1] * (1 + 0.005))
            line[2].set_y(y)
            line[3].set_x(x)
            line[3].set_y(axes2.get_ylim()[0] * (1 + 0.02))
            if y >= 0:
                line[2].set_color('red')
            else:
                line[2].set_color('green')
            axes2.figure.canvas.draw()
        except Exception as e:
            pass
    def __create_canvas(self, pannel, code, title, name, price, close_callback=None):
        TickDataProcess.clear(code)
        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
            self.set_mouse_data(event.xdata, event.ydata, code, axes)
        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 code in self.mark_lines:
                if self.mark_lines.get(code):
                    line = self.mark_lines.get(code).get("mouse")
                    if line is not None:
                        line.remove()
                        self.mark_lines.get(title).pop("mouse")
                        axes2.figure.canvas.draw()
                        for l in line:
                            l.remove()
                        self.mark_lines.get(code).pop("mouse")
                        axes.figure.canvas.draw()
        def close_canvas(event):
            print("关闭", title)
@@ -1279,17 +1360,23 @@
        # 获取平开价
        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')
        # 设置横坐标9:25-15:00
        xs_str = ["09:25", "09:30", "10:30", "11:30", "14:00", "15:00"]
        xs_int = [DrawManager.get_x_time_as_seconds(f"0000-00-00 {x}:00") for x in xs_str]
        axes.set_xticks(xs_int[1:])
        axes.set_xticklabels(xs_str[1:])
        axes.set_xlim([xs_int[0], xs_int[-1]])
        axes2 = axes.twinx()
        # axes2.grid(color='firebrick', ls='-', lw=0.5)
        axes2.grid(color='firebrick', ls='-', lw=0.5)
        # 鼠标在画布移动
        axes2.figure.canvas.mpl_connect('motion_notify_event', show_mouse_line)
        # 鼠标离开画布
        axes2.figure.canvas.mpl_connect('axes_leave_event', clear_mouse_line)
        # 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))
@@ -1304,8 +1391,8 @@
        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.text(1, 11.5, r'现价:0.0 涨幅:0.00% \n留:0%', fontsize=15, color='red')
        axes2.text(-1, -11.5, r'现价:0.0 涨幅:0.00% \n留:0%', fontsize=15, color='red')
        axes2.spines['top'].set_visible(False)
        axes.spines['top'].set_visible(False)
@@ -1313,15 +1400,18 @@
        axes.spines['bottom'].set_visible(False)
        # 中轴线加粗
        hline = axes2.axhline(0, linestyle='-', color='firebrick', lw=2)
        hline = axes2.axhline(0, linestyle='-', color='firebrick', lw=2, zorder=1)
        # 设置坐标轴标记点为黑色
        axes.tick_params(axis='x', colors='black')
        axes.tick_params(axis='x', colors='firebrick')
        axes.tick_params(axis='y', colors='black')
        axes2.tick_params(axis='x', colors='black')
        axes2.tick_params(axis='x', colors='firebrick')
        axes2.tick_params(axis='y', colors='black')
        return (axes2, line, average_line_1m, average_line, axes)
        def update_data(i):
            print("更新数据:", i)
        return axes2, line, average_line_1m, average_line, axes
    def __show_top(self, event):
        if event.Selection:
@@ -1386,20 +1476,42 @@
                finally:
                    time.sleep(3)
        t1 =  threading.Thread(target=lambda: request(code), daemon=True)
        t1 = threading.Thread(target=lambda: request(code), daemon=True)
        t1.start()
        return t1
# 绘图管理器
class DrawManager:
    X_RANGE_MINIUTES = 60
    X_DATA_MINIUTES = 56
    X_RANGE_MINIUTES = 335
    X_DATA_MINIUTES = 335
    h_lines_dict = {}
    cost_mark_dict = {}
    last_max_rate_dict = {}
    cost_rate_dict = {}
    sell_points_dict = {}
    # 添加卖点
    @classmethod
    def add_sell_point(cls, code, time_str, rate):
        if code not in cls.sell_points_dict:
            cls.sell_points_dict[code] = queue.Queue()
        # 暂时注释掉
        # cls.sell_points_dict[code].put_nowait((cls.get_x_time_as_seconds(time_str), rate))
    @classmethod
    def get_x_time_as_seconds(cls, created_at_str):
        time_ = created_at_str[-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
    @classmethod
    def set_cost_rate(cls, code, rate):
@@ -1415,27 +1527,27 @@
            for data in results:
                datas.append(juejin_core.parse_tick(data))
            # 保存数据
            last_time = time_range[0]
            last_time = None
            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:
                time_s = int(time_.replace(":", ""))
                if time_s < int("092500") or time_s > int("150000"):
                    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:
                if int("113000") < time_s < int("130000"):
                    continue
                # 不是今天的数据不保存
                if day != created_at[:10]:
                    continue
                # 每隔15s保存一次
                if last_time and tool.trade_time_sub(created_at[-8:], last_time) >= 15:
                if last_time is None or tool.trade_time_sub(time_, last_time) >= 15:
                    last_time = created_at[-8:]
                    codeDataManager.save_data(data)
    def init_code_datas(self):
        global code_datas
        global max_min_prices
        codeDataManager = code_data_manager.CodeDataManager()
@@ -1450,6 +1562,9 @@
                max_min_prices[data[0]] = (data[1], data[2])
            for code in codes:
                # 清除Y轴最大振幅
                if code in self.last_max_rate_dict:
                    self.last_max_rate_dict.pop(code)
                # 加载历史数据
                code_datas[code] = []
                old_datas = codeDataManager.get_datas(code)
@@ -1461,13 +1576,31 @@
                    min_time = "11:30:00"
                min_time = tool.trade_time_add_second(min_time, 0 - DrawManager.X_DATA_MINIUTES * 60)
                # 如果最小时间小于9:25则取9:25
                if int(min_time.replace(":", "")) < int("092500"):
                    min_time = "09:25:00"
                ranges = codeDataManager.get_lack_datas_time_range(old_datas, min_time)
                # TODO 测试注释
                # if len(ranges) > 0:
                #     self.__load_lack_datas(code, ranges)
                #     old_datas = codeDataManager.get_datas(code)
                old_datas = []
                latest_time = min_time
                if old_datas:
                    latest_time = old_datas[-1]['created_at'][-8:]
                now_time_str = tool.get_now_time_str()
                if tool.trade_time_sub(now_time_str, "15:00:00") > 0:
                    now_time_str = "15:00:00"
                if tool.trade_time_sub(now_time_str, latest_time) > 15:
                    # 加载到当前时间
                    if ranges:
                        ranges.append((ranges[-1][1], tool.get_now_time_str()))
                    else:
                        ranges.append((latest_time, tool.get_now_time_str()))
                if len(ranges) > 0:
                    self.__load_lack_datas(code, ranges)
                    old_datas = codeDataManager.get_datas(code)
                # old_datas = []
                if old_datas:
                    code_datas[code].extend(old_datas)
                    # self.update(code, code_datas[code])
@@ -1475,12 +1608,17 @@
    @classmethod
    def __format_max_rate(cls, max_rate):
        PERCENT_RATE = 1
        line_count = 0
        if max_rate % 0.6 < 0.0001:
            line_count = int(round(max_rate // 0.6))
        if max_rate % PERCENT_RATE < 0.0001:
            line_count = int(round(max_rate // PERCENT_RATE))
        else:
            line_count = int(round(max_rate // 0.6)) + 1
        return line_count * 0.6, 0.6
            line_count = int(round(max_rate // PERCENT_RATE)) + 1
        rate = line_count * PERCENT_RATE
        # 还未接近涨停多增加PERCENT_RATE
        if rate + PERCENT_RATE < 9.8:
            rate += PERCENT_RATE
        return rate, PERCENT_RATE
    @classmethod
    def set_y_info(cls, code, max_rate, axes, axes2, price):
@@ -1526,23 +1664,21 @@
                yticks_labels.append("")
        axes2.set_ylabel(u'')
        # 设置纵轴的值的范围
        axes2.set_ylim(0 - max_rate * (1), max_rate * (1))
        axes.set_ylim((100 - max_rate) * price / 100, (100 + max_rate) * price / 100)
        axes2.set_ylim(0 - max_rate, max_rate)
        axes.set_ylim(0 - max_rate, max_rate)
        axes2.set_yticks(yticks2)
        axes2.set_yticklabels(yticks2_labels)
        axes.set_yticks(yticks)
        axes.set_yticklabels(yticks_labels)
        # axes.axhline(0, color='firebrick', linewidth=2)
        # 设置纵坐标数值颜色
        for i in range(0, line_count):
            axes.get_yticklabels()[i].set_color("green")
            # axes.get_yticklabels()[i].set_color("green")
            axes2.get_yticklabels()[i].set_color("green")
        for i in range(line_count, line_count):
            axes.get_yticklabels()[i].set_color("white")
            # axes.get_yticklabels()[i].set_color("white")
            axes2.get_yticklabels()[i].set_color("white")
        for i in range(line_count + 1, line_count * 2 + 1):
            axes.get_yticklabels()[i].set_color("red")
            # axes.get_yticklabels()[i].set_color("red")
            axes2.get_yticklabels()[i].set_color("red")
    @classmethod
@@ -1569,26 +1705,13 @@
    # 更新数据
    def __update_data(self, code, axes, datas, pre_price, y_max_rate=10.5, cost_rate=None):
        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)
        def on_pick(event):
            ind = event.ind[0]
            offset = event.artist.get_offsets()[ind]
            x = offset[0]
            y = offset[1]
            TickFrame.set_mouse_data(x, y, code, axes[4])
            print(f"Clicked on point ({x}, {y})")
        y_max_rate, a = self.__format_max_rate(y_max_rate)
        if code not in self.last_max_rate_dict or self.last_max_rate_dict[code] < y_max_rate:
@@ -1616,7 +1739,7 @@
        ys_average_rate_1m = []
        ys_average_rate = []
        for data in datas:
            xs.append(get_time_as_seconds(data["created_at"]))
            xs.append(self.get_x_time_as_seconds(data["created_at"]))
            ys_rate.append(data["rate"] * 100)
            ys_average_rate_1m.append(data["average_rate"] * 100 + 1.5)
@@ -1626,36 +1749,52 @@
        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))
        # end_x = "0000-00-00 " + tool.trade_time_add_second(datas[0]["created_at"][-8:], self.X_RANGE_MINIUTES * 60)
        # axes[0].set_xlim(self.get_x_time_as_seconds(datas[0]["created_at"]), self.get_x_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)
        # 暂时不设置X坐标
        # 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)
        point_x = xs[0] + 1
        point_y = ys_rate[0]
        if code not in self.sell_points_dict:
            self.sell_points_dict[code] = queue.Queue()
        if code in self.sell_points_dict:
            if not self.sell_points_dict[code].empty():
                item = self.sell_points_dict[code].get_nowait()
                if item:
                    points = axes[0].scatter(item[0], item[1], s=15, c='green', zorder=2, picker=5)
                    # 设置点击事件
                    axes[0].figure.canvas.mpl_connect('pick_event', on_pick)
        # 不需要一分钟均线
        # 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留:{}".format(round(datas[-1]["rate"] * 100, 2), '无持仓' if self.cost_rate_dict.get(
            code) is None else f"{self.cost_rate_dict.get(code)}%"))
        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[0].set_text("{}% ".format(round(datas[-1]["rate"] * 100, 2)))
        if datas[-1]["rate"] > 0:
            texts[0].set_color('red')
        elif datas[-1]["rate"] == 0:
            texts[0].set_color('white')
        else:
            texts[0].set_color('green')
        # texts[1].set_text("{}".format(datas[-1]["created_at"].split(" ")[1]))
        texts[1].set_text("")
        texts[0].set_x(xms[1] - 130)
        texts[0].set_y(yms[1] + yms[1] / 20)
        texts[1].set_x(xms[0])
        texts[1].set_y(yms[0] - 1.5)
        texts[1].set_y(yms[0] - yms[1] / 9)
        # 删除之前
        if code in self.lines:
@@ -1685,7 +1824,7 @@
                max_price = d['price']
        y_max_rate = round(abs(max_price - pre_price) * 100 / pre_price, 2)
        # 展示最近的600个
        datas = datas[0 - self.X_DATA_MINIUTES * 20:]
        # datas = datas[0 - self.X_DATA_MINIUTES * 20:]
        # 修正量数据
        last_info = None
        for i in range(1, len(datas)):
@@ -1728,6 +1867,7 @@
                        self.frame.scrollTo(index)
                        break
            except Exception as e:
                # logging.exception(e)
                # print(str(e))
                pass
            time.sleep(0.005)
@@ -1738,19 +1878,27 @@
    def __show_main_frame(self):
        self.frame.Show()
    def __init_data(self):
        try:
            codes = CodesSettingFrame.get_position_codes()
            if codes:
                juejin_core.GPCodeManager().set_codes(codes)
        except:
             pass
    def OnInit(self):
        self.SetAppName(APP_TITLE)
        self.__init_data()
        self.frame = TickFrame()
        self.floatFrame = FloatFrame()
        # self.floatFrame = FloatFrame()
        global_datas["tickFrame"] = self.frame
        global_datas["floatFrame"] = self.floatFrame
        # # 测试
        # global_datas["floatFrame"].Show()
        # global_datas["floatFrame"] = self.floatFrame
        t1 = threading.Thread(target=lambda: self.__refresh())
        # 后台运行
        t1.setDaemon(True)
        t1.start()
        self.__show_main_frame()
        return True
@@ -1777,6 +1925,11 @@
                        last_time = code_datas[code][-1]["created_at"].split(" ")[1]
                    else:
                        last_time = code_datas[code][-1]["created_at"].strftime("%H:%M:%S")
                if tool.trade_time_sub(create_time, "09:30:00") < 0:
                    TickDataProcess.init_origin_price(code, data['price'], data['rate'] * 100)
                else:
                    if TickDataProcess.process_tick_data(code, create_time, data['price'], data['rate'] * 100):
                        drawManager.add_sell_point(code, create_time, data['rate'] * 100)
                # 如果相差15s就添加进去
                if last_time is None or tool.trade_time_sub(create_time, last_time) >= 15:
                    code_datas[code].append(data)
@@ -1931,7 +2084,5 @@
if __name__ == "__main__":
    # app = mainApp()
    # app.MainLoop()
    # ocr_ths_code()
    SocketApiUtil.get_cost_price("002207")
    app = mainApp()
    app.MainLoop()
juejin_core.py
@@ -104,23 +104,21 @@
    __pipe = pipe
    strategy_id, token = setting.get_juejin_params()
    gmapi.set_token(token)
    # gmapi.run(strategy_id=strategy_id,
    #           filename='juejin_core.py',
    #           mode=gmapi.MODE_LIVE,
    #           token=token)
    # 测试订阅
    gmapi.run(strategy_id=strategy_id,
              filename='juejin_core.py',
              mode=gmapi.MODE_BACKTEST,
              backtest_start_time="2023-09-11 09:25:00",
              backtest_end_time="2023-09-11 11:25:00",
              backtest_initial_cash=1000000,
              backtest_transaction_ratio=1, backtest_commission_ratio=0,
              backtest_slippage_ratio=0, backtest_adjust=gmapi.ADJUST_NONE, backtest_check_cache=0,
              serv_addr='', backtest_match_mode=0,
              mode=gmapi.MODE_LIVE,
              token=token)
    # 测试订阅
    # gmapi.run(strategy_id=strategy_id,
    #           filename='juejin_core.py',
    #           mode=gmapi.MODE_BACKTEST,
    #           backtest_start_time="2023-10-09 09:25:00",
    #           backtest_end_time="2023-10-09 09:40:00",
    #           backtest_initial_cash=1000000,
    #           backtest_transaction_ratio=1, backtest_commission_ratio=0,
    #           backtest_slippage_ratio=0, backtest_adjust=gmapi.ADJUST_NONE, backtest_check_cache=0,
    #           serv_addr='', backtest_match_mode=0,
    #           token=token)
    subscript()
kp_html/kp/codes_list.html
@@ -118,10 +118,11 @@
                            <td>
                                <div style="width: 100%;display: flex;justify-content: space-between;flex-wrap: wrap;"
                                    class="scroll-y">
                                    <div style="width: 100%;" v-for="(item,index) in trade_record.records">
                                        <span>{{item[0]}} 【{{item[1]}}】{{item[2]}} <a href="javascript:void()"
                                    <div class="record-item" style="width: 520px; " v-for="(item,index) in trade_record.records">
                                        <div>{{item[0]}}</div> <div>【{{item[1]}}】{{item[2]}} <a href="javascript:void()"
                                                style='color:red' v-if="item[3]&&item[3].length>0"
                                                @click="show_more_records(item[3])">更多</a></span>
                                                @click="show_more_records(item[3])">更多</a></div>
                                    </div>
                                </div>
@@ -187,6 +188,9 @@
                                <div style="line-height: 20px;margin-top: 5px;"><input type="checkbox" value="1" checked
                                        v-on:click="hidden_cancel_check($event)" /> <label>隐藏撤单</label> </div>
                                <div class="column-space"></div>
                                <div style="line-height: 20px;margin-top: 5px;"><input type="checkbox" value="1" checked
                                        v-on:click="hidden_sell_check($event)" /> <label>隐藏卖</label> </div>
                                <div class="column-space"></div>
                                <div style="line-height: 20px;margin-top: 5px;"><input type="checkbox" value="1"
                                        v-on:click="hidden_canceled_check($event)" /> <label>隐藏已撤</label> </div>
                                <div style="line-height: 20px;margin-top: 5px;margin-left: 5px;"><input type="checkbox" value="1"
kp_html/kp/css/banshuping.css
@@ -488,4 +488,22 @@
    margin-top: -8px;
}
.record-item{
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    height: auto;
}
.record-item div:first{
    width: 10%;
}
.record-item div:last-child{
    width: 89%;
    word-wrap:break-word;
    work-break:break-all;
    white-space: pre-wrap;
}
kp_html/kp/index23-05-04.html
@@ -307,7 +307,6 @@
                <!-- 待买池 -->
                <div class="table-name plate-container"
                    style="float: left;width: 100%;text-align: center; background: #000;height:150px">
                    <span v-for="(item,i) in first_info.limit_up_reason_statistic"
                        v-on:click="show_want_codes(item[0])">
                        <span class="num-style red">{{item[0]}}({{item[1]}}&{{item[2]}})</span>&nbsp;&nbsp;&nbsp;&nbsp;
kp_html/kp/js/code_list.js
@@ -45,6 +45,7 @@
            //隐藏撤单
            hidden_cancel:true,
            hidden_little_money:false,
            hidden_sell:true,
            l2_datas: [],
            l2_colors_class: ["color-single-start", "color-single-exec", "color-cancel",
                "color-real-order"
@@ -181,6 +182,10 @@
                app.hidden_cancel = e.currentTarget.checked;
            },
            
            hidden_sell_check: function(e) {
                app.hidden_sell = e.currentTarget.checked;
            },
            hidden_little_money_check:function(e){
                app.hidden_little_money = e.currentTarget.checked;
            },
@@ -270,6 +275,11 @@
                if(app.hidden_canceled&&(item[2][6].indexOf('买撤')>=0||(item[2][8]!=null&&item[2][8].indexOf("-")>0))){
                    return false;
                }
                if(app.hidden_sell&&item[2][6].indexOf('卖')>=0){
                    return false;
                }
                if(app.hidden_cancel&&(item[2][6].indexOf('买撤')>=0)){
                    return false;
                }
kpl/gui.py
@@ -337,7 +337,7 @@
            "type": type,
            "data": datas
        }
        requests.post("http://43.138.167.68:9004/upload_kpl_data", json.dumps(root_data))
        requests.post("http://192.168.3.252:9004/upload_kpl_data", json.dumps(root_data))
    def OnExit(self, e):
        sys.exit(0)
main.py
@@ -13,7 +13,7 @@
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMessageBox, QLabel, QDialog, QVBoxLayout, QPushButton, \
    QWidget
    QWidget, QMenu
from PyQt5.QtCore import Qt, pyqtSlot, QObject, pyqtSignal, QTimer, QUrl, QPoint
@@ -44,6 +44,7 @@
    def __http_request(self, url, callback_info):
        try:
            result = network_util.http_get(url)
            print("请求结果:", result)
            self.signal_request.emit(callback_info[0], callback_info[1], result)
            return result
        except Exception as e:
@@ -216,8 +217,15 @@
    # 设置交易数据
    def set_trade_data(self, code, code_name, trade_data, trade_record, initiative_buy_codes, passive_buy_codes):
        self.webview.page().runJavaScript(
            f"app.set_trade_info('{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}')")
@@ -253,8 +261,6 @@
        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}')")
    # 菜单及菜单点击事件
    def __menu(self):
@@ -361,14 +367,17 @@
        action.triggered.connect(__show_float_callback)
        view_.addAction(action)
        action = QAction("&打开分时看盘", self)
        action.triggered.connect(__show_main_callback)
        view_.addAction(action)
        action = QAction("&打开历史消息", self)
        action.triggered.connect(__show_his_msg_window)
        view_.addAction(action)
        view_ = menubar.addMenu('&代码管理')
        view_.aboutToShow.connect(__manage_code)
        view_ = menubar.addMenu('&打开分时')
        view_.aboutToShow.connect(__show_main_callback)
    def __init__(self, wx_pipe):
        super().__init__()
        self.wx_pipe = wx_pipe
network_util.py
@@ -6,7 +6,10 @@
import constant
SOCKET_PORT = 9001
# B类
HTTP_PORT = 9004
# A类
# HTTP_PORT = 9005
def socket_request(data):
res/codes.txt
@@ -1,2 +1,4 @@
002528
601136
600088
600571
601599
002280
res/setting.conf
@@ -1,10 +1,10 @@
[config]
stay_on_top = 1
window_info = [[-1711, 194, 1280, 800], [1473, 621, 320, 195]]
xgb_window_info = [-554, 369, 1918, 1008]
window_watch_float_info = [-628, 346, 435, 220]
window_tick_info = [-1575, 199, 1181, 737]
kp_second_window_info = [-2274, 283, 1920, 1017]
xgb_window_info = [-2247, 492, 1920, 1009]
window_watch_float_info = [146, 419, 435, 220]
window_tick_info = [-1431, 206, 1227, 661]
kp_second_window_info = [-1307, 218, 639, 797]
code_attribute_window_info = [-650, 315, 291, 278]
client = hxh
float_frame_auto_focus = 1
sell_processor.py
New file
@@ -0,0 +1,55 @@
import juejin_core
import tool
class TickDataProcess:
    # 最高价
    __highest_price_infos = {}
    @classmethod
    def init_origin_price(cls, code, price, rate_percent):
        cls.__highest_price_infos[code] = ("09:25:00", price, rate_percent)
    # 价格信息(时间,现价,涨幅)
    # return 是否减仓
    @classmethod
    def process_tick_data(cls, code, time_str, price, rate_percent, max_amplitude_rate_percent=1):
        # 非交易时间
        if tool.trade_time_sub(time_str, "09:30:00") < 0:
            return False
        now_price_info = (time_str, price, rate_percent)
        # 如果比最高价高就取最高价
        if code not in cls.__highest_price_infos:
            cls.__highest_price_infos[code] = now_price_info
        if cls.__highest_price_infos.get(code)[2] < now_price_info[2]:
            cls.__highest_price_infos[code] = now_price_info
        else:
            # 低于最高点阈值
            if cls.__highest_price_infos.get(code)[2] - now_price_info[2] > max_amplitude_rate_percent:
                # 触发卖
                cls.__highest_price_infos[code] = now_price_info
                return True
        return False
    @classmethod
    def clear(cls, code):
        if code in cls.__highest_price_infos:
            cls.__highest_price_infos.pop(code)
if __name__ == '__main__':
    day = tool.get_now_date_str()
    code = "603189"
    time_range = ["09:25:00", "15:00:00"]
    results = juejin_core.GPCodeManager().get_history_tick(code, day + " " + time_range[0],
                                                           day + " " + time_range[1])
    pre_price = 14.74
    TickDataProcess.init_origin_price(code, results[0]['price'],
                                      round((results[0]['price'] - pre_price) * 100 / pre_price, 2))
    for result in results:
        rate = round((result['price'] - pre_price) * 100 / pre_price, 2)
        r = TickDataProcess.process_tick_data(code, result['created_at'].strftime("%H:%M:%S"), result['price'],
                                              rate, max_amplitude_rate_percent=0.6)
        if r:
            print("减仓", result['created_at'].strftime("%H:%M:%S"), rate, result['price'])
test.py
File was deleted
ths_util.py
@@ -33,7 +33,7 @@
    hwnds = win32_util.search_window("网上股票交易系统")
    if hwnds:
        for hwnd in hwnds:
            content_hwnd =  win32gui.FindWindowEx(hwnd, None, "AfxMDIFrame140s", None)
            content_hwnd = win32gui.FindWindowEx(hwnd, None, "AfxMDIFrame140s", None)
            temp = win32gui.FindWindowEx(content_hwnd, None, "#32770", None)
            for i in range(10):
                if win32gui.IsWindowVisible(temp):
@@ -68,8 +68,13 @@
    hwnds = win32_util.search_window("同花顺")
    if hwnds:
        hwnd = hwnds[0]
        hwnd = win32gui.FindWindowEx(hwnd, None, "AfxFrameOrView100s", None)
        return hwnd
        child = None
        for i in range(0, 20):
            child = win32gui.FindWindowEx(hwnd, child, None, None)
            if child:
                left, top, right, bottom = win32gui.GetWindowRect(child)
                if right - left > 1000 and bottom - top > 800:
                    return child
    return None
@@ -81,4 +86,4 @@
if __name__ == "__main__":
    print(get_trade_refesh_hwnd())
    print(get_ths_main_content_hwnd())
tool.py
@@ -31,10 +31,10 @@
    split_time = get_time_as_second("11:30:00")
    time_1 = get_time_as_second(time_str_1)
    time_2 = get_time_as_second(time_str_2)
    if time_1 > split_time >= time_2:
    if time_2 < split_time < time_1:
        time_2 += 90 * 60
    else:
        if time_1 < split_time <= time_2:
        if time_1 < split_time < time_2:
            time_2 -= 90 * 60
    return time_1 - time_2
@@ -77,10 +77,11 @@
def get_now_date_str():
    # TODO 测试
    return datetime.datetime.now().strftime("%Y-%m-%d")
if __name__ == "__main__":
    print(trade_time_sub("13:00:00", "11:30:00"))
    print(trade_time_sub("10:10:56", "11:30:00"))
    print(trade_time_sub("13:00:00", "11:29:00"))
    print(trade_time_sub("11:29:00", "13:00:00"))
utils/log_util.py
New file
@@ -0,0 +1,58 @@
import os
import sys
from loguru import logger
import tool
class MyLogger:
    def __init__(self):
        logger.remove()
        program_path = sys.argv[0]
        absolute_path = os.path.abspath(program_path)
        #   每一天生成一个日志文件,历史日志文件采用zip压缩,异步写入日志
        logger.add(self.get_path("datas", "l1"),
                   filter=lambda record: record["extra"].get("name") == "l1",
                   rotation="00:00", compression="zip", enqueue=True)
    def get_base_path(self):
        program_path = sys.argv[0]
        absolute_path = os.path.abspath(program_path)
        directory_path = os.path.dirname(absolute_path)
        return directory_path
    def get_path(self, dir_name, log_name):
        path_str = "{}/{}/{}".format(self.get_base_path(), dir_name, log_name) + ".{time:YYYY-MM-DD}.log"
        return path_str
    def get_logger(self, log_name):
        return logger.bind(name=log_name)
__mylogger = MyLogger()
logger_l1 = __mylogger.get_logger("l1")
def save_l1_data(code, data):
    logger_l1.info(f"{code}#{data}")
def get_l1_datas(code):
    fdatas = []
    path_str = "{}\\{}\\{}".format(__mylogger.get_base_path(), "datas", "l1") + f".{tool.get_now_date_str()}.log"
    if os.path.exists(path_str):
        with open(path_str, 'r') as f:
            while True:
                line = f.readline()
                if not line:
                    break
                if line.find(f"{code}#") < 0:
                    continue
                data = line.split("#")[1]
                data = eval(data)
                if abs(data["rate"]) > 0.3:
                    continue
                fdatas.append(data)
    return fdatas