| | |
| | | import json |
| | | import os |
| | | import socket |
| | | import sys |
| | | import time |
| | |
| | | from matplotlib.widgets import Button |
| | | |
| | | import wx |
| | | import wx.html |
| | | from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas |
| | | from matplotlib.figure import Figure |
| | | |
| | |
| | | toastone = wx.MessageDialog(None, content, "提示", wx.YES_DEFAULT | wx.ICON_INFORMATION) |
| | | if toastone.ShowModal() == wx.ID_YES: # 如果点击了提示框的确定按钮 |
| | | click() |
| | | toastone.Destroy() |
| | | |
| | | |
| | | def show_sure(content, callback): |
| | | toastone = wx.MessageDialog(None, content, "提示", wx.YES_NO | wx.ICON_INFORMATION) |
| | | result = toastone.ShowModal() |
| | | if result == wx.ID_YES: # 如果点击了提示框的确定按钮 |
| | | callback(True) |
| | | toastone.Destroy() |
| | | elif result == wx.ID_NO: |
| | | callback(False) |
| | | toastone.Destroy() |
| | | |
| | | |
| | |
| | | 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)) |
| | | if code is None: |
| | | code = ocr_util.recognize_code(img) |
| | | return code |
| | | |
| | | |
| | | # 悬浮框 |
| | | class FloatFrame(wx.Frame): |
| | | def __init__(self, position): |
| | | wx.Frame.__init__(self, None, -1, "悬浮盯盘", style=wx.CAPTION ^ wx.STAY_ON_TOP, |
| | | wx.Frame.__init__(self, None, -1, "悬浮盯盘", style=wx.CAPTION ^ wx.MINIMIZE_BOX ^ wx.CLOSE_BOX ^ wx.STAY_ON_TOP, |
| | | size=(320, 195)) |
| | | self.SetBackgroundColour(wx.Colour(224, 224, 224)) |
| | | self.SetTransparent(230) |
| | | if position: |
| | | self.SetPosition(wx.Point(position[0], position[1])) |
| | | boxsier = wx.FlexGridSizer(5, 2, 2, 20) |
| | | |
| | | boxsier = wx.FlexGridSizer(5, 2, 2, 5) |
| | | bs1 = wx.BoxSizer(wx.HORIZONTAL) |
| | | self.btn_remove_black = wx.Button(self, label="移除黑名单", size=(70, 30)) |
| | | boxsier.Add(self.btn_remove_black, 0, wx.TOP, 5) |
| | | self.btn_close_buy = wx.Button(self, label="关闭交易", size=(70, 30)) |
| | | self.btn_close_buy.SetForegroundColour("#FF3232") |
| | | bs1.Add(self.btn_remove_black) |
| | | bs1.Add(self.btn_close_buy) |
| | | boxsier.Add(bs1, 0, wx.TOP, 5) |
| | | |
| | | bs1 = wx.BoxSizer(wx.HORIZONTAL) |
| | | |
| | | self.btn_remove_white = wx.Button(self, label="移除白名单", size=(70, 30)) |
| | | self.btn_open_buy = wx.Button(self, label="开启交易", size=(70, 30), ) |
| | | self.btn_open_buy.SetForegroundColour("#00e600") |
| | | |
| | | bs1.Add(self.btn_open_buy, 0, wx.TOP, 5) |
| | | bs1.Add(self.btn_remove_white, 0, wx.TOP, 5) |
| | | boxsier.Add(bs1, 0, wx.LEFT, 80) |
| | | |
| | | boxsier.Add(bs1, 0, wx.LEFT, 0) |
| | | |
| | | label = wx.StaticText(self, label="交易刷新:") |
| | | bs1 = wx.BoxSizer(wx.HORIZONTAL) |
| | |
| | | self.btn_want_buy = wx.Button(self, label="加入想买单", size=(70, 25)) |
| | | bs1.Add(self.btn_want_buy) |
| | | |
| | | boxsier.Add(bs1, 0, wx.LEFT, 35) |
| | | boxsier.Add(bs1, 0, wx.LEFT, 25) |
| | | |
| | | label = wx.StaticText(self, label="分组刷新:") |
| | | bs1 = wx.BoxSizer(wx.HORIZONTAL) |
| | |
| | | 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) |
| | | boxsier.Add(self.btn_want_buy_remove, 0, wx.LEFT, 70) |
| | | |
| | | self.edit_code = wx.TextCtrl(self, size=(80, -1)) |
| | | boxsier.Add(self.edit_code) |
| | |
| | | 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) |
| | | boxsier.Add(bs1, 0, wx.LEFT, 22) |
| | | |
| | | # 绑定 |
| | | self.btn_open_buy.Bind(wx.EVT_BUTTON, self.__open_buy) |
| | | self.btn_close_buy.Bind(wx.EVT_BUTTON, self.__close_buy) |
| | | |
| | | self.btn_black.Bind(wx.EVT_BUTTON, self.add_black) |
| | | self.btn_white.Bind(wx.EVT_BUTTON, self.add_white) |
| | | self.btn_remove_black.Bind(wx.EVT_BUTTON, self.remove_from_black) |
| | |
| | | client.close() |
| | | return result.decode("gbk") |
| | | |
| | | def __request_buy(self, is_open): |
| | | client = socket.socket() # 生成socket,连接server |
| | | ip_port = ("192.168.3.252", 9001) # server地址和端口号(最好是10000以后) |
| | | client.connect(ip_port) |
| | | data = {"type": 501, "data": {"open": is_open}} |
| | | client.send(json.dumps(data).encode("utf-8")) |
| | | # 读取内容 |
| | | result = client.recv(10240) |
| | | client.close() |
| | | return result.decode("gbk") |
| | | |
| | | class mainFrame(wx.Frame): |
| | | def __open_buy(self, event): |
| | | def open_buy(sure): |
| | | if sure: |
| | | try: |
| | | result = self.__request_buy(True) |
| | | msg = json.loads(result)["msg"] |
| | | show_info(msg, None) |
| | | except Exception as e: |
| | | show_warning(str(e), None) |
| | | |
| | | show_sure("是否开启交易", open_buy) |
| | | |
| | | def __close_buy(self, event): |
| | | def close_buy(sure): |
| | | if sure: |
| | | try: |
| | | result = self.__request_buy(False) |
| | | msg = json.loads(result)["msg"] |
| | | show_info(msg, None) |
| | | except Exception as e: |
| | | show_warning(str(e), None) |
| | | |
| | | show_sure("是否关闭交易", close_buy) |
| | | |
| | | |
| | | # 代码数据 |
| | | class CodeDataFrame(wx.Frame): |
| | | def __init__(self, set_codes_success_callback): |
| | | # style=wx.CAPTION | |
| | | wx.Frame.__init__(self, None, -1, "数据", |
| | | style=wx.CAPTION | wx.STAY_ON_TOP | wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.CLOSE_BOX | wx.RESIZE_BORDER, |
| | | size=(320, 195)) |
| | | self.set_codes_success = set_codes_success_callback |
| | | self.SetBackgroundColour(wx.Colour(224, 224, 224)) |
| | | # self.SetTransparent(230) |
| | | boxsier = wx.BoxSizer(wx.VERTICAL) |
| | | self.data_text = wx.html.HtmlWindow(self, size=wx.Size(self.Size[0], self.Size[ |
| | | 1])) |
| | | self.data_text.SetBackgroundColour(wx.Colour(224, 224, 224)) |
| | | if "gtk2" in wx.PlatformInfo: |
| | | self.data_text.SetStandardFonts() |
| | | boxsier.Add(self.data_text) |
| | | self.latest_code = '' |
| | | self.SetSizer(boxsier) |
| | | window_info = setting.get_xgb_window_info() |
| | | if window_info: |
| | | self.SetPosition(wx.Point(window_info[0], window_info[1])) |
| | | self.Size = wx.Size(window_info[2], window_info[3]) |
| | | self.Bind(wx.EVT_CLOSE, self.OnExit) |
| | | self.Bind(wx.EVT_SIZE, self.OnResize) |
| | | self.data_text.SetPage("") |
| | | self.__menu() |
| | | |
| | | def set_data(self, code, data): |
| | | self.data_text.SetPage(data) |
| | | |
| | | # 设置代码 |
| | | def set_code(self, code): |
| | | if self.latest_code == code: |
| | | return |
| | | self.latest_code = code |
| | | t1 = threading.Thread(target=lambda: self.__init_data(code)) |
| | | t1.setDaemon(True) |
| | | t1.start() |
| | | |
| | | def __init_data(self, code): |
| | | if code: |
| | | # 请求网络 |
| | | result_data = self.__request(code) |
| | | if result_data: |
| | | result_json = json.loads(result_data) |
| | | if result_json['code'] == 0: |
| | | code = result_json['data']['code'] |
| | | data = result_json['data']['data'] |
| | | wx.CallAfter(self.set_data, code, data) |
| | | |
| | | def __request(self, code, type=71): |
| | | client = socket.socket() # 生成socket,连接server |
| | | ip_port = ("192.168.3.252", 9001) # server地址和端口号(最好是10000以后) |
| | | client.connect(ip_port) |
| | | data = {"type": type, "data": {"code": code}} |
| | | client.send(json.dumps(data).encode("utf-8")) |
| | | # 读取内容 |
| | | result = client.recv(102400) |
| | | client.close() |
| | | return result.decode("gbk") |
| | | |
| | | def OnExit(self, e): |
| | | try: |
| | | setting.set_xgb_window_info((self.Position[0], self.Position[1], self.Size[0], self.Size[1])) |
| | | except Exception as e: |
| | | print("") |
| | | jueJinProcess.terminate() |
| | | sys.exit(0) |
| | | |
| | | def OnResize(self, e): |
| | | print("变化后的尺寸", e.Size) |
| | | self.data_text.Size = e.Size |
| | | |
| | | def __menu(self): |
| | | self.mainmenu = wx.MenuBar() |
| | | setting_ = wx.Menu() |
| | | # setting_.Append(104, '&钉住', '', kind=wx.ITEM_CHECK) |
| | | # setting_.Check(104, setting.is_stay_on_top()) |
| | | setting_.AppendSeparator() |
| | | setting_.Append(101, '&掘金参数配置', 'Open a new document') |
| | | setting_.AppendSeparator() |
| | | setting_.Append(103, '&代码管理', 'Open a new document') |
| | | setting_.Append(102, '&下载涨停代码', 'Open a new document') |
| | | |
| | | self.mainmenu.Append(setting_, '&设置') |
| | | |
| | | auto_help = wx.Menu() |
| | | auto_help.Append(202, '&同花顺设置', '') |
| | | self.mainmenu.Append(auto_help, '&自动化') |
| | | # 设置事件 |
| | | self.Bind(wx.EVT_MENU, self.__set_juejin_params, id=101) |
| | | self.Bind(wx.EVT_MENU, self.__download_codes, id=102) |
| | | self.Bind(wx.EVT_MENU, self.__manage_code, id=103) |
| | | self.Bind(wx.EVT_MENU, self.__manage_ths_pos, id=202) |
| | | |
| | | self.SetMenuBar(self.mainmenu) |
| | | |
| | | def __set_juejin_params(self, event): |
| | | ps = self.GetPosition() |
| | | size = self.GetSize() |
| | | JueJinSettingFrame((ps[0] + size[0] / 2, ps[1] + size[1] / 2)).Show() |
| | | |
| | | def __download_codes(self, event): |
| | | try: |
| | | result = self.__request("", 72) |
| | | result = json.loads(result) |
| | | if result['code'] == 0: |
| | | codes_dict = result['data'] |
| | | if codes_dict: |
| | | for key in codes_dict: |
| | | # 将代码保存到桌面 |
| | | path = f"C:\\Users\\Administrator\\Desktop\\涨停代码{key}.txt" |
| | | with open(path, mode='w') as f: |
| | | for c in codes_dict[key]: |
| | | f.write(c) |
| | | f.write("\n") |
| | | show_info("下载成功", None) |
| | | else: |
| | | show_warning(result['msg'], None) |
| | | except Exception as e: |
| | | show_warning(str(e), None) |
| | | |
| | | def __manage_code(self, event): |
| | | ps = self.GetPosition() |
| | | size = self.GetSize() |
| | | CodesSettingFrame((ps[0] + size[0] / 2, ps[1] + size[1] / 2), self.set_codes_success).Show() |
| | | |
| | | def __manage_ths_pos(self, event): |
| | | ps = self.GetPosition() |
| | | size = self.GetSize() |
| | | THSPositionSettingFrame((ps[0] + size[0] / 2, ps[1] + size[1] / 2)).Show() |
| | | |
| | | |
| | | class MainFrame(wx.Frame): |
| | | def __init__(self): |
| | | '''构造函数''' |
| | | wx.Frame.__init__(self, None, -1, APP_TITLE, style=wx.DEFAULT_FRAME_STYLE, |
| | |
| | | # 默认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)) |
| | |
| | | self.scroll = None |
| | | self.mark_lines = {} |
| | | self.col = 1 |
| | | self.ratio = 0.76 |
| | | self.__re_draw() |
| | | |
| | | self.timer = wx.Timer(self) # 创建定时器 |
| | |
| | | for p in self.panels: |
| | | p.Destroy() |
| | | self.boxsier.Clear() |
| | | pannel_height = round((self.Size[0] - (self.col - 1) * space) / self.col * (450 / 800)) |
| | | |
| | | pannel_height = round((self.Size[0] - (self.col - 1) * space) / self.col * self.ratio) |
| | | |
| | | self.scroll.SetScrollbars(1, 1, 0, pannel_height * rows) |
| | | self.scroll.SetScrollRate(0, pannel_height) |
| | |
| | | # |
| | | # 初始化数据 |
| | | drawManager = DrawManager(axes_list, codes_name) |
| | | drawManager.init_code_datas() |
| | | t1 = threading.Thread(target=lambda: drawManager.init_code_datas()) |
| | | # 后台运行 |
| | | t1.setDaemon(True) |
| | | t1.start() |
| | | |
| | | def __create_canvas(self, pannel, title, name, price, close_callback=None): |
| | | def show_mouse_line(event): |
| | |
| | | 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 = Figure(figsize=(width_dpi, round(width_dpi * (self.ratio), 2))) |
| | | # 设置外边距 |
| | | figure_score.subplots_adjust(left=0.01, bottom=0.15, right=0.85) |
| | | right_padding_px = 85 |
| | | right = round((self.Size[0] - right_padding_px) / self.Size[0], 4) |
| | | figure_score.subplots_adjust(left=0.01, bottom=0.15, top=0.92, |
| | | right=right) |
| | | # 设置字体颜色 |
| | | plt.rcParams["text.color"] = "red" |
| | | plt.rcParams["axes.labelcolor"] = "red" |
| | |
| | | yticks_labels.append("") |
| | | # 加粗中轴线 |
| | | |
| | | axes2.set_ylabel(u'涨幅') |
| | | axes2.set_ylabel(u'') |
| | | # 设置纵轴的值的范围 |
| | | axes2.set_ylim(0 - max_rate * (1), max_rate * (1)) |
| | | axes2.set_yticks(yticks2) |
| | |
| | | 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) |
| | |
| | | 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.timer.StartOnce(1000) |
| | | # 降低重绘频率 |
| | | # self.__re_draw() |
| | | |
| | | def set_codes_success(self): |
| | | print("设置代码成功回调") |
| | | p2.send("resub") |
| | | self.__re_draw() |
| | | |
| | | |
| | | # 绘图管理器 |
| | |
| | | |
| | | if old_datas: |
| | | code_datas[code].extend(old_datas) |
| | | self.update(code, code_datas[code]) |
| | | # self.update(code, code_datas[code]) |
| | | wx.CallAfter(self.update, code, code_datas[code]) |
| | | |
| | | # 更新数据 |
| | | def __update_data(self, code, axes, datas, min_rate, max_rate): |
| | |
| | | self.lines = {} |
| | | |
| | | def update(self, code, datas): |
| | | __start_time = time.time() |
| | | # 获取前高和前低 |
| | | max_rate = None |
| | | min_rate = None |
| | |
| | | |
| | | # 展示最近的600个 |
| | | datas = datas[-450:] |
| | | # 修正量数据 |
| | | last_info = None |
| | | for i in range(1, len(datas)): |
| | | # 如果在 |
| | | if not last_info: |
| | | last_info = datas[i] |
| | | if int(datas[i]["created_at"][-2:]) - int(datas[i - 1]["created_at"][-2:]) < 0: |
| | | last_info = datas[i] |
| | | datas[i]['average_rate'] = last_info['average_rate'] |
| | | |
| | | for i in range(0, len(self.codes_info)): |
| | | if self.codes_info[i][0] == code: |
| | | try: |
| | | self.__update_data(code, self.axes_list[i], datas, min_rate, max_rate) |
| | | except Exception as e: |
| | | logging.exception(e) |
| | | print("绘图花费时间:", time.time() - __start_time) |
| | | |
| | | |
| | | class mainApp(wx.App): |
| | |
| | | if not code: |
| | | time.sleep(0.1) |
| | | continue |
| | | self.xgb.set_code(code) |
| | | # 1s更新一次 |
| | | if round(time.time()) - last_time > 5: |
| | | codes = juejin_core.GPCodeManager().get_codes() |
| | |
| | | self.Frame.scrollTo(index) |
| | | break |
| | | except Exception as e: |
| | | print(str(e)) |
| | | # print(str(e)) |
| | | pass |
| | | time.sleep(0.005) |
| | | |
| | | def OnInit(self): |
| | | self.SetAppName(APP_TITLE) |
| | | self.Frame = mainFrame() |
| | | # self.Frame = FloatFrame(None) |
| | | self.Frame.Show() |
| | | self.frame = MainFrame() |
| | | self.frame.Show() |
| | | # 传递代码设置成功的回调 |
| | | self.xgb = CodeDataFrame(self.frame.set_codes_success) |
| | | self.xgb.Show() |
| | | t1 = threading.Thread(target=lambda: self.__refresh()) |
| | | # 后台运行 |
| | | t1.setDaemon(True) |
| | |
| | | |
| | | |
| | | 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)) |
| | | code = ocr_util.recognize_code("C:\\Users\\Administrator\\Desktop\\test.png.bmp") |
| | | print(code) |
| | | |
| | | # 打包命令 |
| | | # cd D:\workspace\GP\trade_desk |
| | | # D:\workspace\GP\trade_desk\dist\env\pk_env\Scripts\pyinstaller.exe main.spec |
| | | |
| | | if __name__ == "__main__": |
| | | print(pow(3, 1)) |