import hashlib
|
import json
|
import logging
|
import socket
|
import socketserver
|
import sys
|
import time
|
|
from huaxin import traderapi, log
|
from huaxin.log import logger
|
|
UserID = '00043201'
|
# 登陆密码
|
Password = '45249973'
|
# 投资者账户
|
InvestorID = '11160150'
|
# 经济公司部门代码
|
DepartmentID = '0003'
|
# 资金账户
|
AccountID = '00043201'
|
# 沪市股东账号
|
SSE_ShareHolderID = 'A00043201'
|
# 深市股东账号
|
SZSE_ShareHolderID = '700043201'
|
|
# # 登录用户
|
# UserID = '00572083'
|
# # 登陆密码
|
# Password = '16121950'
|
# # 投资者账户
|
# InvestorID = '11160150'
|
# # 经济公司部门代码
|
# DepartmentID = '0057'
|
# # 资金账户
|
# AccountID = '00572083'
|
# # 沪市股东账号
|
# SSE_ShareHolderID = 'A00572083'
|
# # 深市股东账号
|
# SZSE_ShareHolderID = '700572083'
|
|
|
TYPE_BUY = 0
|
TYPE_CANCEL_BUY = 1
|
TYPE_LIST_DELEGATE = 2
|
TYPE_LIST_TRADED = 3
|
TYPE_LIST_POSITION = 4
|
|
|
class TradeSimpleApi:
|
req_id = 0
|
__buy_sinfo_set = set()
|
__cancel_buy_sinfo_set = set()
|
|
# sinfo char(32)
|
def buy(self, code, count, price, sinfo):
|
if sinfo in self.__buy_sinfo_set:
|
raise Exception(f'下单请求已经提交:{sinfo}')
|
self.__buy_sinfo_set.add(sinfo)
|
self.req_id += 1
|
# 请求报单
|
req_field = traderapi.CTORATstpInputOrderField()
|
# TORA_TSTP_EXD_COMM(0): 通用(内部使用)
|
# TORA_TSTP_EXD_SSE(1): 上海交易所
|
# TORA_TSTP_EXD_SZSE(2): 深圳交易所
|
# TORA_TSTP_EXD_HK(3): 香港交易所
|
# TORA_TSTP_EXD_BSE(4): 北京证券交易所
|
if code.find('00') == 0:
|
req_field.ExchangeID = traderapi.TORA_TSTP_EXD_SZSE
|
req_field.ShareholderID = SZSE_ShareHolderID
|
elif code.find('60') == 0:
|
req_field.ExchangeID = traderapi.TORA_TSTP_EXD_SSE
|
req_field.ShareholderID = SSE_ShareHolderID
|
|
# 证券代码
|
req_field.SecurityID = code
|
req_field.Direction = traderapi.TORA_TSTP_D_Buy
|
req_field.VolumeTotalOriginal = count
|
req_field.SInfo = sinfo
|
|
'''
|
上交所支持限价指令和最优五档剩撤、最优五档剩转限两种市价指令,对于科创板额外支持本方最优和对手方最优两种市价指令和盘后固定价格申报指令
|
深交所支持限价指令和立即成交剩余撤销、全额成交或撤销、本方最优、对手方最优和最优五档剩撤五种市价指令
|
限价指令和上交所科创板盘后固定价格申报指令需填写报单价格,其它市价指令无需填写报单价格
|
以下以上交所限价指令为例,其它指令参考开发指南相关说明填写OrderPriceType、TimeCondition和VolumeCondition三个字段:
|
'''
|
req_field.LimitPrice = price
|
req_field.OrderPriceType = traderapi.TORA_TSTP_OPT_LimitPrice
|
req_field.TimeCondition = traderapi.TORA_TSTP_TC_GFD
|
req_field.VolumeCondition = traderapi.TORA_TSTP_VC_AV
|
|
'''
|
OrderRef为报单引用,类型为整型,该字段报单时为选填
|
若不填写,则系统会为每笔报单自动分配一个报单引用
|
若填写,则需保证同一个TCP会话下报单引用严格单调递增,不要求连续递增,至少需从1开始编号
|
'''
|
# req_field.OrderRef = 1
|
|
'''
|
InvestorID为选填,若填写则需保证填写正确
|
Operway为委托方式,根据券商要求填写,无特殊说明置空即可
|
终端自定义字段,终端可根据需要填写如下字段的值,该字段值不会被柜台系统修改,在报单回报和查询报单时返回给终端
|
'''
|
# req_field.SInfo = 'sinfo'
|
# req_field.IInfo = 123
|
|
'''
|
其它字段置空
|
'''
|
ret = api.ReqOrderInsert(req_field, self.req_id)
|
if ret != 0:
|
raise Exception('ReqOrderInsert fail, ret[%d]' % ret)
|
return
|
|
def cancel_buy(self, code, order_sys_id, sinfo):
|
if sinfo in self.__cancel_buy_sinfo_set:
|
raise Exception(f'撤单请求已经提交:{sinfo}')
|
self.__cancel_buy_sinfo_set.add(sinfo)
|
self.req_id += 1
|
# 请求撤单
|
req_field = traderapi.CTORATstpInputOrderActionField()
|
if code.find('00') == 0:
|
req_field.ExchangeID = traderapi.TORA_TSTP_EXD_SZSE
|
elif code.find('60') == 0:
|
req_field.ExchangeID = traderapi.TORA_TSTP_EXD_SSE
|
|
req_field.ActionFlag = traderapi.TORA_TSTP_AF_Delete
|
|
# 撤单支持以下两种方式定位原始报单:
|
# (1)报单引用方式
|
# req_field.FrontID = self.__front_id
|
# req_field.SessionID = self.__session_id
|
# req_field.OrderRef = 1
|
# (2)系统报单编号方式
|
req_field.OrderSysID = order_sys_id
|
|
# OrderActionRef报单操作引用,用法同报单引用,可根据需要选填
|
|
'''
|
终端自定义字段,终端可根据需要填写如下字段的值,该字段值不会被柜台系统修改,在查询撤单时返回给终端
|
'''
|
req_field.SInfo = sinfo
|
# req_field.IInfo = 123
|
|
'''
|
委托方式字段根据券商要求填写,无特殊说明置空即可
|
其它字段置空
|
'''
|
ret = api.ReqOrderAction(req_field, self.req_id)
|
if ret != 0:
|
raise Exception('ReqOrderAction fail, ret[%d]' % ret)
|
return
|
|
# 查询当日可撤销的委托
|
def list_delegate_orders(self):
|
self.req_id += 1
|
req_id = self.req_id
|
req_field = traderapi.CTORATstpQryOrderField()
|
# 以下字段不填表示不设过滤条件,即查询所有报单
|
# req_field.SecurityID = '600000'
|
# req_field.InsertTimeStart = '09:35:00'
|
# req_field.InsertTimeEnd = '10:00:00'
|
# IsCancel字段填1表示只查询可撤报单
|
req_field.IsCancel = 1
|
# req_field.SInfo = sinfo
|
ret = api.ReqQryOrder(req_field, req_id)
|
if ret != 0:
|
raise Exception('ReqQryOrder fail, ret[%d]' % ret)
|
return req_id
|
|
# 查询当日成交的订单
|
def list_traded_orders(self):
|
self.req_id += 1
|
req_id = self.req_id
|
req_field = traderapi.CTORATstpQryTradeField()
|
ret = api.ReqQryTrade(req_field, req_id)
|
if ret != 0:
|
raise Exception('ReqQryTrade fail, ret[%d]' % ret)
|
return req_id
|
|
# 查询持仓
|
def list_positions(self):
|
self.req_id += 1
|
req_id = self.req_id
|
req_field = traderapi.CTORATstpQryPositionField()
|
ret = api.ReqQryPosition(req_field, req_id)
|
if ret != 0:
|
print('ReqQryPosition fail, ret[%d]' % ret)
|
return req_id
|
|
def login(self):
|
# 请求登录
|
login_req = traderapi.CTORATstpReqUserLoginField()
|
|
# 支持以用户代码、资金账号和股东账号方式登录
|
# (1)以用户代码方式登录
|
login_req.LogInAccount = UserID
|
login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_UserID
|
# (2)以资金账号方式登录
|
# login_req.DepartmentID = DepartmentID
|
# login_req.LogInAccount = AccountID
|
# login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_AccountID
|
# (3)以上海股东账号方式登录
|
# login_req.LogInAccount = SSE_ShareHolderID
|
# login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_SHAStock
|
# (4)以深圳股东账号方式登录
|
# login_req.LogInAccount = SZSE_ShareHolderID
|
# login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_SZAStock
|
|
# 支持以密码和指纹(移动设备)方式认证
|
# (1)密码认证
|
# 密码认证时AuthMode可不填
|
# login_req.AuthMode = traderapi.TORA_TSTP_AM_Password
|
login_req.Password = Password
|
# (2)指纹认证
|
# 非密码认证时AuthMode必填
|
# login_req.AuthMode = traderapi.TORA_TSTP_AM_FingerPrint
|
# login_req.DeviceID = '03873902'
|
# login_req.CertSerial = '9FAC09383D3920CAEFF039'
|
|
# 终端信息采集
|
# UserProductInfo填写终端名称
|
login_req.UserProductInfo = 'jiabei'
|
# 按照监管要求填写终端信息
|
login_req.TerminalInfo = 'PC;IIP=123.112.154.118;IPORT=50361;LIP=192.168.118.107;MAC=54EE750B1713FCF8AE5CBD58;HD=TF655AY91GHRVL'
|
# 以下内外网IP地址若不填则柜台系统自动采集,若填写则以终端填值为准报送
|
# login_req.MacAddress = '5C-87-9C-96-F3-E3'
|
# login_req.InnerIPAddress = '10.0.1.102'
|
# login_req.OuterIPAddress = '58.246.43.50'
|
|
TradeSimpleApi.req_id += 1
|
ret = api.ReqUserLogin(login_req, TradeSimpleApi.req_id)
|
if ret != 0:
|
raise Exception('ReqUserLogin fail, ret[%d]' % ret)
|
|
|
class TraderSpi(traderapi.CTORATstpTraderSpi):
|
def __init__(self, callback):
|
traderapi.CTORATstpTraderSpi.__init__(self)
|
self.__front_id = 0
|
self.__session_id = 0
|
self.__data_callback = callback
|
self.__temp_order_list_dict = {}
|
self.__temp_position_list_dict = {}
|
|
def OnFrontConnected(self) -> "void":
|
logger.info('OnFrontConnected')
|
|
# 获取终端信息
|
TradeSimpleApi.req_id += 1
|
|
ret = api.ReqGetConnectionInfo(TradeSimpleApi.req_id)
|
if ret != 0:
|
logger.info('ReqGetConnectionInfo fail, ret[%d]' % ret)
|
|
def OnFrontDisconnected(self, nReason: "int") -> "void":
|
logger.info('OnFrontDisconnected: [%d]' % nReason)
|
|
def OnRspGetConnectionInfo(self, pConnectionInfoField: "CTORATstpConnectionInfoField",
|
pRspInfoField: "CTORATstpRspInfoField", nRequestID: "int") -> "void":
|
if pRspInfoField.ErrorID == 0:
|
logger.info('inner_ip_address[%s]' % pConnectionInfoField.InnerIPAddress)
|
logger.info('inner_port[%d]' % pConnectionInfoField.InnerPort)
|
logger.info('outer_ip_address[%s]' % pConnectionInfoField.OuterIPAddress)
|
logger.info('outer_port[%d]' % pConnectionInfoField.OuterPort)
|
logger.info('mac_address[%s]' % pConnectionInfoField.MacAddress)
|
|
# 请求登录
|
login_req = traderapi.CTORATstpReqUserLoginField()
|
|
# 支持以用户代码、资金账号和股东账号方式登录
|
# (1)以用户代码方式登录
|
login_req.LogInAccount = UserID
|
login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_UserID
|
# (2)以资金账号方式登录
|
# login_req.DepartmentID = DepartmentID
|
# login_req.LogInAccount = AccountID
|
# login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_AccountID
|
# (3)以上海股东账号方式登录
|
# login_req.LogInAccount = SSE_ShareHolderID
|
# login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_SHAStock
|
# (4)以深圳股东账号方式登录
|
# login_req.LogInAccount = SZSE_ShareHolderID
|
# login_req.LogInAccountType = traderapi.TORA_TSTP_LACT_SZAStock
|
|
# 支持以密码和指纹(移动设备)方式认证
|
# (1)密码认证
|
# 密码认证时AuthMode可不填
|
# login_req.AuthMode = traderapi.TORA_TSTP_AM_Password
|
login_req.Password = Password
|
# (2)指纹认证
|
# 非密码认证时AuthMode必填
|
# login_req.AuthMode = traderapi.TORA_TSTP_AM_FingerPrint
|
# login_req.DeviceID = '03873902'
|
# login_req.CertSerial = '9FAC09383D3920CAEFF039'
|
|
# 终端信息采集
|
# UserProductInfo填写终端名称
|
login_req.UserProductInfo = 'jiabei'
|
# 按照监管要求填写终端信息
|
login_req.TerminalInfo = 'PC;IIP=123.112.154.118;IPORT=50361;LIP=192.168.118.107;MAC=54EE750B1713FCF8AE5CBD58;HD=TF655AY91GHRVL'
|
# 以下内外网IP地址若不填则柜台系统自动采集,若填写则以终端填值为准报送
|
# login_req.MacAddress = '5C-87-9C-96-F3-E3'
|
# login_req.InnerIPAddress = '10.0.1.102'
|
# login_req.OuterIPAddress = '58.246.43.50'
|
|
TradeSimpleApi.req_id += 1
|
ret = api.ReqUserLogin(login_req, TradeSimpleApi.req_id)
|
if ret != 0:
|
logger.info('ReqUserLogin fail, ret[%d]' % ret)
|
else:
|
logger.info('GetConnectionInfo fail, [%d] [%d] [%s]!!!' % (
|
nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspUserLogin(self, pRspUserLoginField: "CTORATstpRspUserLoginField", pRspInfoField: "CTORATstpRspInfoField",
|
nRequestID: "int") -> "void":
|
if pRspInfoField.ErrorID == 0:
|
logger.info('Login success! [%d]' % nRequestID)
|
self.__front_id = pRspUserLoginField.FrontID
|
self.__session_id = pRspUserLoginField.SessionID
|
|
if 1:
|
# 查询股东账号
|
req_field = traderapi.CTORATstpQryShareholderAccountField()
|
|
# 以下字段不填表示不设过滤条件,即查询所有股东账号
|
# req_field.ExchangeID = traderapi.TORA_TSTP_EXD_SSE
|
|
TradeSimpleApi.req_id += 1
|
ret = api.ReqQryShareholderAccount(req_field, TradeSimpleApi.req_id)
|
if ret != 0:
|
logger.info('ReqQryShareholderAccount fail, ret[%d]' % ret)
|
|
else:
|
logger.info('Login fail!!! [%d] [%d] [%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspUserPasswordUpdate(self, pUserPasswordUpdateField: "CTORATstpUserPasswordUpdateField",
|
pRspInfoField: "CTORATstpRspInfoField", nRequestID: "int") -> "void":
|
if pRspInfoField.ErrorID == 0:
|
logger.info('OnRspUserPasswordUpdate: OK! [%d]' % nRequestID)
|
else:
|
logger.info('OnRspUserPasswordUpdate: Error! [%d] [%d] [%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspOrderInsert(self, pInputOrderField: "CTORATstpInputOrderField", pRspInfoField: "CTORATstpRspInfoField",
|
nRequestID: "int") -> "void":
|
if pRspInfoField.ErrorID == 0:
|
logger.info('[%d] OnRspOrderInsert: OK! [%d]' % (round(time.time() * 1000), nRequestID))
|
else:
|
logger.info('OnRspOrderInsert: Error! [%d] [%d] [%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
self.__data_callback(TYPE_BUY, nRequestID, {"sinfo": pInputOrderField.SInfo, "orderStatus": -1,
|
"orderStatusMsg": pRspInfoField.ErrorMsg})
|
|
def OnRspOrderAction(self, pInputOrderActionField: "CTORATstpInputOrderActionField",
|
pRspInfoField: "CTORATstpRspInfoField", nRequestID: "int") -> "void":
|
if pRspInfoField.ErrorID == 0:
|
logger.info('OnRspOrderAction: OK! [%d]' % nRequestID)
|
self.__data_callback(TYPE_CANCEL_BUY, nRequestID, {"sinfo": pInputOrderActionField.SInfo,
|
"orderSysID": pInputOrderActionField.OrderSysID,
|
"cancel": 1})
|
|
else:
|
logger.info('OnRspOrderAction: Error! [%d] [%d] [%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
self.__data_callback(TYPE_CANCEL_BUY, nRequestID, {"sinfo": pInputOrderActionField.SInfo,
|
"orderSysID": pInputOrderActionField.OrderSysID,
|
"cancel": 0, "errorID": pRspInfoField.ErrorID,
|
"errorMsg": pRspInfoField.ErrorMsg})
|
|
def OnRspInquiryJZFund(self, pRspInquiryJZFundField: "CTORATstpRspInquiryJZFundField",
|
pRspInfoField: "CTORATstpRspInfoField", nRequestID: "int") -> "void":
|
if pRspInfoField.ErrorID == 0:
|
logger.info('OnRspInquiryJZFund: OK! [%d] [%.2f] [%.2f]'
|
% (nRequestID, pRspInquiryJZFundField.UsefulMoney, pRspInquiryJZFundField.FetchLimit))
|
else:
|
logger.info('OnRspInquiryJZFund: Error! [%d] [%d] [%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspTransferFund(self, pInputTransferFundField: "CTORATstpInputTransferFundField",
|
pRspInfoField: "CTORATstpRspInfoField", nRequestID: "int") -> "void":
|
if pRspInfoField.ErrorID == 0:
|
logger.info('OnRspTransferFund: OK! [%d]' % nRequestID)
|
else:
|
logger.info('OnRspTransferFund: Error! [%d] [%d] [%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRtnOrder(self, pOrderField: "CTORATstpOrderField") -> "void":
|
logger.info(
|
'[%d] OnRtnOrder: SInfo[%s] InvestorID[%s] SecurityID[%s] OrderRef[%d] OrderLocalID[%s] LimitPrice[%.2f] VolumeTotalOriginal[%d] OrderSysID[%s] OrderStatus[%s]'
|
% (round(time.time() * 1000), pOrderField.SInfo, pOrderField.InvestorID, pOrderField.SecurityID,
|
pOrderField.OrderRef, pOrderField.OrderLocalID,
|
pOrderField.LimitPrice, pOrderField.VolumeTotalOriginal, pOrderField.OrderSysID,
|
pOrderField.OrderStatus))
|
if pOrderField.OrderStatus != traderapi.TORA_TSTP_OST_Unknown:
|
self.__data_callback(TYPE_BUY, 0, {"sinfo": pOrderField.SInfo, "securityId": pOrderField.SecurityID,
|
"orderLocalId": pOrderField.OrderLocalID,
|
"orderStatus": pOrderField.OrderStatus,
|
"statusMsg": pOrderField.StatusMsg,
|
"orderSysID": pOrderField.OrderSysID,
|
"accountId": pOrderField.AccountID})
|
|
def OnRtnTrade(self, pTradeField: "CTORATstpTradeField") -> "void":
|
logger.info(
|
'OnRtnTrade: TradeID[%s] InvestorID[%s] SecurityID[%s] OrderRef[%d] OrderLocalID[%s] Price[%.2f] Volume[%d]'
|
% (pTradeField.TradeID, pTradeField.InvestorID, pTradeField.SecurityID,
|
pTradeField.OrderRef, pTradeField.OrderLocalID, pTradeField.Price, pTradeField.Volume))
|
|
def OnRtnMarketStatus(self, pMarketStatusField: "CTORATstpMarketStatusField") -> "void":
|
# TORA_TSTP_MKD_SHA(1): 上海A股
|
# TORA_TSTP_MKD_SZA(2): 深圳A股
|
# TORA_TSTP_MKD_BJMain(a):北京主板
|
|
# TORA_TSTP_MST_UnKnown( # ):未知
|
# TORA_TSTP_MST_BeforeTrading(0): 开盘前
|
# TORA_TSTP_MST_Continous(1): 连续交易
|
# TORA_TSTP_MST_Closed(2): 收盘
|
# TORA_TSTP_MST_OpenCallAuction(3): 开盘集合竞价
|
logger.info('OnRtnMarketStatus: MarketID[%s] MarketStatus[%s]'
|
% (pMarketStatusField.MarketID, pMarketStatusField.MarketStatus))
|
|
def OnRspQrySecurity(self, pSecurityField: "CTORATstpSecurityField", pRspInfoField: "CTORATstpRspInfoField",
|
nRequestID: "int", bIsLast: "bool") -> "void":
|
if bIsLast != 1:
|
logger.info(
|
'OnRspQrySecurity[%d]: SecurityID[%s] SecurityName[%s] MarketID[%s] OrderUnit[%s] OpenDate[%s] UpperLimitPrice[%.2f] LowerLimitPrice[%.2f]'
|
% (nRequestID, pSecurityField.SecurityID, pSecurityField.SecurityName, pSecurityField.MarketID,
|
pSecurityField.OrderUnit, pSecurityField.OpenDate, pSecurityField.UpperLimitPrice,
|
pSecurityField.LowerLimitPrice))
|
else:
|
logger.info('查询合约结束[%d] ErrorID[%d] ErrorMsg[%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspQryInvestor(self, pInvestorField: "CTORATstpInvestorField", pRspInfoField: "CTORATstpRspInfoField",
|
nRequestID: "int", bIsLast: "bool") -> "void":
|
if bIsLast != 1:
|
logger.info('OnRspQryInvestor[%d]: InvestorID[%s] InvestorName[%s] Operways[%s]'
|
% (nRequestID, pInvestorField.InvestorID, pInvestorField.InvestorName,
|
pInvestorField.Operways))
|
else:
|
logger.info('查询投资者结束[%d] ErrorID[%d] ErrorMsg[%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspQryShareholderAccount(self, pShareholderAccountField: "CTORATstpShareholderAccountField",
|
pRspInfoField: "CTORATstpRspInfoField", nRequestID: "int",
|
bIsLast: "bool") -> "void":
|
if bIsLast != 1:
|
logger.info('OnRspQryShareholderAccount[%d]: InvestorID[%s] ExchangeID[%s] ShareholderID[%s]'
|
% (nRequestID, pShareholderAccountField.InvestorID, pShareholderAccountField.ExchangeID,
|
pShareholderAccountField.ShareholderID))
|
else:
|
logger.info('查询股东账户结束[%d] ErrorID[%d] ErrorMsg[%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspQryTradingAccount(self, pTradingAccountField: "CTORATstpTradingAccountField",
|
pRspInfoField: "CTORATstpRspInfoField", nRequestID: "int", bIsLast: "bool") -> "void":
|
if bIsLast != 1:
|
logger.info(
|
'OnRspQryTradingAccount[%d]: DepartmentID[%s] InvestorID[%s] AccountID[%s] CurrencyID[%s] UsefulMoney[%.2f] FetchLimit[%.2f]'
|
% (nRequestID, pTradingAccountField.DepartmentID, pTradingAccountField.InvestorID,
|
pTradingAccountField.AccountID, pTradingAccountField.CurrencyID,
|
pTradingAccountField.UsefulMoney, pTradingAccountField.FetchLimit))
|
else:
|
logger.info('查询资金账号结束[%d] ErrorID[%d] ErrorMsg[%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
|
def OnRspQryOrder(self, pOrderField: "CTORATstpOrderField", pRspInfoField: "CTORATstpRspInfoField",
|
nRequestID: "int", bIsLast: "bool") -> "void":
|
print('查询报单回调', bIsLast)
|
if nRequestID not in self.__temp_order_list_dict:
|
self.__temp_order_list_dict[nRequestID] = []
|
if not bIsLast:
|
logger.info(
|
'OnRspQryOrder[%d]: SecurityID[%s] OrderLocalID[%s] Direction[%s] OrderRef[%d] OrderSysID[%s] VolumeTraded[%d] OrderStatus[%s] OrderSubmitStatus[%s], StatusMsg[%s]'
|
% (nRequestID, pOrderField.SecurityID, pOrderField.OrderLocalID, pOrderField.Direction,
|
pOrderField.OrderRef, pOrderField.OrderSysID,
|
pOrderField.VolumeTraded, pOrderField.OrderStatus, pOrderField.OrderSubmitStatus,
|
pOrderField.StatusMsg))
|
self.__temp_order_list_dict[nRequestID].append(
|
{"securityID": pOrderField.SecurityID, "orderLocalID": pOrderField.OrderLocalID,
|
"direction": pOrderField.Direction, "orderSysID": pOrderField.OrderSysID,
|
"insertTime": pOrderField.InsertTime, "insertDate": pOrderField.InsertDate,
|
"acceptTime": pOrderField.AcceptTime, "cancelTime": pOrderField.CancelTime,
|
"limitPrice": pOrderField.LimitPrice,
|
"turnover": pOrderField.Turnover,
|
"volume": pOrderField.VolumeTotalOriginal,
|
"volumeTraded": pOrderField.VolumeTraded, "orderStatus": pOrderField.OrderStatus,
|
"orderSubmitStatus": pOrderField.OrderSubmitStatus, "statusMsg": pOrderField.StatusMsg})
|
|
else:
|
logger.info('查询报单结束[%d] ErrorID[%d] ErrorMsg[%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
self.__data_callback(TYPE_LIST_DELEGATE, nRequestID, self.__temp_order_list_dict[nRequestID])
|
self.__temp_order_list_dict.pop(nRequestID)
|
|
def OnRspQryPosition(self, pPositionField: "CTORATstpPositionField", pRspInfoField: "CTORATstpRspInfoField",
|
nRequestID: "int", bIsLast: "bool") -> "void":
|
if nRequestID not in self.__temp_position_list_dict:
|
self.__temp_position_list_dict[nRequestID] = []
|
if bIsLast != 1:
|
self.__temp_position_list_dict[nRequestID].append(
|
{"securityID": pPositionField.SecurityID, "totalPosCost": pPositionField.TotalPosCost,
|
"availablePosition": pPositionField.AvailablePosition})
|
else:
|
logger.info('查询持仓结束[%d] ErrorID[%d] ErrorMsg[%s]'
|
% (nRequestID, pRspInfoField.ErrorID, pRspInfoField.ErrorMsg))
|
self.__data_callback(TYPE_LIST_POSITION, nRequestID, self.__temp_position_list_dict[nRequestID])
|
self.__temp_position_list_dict.pop(nRequestID)
|
|
# 成交回报,参数pTradeField是一个CTORATstpTradeField类对象
|
def OnRtnTrade(self, pTradeField: "CTORATstpTradeField") -> "void":
|
logger.info("OnRtnTrade")
|
|
# 查询成交响应,参数pTradeField是一个CTORATstpTradeField类对象
|
def OnRspQryTrade(self, pTradeField: "CTORATstpTradeField", pRspInfoField: "CTORATstpRspInfoField",
|
nRequestID: "int", bIsLast: "bool") -> "void":
|
logger.info("查询成交响应")
|
if nRequestID not in self.__temp_order_list_dict:
|
self.__temp_order_list_dict[nRequestID] = []
|
if not bIsLast:
|
self.__temp_order_list_dict[nRequestID].append(
|
{"tradeID": pTradeField.TradeID, "securityID": pTradeField.SecurityID,
|
"orderLocalID": pTradeField.OrderLocalID,
|
"direction": pTradeField.Direction, "orderSysID": pTradeField.OrderSysID, "price": pTradeField.Price,
|
"tradeTime": pTradeField.TradeTime,
|
"volume": pTradeField.Volume, "tradeDate": pTradeField.TradeDate, "tradingDay": pTradeField.TradingDay,
|
"PbuID": pTradeField.PbuID, "accountID": pTradeField.AccountID})
|
else:
|
self.__data_callback(TYPE_LIST_TRADED, nRequestID, self.__temp_order_list_dict[nRequestID])
|
self.__temp_order_list_dict.pop(nRequestID)
|
|
|
class MyTCPServer(socketserver.TCPServer):
|
def __init__(self, server_address, RequestHandlerClass):
|
socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate=True)
|
|
|
# 如果使用异步的形式则需要再重写ThreadingTCPServer
|
class MyThreadingTCPServer(socketserver.ThreadingMixIn, MyTCPServer): pass
|
|
|
class MyBaseRequestHandle(socketserver.BaseRequestHandler):
|
__inited = False
|
|
@classmethod
|
def traderapi_callback(cls, type, req_id, data):
|
print("回调", type, req_id, data)
|
key = req_id
|
if type == TYPE_BUY or type == TYPE_CANCEL_BUY:
|
key = data["sinfo"]
|
try:
|
if cls.__req_socket_dict and key in cls.__req_socket_dict:
|
cls.__req_socket_dict[key].send(json.dumps({"code": 0, "data": data}).encode('gbk'))
|
cls.__req_socket_dict.pop(key)
|
except Exception as e:
|
logging.exception(e)
|
|
def setup(self):
|
self.__init()
|
|
@classmethod
|
def __init(cls):
|
if cls.__inited:
|
return True
|
cls.__inited = True
|
cls.__req_socket_dict = {}
|
cls.__tradeSimpleApi = TradeSimpleApi()
|
|
def __is_sign_right(self, data_json):
|
list_str = []
|
sign = data_json["sign"]
|
data_json.pop("sign")
|
for k in data_json:
|
list_str.append(f"{k}={data_json[k]}")
|
list_str.sort()
|
__str = "&".join(list_str) + "JiaBei@!*."
|
md5 = hashlib.md5(__str.encode(encoding='utf-8')).hexdigest()
|
if md5 != sign:
|
raise Exception("签名出错")
|
|
def handle(self):
|
host = self.client_address[0]
|
super().handle()
|
sk: socket.socket = self.request
|
while True:
|
try:
|
data = sk.recv(1024 * 100)
|
data_str = str(data, encoding="utf-8")
|
if not data_str:
|
continue
|
logger.info(f"接收到数据:{data_str}")
|
return_str = ''
|
try:
|
data_json = json.loads(data_str)
|
self.__is_sign_right(data_json)
|
_type = data_json["type"]
|
_req_id = data_json["req_id"]
|
_data = data_json.get("data")
|
|
if _type == 0:
|
# 下单
|
_data = json.loads(_data)
|
code = _data["code"]
|
count = _data["count"]
|
price = round(float(_data["price"]), 2)
|
logger.info(f"下单参数:{code} {count} {price}")
|
self.__tradeSimpleApi.buy(code, count, price, _req_id)
|
# 下单成功
|
self.__req_socket_dict[_req_id] = sk
|
# 等待3s
|
time.sleep(2)
|
elif _type == 1:
|
# 撤单
|
_data = json.loads(_data)
|
code = _data["code"]
|
order_sys_id = _data["order_sys_id"]
|
logger.info("撤单参数:{code} {order_sys_id}")
|
self.__tradeSimpleApi.cancel_buy(code, order_sys_id, _req_id)
|
# 撤单成功
|
self.__req_socket_dict[_req_id] = sk
|
time.sleep(2)
|
elif _type == 2:
|
# 委托列表
|
local_req_id = self.__tradeSimpleApi.list_delegate_orders()
|
self.__req_socket_dict[local_req_id] = sk
|
time.sleep(3)
|
elif _type == 3:
|
# 成交列表
|
local_req_id = self.__tradeSimpleApi.list_traded_orders()
|
self.__req_socket_dict[local_req_id] = sk
|
time.sleep(3)
|
elif _type == 4:
|
# 成交列表
|
local_req_id = self.__tradeSimpleApi.list_positions()
|
self.__req_socket_dict[local_req_id] = sk
|
time.sleep(3)
|
elif _type == 100:
|
# 活动日志
|
_data = json.loads(_data)
|
start = _data["start"]
|
count = _data["count"]
|
total_count = len(log.log_records)
|
records = log.log_records[start:start + count]
|
return_str = json.dumps({"code": 0, "data": {"count": total_count, "data": records}})
|
sk.send(return_str.encode())
|
elif _type == 101:
|
# 清除日志
|
log.log_records.clear()
|
return_str = json.dumps({"code": 0, "msg": "清除成功"})
|
sk.send(return_str.encode())
|
else:
|
sk.send(return_str.encode())
|
|
pass
|
except Exception as e:
|
return_str = json.dumps({"code": 1, "msg": str(e)})
|
sk.send(return_str.encode())
|
except:
|
pass
|
|
def finish(self):
|
super().finish()
|
|
|
def __init_trade_data_server():
|
logger.info("初始化交易服务器")
|
global api
|
api = traderapi.CTORATstpTraderApi.CreateTstpTraderApi('./flow', False)
|
# 创建回调对象
|
global spi
|
spi = TraderSpi(MyBaseRequestHandle.traderapi_callback)
|
# 注册回调接口
|
api.RegisterSpi(spi)
|
|
# 注册多个交易前置服务地址,用逗号隔开
|
# api.RegisterFront('tcp://10.0.1.101:6500,tcp://10.0.1.101:26500')
|
# 注册名字服务器地址,支持多服务地址逗号隔开
|
# api.RegisterNameServer('tcp://10.0.1.101:52370')
|
# api.RegisterNameServer('tcp://10.0.1.101:52370,tcp://10.0.1.101:62370')
|
|
if 1: # 模拟环境,TCP 直连Front方式
|
# 注册单个交易前置服务地址
|
TD_TCP_FrontAddress = "tcp://210.14.72.21:4400" # 仿真交易环境
|
# TD_TCP_FrontAddress = "tcp://210.14.72.15:4400" # 24小时环境A套
|
# TD_TCP_FrontAddress="tcp://210.14.72.16:9500" #24小时环境B套
|
api.RegisterFront(TD_TCP_FrontAddress)
|
# 注册多个交易前置服务地址,用逗号隔开 形如: api.RegisterFront("tcp://10.0.1.101:6500,tcp://10.0.1.101:26500")
|
print("TD_TCP_FensAddress[sim or 24H]::%s\n" % TD_TCP_FrontAddress)
|
|
else: # 模拟环境,FENS名字服务器方式
|
TD_TCP_FensAddress = "tcp://210.14.72.21:42370"; # 模拟环境通用fens地址
|
'''********************************************************************************
|
* 注册 fens 地址前还需注册 fens 用户信息,包括环境编号、节点编号、Fens 用户代码等信息
|
* 使用名字服务器的好处是当券商系统部署方式发生调整时外围终端无需做任何前置地址修改
|
* *****************************************************************************'''
|
fens_user_info_field = traderapi.CTORATstpFensUserInfoField()
|
fens_user_info_field.FensEnvID = "stock" # 必填项,暂时固定为“stock”表示普通现货柜台
|
fens_user_info_field.FensNodeID = "sim" # 必填项,生产环境需按实际填写,仿真环境为sim
|
fens_user_info_field.FensNodeID, = "24a" # 必填项,生产环境需按实际填写,24小时A套环境为24a
|
# fens_user_info_field.FensNodeID="24b" #必填项,生产环境需按实际填写,24小时B套环境为24b
|
api.RegisterFensUserInfo(fens_user_info_field)
|
api.RegisterNameServer(TD_TCP_FensAddress)
|
# 注册名字服务器地址,支持多服务地址逗号隔开 形如:api.RegisterNameServer('tcp://10.0.1.101:52370,tcp://10.0.1.101:62370')
|
print("TD_TCP_FensAddress[%s]::%s\n" % (fens_user_info_field.FensNodeID, TD_TCP_FensAddress))
|
|
# 订阅私有流
|
api.SubscribePrivateTopic(traderapi.TORA_TERT_QUICK)
|
# 订阅公有流
|
api.SubscribePublicTopic(traderapi.TORA_TERT_QUICK)
|
# 启动接口
|
api.Init()
|
|
|
def run():
|
__init_trade_data_server()
|
laddr = "", 9002
|
tcpserver = MyThreadingTCPServer(laddr, MyBaseRequestHandle) # 注意:参数是MyBaseRequestHandle
|
# tcpserver.handle_request() # 只接受一个客户端连接
|
tcpserver.serve_forever()
|
|
|
if __name__ == "__main__":
|
run()
|