fa45d8aa5f
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
Privoxy对node122:18003返回500,直连正常
329 lines
11 KiB
Python
329 lines
11 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding:utf-8 -*-
|
|
|
|
'''
|
|
Created on 2016年9月25日
|
|
@author: Jimmy Liu
|
|
@group : waditu
|
|
@contact: jimmysoa@sina.cn
|
|
'''
|
|
import six
|
|
import pandas as pd
|
|
import requests
|
|
import time
|
|
from threading import Thread
|
|
from tushare.trader import vars as vs
|
|
from tushare.trader import utils
|
|
from tushare.util import upass as up
|
|
from tushare.util.upass import set_broker
|
|
|
|
class TraderAPI(object):
|
|
"""
|
|
股票实盘交易接口
|
|
提醒:本文涉及的思路和内容仅限于量化投资及程序化交易的研究与尝试,不作为个人或机构常规程序化交易的依据,
|
|
不对实盘的交易风险和政策风险产生的影响负责,如有问题请与我联系。
|
|
投资有风险,下单须谨慎。
|
|
"""
|
|
def __init__(self, broker = ''):
|
|
if broker == '':
|
|
return None
|
|
self.broker = broker
|
|
self.trade_prefix = vs.CSC_PREFIX % (vs.P_TYPE['https'],
|
|
vs.DOMAINS['csc'],
|
|
vs.PAGES['csclogin'])
|
|
self.heart_active = True
|
|
self.s = requests.session()
|
|
if six.PY2:
|
|
self.heart_thread = Thread(target = self.send_heartbeat)
|
|
self.heart_thread.setDaemon(True)
|
|
else:
|
|
self.heart_thread = Thread(target = self.send_heartbeat,
|
|
daemon=True)
|
|
|
|
|
|
def login(self):
|
|
self.s.headers.update(vs.AGENT)
|
|
self.s.get(vs.CSC_PREFIX % (vs.P_TYPE['https'], vs.DOMAINS['csc'],
|
|
vs.PAGES['csclogin']))
|
|
res = self.s.get(vs.V_CODE_URL%(vs.P_TYPE['https'],
|
|
vs.DOMAINS['cscsh'],
|
|
vs.PAGES['vimg']))
|
|
if self._login(utils.get_vcode('csc', res)) is False:
|
|
print('请确认账号或密码是否正确 ,或券商服务器是否处于维护中。 ')
|
|
self.keepalive()
|
|
|
|
|
|
def _login(self, v_code):
|
|
brokerinfo = up.get_broker(self.broker)
|
|
user = brokerinfo['user'][0]
|
|
login_params = dict(
|
|
inputid = user,
|
|
j_username = user,
|
|
j_inputid = user,
|
|
AppendCode = v_code,
|
|
isCheckAppendCode = 'false',
|
|
logined = 'false',
|
|
f_tdx = '',
|
|
j_cpu = '',
|
|
j_password = brokerinfo['passwd'][0]
|
|
)
|
|
logined = self.s.post(vs.CSC_LOGIN_ACTION % (vs.P_TYPE['https'],
|
|
vs.DOMAINS['csc']),
|
|
params = login_params)
|
|
if logined.text.find(u'消息中心') != -1:
|
|
return True
|
|
return False
|
|
|
|
|
|
def keepalive(self):
|
|
if self.heart_thread.is_alive():
|
|
self.heart_active = True
|
|
else:
|
|
self.heart_thread.start()
|
|
|
|
|
|
def send_heartbeat(self):
|
|
while True:
|
|
if self.heart_active:
|
|
try:
|
|
response = self.heartbeat()
|
|
self.check_account_live(response)
|
|
except:
|
|
self.login()
|
|
time.sleep(100)
|
|
else:
|
|
time.sleep(10)
|
|
|
|
|
|
def heartbeat(self):
|
|
return self.baseinfo
|
|
|
|
|
|
def exit(self):
|
|
self.heart_active = False
|
|
|
|
|
|
def buy(self, stkcode, price=0, count=0, amount=0):
|
|
"""
|
|
买入证券
|
|
params
|
|
---------
|
|
stkcode:股票代码,string
|
|
pricce:委托价格,int
|
|
count:买入数量
|
|
amount:买入金额
|
|
"""
|
|
jsonobj = utils.get_jdata(self._trading(stkcode, price,
|
|
count, amount, 'B', 'buy'))
|
|
res = True if jsonobj['result'] == 'true' else False
|
|
return res
|
|
|
|
|
|
def sell(self, stkcode, price=0, count=0, amount=0):
|
|
"""
|
|
卖出证券
|
|
params
|
|
---------
|
|
stkcode:股票代码,string
|
|
pricce:委托价格,int
|
|
count:卖出数量
|
|
amount:卖出金额
|
|
"""
|
|
jsonobj = utils.get_jdata(self._trading(stkcode, price, count,
|
|
amount, 'S', 'sell'))
|
|
res = True if jsonobj['result'] == 'true' else False
|
|
return res
|
|
|
|
|
|
def _trading(self, stkcode, price, count, amount, tradeflag, tradetype):
|
|
txtdata = self.s.get(vs.TRADE_CHECK_URL % (vs.P_TYPE['https'],
|
|
vs.DOMAINS['csc'],
|
|
vs.PAGES['tradecheck'],
|
|
tradeflag, stkcode,
|
|
tradetype, utils.nowtime_str()))
|
|
jsonobj = utils.get_jdata(txtdata)
|
|
list = jsonobj['returnList'][0]
|
|
secuid = list['buysSecuid']
|
|
fundavl = list['fundavl']
|
|
stkname = list['stkname']
|
|
if secuid is not None:
|
|
if tradeflag == 'B':
|
|
buytype = vs.BUY
|
|
count = count if count else amount // price // 100 * 100
|
|
else:
|
|
buytype = vs.SELL
|
|
count = count if count else amount // price
|
|
|
|
tradeparams = dict(
|
|
stkname = stkname,
|
|
stkcode = stkcode,
|
|
secuid = secuid,
|
|
buytype = buytype,
|
|
bsflag = tradeflag,
|
|
maxstkqty = '',
|
|
buycount = count,
|
|
buyprice = price,
|
|
_ = utils.nowtime_str()
|
|
)
|
|
tradeResult = self.s.post(vs.TRADE_URL % (vs.P_TYPE['https'],
|
|
vs.DOMAINS['csc'],
|
|
vs.PAGES['trade']),
|
|
params = tradeparams)
|
|
return tradeResult
|
|
return None
|
|
|
|
|
|
def position(self):
|
|
"""
|
|
获取持仓列表
|
|
return:DataFrame
|
|
----------------------
|
|
stkcode:证券代码
|
|
stkname:证券名称
|
|
stkqty :证券数量
|
|
stkavl :可用数量
|
|
lastprice:最新价格
|
|
costprice:成本价
|
|
income :参考盈亏(元)
|
|
"""
|
|
return self._get_position()
|
|
|
|
|
|
def _get_position(self):
|
|
self.s.headers.update(vs.AGENT)
|
|
txtdata = self.s.get(vs.BASE_URL % (vs.P_TYPE['https'],
|
|
vs.DOMAINS['csc'],
|
|
vs.PAGES['position']))
|
|
jsonobj = utils.get_jdata(txtdata)
|
|
df = pd.DataFrame(jsonobj['data'], columns=vs.POSITION_COLS)
|
|
return df
|
|
|
|
|
|
def entrust_list(self):
|
|
"""
|
|
获取委托单列表
|
|
return:DataFrame
|
|
----------
|
|
ordersno:委托单号
|
|
stkcode:证券代码
|
|
stkname:证券名称
|
|
bsflagState:买卖标志
|
|
orderqty:委托数量
|
|
matchqty:成交数量
|
|
orderprice:委托价格
|
|
operdate:交易日期
|
|
opertime:交易时间
|
|
orderdate:下单日期
|
|
state:状态
|
|
"""
|
|
txtdata = self.s.get(vs.ENTRUST_LIST_URL % (vs.P_TYPE['https'],
|
|
vs.DOMAINS['csc'],
|
|
vs.PAGES['entrustlist'],
|
|
utils.nowtime_str()))
|
|
jsonobj = utils.get_jdata(txtdata)
|
|
df = pd.DataFrame(jsonobj['data'], columns=vs.ENTRUST_LIST_COLS)
|
|
return df
|
|
|
|
|
|
def deal_list(self, begin=None, end=None):
|
|
"""
|
|
获取成交列表
|
|
params
|
|
-----------
|
|
begin:开始日期 YYYYMMDD
|
|
end:结束日期 YYYYMMDD
|
|
|
|
return: DataFrame
|
|
-----------
|
|
ordersno:委托单号
|
|
matchcode:成交编号
|
|
trddate:交易日期
|
|
matchtime:交易时间
|
|
stkcode:证券代码
|
|
stkname:证券名称
|
|
bsflagState:买卖标志
|
|
orderprice:委托价格
|
|
matchprice:成交价格
|
|
orderqty:委托数量
|
|
matchqty:成交数量
|
|
matchamt:成交金额
|
|
"""
|
|
daterange = ''
|
|
if (begin is None) & (end is None):
|
|
selecttype = 'intraDay'
|
|
else:
|
|
daterange = vs.DEAL_DATE_RANGE % (begin, end)
|
|
selecttype = 'all'
|
|
txtdata = self.s.get(vs.DEAL_LIST_URL % (vs.P_TYPE['https'],
|
|
vs.DOMAINS['csc'],
|
|
vs.PAGES['deallist'],
|
|
selecttype, daterange,
|
|
utils.nowtime_str()))
|
|
jsonobj = utils.get_jdata(txtdata)
|
|
df = pd.DataFrame(jsonobj['data'], columns=vs.DEAL_LIST_COLS)
|
|
return df
|
|
|
|
|
|
def cancel(self, ordersno='', orderdate=''):
|
|
"""
|
|
撤单
|
|
params
|
|
-----------
|
|
ordersno:委托单号,多个以逗号分隔,e.g. 1866,1867
|
|
orderdata:委托日期 YYYYMMDD,多个以逗号分隔,对应委托单好
|
|
return
|
|
------------
|
|
string
|
|
"""
|
|
if (ordersno != '') & (orderdate != ''):
|
|
params = dict(
|
|
ordersno = ordersno,
|
|
orderdate = orderdate,
|
|
_ = utils.nowtime_str()
|
|
|
|
)
|
|
result = self.s.post(vs.CANCEL_URL % (vs.P_TYPE['https'], vs.DOMAINS['csc'], vs.PAGES['cancel']),
|
|
params = params)
|
|
jsonobj = utils.get_jdata(result.text)
|
|
return jsonobj['msgMap']['ResultSucess']
|
|
return None
|
|
|
|
|
|
def baseinfo(self):
|
|
"""
|
|
获取帐户基本信息
|
|
return: Series
|
|
-------------
|
|
fundid:帐户ID
|
|
gpsz: 股票市值
|
|
fundvalue:基金市值
|
|
jihelicai:集合理财
|
|
fundbal:帐户余额
|
|
marketvalue:总资产
|
|
fundavl:可用余额
|
|
daixiao:代销份额
|
|
otc:OTC份额
|
|
"""
|
|
return self._get_baseinfo()
|
|
|
|
def _get_baseinfo(self):
|
|
self.s.headers.update(vs.AGENT)
|
|
txtdata = self.s.get(vs.BASE_URL % (vs.P_TYPE['https'], vs.DOMAINS['csc'], vs.PAGES['baseInfo']))
|
|
jsonobj = utils.get_jdata(txtdata)
|
|
stkdata = jsonobj['data']['moneytype0']
|
|
stkdata['fundid'] = jsonobj['fundid']
|
|
return pd.Series(stkdata)
|
|
|
|
|
|
def check_login_status(self, return_data):
|
|
if hasattr(return_data, 'get') and return_data.get('error_no') == '-1':
|
|
raise NotLoginError
|
|
|
|
|
|
class NotLoginError(Exception):
|
|
def __init__(self, result=None):
|
|
super(NotLoginError, self).__init__()
|
|
self.result = result
|
|
def heartbeat(self):
|
|
return self.baseinfo
|
|
|