Skip to content

Commit

Permalink
加入了一些功能, 若有帮助,请合并到主版本中 (#407)
Browse files Browse the repository at this point in the history
* 1,加入了针对 广发证券客户端的支持。 此客户端,为同花顺定制版本;
2,加入了 cancel_all_entrusts 函数,实现批量取消;
3,在_switch_left_menus/_switch_left_menus_by_shortcut中,加入了close_pop_windows函数,避免意外弹窗导致函数失败;
4,完善了edit空间输入字符串功能

* 1,完善readme

* Revert "1,完善readme"

This reverts commit ef64162
  • Loading branch information
xiangsf authored Oct 12, 2020
1 parent dd7c5bc commit 4bbe9fe
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 1 deletion.
5 changes: 5 additions & 0 deletions easytrader/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ def use(broker, debug=False, **kwargs):

return GJClientTrader()

if broker.lower() in ["gf_client", "广发客户端"]:
from .gf_clienttrader import GFClientTrader

return GFClientTrader()

if broker.lower() in ["ths", "同花顺客户端"]:
from .clienttrader import ClientTrader

Expand Down
76 changes: 75 additions & 1 deletion easytrader/clienttrader.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import time
from typing import Type, Union

import hashlib, binascii

import easyutils
from pywinauto import findwindows, timings

Expand All @@ -23,7 +25,6 @@
import pywinauto
import pywinauto.clipboard


class IClientTrader(abc.ABC):
@property
@abc.abstractmethod
Expand Down Expand Up @@ -174,6 +175,29 @@ def cancel_entrust(self, entrust_no):
return self._handle_pop_dialogs()
return {"message": "委托单状态错误不能撤单, 该委托单可能已经成交或者已撤"}

def cancel_all_entrusts(self):
self.refresh()
self._switch_left_menus(["撤单[F3]"])

# 点击全部撤销控件
self._app.top_window().child_window(
control_id=self._config.TRADE_CANCEL_ALL_ENTRUST_CONTROL_ID, class_name="Button", title_re="""全撤.*"""
).click()
self.wait(0.2)

# 等待出现 确认兑换框
if self.is_exist_pop_dialog():
# 点击是 按钮
w = self._app.top_window()
if w is not None:
btn = w["是(Y)"]
if btn is not None:
btn.click()
self.wait(0.2)

# 如果出现了确认窗口
self.close_pop_dialog()

@perf_clock
def repo(self, security, price, amount, **kwargs):
self._switch_left_menus(["债券回购", "融资回购(正回购)"])
Expand Down Expand Up @@ -278,6 +302,24 @@ def _set_market_trade_type(self, ttype):
return
raise TypeError("不支持对应的市价类型: {}".format(ttype))

def _set_stock_exchange_type(self, ttype):
"""根据选择的市价交易类型选择对应的下拉选项"""
selects = self._main.child_window(
control_id=self._config.TRADE_STOCK_EXCHANGE_CONTROL_ID, class_name="ComboBox"
)

for i, text in enumerate(selects.texts()):
# skip 0 index, because 0 index is current select index
if i == 0:
if ttype.strip() == text.strip(): # 当前已经选中
return
else:
continue
if ttype.strip() == text.strip():
selects.select(i - 1)
return
raise TypeError("不支持对应的市场类型: {}".format(ttype))

def auto_ipo(self):
self._switch_left_menus(self._config.AUTO_IPO_MENU_PATH)

Expand Down Expand Up @@ -330,6 +372,21 @@ def is_exist_pop_dialog(self):
logger.exception("check pop dialog timeout")
return False

@perf_clock
def close_pop_dialog(self):
try:
if self._main.wrapper_object() != self._app.top_window().wrapper_object():
w = self._app.top_window()
if w is not None:
w.close()
self.wait(0.2)
except (
findwindows.ElementNotFoundError,
timings.TimeoutError,
RuntimeError,
) as ex:
pass

def _run_exe_path(self, exe_path):
return os.path.join(os.path.dirname(exe_path), "xiadan.exe")

Expand Down Expand Up @@ -397,6 +454,14 @@ def _set_trade_params(self, security, price, amount):
# wait security input finish
self.wait(0.1)

# 设置交易所
if security.lower().startswith("sz"):
self._set_stock_exchange_type("深圳A股")
if security.lower().startswith("sh"):
self._set_stock_exchange_type("上海A股")

self.wait(0.1)

self._type_edit_control_keys(
self._config.TRADE_PRICE_CONTROL_ID,
easyutils.round_price_by_code(price, code),
Expand Down Expand Up @@ -439,19 +504,28 @@ def _type_edit_control_keys(self, control_id, text):
editor.select()
editor.type_keys(text)

def type_edit_control_keys(self, editor, text):
if not self._editor_need_type_keys:
editor.set_edit_text(text)
else:
editor.select()
editor.type_keys(text)

def _collapse_left_menus(self):
items = self._get_left_menus_handle().roots()
for item in items:
item.collapse()

@perf_clock
def _switch_left_menus(self, path, sleep=0.2):
self.close_pop_dialog()
self._get_left_menus_handle().get_item(path).select()
self._app.top_window().type_keys('{ESC}')
self._app.top_window().type_keys('{F5}')
self.wait(sleep)

def _switch_left_menus_by_shortcut(self, shortcut, sleep=0.5):
self.close_pop_dialog()
self._app.top_window().type_keys(shortcut)
self.wait(sleep)

Expand Down
25 changes: 25 additions & 0 deletions easytrader/config/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ def create(broker):
return HT
if broker == "gj":
return GJ
if broker == "gf":
return GF
if broker == "ths":
return CommonConfig
if broker == "wk":
Expand All @@ -19,6 +21,12 @@ class CommonConfig:
DEFAULT_EXE_PATH: str = ""
TITLE = "网上股票交易系统5.0"

# 交易所类型。 深圳A股、上海A股
TRADE_STOCK_EXCHANGE_CONTROL_ID = 1003

# 撤销界面上, 全部撤销按钮
TRADE_CANCEL_ALL_ENTRUST_CONTROL_ID = 30001

TRADE_SECURITY_CONTROL_ID = 1032
TRADE_PRICE_CONTROL_ID = 1033
TRADE_AMOUNT_CONTROL_ID = 1034
Expand Down Expand Up @@ -135,6 +143,23 @@ class GJ(CommonConfig):

AUTO_IPO_MENU_PATH = ["新股申购", "新股批量申购"]

class GF(CommonConfig):
DEFAULT_EXE_PATH = "C:\\gfzqrzrq\\xiadan.exe"
TITLE = "核新网上交易系统"

GRID_DTYPE = {
"操作日期": str,
"委托编号": str,
"申请编号": str,
"合同编号": str,
"证券代码": str,
"股东代码": str,
"资金帐号": str,
"资金帐户": str,
"发生日期": str,
}

AUTO_IPO_MENU_PATH = ["新股申购", "批量新股申购"]

class WK(HT):
pass
Expand Down
84 changes: 84 additions & 0 deletions easytrader/gf_clienttrader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
import re
import tempfile
import time
import os

import pywinauto
import pywinauto.clipboard

from easytrader import clienttrader
from easytrader.utils.captcha import recognize_verify_code


class GFClientTrader(clienttrader.BaseLoginClientTrader):
@property
def broker_type(self):
return "gf"

def login(self, user, password, exe_path, comm_password=None, **kwargs):
"""
登陆客户端
:param user: 账号
:param password: 明文密码
:param exe_path: 客户端路径类似 'C:\\中国银河证券双子星3.2\\Binarystar.exe',
默认 'C:\\中国银河证券双子星3.2\\Binarystar.exe'
:param comm_password: 通讯密码, 华泰需要,可不设
:return:
"""
try:
self._app = pywinauto.Application().connect(
path=self._run_exe_path(exe_path), timeout=1
)
# pylint: disable=broad-except
except Exception:
self._app = pywinauto.Application().start(exe_path)

# wait login window ready
while True:
try:
self._app.top_window().Edit1.wait("ready")
break
except RuntimeError:
pass

self.type_edit_control_keys(self._app.top_window().Edit1, user)
self.type_edit_control_keys(self._app.top_window().Edit2, password)
edit3 = self._app.top_window().window(control_id=0x3eb)
while True:
try:
code = self._handle_verify_code()
self.type_edit_control_keys(edit3, code)
time.sleep(1)
self._app.top_window()["登录(Y)"].click()
# detect login is success or not
try:
self._app.top_window().wait_not("exists", 5)
break

# pylint: disable=broad-except
except Exception:
self._app.top_window()["确定"].click()

# pylint: disable=broad-except
except Exception:
pass

self._app = pywinauto.Application().connect(
path=self._run_exe_path(exe_path), timeout=10
)
self._main = self._app.window(title_re="""{title}.*""".format(title=self._config.TITLE))
self.close_pop_dialog()

def _handle_verify_code(self):
control = self._app.top_window().window(control_id=0x5db)
control.click()
time.sleep(0.2)
file_path = tempfile.mktemp() + ".jpg"
control.capture_as_image().save(file_path)
time.sleep(0.2)
vcode = recognize_verify_code(file_path, "gf_client")
if os.path.exists(file_path):
os.remove(file_path)
return "".join(re.findall("[a-zA-Z0-9]+", vcode))

1 comment on commit 4bbe9fe

@zhoubeiqing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very useful🦄

Please sign in to comment.