| | |
| | | """ |
| | | |
| | | import array |
| | | import logging |
| | | import threading |
| | | import time |
| | | import random |
| | |
| | | import win32gui |
| | | import win32con |
| | | |
| | | import constant |
| | | from code_attribute import gpcode_manager |
| | | from db.redis_manager_delegate import RedisUtils |
| | | from ocr import ocr_util |
| | | from trade import l2_trade_util |
| | | from db import redis_manager |
| | | from log import * |
| | | from tool import async_call |
| | | from db import redis_manager_delegate as redis_manager |
| | | from log_module.log import * |
| | | from utils.tool import async_call |
| | | from utils import win32_util, capture_util, tool |
| | | |
| | | |
| | | class THSGuiTrade(object): |
| | |
| | | # 初始化设置 |
| | | # 获取交易窗口的锁 |
| | | cls.__instance.buy_lock = threading.RLock() |
| | | cls.__instance.buy_cancel_lock = threading.RLock() |
| | | cls.__instance.buy_cancel_locks = {} |
| | | cls.__instance.buy_win_list = cls.get_buy_wins() |
| | | print("交易窗口", cls.__instance.buy_win_list) |
| | | # print("交易窗口", cls.__instance.buy_win_list) |
| | | cls.__instance.using_buy_wins = set() |
| | | cls.__instance.cancel_win = cls.__instance.getCancelBuyWin() |
| | | cls.__instance.cancel_wins = cls.__instance.getCancelBuyWins() |
| | | return cls.__instance |
| | | |
| | | # 刷新窗口句柄 |
| | | def refresh_hwnds(self): |
| | | self.cancel_win = self.__instance.getCancelBuyWin() |
| | | self.cancel_wins = self.__instance.getCancelBuyWins() |
| | | self.buy_win_list = self.get_buy_wins() |
| | | |
| | | # 打开交易环境 |
| | |
| | | raise Exception("下单窗口最低需要10个") |
| | | |
| | | # 检测撤单窗口 |
| | | cancel_trade_win = cls.getCancelBuyWin() |
| | | if cancel_trade_win <= 0: |
| | | cancel_trade_wins = cls.getCancelBuyWins() |
| | | if len(cancel_trade_wins) <= 0: |
| | | raise Exception("委托撤销窗口未打开") |
| | | else: |
| | | pos = win32gui.GetWindowRect(cancel_trade_win) |
| | | pos = win32gui.GetWindowRect(cancel_trade_wins[0]) |
| | | width = pos[2] - pos[0] |
| | | height = pos[3] - pos[1] |
| | | if width <= 0 or height <= 0: |
| | |
| | | |
| | | # 获取撤单窗口 |
| | | @classmethod |
| | | def getCancelBuyWin(cls): |
| | | def getCancelBuyWin(cls, code=None): |
| | | # 获取代码位分配的下单窗口 |
| | | if code: |
| | | trade_win = THSBuyWinManagerNew.get_distributed_code_win(code) |
| | | if trade_win and win32gui.IsWindowVisible(trade_win): |
| | | top_win = win32_util.get_top_window(trade_win) |
| | | if cls.getText(top_win) == '专业版下单' and win32gui.IsWindowVisible(top_win): |
| | | return top_win |
| | | |
| | | wins = cls.getCancelBuyWins() |
| | | if wins: |
| | | return wins[0] |
| | | else: |
| | | return 0 |
| | | |
| | | @classmethod |
| | | def getCancelBuyWins(cls): |
| | | cancel_buy_wins = set() |
| | | hWndList = [] |
| | | win32gui.EnumWindows(lambda hWnd, param: param.append(hWnd), hWndList) |
| | | for hwnd in hWndList: |
| | |
| | | width = pos[2] - pos[0] |
| | | height = pos[3] - pos[1] |
| | | if width > 200 and height > 200: |
| | | return hwnd |
| | | cancel_buy_wins.add(hwnd) |
| | | except: |
| | | pass |
| | | return 0 |
| | | return list(cancel_buy_wins) |
| | | |
| | | def input_number(self, hwnd, num_str): |
| | | for i in range(10): |
| | |
| | | # raise Exception(error) |
| | | |
| | | # TODO 暂时不验证涨停价 |
| | | if not constant.TEST: |
| | | if abs(float(limit_up_price_now) - float(limit_up_price)) >= 0.01: |
| | | error = "涨停价验证出错 {}-{}".format(limit_up_price, limit_up_price_now) |
| | | raise Exception(error) |
| | | # if not constant.TEST: |
| | | # if abs(float(limit_up_price_now) - float(limit_up_price)) >= 0.001: |
| | | # error = "涨停价验证出错 {}-{}".format(limit_up_price, limit_up_price_now) |
| | | # raise Exception(error) |
| | | |
| | | # 开始交易,买入按钮ID:0x000003EE |
| | | # buy_hwnd = win32gui.GetDlgItem(win, 0x000003EE) |
| | |
| | | pass |
| | | return 0 |
| | | |
| | | def __get_code_input(self): |
| | | win = self.cancel_win |
| | | if win <= 0 or not win32gui.IsWindowVisible(win): |
| | | self.cancel_win = self.getCancelBuyWin() |
| | | win = self.cancel_win |
| | | if win <= 0: |
| | | raise Exception("无法找到取消委托窗口") |
| | | def __get_code_input(self, code=None): |
| | | win = self.getCancelBuyWin(code) |
| | | if win <= 0: |
| | | raise Exception("无法找到取消委托窗口") |
| | | t = time.time() |
| | | print(t) |
| | | start = int(round(t * 1000)) |
| | |
| | | def cancel_buy(self, code): |
| | | if not constant.TRADE_ENABLE: |
| | | return |
| | | self.buy_cancel_lock.acquire() |
| | | main_win = self.getCancelBuyWin(code) |
| | | if main_win <= 0: |
| | | raise Exception("尚未找到交易窗口") |
| | | if main_win not in self.buy_cancel_locks: |
| | | self.buy_cancel_locks[main_win] = threading.RLock() |
| | | |
| | | self.buy_cancel_locks[main_win].acquire() |
| | | logger_trade_gui.info("开始获取撤单控件:code-{}".format(code)) |
| | | code_input, win = self.__get_code_input() |
| | | code_input, win = self.__get_code_input(code) |
| | | try: |
| | | logger_trade_gui.info("开始撤单:code-{}".format(code)) |
| | | start = int(round(time.time() * 1000)) |
| | |
| | | logger_trade_gui.info("撤单成功:code-{} 耗时:{}".format(code, end - start)) |
| | | time.sleep(0.03) |
| | | finally: |
| | | self.buy_cancel_lock.release() |
| | | self.buy_cancel_locks[main_win].release() |
| | | # 清空代码框 |
| | | self.input_number(code_input, "") |
| | | # 再次清除代码框 |
| | |
| | | @async_call |
| | | def refresh_data(self): |
| | | # 获取到专业下单页面 |
| | | win = self.getCancelBuyWin() |
| | | child_win = None |
| | | refresh_btn = None |
| | | for i in range(0, 20): |
| | | child_win = win32gui.FindWindowEx(win, child_win, "#32770", None) |
| | | if not child_win: |
| | | break |
| | | if not win32gui.IsWindowVisible(child_win): |
| | | continue |
| | | temp = win32gui.FindWindowEx(child_win, None, "Button", "还原") |
| | | if temp: |
| | | refresh_btn = win32gui.GetDlgItem(child_win, 0x00000457) |
| | | break |
| | | if refresh_btn: |
| | | # 点击刷新 |
| | | THSGuiUtil.click(refresh_btn) |
| | | wins = self.getCancelBuyWins() |
| | | if wins: |
| | | for win in wins: |
| | | refresh_btn = None |
| | | child_win = None |
| | | for i in range(0, 20): |
| | | child_win = win32gui.FindWindowEx(win, child_win, "#32770", None) |
| | | if not child_win: |
| | | break |
| | | if not win32gui.IsWindowVisible(child_win): |
| | | continue |
| | | temp = win32gui.FindWindowEx(child_win, None, "Button", "还原") |
| | | if temp: |
| | | refresh_btn = win32gui.GetDlgItem(child_win, 0x00000457) |
| | | break |
| | | if refresh_btn: |
| | | # 点击刷新 |
| | | THSGuiUtil.click(refresh_btn) |
| | | |
| | | for w in wins: |
| | | if w not in self.buy_cancel_locks: |
| | | self.buy_cancel_locks[w] = threading.RLock() |
| | | |
| | | |
| | | class THSGuiUtil: |
| | | @classmethod |
| | | def getText(cls, hwnd): |
| | | bufSize = win32gui.SendMessage(hwnd, win32con.WM_GETTEXTLENGTH, 0, 0) + 1 |
| | | buffer = array.array('b', b'\x00\x00' * bufSize) |
| | | win32gui.SendMessage(hwnd, win32con.WM_GETTEXT, bufSize, buffer) |
| | | text = win32gui.PyGetString(buffer.buffer_info()[0], bufSize - 1) |
| | | return text.replace("\x00", "").strip() |
| | | try: |
| | | buffer = array.array('b', b'\x00\x00' * bufSize) |
| | | win32gui.SendMessage(hwnd, win32con.WM_GETTEXT, bufSize, buffer) |
| | | text = win32gui.PyGetString(buffer.buffer_info()[0], bufSize - 1) |
| | | return text.replace("\x00", "").strip() |
| | | except: |
| | | return "" |
| | | |
| | | # 添加下单窗口 |
| | | @classmethod |
| | |
| | | THSGuiTrade().input_number(hwnd1, code) |
| | | |
| | | |
| | | # 过时 同花顺买入窗口管理器 |
| | | class __THSBuyWinManager: |
| | | redisManager = redis_manager.RedisManager(2) |
| | | |
| | | @classmethod |
| | | def __get_redis(cls): |
| | | return cls.redisManager.getRedis() |
| | | |
| | | # 保存窗口代码分配 |
| | | @classmethod |
| | | def __save_code_win(cls, code, win): |
| | | key = "buywin_distribute-{}".format(code) |
| | | cls.__get_redis().setex(key, tool.get_expire(), win) |
| | | |
| | | # 获取窗口分配的代码 |
| | | @classmethod |
| | | def __get_code_win(cls, code): |
| | | key = "buywin_distribute-{}".format(code) |
| | | win = cls.__get_redis().get(key) |
| | | if win is not None: |
| | | return int(win) |
| | | return None |
| | | |
| | | # 删除代码窗口分配 |
| | | @classmethod |
| | | def __del_code_win(cls, code): |
| | | key = "buywin_distribute-{}".format(code) |
| | | cls.__get_redis().delete(key) |
| | | |
| | | # 获取所有已经分配窗口的代码 |
| | | @classmethod |
| | | def __get_distributed_win_codes(cls): |
| | | key = "buywin_distribute-*" |
| | | keys = cls.__get_redis().keys(key) |
| | | codes = [] |
| | | for k in keys: |
| | | codes.append(k.replace("buywin_distribute-", "")) |
| | | return codes |
| | | |
| | | # 获取可用的窗口 |
| | | @classmethod |
| | | def __get_available_win(cls): |
| | | # 是否有可用的还未分配的窗口 |
| | | key = "buywin_distribute-*" |
| | | keys = cls.__get_redis().keys(key) |
| | | win_list = THSGuiTrade().get_buy_wins() |
| | | if len(win_list) < 1: |
| | | raise Exception("必须要有一个买入窗口") |
| | | win_set = set(win_list) |
| | | for k in keys: |
| | | win = int(cls.__get_redis().get(k)) |
| | | if win in win_set: |
| | | win_set.remove(win) |
| | | if len(win_set) > 0: |
| | | return win_set.pop() |
| | | |
| | | # 没有剩余的窗口,新增加窗口 |
| | | win = THSGuiUtil.add_buy_win() |
| | | if win: |
| | | return win |
| | | else: |
| | | raise Exception("新增窗口失败") |
| | | |
| | | # 为代码分配窗口 |
| | | @classmethod |
| | | def distribute_win_for_code(cls, code): |
| | | # 获取是否已经分配 |
| | | win = cls.__get_code_win(code) |
| | | if win is not None: |
| | | # 已经分配的窗口是否有效 |
| | | if THSGuiUtil.is_win_exist(win): |
| | | # 填充代码 |
| | | THSGuiUtil.set_buy_window_code(win, code) |
| | | return win |
| | | |
| | | # 获取可用的窗口 |
| | | win = cls.__get_available_win() |
| | | if win is None: |
| | | raise Exception("窗口已经分配完毕,无可用窗口") |
| | | # 保存窗口分配信息 |
| | | cls.__save_code_win(code, win) |
| | | THSGuiUtil.set_buy_window_code(win, code) |
| | | return win |
| | | |
| | | # 删除代码窗口分配 |
| | | @classmethod |
| | | def cancel_distribute_win_for_code(cls, code): |
| | | win = cls.__get_code_win(code) |
| | | if win is not None: |
| | | # 清除代码 |
| | | THSGuiUtil.clear_buy_window_code(win) |
| | | cls.__del_code_win(code) |
| | | |
| | | # 获取代码已经分配的窗口 |
| | | @classmethod |
| | | def get_distributed_code_win(cls, code): |
| | | win = cls.__get_code_win(code) |
| | | if not THSGuiUtil.is_win_exist(win): |
| | | # 删除不存在的窗口 |
| | | cls.__del_code_win(code) |
| | | return None |
| | | return win |
| | | |
| | | @classmethod |
| | | def fill_codes(cls, codes): |
| | | codes_ = gpcode_manager.get_gp_list() |
| | | # 先删除没有的代码 |
| | | old_codes = cls.__get_distributed_win_codes() |
| | | for code in old_codes: |
| | | if code not in codes_: |
| | | cls.cancel_distribute_win_for_code(code) |
| | | add_codes = codes[0:10] |
| | | del_codes = codes[10:] |
| | | |
| | | for code in del_codes: |
| | | cls.cancel_distribute_win_for_code(code) |
| | | |
| | | for code in add_codes: |
| | | # 已经加入进去的不做操作 |
| | | if code in old_codes: |
| | | continue |
| | | win = cls.distribute_win_for_code(code) |
| | | print("分配的窗口:", win, THSGuiUtil.is_win_exist(win)) |
| | | |
| | | |
| | | # 同花顺买入窗口管理器 |
| | | class THSBuyWinManagerNew: |
| | | redisManager = redis_manager.RedisManager(2) |
| | |
| | | def get_buy_wins(cls): |
| | | buy_win_list = [] |
| | | hWndList = [] |
| | | main_hwnd = None |
| | | main_hwnds = [] |
| | | win32gui.EnumWindows(lambda hWnd, param: param.append(hWnd), hWndList) |
| | | for hwnd in hWndList: |
| | | if THSGuiUtil.getText(hwnd) == "专业版下单": |
| | | main_hwnd = hwnd |
| | | break |
| | | if not main_hwnd: |
| | | if win32gui.IsWindowVisible(hwnd) and THSGuiUtil.getText(hwnd) == "专业版下单": |
| | | main_hwnds.append(hwnd) |
| | | if not main_hwnds: |
| | | raise Exception("专业版下单未打开") |
| | | child_win = None |
| | | for i in range(0, 20): |
| | | child_win = win32gui.FindWindowEx(main_hwnd, child_win, "#32770", None) |
| | | if not child_win: |
| | | break |
| | | if not win32gui.IsWindowVisible(child_win): |
| | | continue |
| | | temp = win32gui.FindWindowEx(child_win, None, "Button", "撤单") |
| | | if temp: |
| | | buy_win_list.append(child_win) |
| | | for main_hwnd in main_hwnds: |
| | | for i in range(0, 20): |
| | | child_win = win32gui.FindWindowEx(main_hwnd, child_win, "#32770", None) |
| | | if not child_win: |
| | | break |
| | | if not win32gui.IsWindowVisible(child_win): |
| | | continue |
| | | temp = win32gui.FindWindowEx(child_win, None, "Button", "撤单") |
| | | if temp: |
| | | buy_win_list.append(child_win) |
| | | return buy_win_list |
| | | |
| | | @classmethod |
| | |
| | | @classmethod |
| | | def __save_code_win(cls, code, win): |
| | | key = "buywin_distribute-{}".format(code) |
| | | cls.__get_redis().setex(key, tool.get_expire(), win) |
| | | RedisUtils.setex(cls.__get_redis(), key, tool.get_expire(), win) |
| | | |
| | | # 获取窗口分配的代码 |
| | | @classmethod |
| | | def __get_code_win(cls, code): |
| | | key = "buywin_distribute-{}".format(code) |
| | | win = cls.__get_redis().get(key) |
| | | win = RedisUtils.get(cls.__get_redis(), key) |
| | | if win is not None: |
| | | return int(win) |
| | | return None |
| | |
| | | @classmethod |
| | | def __del_code_win(cls, code): |
| | | key = "buywin_distribute-{}".format(code) |
| | | cls.__get_redis().delete(key) |
| | | RedisUtils.delete(cls.__get_redis(), key) |
| | | |
| | | # 获取所有已经分配窗口的代码 |
| | | @classmethod |
| | | def __get_distributed_win_codes(cls): |
| | | key = "buywin_distribute-*" |
| | | keys = cls.__get_redis().keys(key) |
| | | keys = RedisUtils.keys(cls.__get_redis(), key) |
| | | codes = [] |
| | | for k in keys: |
| | | codes.append(k.replace("buywin_distribute-", "")) |
| | |
| | | def __get_available_win(cls): |
| | | # 是否有可用的还未分配的窗口 |
| | | key = "buywin_distribute-*" |
| | | keys = cls.__get_redis().keys(key) |
| | | keys = RedisUtils.keys(cls.__get_redis(), key) |
| | | win_list = cls.get_buy_wins() |
| | | if len(win_list) < 1: |
| | | raise Exception("必须要有一个买入窗口") |
| | | win_set = set(win_list) |
| | | for k in keys: |
| | | win = int(cls.__get_redis().get(k)) |
| | | win = int(RedisUtils.get(cls.__get_redis(),k)) |
| | | if win in win_set: |
| | | win_set.remove(win) |
| | | if len(win_set) > 0: |
| | | return win_set.pop() |
| | | win_list = list(win_set) |
| | | random.shuffle(win_list) |
| | | return win_list[0] |
| | | |
| | | # 没有剩余的窗口,新增加窗口 |
| | | raise Exception("没有剩余窗口") |
| | |
| | | # 获取可用的窗口 |
| | | win = cls.__get_available_win() |
| | | if win is None: |
| | | logger_buy_win_distibute.error(f"无可用窗口:{code}") |
| | | raise Exception("窗口已经分配完毕,无可用窗口") |
| | | # 保存窗口分配信息 |
| | | cls.__save_code_win(code, win) |
| | | # 设置代码多试几次 |
| | | is_success = False |
| | | for i in range(0, 3): |
| | | THSGuiUtil.set_buy_window_code(cls.get_trade_win(win), code) |
| | | time.sleep(0.5) |
| | | code_name_win = cls.__get_code_name(win) |
| | | if code_name == code_name_win: |
| | | break |
| | | if code_name == code_name_win or code_name_win.find(code_name) > -1: |
| | | if cls.__is_buy_limit_up_price(win): |
| | | is_success = True |
| | | break |
| | | else: |
| | | cls.__del_code_win(code) |
| | | THSGuiUtil.set_buy_window_code(cls.get_trade_win(win), "") |
| | | raise Exception("不是买涨停价") |
| | | if is_success: |
| | | logger_buy_win_distibute.info(f"新分配窗口成功:{code}-{win}") |
| | | else: |
| | | logger_buy_win_distibute.info(f"新分配窗口失败:{code}-{win}") |
| | | return win |
| | | |
| | | # 删除代码窗口分配 |
| | |
| | | return None |
| | | return win |
| | | |
| | | # 获取已分配的交易框信息 |
| | | @classmethod |
| | | def get_distributed_code_wins(cls): |
| | | key = "buywin_distribute-*" |
| | | keys = RedisUtils.keys(cls.__get_redis(), key) |
| | | results = [] |
| | | for k in keys: |
| | | code = k.split("-")[-1] |
| | | win = RedisUtils.get(cls.__get_redis(), k) |
| | | results.append((code, win)) |
| | | return results |
| | | |
| | | # 获取代码名称 |
| | | @classmethod |
| | | def __get_code_name(cls, win): |
| | |
| | | if name is not None: |
| | | name = name.replace(" ", "") |
| | | return tool.strQ2B(name) |
| | | |
| | | # 是否是涨停价 |
| | | @classmethod |
| | | def __is_buy_limit_up_price(cls, win): |
| | | trade_win = cls.get_trade_win(win) |
| | | if trade_win is None: |
| | | return None |
| | | price_win = win32gui.GetDlgItem(trade_win, 0x00000409) |
| | | ocr_result = ocr_util.OcrUtil.ocr_with_key(capture_util.window_capture(price_win), "涨停价|张停价") |
| | | if ocr_result: |
| | | return True |
| | | return False |
| | | |
| | | @classmethod |
| | | def fill_codes(cls, codes): |
| | |
| | | else: |
| | | new_delete_codes.append(code) |
| | | |
| | | add_codes = new_codes[0:10] |
| | | del_codes = new_codes[10:] |
| | | cancel_wins = THSGuiTrade.getCancelBuyWins() |
| | | add_codes_num = len(cancel_wins) * 10 |
| | | add_codes = new_codes[0:add_codes_num] |
| | | del_codes = new_codes[add_codes_num:] |
| | | del_codes.extend(new_delete_codes) |
| | | |
| | | for code in del_codes: |
| | |
| | | if name_codes.get(code_name) != code: |
| | | cls.cancel_distribute_win_for_code(code) |
| | | continue |
| | | win = cls.distribute_win_for_code(code, gpcode_manager.get_code_name(code)) |
| | | print("分配的窗口:", win, THSGuiUtil.is_win_exist(win)) |
| | | try: |
| | | win = cls.distribute_win_for_code(code, gpcode_manager.get_code_name(code)) |
| | | print("分配的窗口:", win, THSGuiUtil.is_win_exist(win)) |
| | | except Exception as e: |
| | | logging.exception(e) |
| | | |
| | | |
| | | # 根据涨幅高低分配交易窗口 |
| | | def re_distribute_buy_win(codes): |
| | | THSBuyWinManagerNew.fill_codes(codes) |
| | | |
| | | |
| | | class GUITest: |
| | |
| | | |
| | | |
| | | if __name__ == '__main__': |
| | | try: |
| | | THSGuiTrade().cancel_buy_again("000637") |
| | | except Exception as e: |
| | | print(e) |
| | | # GUITest().test_distribute() |
| | | # try: |
| | | # THSGuiUtil.set_buy_window_code(0x000112D0, "000333") |
| | | # THSGuiTrade().cancel_buy_again("000637") |
| | | # except Exception as e: |
| | | # print(e) |
| | | print(ocr_util.OcrUtil.ocr_with_key(capture_util.window_capture(0x000314EA), "涨停价|张停价")) |
| | | |
| | | # THSGuiTrade().buy("600613", 10.29) |