Skip to content

Commit c32c054

Browse files
committed
feat(yh_client): refactor && update README.md
1 parent 711426a commit c32c054

File tree

4 files changed

+114
-82
lines changed

4 files changed

+114
-82
lines changed

README.md

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323

2424
### 支持券商
2525

26-
* 银河
26+
* 银河
2727
* 广发
28-
* 佣金宝
28+
* 银河客户端(支持自动登陆), 须在 `windows` 平台下载 `银河双子星` 客户端
29+
* 佣金宝(web已经关闭)
2930

3031
### 模拟交易
3132

@@ -37,9 +38,8 @@
3738
3839
> 银河可以直接自动登录
3940
40-
> 佣金宝 的自动登录需要安装以下二者之一, 广发的自动登录需要安装下列的 tesseract:
41+
> 广发的自动登录需要安装下列的 tesseract:
4142
42-
* `JAVA` : 推荐, 识别率高,安装简单, 需要命令行下 `java -version` 可用 (感谢空中园的贡献)
4343
* `tesseract` : 非 `pytesseract`, 需要单独安装, [地址](https://github.com/tesseract-ocr/tesseract/wiki),保证在命令行下 `tesseract` 可用
4444

4545
### 安装
@@ -72,10 +72,10 @@ import easytrader
7272
user = easytrader.use('yh') # 银河支持 ['yh', 'YH', '银河']
7373
```
7474

75-
##### 佣金宝
75+
##### 银河客户端
7676

7777
```python
78-
user = easytrader.use('yjb') # 佣金宝支持 ['yjb', 'YJB', '佣金宝']
78+
user = easytrader.use('yh_client') # 银河客户端支持 ['yh_client', 'YH_CLIENT', '银河客户端']
7979
```
8080

8181
##### 广发
@@ -90,12 +90,12 @@ user = easytrader.use('gf') # 广发支持 ['gf', 'GF', '广发']
9090
##### 使用配置文件
9191

9292
```python
93-
user.prepare('/path/to/your/ht.json') // 或者 yjb.json 或者 yh.json 等配置文件路径
93+
user.prepare('/path/to/your/ht.json') // 或者 yh.json 或者 yh_client.json 等配置文件路径
9494
```
9595

9696
##### 参数登录
9797
```
98-
user.prepare(user='用户名', password='券商加密后的密码, 雪球为明文密码')
98+
user.prepare(user='用户名', password='券商加密后的密码, 雪球、银河客户端为明文密码')
9999
```
100100

101101
****:
@@ -105,9 +105,9 @@ user.prepare(user='用户名', password='券商加密后的密码, 雪球为明
105105

106106
格式可以参照 `Github` 目录下对应的 `json` 文件
107107

108-
* 佣金宝需要配置 `yjb.json` 并填入相关信息, 其中的 `password` 为加密后的 `password`
109-
* 银河类似佣金宝
108+
* 银河类似下面文章中所说的方法。 通过在 `web` 手动登陆后等待一段时间出现锁屏, 然后需要输入密码解锁,银河的加密密码可以通过这个解锁锁屏的请求抓取到
110109
* 雪球配置中 `username` 为邮箱, `account` 为手机, 填两者之一即可,另一项改为 `""`, 密码直接填写登录的明文密码即可,不需要抓取 `POST` 的密码
110+
* 银河客户端直接使用明文的账号和密码即可
111111

112112
[如何获取配置所需信息, 可参考此文章](http://www.celuetan.com/topic/5731e9ee705ee8f61eb681fd)
113113

@@ -212,6 +212,15 @@ user.sell('162411', price=0.55, amount=100)
212212
```python
213213
user.cancel_entrust('委托单号', '股票代码')
214214
```
215+
216+
##### 银河客户端
217+
218+
219+
```python
220+
user.cancel_entrust('股票6位代码,不带前缀', "撤单方向,可使用 ['buy', 'sell']"
221+
```
222+
223+
215224
#### 查询交割单
216225

217226
需要注意通常券商只会返回有限天数最新的交割单,如查询2015年整年数据, 华泰只会返回年末的90天的交割单

easytrader/yh_clienttrader.py

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
import tempfile
66
import time
77
import traceback
8-
import pyperclip
98
import win32api
10-
import win32clipboard as cp
119
import win32gui
1210
from io import StringIO
1311

1412
import pandas as pd
13+
import pyperclip
1514
import win32com.client
1615
import win32con
1716
from PIL import ImageGrab
@@ -24,7 +23,22 @@ class YHClientTrader():
2423
def __init__(self):
2524
self.Title = '网上股票交易系统5.0'
2625

27-
def login(self, user, password, exe_path='C:\中国银河证券双子星3.2\Binarystar.exe'):
26+
def prepare(self, config_path=None, user=None, password=None, exe_path='C:\中国银河证券双子星3.2\Binarystar.exe'):
27+
"""
28+
登陆银河客户端
29+
:param config_path: 银河登陆配置文件,跟参数登陆方式二选一
30+
:param user: 银河账号
31+
:param password: 银河明文密码
32+
:param exe_path: 银河客户端路径
33+
:return:
34+
"""
35+
if config_path is not None:
36+
account = helpers.file2dict(config_path)
37+
user = account['user']
38+
password = account['password']
39+
self.login(user, password, exe_path)
40+
41+
def login(self, user, password, exe_path):
2842
if self._has_main_window():
2943
self._get_handles()
3044
log.info('检测到交易客户端已启动,连接完毕')
@@ -46,15 +60,15 @@ def login(self, user, password, exe_path='C:\中国银河证券双子星3.2\Bina
4660
self._set_trade_mode()
4761
self._set_login_name(user)
4862
self._set_login_password(password)
49-
for _ in range(3):
63+
for _ in range(10):
5064
self._set_login_verify_code()
5165
self._click_login_button()
5266
time.sleep(3)
5367
if not self._has_login_window():
5468
break
5569
self._click_login_verify_code()
5670

57-
for _ in range(30):
71+
for _ in range(60):
5872
if self._has_main_window():
5973
self._get_handles()
6074
break
@@ -64,7 +78,7 @@ def login(self, user, password, exe_path='C:\中国银河证券双子星3.2\Bina
6478
log.info('客户端登陆成功')
6579

6680
def _set_login_verify_code(self):
67-
verify_code_image = self._get_verify_code()
81+
verify_code_image = self._grab_verify_code()
6882
image_path = tempfile.mktemp() + '.jpg'
6983
verify_code_image.save(image_path)
7084
result = helpers.recognize_verify_code(image_path, 'yh_client')
@@ -89,7 +103,6 @@ def _set_login_password(self, password):
89103
def _has_login_window(self):
90104
for title in [' - 北京电信', ' - 北京电信 - 北京电信']:
91105
self.login_hwnd = win32gui.FindWindow(None, title)
92-
log.debug('检测到登陆窗口句柄:{}'.format(self.login_hwnd))
93106
if self.login_hwnd != 0:
94107
return True
95108
return False
@@ -101,13 +114,14 @@ def _input_login_verify_code(self, code):
101114
def _click_login_verify_code(self):
102115
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x56ba)
103116
rect = win32gui.GetWindowRect(input_hwnd)
104-
self.click(rect[0] + 5, rect[1] + 5)
117+
self._mouse_click(rect[0] + 5, rect[1] + 5)
118+
119+
@staticmethod
120+
def _mouse_click(x, y):
121+
win32api.SetCursorPos((x, y))
122+
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
123+
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
105124

106-
def click(self, x,y):
107-
win32api.SetCursorPos((x,y))
108-
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,x,y,0,0)
109-
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,x,y,0,0)
110-
111125
def _click_login_button(self):
112126
time.sleep(1)
113127
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x1)
@@ -120,52 +134,57 @@ def _has_main_window(self):
120134
return False
121135
return True
122136

123-
def _get_verify_code(self):
137+
def _grab_verify_code(self):
124138
verify_code_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x56ba)
125-
self.set_foreground_window(self.login_hwnd)
139+
self._set_foreground_window(self.login_hwnd)
126140
time.sleep(1)
127141
rect = win32gui.GetWindowRect(verify_code_hwnd)
128142
return ImageGrab.grab(rect)
129143

130144
def _get_handles(self):
131-
Main = win32gui.FindWindow(0, self.Title) # 交易窗口
132-
Frame = win32gui.GetDlgItem(Main, 59648) # 操作窗口框架
133-
Afxwnd = win32gui.GetDlgItem(Frame, 59648) # 操作窗口框架
134-
Hexin = win32gui.GetDlgItem(Afxwnd, 129)
135-
Scrolwnd = win32gui.GetDlgItem(Hexin, 200) # 左部折叠菜单控件
136-
treev = win32gui.GetDlgItem(Scrolwnd, 129) # 左部折叠菜单控件
145+
trade_main_hwnd = win32gui.FindWindow(0, self.Title) # 交易窗口
146+
operate_frame_hwnd = win32gui.GetDlgItem(trade_main_hwnd, 59648) # 操作窗口框架
147+
operate_frame_afx_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59648) # 操作窗口框架
148+
hexin_hwnd = win32gui.GetDlgItem(operate_frame_afx_hwnd, 129)
149+
scroll_hwnd = win32gui.GetDlgItem(hexin_hwnd, 200) # 左部折叠菜单控件
150+
tree_view_hwnd = win32gui.GetDlgItem(scroll_hwnd, 129) # 左部折叠菜单控件
137151

138152
# 获取委托窗口所有控件句柄
139-
win32api.PostMessage(treev, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
153+
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
140154
time.sleep(0.5)
141-
F_Bentrust = win32gui.GetDlgItem(Frame, 59649) # 委托窗口框架
142-
self.E_Bsymbol = win32gui.GetDlgItem(F_Bentrust, 1032) # 买入代码输入框
143-
self.E_Bprice = win32gui.GetDlgItem(F_Bentrust, 1033) # 买入价格输入框
144-
self.E_Bvol = win32gui.GetDlgItem(F_Bentrust, 1034) # 买入数量输入框
145-
self.B_Buy = win32gui.GetDlgItem(F_Bentrust, 1006) # 买入确认按钮
146-
self.B_refresh = win32gui.GetDlgItem(F_Bentrust, 32790) # 刷新持仓按钮
147-
F_Bhexin = win32gui.GetDlgItem(F_Bentrust, 1047) # 持仓显示框架
148-
F_Bhexinsub = win32gui.GetDlgItem(F_Bhexin, 200) # 持仓显示框架
149-
self.G_position = win32gui.GetDlgItem(F_Bhexinsub, 1047) # 持仓列表
150-
win32api.PostMessage(treev, win32con.WM_KEYDOWN, win32con.VK_F2, 0)
155+
156+
# 买入相关
157+
entrust_window_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 委托窗口框架
158+
self.buy_stock_code_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1032) # 买入代码输入框
159+
self.buy_price_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1033) # 买入价格输入框
160+
self.buy_amount_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1034) # 买入数量输入框
161+
self.buy_btn_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1006) # 买入确认按钮
162+
self.refresh_entrust_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 32790) # 刷新持仓按钮
163+
entrust_frame_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1047) # 持仓显示框架
164+
entrust_sub_frame_hwnd = win32gui.GetDlgItem(entrust_frame_hwnd, 200) # 持仓显示框架
165+
self.position_list_hwnd = win32gui.GetDlgItem(entrust_sub_frame_hwnd, 1047) # 持仓列表
166+
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F2, 0)
151167
time.sleep(0.5)
152-
F_Sentrust = win32gui.GetDlgItem(Frame, 59649) # 委托窗口框架
153-
self.E_Ssymbol = win32gui.GetDlgItem(F_Sentrust, 1032) # 卖出代码输入框
154-
self.E_Sprice = win32gui.GetDlgItem(F_Sentrust, 1033) # 卖出价格输入框
155-
self.E_Svol = win32gui.GetDlgItem(F_Sentrust, 1034) # 卖出数量输入框
156-
self.B_Sell = win32gui.GetDlgItem(F_Sentrust, 1006) # 卖出确认按钮
168+
169+
# 卖出相关
170+
sell_entrust_frame_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 委托窗口框架
171+
self.sell_stock_code_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1032) # 卖出代码输入框
172+
self.sell_price_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1033) # 卖出价格输入框
173+
self.sell_amount_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1034) # 卖出数量输入框
174+
self.sell_btn_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1006) # 卖出确认按钮
157175

158176
# 撤单窗口
159-
win32api.PostMessage(treev, win32con.WM_KEYDOWN, win32con.VK_F3, 0)
177+
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F3, 0)
160178
time.sleep(0.5)
161-
F_Centrust = win32gui.GetDlgItem(Frame, 59649) # 撤单窗口框架
162-
self.E_Csymbol = win32gui.GetDlgItem(F_Centrust, 3348) # 卖出代码输入框
163-
self.B_Csort = win32gui.GetDlgItem(F_Centrust, 3349) # 查询代码按钮
164-
self.B_Cbuy = win32gui.GetDlgItem(F_Centrust, 30002) # 撤买
165-
self.B_Csell = win32gui.GetDlgItem(F_Centrust, 30003) # 撤卖
166-
F_Chexin = win32gui.GetDlgItem(F_Centrust, 1047)
167-
F_Chexinsub = win32gui.GetDlgItem(F_Chexin, 200)
168-
self.G_entrust = win32gui.GetDlgItem(F_Chexinsub, 1047) # 委托列表
179+
cancel_entrust_window_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 撤单窗口框架
180+
self.cancel_stock_code_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 3348) # 卖出代码输入框
181+
self.cancel_query_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 3349) # 查询代码按钮
182+
self.cancel_buy_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 30002) # 撤买
183+
self.cancel_sell_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 30003) # 撤卖
184+
185+
chexin_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 1047)
186+
chexin_sub_hwnd = win32gui.GetDlgItem(chexin_hwnd, 200)
187+
self.entrust_list_hwnd = win32gui.GetDlgItem(chexin_sub_hwnd, 1047) # 委托列表
169188

170189
def buy(self, stock_code, price, amount):
171190
"""
@@ -179,12 +198,12 @@ def buy(self, stock_code, price, amount):
179198
price = str(price)
180199

181200
try:
182-
win32gui.SendMessage(self.E_Bsymbol, win32con.WM_SETTEXT, None, stock_code) # 输入买入代码
183-
win32gui.SendMessage(self.E_Bprice, win32con.WM_SETTEXT, None, price) # 输入买入价格
201+
win32gui.SendMessage(self.buy_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入买入代码
202+
win32gui.SendMessage(self.buy_price_hwnd, win32con.WM_SETTEXT, None, price) # 输入买入价格
184203
time.sleep(0.2)
185-
win32gui.SendMessage(self.E_Bvol, win32con.WM_SETTEXT, None, amount) # 输入买入数量
204+
win32gui.SendMessage(self.buy_amount_hwnd, win32con.WM_SETTEXT, None, amount) # 输入买入数量
186205
time.sleep(0.2)
187-
win32gui.SendMessage(self.B_Buy, win32con.BM_CLICK, None, None) # 买入确定
206+
win32gui.SendMessage(self.buy_btn_hwnd, win32con.BM_CLICK, None, None) # 买入确定
188207
time.sleep(0.3)
189208
except:
190209
traceback.print_exc()
@@ -203,14 +222,13 @@ def sell(self, stock_code, price, amount):
203222
price = str(price)
204223

205224
try:
206-
win32gui.SendMessage(self.E_Ssymbol, win32con.WM_SETTEXT, None, stock_code) # 输入卖出代码
207-
win32gui.SendMessage(self.E_Sprice, win32con.WM_SETTEXT, None, price) # 输入卖出价格
208-
win32gui.SendMessage(self.E_Sprice, win32con.BM_CLICK, None, None) # 输入卖出价格
209-
print('add click')
225+
win32gui.SendMessage(self.sell_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入卖出代码
226+
win32gui.SendMessage(self.sell_price_hwnd, win32con.WM_SETTEXT, None, price) # 输入卖出价格
227+
win32gui.SendMessage(self.sell_price_hwnd, win32con.BM_CLICK, None, None) # 输入卖出价格
210228
time.sleep(0.2)
211-
win32gui.SendMessage(self.E_Svol, win32con.WM_SETTEXT, None, amount) # 输入卖出数量
229+
win32gui.SendMessage(self.sell_amount_hwnd, win32con.WM_SETTEXT, None, amount) # 输入卖出数量
212230
time.sleep(0.2)
213-
win32gui.SendMessage(self.B_Sell, win32con.BM_CLICK, None, None) # 卖出确定
231+
win32gui.SendMessage(self.sell_btn_hwnd, win32con.BM_CLICK, None, None) # 卖出确定
214232
time.sleep(0.3)
215233
except:
216234
traceback.print_exc()
@@ -227,15 +245,15 @@ def cancel_entrust(self, stock_code, direction):
227245
direction = 0 if direction == 'buy' else 1
228246

229247
try:
230-
win32gui.SendMessage(self.B_refresh, win32con.BM_CLICK, None, None) # 刷新持仓
248+
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
231249
time.sleep(0.2)
232-
win32gui.SendMessage(self.E_Csymbol, win32con.WM_SETTEXT, None, stock_code) # 输入撤单
233-
win32gui.SendMessage(self.B_Csort, win32con.BM_CLICK, None, None) # 查询代码
250+
win32gui.SendMessage(self.cancel_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入撤单
251+
win32gui.SendMessage(self.cancel_query_hwnd, win32con.BM_CLICK, None, None) # 查询代码
234252
time.sleep(0.2)
235253
if direction == 0:
236-
win32gui.SendMessage(self.B_Cbuy, win32con.BM_CLICK, None, None) # 撤买
254+
win32gui.SendMessage(self.cancel_buy_hwnd, win32con.BM_CLICK, None, None) # 撤买
237255
elif direction == 1:
238-
win32gui.SendMessage(self.B_Csell, win32con.BM_CLICK, None, None) # 撤卖
256+
win32gui.SendMessage(self.cancel_sell_hwnd, win32con.BM_CLICK, None, None) # 撤卖
239257
except:
240258
traceback.print_exc()
241259
return False
@@ -247,11 +265,11 @@ def position(self):
247265
return self.get_position()
248266

249267
def get_position(self):
250-
win32gui.SendMessage(self.B_refresh, win32con.BM_CLICK, None, None) # 刷新持仓
268+
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
251269
time.sleep(0.1)
252-
self.set_foreground_window(self.G_position)
270+
self._set_foreground_window(self.position_list_hwnd)
253271
time.sleep(0.1)
254-
data = self.read_clipboard()
272+
data = self._read_clipboard()
255273
return self.project_copy_data(data)
256274

257275
@staticmethod
@@ -260,10 +278,9 @@ def project_copy_data(copy_data):
260278
df = pd.read_csv(reader, delim_whitespace=True)
261279
return df.to_dict('records')
262280

263-
def read_clipboard(self):
264-
for _ in range(10):
281+
def _read_clipboard(self):
282+
for _ in range(15):
265283
try:
266-
#self.set_foreground_window(self.Main)
267284
win32api.keybd_event(17, 0, 0, 0)
268285
win32api.keybd_event(67, 0, 0, 0)
269286
win32api.keybd_event(67, 0, win32con.KEYEVENTF_KEYUP, 0)
@@ -277,13 +294,13 @@ def read_clipboard(self):
277294
raise Exception('read clipbord failed')
278295

279296
@staticmethod
280-
def project_position_str(raw):
297+
def _project_position_str(raw):
281298
reader = StringIO(raw)
282299
df = pd.read_csv(reader, delim_whitespace=True)
283300
return df
284301

285302
@staticmethod
286-
def set_foreground_window(hwnd):
303+
def _set_foreground_window(hwnd):
287304
shell = win32com.client.Dispatch('WScript.Shell')
288305
shell.SendKeys('%')
289306
win32gui.SetForegroundWindow(hwnd)
@@ -293,9 +310,9 @@ def entrust(self):
293310
return self.get_entrust()
294311

295312
def get_entrust(self):
296-
win32gui.SendMessage(self.B_refresh, win32con.BM_CLICK, None, None) # 刷新持仓
313+
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
297314
time.sleep(0.2)
298-
self.set_foreground_window(self.G_entrust)
315+
self._set_foreground_window(self.entrust_list_hwnd)
299316
time.sleep(0.2)
300-
data = self.read_clipboard()
317+
data = self._read_clipboard()
301318
return self.project_copy_data(data)

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ six
77
flask
88
Pillow
99
pytesseract
10+
pandas
11+
pyperclip

yh_client.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"user": "银河用户名",
3+
"password": "银河明文密码"
4+
}

0 commit comments

Comments
 (0)