Skip to content

Commit 24c6667

Browse files
yerrickshidenggui
authored andcommitted
增加国金全能行客户端 (shidenggui#221)
* add gj_clienttrader * update gj_clienttrader with yh_clienttrader * add demo json
1 parent e57bee8 commit 24c6667

File tree

2 files changed

+362
-0
lines changed

2 files changed

+362
-0
lines changed

easytrader/gj_clienttrader.py

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
# coding:utf8
2+
from __future__ import division
3+
import sys
4+
import os
5+
import subprocess
6+
import tempfile
7+
import time
8+
import traceback
9+
import win32api
10+
import win32gui
11+
from io import StringIO
12+
13+
import pandas as pd
14+
import pyperclip
15+
import win32com.client
16+
import win32con
17+
from PIL import ImageGrab
18+
19+
from . import helpers
20+
from .log import log
21+
22+
23+
def findWindowSubwindowEqualText(tt):
24+
hwnds = []
25+
def findSub(hwnd,param):
26+
if win32gui.FindWindowEx(hwnd,0,None,tt)!=0:
27+
param.append(hwnd)
28+
win32gui.EnumWindows(findSub, hwnds)
29+
return hwnds
30+
31+
class GJClientTrader():
32+
def __init__(self):
33+
self.LoginTitle = ['用户登录']
34+
self.Title = '网上股票交易系统5.0'
35+
36+
def prepare(self, config_path=None, user=None, password=None, exe_path='C:\\全能行证券交易终端\\xiadan.exe'):
37+
"""
38+
登陆银河客户端
39+
:param config_path: 银河登陆配置文件,跟参数登陆方式二选一
40+
:param user: 银河账号
41+
:param password: 银河明文密码
42+
:param exe_path: 银河客户端路径
43+
:return:
44+
"""
45+
if config_path is not None:
46+
account = helpers.file2dict(config_path)
47+
user = account['user']
48+
password = account['password']
49+
self.login(user, password, exe_path)
50+
51+
def login(self, user, password, exe_path):
52+
if self._has_main_window():
53+
self._get_handles()
54+
log.info('检测到交易客户端已启动,连接完毕')
55+
return
56+
if not self._has_login_window():
57+
if not os.path.exists(exe_path):
58+
raise FileNotFoundError('在 {} 未找到应用程序,请用 exe_path 指定应用程序目录'.format(exe_path))
59+
subprocess.Popen(exe_path)
60+
# 检测登陆窗口
61+
for _ in range(30):
62+
if self._has_login_window():
63+
break
64+
time.sleep(1)
65+
else:
66+
raise Exception('启动客户端失败,无法检测到登陆窗口')
67+
log.info('成功检测到客户端登陆窗口')
68+
69+
# 登陆
70+
# self._set_trade_mode()
71+
self._set_login_name(user)
72+
self._set_login_password(password)
73+
for _ in range(10):
74+
self._set_login_verify_code()
75+
self._click_login_button()
76+
time.sleep(3)
77+
self._check_verify_code_wrong()
78+
if not self._has_login_window():
79+
log.info('no login window, login success')
80+
break
81+
self._click_login_verify_code()
82+
83+
for _ in range(60):
84+
if self._has_main_window():
85+
self._get_handles()
86+
break
87+
time.sleep(1)
88+
else:
89+
raise Exception('启动交易客户端失败')
90+
log.info('客户端登陆成功')
91+
92+
def _set_login_verify_code(self):
93+
verify_code_image = self._grab_verify_code()
94+
image_path = tempfile.mktemp() + '.jpg'
95+
verify_code_image.save(image_path)
96+
result = helpers.recognize_verify_code(image_path, 'gj_client')
97+
time.sleep(0.2)
98+
self._input_login_verify_code(result)
99+
time.sleep(0.4)
100+
101+
def _set_trade_mode(self):
102+
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x4f4d)
103+
win32gui.SendMessage(input_hwnd, win32con.BM_CLICK, None, None)
104+
105+
def _set_login_name(self, user):
106+
time.sleep(0.5)
107+
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x3F3)
108+
win32gui.SendMessage(input_hwnd, win32con.WM_SETTEXT, None, user)
109+
110+
def _set_login_password(self, password):
111+
time.sleep(0.5)
112+
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x3F4)
113+
win32gui.SendMessage(input_hwnd, win32con.WM_SETTEXT, None, password)
114+
115+
def _has_login_window(self):
116+
for title in self.LoginTitle:
117+
self.login_hwnd = win32gui.FindWindow(None, title)
118+
if self.login_hwnd != 0:
119+
return True
120+
return False
121+
122+
def _input_login_verify_code(self, code):
123+
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x3eb)
124+
win32gui.SendMessage(input_hwnd, win32con.WM_SETTEXT, None, code)
125+
126+
def _click_login_verify_code(self):
127+
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x5db)
128+
rect = win32gui.GetWindowRect(input_hwnd)
129+
self._mouse_click(rect[0] + 5, rect[1] + 5)
130+
131+
@staticmethod
132+
def _mouse_click(x, y):
133+
win32api.SetCursorPos((x, y))
134+
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
135+
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
136+
137+
def _click_login_button(self):
138+
time.sleep(1)
139+
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x3ee)
140+
win32gui.SendMessage(input_hwnd, win32con.BM_CLICK, None, None)
141+
142+
def _check_verify_code_wrong(self):
143+
hwnds = findWindowSubwindowEqualText("提示")
144+
if len(hwnds)==0:
145+
log.info('Right verify code')
146+
elif len(hwnds)==1:
147+
win32gui.SetForegroundWindow(hwnds[0])
148+
button = win32gui.FindWindowEx(hwnds[0],0,None,"确定")
149+
win32gui.SendMessage(button, win32con.BM_CLICK, None, None)
150+
log.info('Wrong verify code')
151+
else:
152+
raise Exception("_check_verify_code_wrong too many windows %d"%len(hwnds))
153+
154+
def _has_main_window(self):
155+
try:
156+
self._get_handles()
157+
except:
158+
return False
159+
return True
160+
161+
def _grab_verify_code(self):
162+
verify_code_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x5db)
163+
self._set_foreground_window(self.login_hwnd)
164+
time.sleep(1)
165+
rect = win32gui.GetWindowRect(verify_code_hwnd)
166+
return ImageGrab.grab(rect)
167+
168+
def _get_handles(self):
169+
trade_main_hwnd = win32gui.FindWindow(0, self.Title) # 交易窗口
170+
operate_frame_hwnd = win32gui.GetDlgItem(trade_main_hwnd, 59648) # 操作窗口框架
171+
operate_frame_afx_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59648) # 操作窗口框架
172+
hexin_hwnd = win32gui.GetDlgItem(operate_frame_afx_hwnd, 129)
173+
scroll_hwnd = win32gui.GetDlgItem(hexin_hwnd, 200) # 左部折叠菜单控件
174+
tree_view_hwnd = win32gui.GetDlgItem(scroll_hwnd, 129) # 左部折叠菜单控件
175+
176+
# 获取委托窗口所有控件句柄
177+
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
178+
time.sleep(0.5)
179+
180+
# 买入相关
181+
entrust_window_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 委托窗口框架
182+
self.buy_stock_code_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1032) # 买入代码输入框
183+
self.buy_price_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1033) # 买入价格输入框
184+
self.buy_amount_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1034) # 买入数量输入框
185+
self.buy_btn_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1006) # 买入确认按钮
186+
self.refresh_entrust_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 32790) # 刷新持仓按钮
187+
entrust_frame_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1047) # 持仓显示框架
188+
entrust_sub_frame_hwnd = win32gui.GetDlgItem(entrust_frame_hwnd, 200) # 持仓显示框架
189+
self.position_list_hwnd = win32gui.GetDlgItem(entrust_sub_frame_hwnd, 1047) # 持仓列表
190+
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F2, 0)
191+
time.sleep(0.5)
192+
193+
# 卖出相关
194+
sell_entrust_frame_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 委托窗口框架
195+
self.sell_stock_code_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1032) # 卖出代码输入框
196+
self.sell_price_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1033) # 卖出价格输入框
197+
self.sell_amount_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1034) # 卖出数量输入框
198+
self.sell_btn_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1006) # 卖出确认按钮
199+
200+
# 撤单窗口
201+
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F3, 0)
202+
time.sleep(0.5)
203+
cancel_entrust_window_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 撤单窗口框架
204+
self.cancel_stock_code_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 3348) # 卖出代码输入框
205+
self.cancel_query_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 3349) # 查询代码按钮
206+
self.cancel_buy_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 30002) # 撤买
207+
self.cancel_sell_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 30003) # 撤卖
208+
209+
chexin_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 1047)
210+
chexin_sub_hwnd = win32gui.GetDlgItem(chexin_hwnd, 200)
211+
self.entrust_list_hwnd = win32gui.GetDlgItem(chexin_sub_hwnd, 1047) # 委托列表
212+
213+
# 资金股票
214+
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F4, 0)
215+
time.sleep(0.5)
216+
self.capital_window_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 0xE901) # 资金股票窗口框架
217+
218+
def balance(self):
219+
return self.get_balance()
220+
221+
def get_balance(self):
222+
self._set_foreground_window(self.capital_window_hwnd)
223+
time.sleep(0.3)
224+
data = self._read_clipboard()
225+
return self.project_copy_data(data)[0]
226+
227+
def buy(self, stock_code, price, amount, **kwargs):
228+
"""
229+
买入股票
230+
:param stock_code: 股票代码
231+
:param price: 买入价格
232+
:param amount: 买入股数
233+
:return: bool: 买入信号是否成功发出
234+
"""
235+
amount = str(amount // 100 * 100)
236+
price = str(price)
237+
238+
try:
239+
win32gui.SendMessage(self.buy_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入买入代码
240+
win32gui.SendMessage(self.buy_price_hwnd, win32con.WM_SETTEXT, None, price) # 输入买入价格
241+
time.sleep(0.2)
242+
win32gui.SendMessage(self.buy_amount_hwnd, win32con.WM_SETTEXT, None, amount) # 输入买入数量
243+
time.sleep(0.2)
244+
win32gui.SendMessage(self.buy_btn_hwnd, win32con.BM_CLICK, None, None) # 买入确定
245+
time.sleep(0.3)
246+
except:
247+
traceback.print_exc()
248+
return False
249+
return True
250+
251+
def sell(self, stock_code, price, amount, **kwargs):
252+
"""
253+
买出股票
254+
:param stock_code: 股票代码
255+
:param price: 卖出价格
256+
:param amount: 卖出股数
257+
:return: bool 卖出操作是否成功
258+
"""
259+
amount = str(amount // 100 * 100)
260+
price = str(price)
261+
262+
try:
263+
win32gui.SendMessage(self.sell_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入卖出代码
264+
win32gui.SendMessage(self.sell_price_hwnd, win32con.WM_SETTEXT, None, price) # 输入卖出价格
265+
win32gui.SendMessage(self.sell_price_hwnd, win32con.BM_CLICK, None, None) # 输入卖出价格
266+
time.sleep(0.2)
267+
win32gui.SendMessage(self.sell_amount_hwnd, win32con.WM_SETTEXT, None, amount) # 输入卖出数量
268+
time.sleep(0.2)
269+
win32gui.SendMessage(self.sell_btn_hwnd, win32con.BM_CLICK, None, None) # 卖出确定
270+
time.sleep(0.3)
271+
except:
272+
traceback.print_exc()
273+
return False
274+
return True
275+
276+
def cancel_entrust(self, stock_code, direction):
277+
"""
278+
撤单
279+
:param stock_code: str 股票代码
280+
:param direction: str 'buy' 撤买, 'sell' 撤卖
281+
:return: bool 撤单信号是否发出
282+
"""
283+
direction = 0 if direction == 'buy' else 1
284+
285+
try:
286+
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
287+
time.sleep(0.2)
288+
win32gui.SendMessage(self.cancel_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入撤单
289+
win32gui.SendMessage(self.cancel_query_hwnd, win32con.BM_CLICK, None, None) # 查询代码
290+
time.sleep(0.2)
291+
if direction == 0:
292+
win32gui.SendMessage(self.cancel_buy_hwnd, win32con.BM_CLICK, None, None) # 撤买
293+
elif direction == 1:
294+
win32gui.SendMessage(self.cancel_sell_hwnd, win32con.BM_CLICK, None, None) # 撤卖
295+
except:
296+
traceback.print_exc()
297+
return False
298+
time.sleep(0.3)
299+
return True
300+
301+
@property
302+
def position(self):
303+
return self.get_position()
304+
305+
def get_position(self):
306+
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
307+
time.sleep(0.1)
308+
self._set_foreground_window(self.position_list_hwnd)
309+
time.sleep(0.1)
310+
data = self._read_clipboard()
311+
return self.project_copy_data(data)
312+
313+
@staticmethod
314+
def project_copy_data(copy_data):
315+
reader = StringIO(copy_data)
316+
df = pd.read_csv(reader, sep = '\t')
317+
return df.to_dict('records')
318+
319+
def _read_clipboard(self):
320+
for _ in range(15):
321+
try:
322+
win32api.keybd_event(17, 0, 0, 0)
323+
win32api.keybd_event(67, 0, 0, 0)
324+
win32api.keybd_event(67, 0, win32con.KEYEVENTF_KEYUP, 0)
325+
win32api.keybd_event(17, 0, win32con.KEYEVENTF_KEYUP, 0)
326+
time.sleep(0.2)
327+
return pyperclip.paste()
328+
except Exception as e:
329+
log.error('open clipboard failed: {}, retry...'.format(e))
330+
time.sleep(1)
331+
else:
332+
raise Exception('read clipbord failed')
333+
334+
@staticmethod
335+
def _project_position_str(raw):
336+
reader = StringIO(raw)
337+
df = pd.read_csv(reader, sep = '\t')
338+
return df
339+
340+
@staticmethod
341+
def _set_foreground_window(hwnd):
342+
import pythoncom
343+
pythoncom.CoInitialize()
344+
shell = win32com.client.Dispatch('WScript.Shell')
345+
shell.SendKeys('%')
346+
win32gui.SetForegroundWindow(hwnd)
347+
348+
@property
349+
def entrust(self):
350+
return self.get_entrust()
351+
352+
def get_entrust(self):
353+
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
354+
time.sleep(0.2)
355+
self._set_foreground_window(self.entrust_list_hwnd)
356+
time.sleep(0.2)
357+
data = self._read_clipboard()
358+
return self.project_copy_data(data)

gj_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)