hikyuu2/hikyuu/puppet.py

298 lines
12 KiB
Python
Raw Normal View History

2017-06-29 14:19:02 +08:00
"""
扯线木偶界面自动化应用编程接口(Puppet UIAutomation API)是扯线木偶量化框架(Puppet Quant Framework)的一个组件
技术群624585416
2017-06-29 14:19:02 +08:00
"""
__author__ = "睿瞳深邃(https://github.com/Raytone-D)"
2017-06-29 14:19:02 +08:00
__project__ = 'Puppet'
__version__ = "0.4.12"
__license__ = 'MIT'
2017-06-29 14:19:02 +08:00
# coding: utf-8
import ctypes
from functools import reduce
import time
import pyperclip
MSG = {'WM_SETTEXT': 12,
'WM_GETTEXT': 13,
'WM_KEYDOWN': 256,
'WM_KEYUP': 257,
'WM_COMMAND': 273,
'BM_CLICK': 245,
'CB_GETCOUNT': 326,
'CB_SETCURSEL': 334,
'CBN_SELCHANGE': 1,
'COPY_DATA': 57634}
NODE = {'FRAME': (59648, 59649),
'FORM': (59648, 59649, 1047, 200, 1047),
'ACCOUNT': (59392, 0, 1711),
'COMBO': (59392, 0, 2322),
'BUY': (161, (1032, 1033, 1034), 1006),
'SELL':(162, (1032, 1033, 1034), 1006),
2017-06-29 14:19:02 +08:00
'撤单': 163,
'双向委托': 512,
'新股申购': 554,
'中签查询': 1070}
TWO_WAY = {'买入代码': 1032,
'买入价格': 1033,
'买入数量': 1034,
'买入': 1006,
'卖出代码': 1035,
'卖出价格': 1058,
'卖出数量': 1039,
'卖出': 1008,
'可用余额': 1038,
'刷新': 32790,
'全撤': 30001,
'撤买': 30002,
'撤卖': 30003,
'报表': 1047}
TAB = {'持仓': ord('W'),
'成交': ord('E'),
'委托': ord('R')}
SCHEDULE = {'证券代码': '',
'证券名称': '',
'实际数量': '',
'市值': ''}
CANCEL = {'全选': 1098,
'撤单': 1099,
'全撤': 30001,
'撤买': 30002,
'撤卖': 30003,
'填单': 3348,
'查单': 3349}
NEW = {'新股代码': 1032,
'新股名称': 1036,
'申购价格': 1033,
'可申购数量': 1018,
'申购数量': 1034,
'申购': 1006}
RAFFLE = ['新股代码', '证券代码', '申购价格', '申购上限']
#CMD = {'COPY': 57634}
2017-06-29 14:19:02 +08:00
VKCODE = {'F1': 112,
'F2': 113,
'F3': 114,
'F4': 115,
'F5': 116,
'F6': 117}
MKT = {'CYB': '3',
'SH': '7',
'SZ': '0',
'创业板': '3',
'沪市': '7',
'深市': '0'}
op = ctypes.windll.user32
def switch_combo(index, idCombo, hCombo):
op.SendMessageW(hCombo, MSG['CB_SETCURSEL'], index, 0)
op.SendMessageW(op.GetParent(hCombo), MSG['WM_COMMAND'], MSG['CBN_SELCHANGE']<<16|idCombo, hCombo)
class Puppet:
2017-06-29 14:19:02 +08:00
"""
界面自动化操控包装类
2017-06-29 14:19:02 +08:00
# 方法 # '委买': buy(), '委卖': sell(), '撤单': cancel(), '打新': raffle(),
# 属性 # '帐号': account, '可用余额': balance, '持仓': position, '成交': deals, '可撤委托': cancelable,
# # '新股': new, '中签': bingo,
"""
def __init__(self, main=None, title='网上股票交易系统5.0'):
2017-06-29 14:19:02 +08:00
print('我正在热身,稍等一下...')
self.main = main or op.FindWindowW(0, title)
self.switch = lambda node: op.SendMessageW(self.main, MSG['WM_COMMAND'], node, 0)
self._order = []
for i in (NODE['BUY'],NODE['SELL']):
node, parts, button = i
self.switch(node)
time.sleep(0.3)
x = reduce(op.GetDlgItem, NODE['FRAME'], self.main)
self._order.append((tuple(op.GetDlgItem(x, v) for v in parts), button, x))
2017-06-29 14:19:02 +08:00
op.SendMessageW(self.main, MSG['WM_COMMAND'], NODE['双向委托'], 0) # 切换到交易操作台
self.wait_a_second = lambda sec=0.2: time.sleep(sec)
self.wait_a_second() # 可调整区间值(0.01~0.5)
self.buff = ctypes.create_unicode_buffer(32)
self.two_way = reduce(op.GetDlgItem, NODE['FRAME'], self.main)
2017-06-29 14:19:02 +08:00
self.members = {k: op.GetDlgItem(self.two_way, v) for k, v in TWO_WAY.items()}
print('我准备好了,开干吧!人生巅峰在前面!') if self.main else print("没找到已登录的客户交易端,我先撤了!")
# 获取登录账号
self.account = reduce(op.GetDlgItem, NODE['ACCOUNT'], self.main)
2017-06-29 14:19:02 +08:00
op.SendMessageW(self.account, MSG['WM_GETTEXT'], 32, self.buff)
self.account = self.buff.value
self.combo = reduce(op.GetDlgItem, NODE['COMBO'], self.main)
2017-06-29 14:19:02 +08:00
self.count = op.SendMessageW(self.combo, MSG['CB_GETCOUNT'])
def switch_tab(self, hCtrl, keyCode, param=0): # 单击
op.PostMessageW(hCtrl, MSG['WM_KEYDOWN'], keyCode, param)
self.wait_a_second(0.5)
op.PostMessageW(hCtrl, MSG['WM_KEYUP'], keyCode, param)
def copy_data(self, key=0): # background mode
"将CVirtualGridCtrl|Custom<n>的数据复制到剪贴板,默认取当前的表格"
if key:
self.switch_tab(self.two_way, key) # 切换到持仓('W')、成交('E')、委托('R')
print("正在等待实时数据返回,请稍候...")
pyperclip.copy('')
# 查到只有列表头的空白数据等3秒...orz
for i in range(10):
time.sleep(0.3)
op.SendMessageW(reduce(op.GetDlgItem, NODE['FORM'], self.main),
MSG['WM_COMMAND'], MSG['COPY_DATA'], NODE['FORM'][-1])
if len(pyperclip.paste().splitlines()) > 1:
break
2017-06-29 14:19:02 +08:00
return pyperclip.paste()
def buy(self, symbol, price, qty):
self.switch(NODE['BUY'][0])
tuple(map(lambda hCtrl, arg: op.SendMessageW(
hCtrl, MSG['WM_SETTEXT'], 0, str(arg)), self._order[0][0], (symbol, price, qty)))
op.PostMessageW(self._order[0][-1], MSG['WM_COMMAND'], self._order[0][1], 0)
def sell(self, symbol, price, qty):
self.switch(NODE['SELL'][0])
tuple(map(lambda hCtrl, arg: op.SendMessageW(
hCtrl, MSG['WM_SETTEXT'], 0, str(arg)), self._order[1][0], (symbol, price, qty)))
op.PostMessageW(self._order[1][-1], MSG['WM_COMMAND'], self._order[1][1], 0)
def buy2(self, symbol, price, qty): # 买入(B)
self.switch(NODE['双向委托'])
2017-06-29 14:19:02 +08:00
op.SendMessageW(self.members['买入代码'], MSG['WM_SETTEXT'], 0, str(symbol))
op.SendMessageW(self.members['买入价格'], MSG['WM_SETTEXT'], 0, str(price))
op.SendMessageW(self.members['买入数量'], MSG['WM_SETTEXT'], 0, str(qty))
#op.SendMessageW(self.members['买入'], MSG['BM_CLICK'], 0, 0)
op.PostMessageW(self.two_way, MSG['WM_COMMAND'], TWO_WAY['买入'], self.members['买入'])
def sell2(self, symbol, price, qty): # 卖出(S)
self.switch(NODE['双向委托'])
2017-06-29 14:19:02 +08:00
op.SendMessageW(self.members['卖出代码'], MSG['WM_SETTEXT'], 0, str(symbol))
op.SendMessageW(self.members['卖出价格'], MSG['WM_SETTEXT'], 0, str(price))
op.SendMessageW(self.members['卖出数量'], MSG['WM_SETTEXT'], 0, str(qty))
#op.SendMessageW(self.members['卖出'], MSG['BM_CLICK'], 0, 0)
op.PostMessageW(self.two_way, MSG['WM_COMMAND'], TWO_WAY['卖出'], self.members['卖出'])
2017-06-29 14:19:02 +08:00
def refresh(self): # 刷新(F5)
op.PostMessageW(self.two_way, MSG['WM_COMMAND'], TWO_WAY['刷新'], self.members['刷新'])
def cancel(self, symbol=None, way='撤买'):
op.SendMessageW(self.main, MSG['WM_COMMAND'], NODE['撤单'], 0) # 切换到撤单操作台
if way and str(symbol).isdecimal():
#print(self.copy_data())
self.cancel_c = reduce(op.GetDlgItem, NODE['FRAME'], self.main)
2017-06-29 14:19:02 +08:00
self.cancel_ctrl = {k: op.GetDlgItem(self.cancel_c, v) for k, v in CANCEL.items()}
op.SendMessageW(self.cancel_ctrl['填单'], MSG['WM_SETTEXT'], 0, symbol)
self.wait_a_second()
op.PostMessageW(self.cancel_c, MSG['WM_COMMAND'], CANCEL['查单'], self.cancel_ctrl['查单'])
op.PostMessageW(self.cancel_c, MSG['WM_COMMAND'], CANCEL[way], self.cancel_ctrl[way])
schedule = self.copy_data()
op.SendMessageW(self.main, MSG['WM_COMMAND'], NODE['双向委托'], 0) # 必须返回交易操作台
return schedule
@property
def balance(self):
print('可用余额: %s' % ('$'*68))
op.SendMessageW(self.members['可用余额'], MSG['WM_GETTEXT'], 32, self.buff)
return self.buff.value
@property
def position(self):
print('实时持仓: %s' % ('$'*68))
return self.copy_data(TAB['持仓'])
@property
def market_value(self):
form = (x.split() for x in self.position.splitlines())
index = next(form).index('市值')
return sum((float(row[index]) for row in form))
@property
def deals(self):
print('当天成交: %s' % ('$'*68))
return self.copy_data(TAB['成交'])
@property
def cancelable(self):
print('可撤委托: %s' % ('$'*68))
return self.cancel(way=False)
@property
def new(self):
print('新股名单: %s' % ('$'*68))
return self.raffle(way=False)
@property
def bingo(self):
print('新股中签: {0}'.format('$'*68))
op.SendMessageW(self.main, MSG['WM_COMMAND'], NODE['中签查询'], 0)
return self.copy_data()
def cancel_all(self): # 全撤(Z)
op.PostMessageW(self.two_way, MSG['WM_COMMAND'], 30001, self.members[30001])
def cancel_buy(self): # 撤买(X)
op.PostMessageW(self.two_way, MSG['WM_COMMAND'], 30002, self.members[30002])
def cancel_sell(self): # 撤卖(C)
op.PostMessageW(self.two_way, MSG['WM_COMMAND'], 30003, self.members[30003])
def cancel_last(self): # 撤最后一笔,仅限华泰定制版有效
op.PostMessageW(self.two_way, MSG['WM_COMMAND'], 2053, self.members[2053])
def cancel_same(self): # 撤相同代码,仅限华泰定制版
#op.PostMessageW(self.two_way, WM_COMMAND, 30022, self.members[30022])
pass
def raffle(self, skip=None, way=True): # 打新股。
op.SendMessageW(self.main, MSG['WM_COMMAND'], NODE['新股申购'], 0)
#close_pop() # 弹窗无需关闭,不影响交易。
schedule = self.copy_data()
if way:
print("开始打新股%s" % ('>'*68))
print(schedule)
self.raffle_c = reduce(op.GetDlgItem, NODE['FRAME'], self.main)
2017-06-29 14:19:02 +08:00
self.raffle_ctrl = {k: op.GetDlgItem(self.raffle_c, v) for k, v in NEW.items()}
new = [x.split() for x in schedule.splitlines()]
index = [new[0].index(x) for x in RAFFLE if x in new[0]] # 索引映射代码0, 价格1, 数量2
new = map(lambda x: [x[y] for y in index], new[1:])
for symbol, price, qty in new:
if symbol[0] == skip:
print({symbol: (qty, "跳过<%s>开头的新股!" % skip)})
continue
if qty == '0':
print({symbol: (qty, "数量为零")})
continue
op.SendMessageW(self.raffle_ctrl['新股代码'], MSG['WM_SETTEXT'], 0, symbol)
self.wait_a_second(1)
#op.SendMessageW(self.raffle_ctrl['可申购数量'], MSG['WM_GETTEXT'], 32, self.buff)
#qty = self.buff.value
op.SendMessageW(self.raffle_ctrl['申购数量'], MSG['WM_SETTEXT'], 0, qty)
self.wait_a_second()
op.PostMessageW(self.raffle_c, MSG['WM_COMMAND'], NEW['申购'], self.raffle_ctrl['申购'])
print({symbol: (qty, "已申购")})
print(self.cancelable)
op.SendMessageW(self.main, MSG['WM_COMMAND'], NODE['双向委托'], 0) # 切换到交易操作台
return schedule
if __name__ == '__main__':
trader = Puppet()
#trader = Puppet(title='广发证券核新网上交易系统7.60')
2017-06-29 14:19:02 +08:00
if trader.account:
print(trader.account) # 帐号
#print(trader.new) # 查当天新股名单
#trader.raffle(MKT['创业板']) # 确定打新股,跳过创业板不打。
#print(trader.balance) # 可用余额
#print(trader.position) # 实时持仓
#print(trader.deals) # 当天成交
#print(trader.cancelable) # 可撤委托
print(trader.market_value)