Skip to content

Commit

Permalink
feat(yh_client): refactor && update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
shidenggui committed Nov 24, 2016
1 parent 711426a commit c32c054
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 82 deletions.
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@

### 支持券商

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

### 模拟交易

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

### 安装
Expand Down Expand Up @@ -72,10 +72,10 @@ import easytrader
user = easytrader.use('yh') # 银河支持 ['yh', 'YH', '银河']
```

##### 佣金宝
##### 银河客户端

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

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

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

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

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

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

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

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

Expand Down Expand Up @@ -212,6 +212,15 @@ user.sell('162411', price=0.55, amount=100)
```python
user.cancel_entrust('委托单号', '股票代码')
```

##### 银河客户端


```python
user.cancel_entrust('股票6位代码,不带前缀', "撤单方向,可使用 ['buy', 'sell']"
```


#### 查询交割单

需要注意通常券商只会返回有限天数最新的交割单,如查询2015年整年数据, 华泰只会返回年末的90天的交割单
Expand Down
161 changes: 89 additions & 72 deletions easytrader/yh_clienttrader.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
import tempfile
import time
import traceback
import pyperclip
import win32api
import win32clipboard as cp
import win32gui
from io import StringIO

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

def login(self, user, password, exe_path='C:\中国银河证券双子星3.2\Binarystar.exe'):
def prepare(self, config_path=None, user=None, password=None, exe_path='C:\中国银河证券双子星3.2\Binarystar.exe'):
"""
登陆银河客户端
:param config_path: 银河登陆配置文件,跟参数登陆方式二选一
:param user: 银河账号
:param password: 银河明文密码
:param exe_path: 银河客户端路径
:return:
"""
if config_path is not None:
account = helpers.file2dict(config_path)
user = account['user']
password = account['password']
self.login(user, password, exe_path)

def login(self, user, password, exe_path):
if self._has_main_window():
self._get_handles()
log.info('检测到交易客户端已启动,连接完毕')
Expand All @@ -46,15 +60,15 @@ def login(self, user, password, exe_path='C:\中国银河证券双子星3.2\Bina
self._set_trade_mode()
self._set_login_name(user)
self._set_login_password(password)
for _ in range(3):
for _ in range(10):
self._set_login_verify_code()
self._click_login_button()
time.sleep(3)
if not self._has_login_window():
break
self._click_login_verify_code()

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

def _set_login_verify_code(self):
verify_code_image = self._get_verify_code()
verify_code_image = self._grab_verify_code()
image_path = tempfile.mktemp() + '.jpg'
verify_code_image.save(image_path)
result = helpers.recognize_verify_code(image_path, 'yh_client')
Expand All @@ -89,7 +103,6 @@ def _set_login_password(self, password):
def _has_login_window(self):
for title in [' - 北京电信', ' - 北京电信 - 北京电信']:
self.login_hwnd = win32gui.FindWindow(None, title)
log.debug('检测到登陆窗口句柄:{}'.format(self.login_hwnd))
if self.login_hwnd != 0:
return True
return False
Expand All @@ -101,13 +114,14 @@ def _input_login_verify_code(self, code):
def _click_login_verify_code(self):
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x56ba)
rect = win32gui.GetWindowRect(input_hwnd)
self.click(rect[0] + 5, rect[1] + 5)
self._mouse_click(rect[0] + 5, rect[1] + 5)

@staticmethod
def _mouse_click(x, y):
win32api.SetCursorPos((x, y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)

def click(self, x,y):
win32api.SetCursorPos((x,y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,x,y,0,0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,x,y,0,0)

def _click_login_button(self):
time.sleep(1)
input_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x1)
Expand All @@ -120,52 +134,57 @@ def _has_main_window(self):
return False
return True

def _get_verify_code(self):
def _grab_verify_code(self):
verify_code_hwnd = win32gui.GetDlgItem(self.login_hwnd, 0x56ba)
self.set_foreground_window(self.login_hwnd)
self._set_foreground_window(self.login_hwnd)
time.sleep(1)
rect = win32gui.GetWindowRect(verify_code_hwnd)
return ImageGrab.grab(rect)

def _get_handles(self):
Main = win32gui.FindWindow(0, self.Title) # 交易窗口
Frame = win32gui.GetDlgItem(Main, 59648) # 操作窗口框架
Afxwnd = win32gui.GetDlgItem(Frame, 59648) # 操作窗口框架
Hexin = win32gui.GetDlgItem(Afxwnd, 129)
Scrolwnd = win32gui.GetDlgItem(Hexin, 200) # 左部折叠菜单控件
treev = win32gui.GetDlgItem(Scrolwnd, 129) # 左部折叠菜单控件
trade_main_hwnd = win32gui.FindWindow(0, self.Title) # 交易窗口
operate_frame_hwnd = win32gui.GetDlgItem(trade_main_hwnd, 59648) # 操作窗口框架
operate_frame_afx_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59648) # 操作窗口框架
hexin_hwnd = win32gui.GetDlgItem(operate_frame_afx_hwnd, 129)
scroll_hwnd = win32gui.GetDlgItem(hexin_hwnd, 200) # 左部折叠菜单控件
tree_view_hwnd = win32gui.GetDlgItem(scroll_hwnd, 129) # 左部折叠菜单控件

# 获取委托窗口所有控件句柄
win32api.PostMessage(treev, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
time.sleep(0.5)
F_Bentrust = win32gui.GetDlgItem(Frame, 59649) # 委托窗口框架
self.E_Bsymbol = win32gui.GetDlgItem(F_Bentrust, 1032) # 买入代码输入框
self.E_Bprice = win32gui.GetDlgItem(F_Bentrust, 1033) # 买入价格输入框
self.E_Bvol = win32gui.GetDlgItem(F_Bentrust, 1034) # 买入数量输入框
self.B_Buy = win32gui.GetDlgItem(F_Bentrust, 1006) # 买入确认按钮
self.B_refresh = win32gui.GetDlgItem(F_Bentrust, 32790) # 刷新持仓按钮
F_Bhexin = win32gui.GetDlgItem(F_Bentrust, 1047) # 持仓显示框架
F_Bhexinsub = win32gui.GetDlgItem(F_Bhexin, 200) # 持仓显示框架
self.G_position = win32gui.GetDlgItem(F_Bhexinsub, 1047) # 持仓列表
win32api.PostMessage(treev, win32con.WM_KEYDOWN, win32con.VK_F2, 0)

# 买入相关
entrust_window_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 委托窗口框架
self.buy_stock_code_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1032) # 买入代码输入框
self.buy_price_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1033) # 买入价格输入框
self.buy_amount_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1034) # 买入数量输入框
self.buy_btn_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1006) # 买入确认按钮
self.refresh_entrust_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 32790) # 刷新持仓按钮
entrust_frame_hwnd = win32gui.GetDlgItem(entrust_window_hwnd, 1047) # 持仓显示框架
entrust_sub_frame_hwnd = win32gui.GetDlgItem(entrust_frame_hwnd, 200) # 持仓显示框架
self.position_list_hwnd = win32gui.GetDlgItem(entrust_sub_frame_hwnd, 1047) # 持仓列表
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F2, 0)
time.sleep(0.5)
F_Sentrust = win32gui.GetDlgItem(Frame, 59649) # 委托窗口框架
self.E_Ssymbol = win32gui.GetDlgItem(F_Sentrust, 1032) # 卖出代码输入框
self.E_Sprice = win32gui.GetDlgItem(F_Sentrust, 1033) # 卖出价格输入框
self.E_Svol = win32gui.GetDlgItem(F_Sentrust, 1034) # 卖出数量输入框
self.B_Sell = win32gui.GetDlgItem(F_Sentrust, 1006) # 卖出确认按钮

# 卖出相关
sell_entrust_frame_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 委托窗口框架
self.sell_stock_code_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1032) # 卖出代码输入框
self.sell_price_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1033) # 卖出价格输入框
self.sell_amount_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1034) # 卖出数量输入框
self.sell_btn_hwnd = win32gui.GetDlgItem(sell_entrust_frame_hwnd, 1006) # 卖出确认按钮

# 撤单窗口
win32api.PostMessage(treev, win32con.WM_KEYDOWN, win32con.VK_F3, 0)
win32api.PostMessage(tree_view_hwnd, win32con.WM_KEYDOWN, win32con.VK_F3, 0)
time.sleep(0.5)
F_Centrust = win32gui.GetDlgItem(Frame, 59649) # 撤单窗口框架
self.E_Csymbol = win32gui.GetDlgItem(F_Centrust, 3348) # 卖出代码输入框
self.B_Csort = win32gui.GetDlgItem(F_Centrust, 3349) # 查询代码按钮
self.B_Cbuy = win32gui.GetDlgItem(F_Centrust, 30002) # 撤买
self.B_Csell = win32gui.GetDlgItem(F_Centrust, 30003) # 撤卖
F_Chexin = win32gui.GetDlgItem(F_Centrust, 1047)
F_Chexinsub = win32gui.GetDlgItem(F_Chexin, 200)
self.G_entrust = win32gui.GetDlgItem(F_Chexinsub, 1047) # 委托列表
cancel_entrust_window_hwnd = win32gui.GetDlgItem(operate_frame_hwnd, 59649) # 撤单窗口框架
self.cancel_stock_code_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 3348) # 卖出代码输入框
self.cancel_query_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 3349) # 查询代码按钮
self.cancel_buy_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 30002) # 撤买
self.cancel_sell_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 30003) # 撤卖

chexin_hwnd = win32gui.GetDlgItem(cancel_entrust_window_hwnd, 1047)
chexin_sub_hwnd = win32gui.GetDlgItem(chexin_hwnd, 200)
self.entrust_list_hwnd = win32gui.GetDlgItem(chexin_sub_hwnd, 1047) # 委托列表

def buy(self, stock_code, price, amount):
"""
Expand All @@ -179,12 +198,12 @@ def buy(self, stock_code, price, amount):
price = str(price)

try:
win32gui.SendMessage(self.E_Bsymbol, win32con.WM_SETTEXT, None, stock_code) # 输入买入代码
win32gui.SendMessage(self.E_Bprice, win32con.WM_SETTEXT, None, price) # 输入买入价格
win32gui.SendMessage(self.buy_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入买入代码
win32gui.SendMessage(self.buy_price_hwnd, win32con.WM_SETTEXT, None, price) # 输入买入价格
time.sleep(0.2)
win32gui.SendMessage(self.E_Bvol, win32con.WM_SETTEXT, None, amount) # 输入买入数量
win32gui.SendMessage(self.buy_amount_hwnd, win32con.WM_SETTEXT, None, amount) # 输入买入数量
time.sleep(0.2)
win32gui.SendMessage(self.B_Buy, win32con.BM_CLICK, None, None) # 买入确定
win32gui.SendMessage(self.buy_btn_hwnd, win32con.BM_CLICK, None, None) # 买入确定
time.sleep(0.3)
except:
traceback.print_exc()
Expand All @@ -203,14 +222,13 @@ def sell(self, stock_code, price, amount):
price = str(price)

try:
win32gui.SendMessage(self.E_Ssymbol, win32con.WM_SETTEXT, None, stock_code) # 输入卖出代码
win32gui.SendMessage(self.E_Sprice, win32con.WM_SETTEXT, None, price) # 输入卖出价格
win32gui.SendMessage(self.E_Sprice, win32con.BM_CLICK, None, None) # 输入卖出价格
print('add click')
win32gui.SendMessage(self.sell_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入卖出代码
win32gui.SendMessage(self.sell_price_hwnd, win32con.WM_SETTEXT, None, price) # 输入卖出价格
win32gui.SendMessage(self.sell_price_hwnd, win32con.BM_CLICK, None, None) # 输入卖出价格
time.sleep(0.2)
win32gui.SendMessage(self.E_Svol, win32con.WM_SETTEXT, None, amount) # 输入卖出数量
win32gui.SendMessage(self.sell_amount_hwnd, win32con.WM_SETTEXT, None, amount) # 输入卖出数量
time.sleep(0.2)
win32gui.SendMessage(self.B_Sell, win32con.BM_CLICK, None, None) # 卖出确定
win32gui.SendMessage(self.sell_btn_hwnd, win32con.BM_CLICK, None, None) # 卖出确定
time.sleep(0.3)
except:
traceback.print_exc()
Expand All @@ -227,15 +245,15 @@ def cancel_entrust(self, stock_code, direction):
direction = 0 if direction == 'buy' else 1

try:
win32gui.SendMessage(self.B_refresh, win32con.BM_CLICK, None, None) # 刷新持仓
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
time.sleep(0.2)
win32gui.SendMessage(self.E_Csymbol, win32con.WM_SETTEXT, None, stock_code) # 输入撤单
win32gui.SendMessage(self.B_Csort, win32con.BM_CLICK, None, None) # 查询代码
win32gui.SendMessage(self.cancel_stock_code_hwnd, win32con.WM_SETTEXT, None, stock_code) # 输入撤单
win32gui.SendMessage(self.cancel_query_hwnd, win32con.BM_CLICK, None, None) # 查询代码
time.sleep(0.2)
if direction == 0:
win32gui.SendMessage(self.B_Cbuy, win32con.BM_CLICK, None, None) # 撤买
win32gui.SendMessage(self.cancel_buy_hwnd, win32con.BM_CLICK, None, None) # 撤买
elif direction == 1:
win32gui.SendMessage(self.B_Csell, win32con.BM_CLICK, None, None) # 撤卖
win32gui.SendMessage(self.cancel_sell_hwnd, win32con.BM_CLICK, None, None) # 撤卖
except:
traceback.print_exc()
return False
Expand All @@ -247,11 +265,11 @@ def position(self):
return self.get_position()

def get_position(self):
win32gui.SendMessage(self.B_refresh, win32con.BM_CLICK, None, None) # 刷新持仓
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
time.sleep(0.1)
self.set_foreground_window(self.G_position)
self._set_foreground_window(self.position_list_hwnd)
time.sleep(0.1)
data = self.read_clipboard()
data = self._read_clipboard()
return self.project_copy_data(data)

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

def read_clipboard(self):
for _ in range(10):
def _read_clipboard(self):
for _ in range(15):
try:
#self.set_foreground_window(self.Main)
win32api.keybd_event(17, 0, 0, 0)
win32api.keybd_event(67, 0, 0, 0)
win32api.keybd_event(67, 0, win32con.KEYEVENTF_KEYUP, 0)
Expand All @@ -277,13 +294,13 @@ def read_clipboard(self):
raise Exception('read clipbord failed')

@staticmethod
def project_position_str(raw):
def _project_position_str(raw):
reader = StringIO(raw)
df = pd.read_csv(reader, delim_whitespace=True)
return df

@staticmethod
def set_foreground_window(hwnd):
def _set_foreground_window(hwnd):
shell = win32com.client.Dispatch('WScript.Shell')
shell.SendKeys('%')
win32gui.SetForegroundWindow(hwnd)
Expand All @@ -293,9 +310,9 @@ def entrust(self):
return self.get_entrust()

def get_entrust(self):
win32gui.SendMessage(self.B_refresh, win32con.BM_CLICK, None, None) # 刷新持仓
win32gui.SendMessage(self.refresh_entrust_hwnd, win32con.BM_CLICK, None, None) # 刷新持仓
time.sleep(0.2)
self.set_foreground_window(self.G_entrust)
self._set_foreground_window(self.entrust_list_hwnd)
time.sleep(0.2)
data = self.read_clipboard()
data = self._read_clipboard()
return self.project_copy_data(data)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ six
flask
Pillow
pytesseract
pandas
pyperclip
4 changes: 4 additions & 0 deletions yh_client.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"user": "银河用户名",
"password": "银河明文密码"
}

0 comments on commit c32c054

Please sign in to comment.