[Mod]更新富途接口为futu-api
This commit is contained in:
parent
dbd967bb6d
commit
97fcd7f19a
@ -3,7 +3,6 @@ websocket-client
|
|||||||
msgpack-python
|
msgpack-python
|
||||||
qdarkstyle
|
qdarkstyle
|
||||||
SortedContainers
|
SortedContainers
|
||||||
futuquant
|
|
||||||
wmi
|
wmi
|
||||||
future
|
future
|
||||||
flask-socketio
|
flask-socketio
|
||||||
@ -16,4 +15,4 @@ pyqtgraph
|
|||||||
qtpy
|
qtpy
|
||||||
psutil
|
psutil
|
||||||
ta-lib
|
ta-lib
|
||||||
|
futu-api
|
||||||
|
@ -1,742 +0,0 @@
|
|||||||
# encoding: UTF-8
|
|
||||||
|
|
||||||
'''
|
|
||||||
vnpy.api.bithumb的gateway接入
|
|
||||||
'''
|
|
||||||
import json
|
|
||||||
from collections import defaultdict
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from vnpy.api.bithumb import BithumbRestApi
|
|
||||||
from vnpy.trader.vtFunction import getJsonPath
|
|
||||||
from vnpy.trader.vtGateway import *
|
|
||||||
|
|
||||||
# 方向映射
|
|
||||||
directionMap = {
|
|
||||||
constant.DIRECTION_LONG: 'bid',
|
|
||||||
constant.DIRECTION_SHORT: 'ask'
|
|
||||||
}
|
|
||||||
directionMapReverse = {v: k for k, v in directionMap.items()}
|
|
||||||
|
|
||||||
# 从https://www.bithumb.com/u1/US127中https://api.bithumb.com/trade/place的API说明中得到
|
|
||||||
minimum_ticks = {
|
|
||||||
'BTC': 0.001,
|
|
||||||
'ETH': 0.01,
|
|
||||||
'DASH': 0.01,
|
|
||||||
'LTC': 0.01,
|
|
||||||
'ETC': 0.1,
|
|
||||||
'XRP': 10,
|
|
||||||
'BCH': 0.001,
|
|
||||||
'XMR': 0.01,
|
|
||||||
'ZEC': 0.01,
|
|
||||||
'QTUM': 0.1,
|
|
||||||
'BTG': 0.1,
|
|
||||||
'EOS': 0.1,
|
|
||||||
'ICX': 1,
|
|
||||||
'VEN': 1,
|
|
||||||
'TRX': 100,
|
|
||||||
'ELF': 10,
|
|
||||||
'MITH': 10,
|
|
||||||
'MCO': 10,
|
|
||||||
'OMG': 0.1,
|
|
||||||
'KNC': 1,
|
|
||||||
'GNT': 10,
|
|
||||||
'HSR': 1,
|
|
||||||
'ZIL': 100,
|
|
||||||
'ETHOS': 1,
|
|
||||||
'PAY': 1,
|
|
||||||
'WAX': 10,
|
|
||||||
'POWR': 10,
|
|
||||||
'LRC': 10,
|
|
||||||
'GTO': 10,
|
|
||||||
'STEEM': 10,
|
|
||||||
'STRAT': 1,
|
|
||||||
'ZRX': 1,
|
|
||||||
'REP': 0.1,
|
|
||||||
'AE': 1,
|
|
||||||
'XEM': 10,
|
|
||||||
'SNT': 10,
|
|
||||||
'ADA': 10
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
class BithumbGateway(VtGateway):
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def __init__(self, eventEngine, gatewayName='BithumbGateway'):
|
|
||||||
super(BithumbGateway, self).__init__(eventEngine, gatewayName)
|
|
||||||
|
|
||||||
self.restApi = RestApi(self) # type: RestApi
|
|
||||||
|
|
||||||
self.qryEnabled = False
|
|
||||||
|
|
||||||
self.fileName = self.gatewayName + '_connect.json'
|
|
||||||
self.filePath = getJsonPath(self.fileName, __file__)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def connect(self):
|
|
||||||
"""连接"""
|
|
||||||
try:
|
|
||||||
f = open(self.filePath)
|
|
||||||
except IOError:
|
|
||||||
log = VtLogData()
|
|
||||||
log.gatewayName = self.gatewayName
|
|
||||||
log.logContent = u'读取连接配置出错,请检查'
|
|
||||||
self.onLog(log)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 解析json文件
|
|
||||||
setting = json.load(f)
|
|
||||||
f.close()
|
|
||||||
try:
|
|
||||||
apiKey = str(setting['apiKey'])
|
|
||||||
apiSecret = str(setting['apiSecret'])
|
|
||||||
# symbols = setting['symbols']
|
|
||||||
except KeyError:
|
|
||||||
log = VtLogData()
|
|
||||||
log.gatewayName = self.gatewayName
|
|
||||||
log.logContent = u'连接配置缺少字段,请检查'
|
|
||||||
self.onLog(log)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 创建行情和交易接口对象
|
|
||||||
self.restApi.connect(apiKey, apiSecret)
|
|
||||||
|
|
||||||
# 初始化并启动查询
|
|
||||||
self.initQuery()
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def subscribe(self, subscribeReq):
|
|
||||||
"""订阅行情"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def sendOrder(self, orderReq):
|
|
||||||
"""发单"""
|
|
||||||
return self.restApi.sendOrder(orderReq)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def cancelOrder(self, cancelOrderReq):
|
|
||||||
"""撤单"""
|
|
||||||
self.restApi.cancelOrder(cancelOrderReq)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def close(self):
|
|
||||||
"""关闭"""
|
|
||||||
self.restApi.close()
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def initQuery(self):
|
|
||||||
"""初始化连续查询"""
|
|
||||||
# if self.qryEnabled:
|
|
||||||
# 需要循环的查询函数列表
|
|
||||||
# self.qryFunctionList = [self.restApi.qryTickers,
|
|
||||||
# self.restApi.qryDepth,
|
|
||||||
# self.restApi.qryPosition,
|
|
||||||
# self.restApi.qryOrder]
|
|
||||||
#
|
|
||||||
# self.qryCount = 0 # 查询触发倒计时
|
|
||||||
# self.qryTrigger = 1 # 查询触发点
|
|
||||||
# self.qryNextFunction = 0 # 上次运行的查询函数索引
|
|
||||||
#
|
|
||||||
# self.startQuery()
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def query(self, event):
|
|
||||||
"""注册到事件处理引擎上的查询函数"""
|
|
||||||
# self.qryCount += 1
|
|
||||||
#
|
|
||||||
# if self.qryCount > self.qryTrigger:
|
|
||||||
# # 清空倒计时
|
|
||||||
# self.qryCount = 0
|
|
||||||
#
|
|
||||||
# # 执行查询函数
|
|
||||||
# function = self.qryFunctionList[self.qryNextFunction]
|
|
||||||
# function()
|
|
||||||
#
|
|
||||||
# # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0
|
|
||||||
# self.qryNextFunction += 1
|
|
||||||
# if self.qryNextFunction == len(self.qryFunctionList):
|
|
||||||
# self.qryNextFunction = 0
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def startQuery(self):
|
|
||||||
"""启动连续查询"""
|
|
||||||
self.eventEngine.register(EVENT_TIMER, self.query)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def setQryEnabled(self, qryEnabled):
|
|
||||||
"""设置是否要启动循环查询"""
|
|
||||||
self.qryEnabled = qryEnabled
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
class RestApi(BithumbRestApi):
|
|
||||||
"""REST API实现"""
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def __init__(self, gateway):
|
|
||||||
"""Constructor"""
|
|
||||||
super(RestApi, self).__init__()
|
|
||||||
|
|
||||||
self.gateway = gateway # type: BithumbGateway # gateway对象
|
|
||||||
self.gatewayName = gateway.gatewayName # gateway对象名称
|
|
||||||
|
|
||||||
self.localID = 0
|
|
||||||
self.tradeID = 0
|
|
||||||
|
|
||||||
self.orders = {} # type: dict[str, VtOrderData] # localID:order
|
|
||||||
self.sysLocalDict = {} # type: dict[str, str] # sysID: localID
|
|
||||||
self.localSysDict = {} # type: dict[str, str] # localID: sysID
|
|
||||||
self.reqOrderDict = {} # type: dict[int, VtOrderData] # reqID:order
|
|
||||||
self.cancelDict = {} # type: dict[str, VtCancelOrderReq] # localID:req
|
|
||||||
|
|
||||||
self.tickDict = {}
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def connect(self, apiKey, apiSecret):
|
|
||||||
"""连接服务器"""
|
|
||||||
self.init(apiKey, apiSecret)
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
# self.symbols = symbols
|
|
||||||
self.writeLog(u'REST API启动成功')
|
|
||||||
|
|
||||||
self.qryContract()
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def writeLog(self, content):
|
|
||||||
"""发出日志"""
|
|
||||||
log = VtLogData()
|
|
||||||
log.gatewayName = self.gatewayName
|
|
||||||
log.logContent = content
|
|
||||||
self.gateway.onLog(log)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def generateLocalOrder(self, ):
|
|
||||||
self.localID += 1
|
|
||||||
localID = str(self.localID)
|
|
||||||
order = VtOrderData()
|
|
||||||
order.gatewayName = self.gatewayName
|
|
||||||
order.status = constant.STATUS_UNKNOWN
|
|
||||||
order.exchange = constant.EXCHANGE_BITHUMB
|
|
||||||
order.orderID = localID
|
|
||||||
order.vtOrderID = '.'.join([self.gatewayName, localID])
|
|
||||||
self.orders[localID] = order
|
|
||||||
return order
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def sendOrder(self, orderReq):
|
|
||||||
"""下单"""
|
|
||||||
req = {
|
|
||||||
'order_currency': orderReq.symbol,
|
|
||||||
'Payment_currency': orderReq.currency, # todo: 无论如何服务器都会以KRW作为单位
|
|
||||||
'type': directionMap[orderReq.direction],
|
|
||||||
'price': int(orderReq.price),
|
|
||||||
'units': orderReq.volume
|
|
||||||
}
|
|
||||||
|
|
||||||
reqid = self.addReq('POST', '/trade/place', self.onSendOrder, postdict=req)
|
|
||||||
|
|
||||||
# 缓存委托数据对象
|
|
||||||
order = self.generateLocalOrder()
|
|
||||||
self.fillLocalOrder(order,
|
|
||||||
orderReq.symbol,
|
|
||||||
orderReq.price,
|
|
||||||
orderReq.volume,
|
|
||||||
orderReq.direction)
|
|
||||||
|
|
||||||
self.reqOrderDict[reqid] = order
|
|
||||||
return order.vtOrderID
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onSendOrder(self, data, reqid): # type: (dict, int)->None
|
|
||||||
"""下单回执"""
|
|
||||||
if self.checkError(u'委托', data):
|
|
||||||
return
|
|
||||||
|
|
||||||
order = self.reqOrderDict[reqid]
|
|
||||||
localID = order.orderID
|
|
||||||
sysID = data['order_id']
|
|
||||||
|
|
||||||
self.saveSysIDForOrder(order, sysID)
|
|
||||||
|
|
||||||
self.gateway.onOrder(order)
|
|
||||||
|
|
||||||
# 发出等待的撤单委托
|
|
||||||
if localID in self.cancelDict:
|
|
||||||
req = self.cancelDict[localID]
|
|
||||||
self.cancelOrder(req)
|
|
||||||
del self.cancelDict[localID]
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
@staticmethod
|
|
||||||
def fillLocalOrder(order, symbol, price, totalVolume, direction):
|
|
||||||
order.symbol = symbol
|
|
||||||
order.vtSymbol = '.'.join([order.symbol, order.exchange])
|
|
||||||
order.price = price
|
|
||||||
order.totalVolume = totalVolume
|
|
||||||
order.direction = direction
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def saveSysIDForOrder(self, order, sysID): # type: (VtOrderData, str)->None
|
|
||||||
self.sysLocalDict[sysID] = order.orderID
|
|
||||||
self.localSysDict[order.orderID] = sysID
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def qryOrder(self, order): # type: (VtOrderData)->None
|
|
||||||
sysID = self.getSysIDForOrder(order)
|
|
||||||
req = {
|
|
||||||
'currency': order.symbol,
|
|
||||||
'order_id': sysID
|
|
||||||
}
|
|
||||||
self.addReq('POST', '/info/orders', self.onQryOrders, postdict=req)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def qryOrders(self, currency='XML'): # type: (VtOrderData)->None
|
|
||||||
sysID = self.getSysIDForOrder(order)
|
|
||||||
req = {
|
|
||||||
'currency': order.symbol,
|
|
||||||
}
|
|
||||||
self.addReq('POST', '/info/orders', self.onQryOrders, postdict=req)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onQryOrders(self, data, reqid):
|
|
||||||
if self.checkError(u'订单查询', data):
|
|
||||||
return
|
|
||||||
orders = data['data']
|
|
||||||
for detail in orders:
|
|
||||||
sysID = detail['order_id']
|
|
||||||
order = self.getOrderBySysID(sysID)
|
|
||||||
if not order:
|
|
||||||
# 查询到了新的order(以前的order)
|
|
||||||
order = self.generateLocalOrder()
|
|
||||||
self.fillLocalOrder(order,
|
|
||||||
detail['order_currency'],
|
|
||||||
detail['price'],
|
|
||||||
detail['units'],
|
|
||||||
directionMapReverse[detail['type']])
|
|
||||||
order.tradedVolume = order.totalVolume - detail['units_remaining']
|
|
||||||
# todo: payment_currency
|
|
||||||
# payment_currency = detail['payment_currency']
|
|
||||||
self.saveSysIDForOrder(order, sysID)
|
|
||||||
|
|
||||||
# 推送
|
|
||||||
self.gateway.onOrder(order)
|
|
||||||
continue
|
|
||||||
|
|
||||||
originalTradeVolume = order.tradedVolume
|
|
||||||
order.tradedVolume = newTradeVolume = order.totalVolume - detail['units_remaining']
|
|
||||||
|
|
||||||
if newTradeVolume != originalTradeVolume:
|
|
||||||
# 推送更新
|
|
||||||
self.gateway.onOrder(order)
|
|
||||||
|
|
||||||
# 尝试更新状态
|
|
||||||
# todo: 这一句还未测试,不知道成交之后date_completed是不是就会有值
|
|
||||||
order.status = constant.STATUS_ALLTRADED if detail['date_completed'] else order.status
|
|
||||||
if order.status == constant.STATUS_ALLTRADED:
|
|
||||||
# 推送成交
|
|
||||||
self.pushOrderAsTraded(order)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def pushOrderAsTraded(self, order):
|
|
||||||
trade = VtTradeData()
|
|
||||||
trade.gatewayName = order.gatewayName
|
|
||||||
trade.symbol = order.symbol
|
|
||||||
trade.vtSymbol = order.vtSymbol
|
|
||||||
trade.orderID = order.orderID
|
|
||||||
trade.vtOrderID = order.vtOrderID
|
|
||||||
self.tradeID += 1
|
|
||||||
trade.tradeID = str(self.tradeID)
|
|
||||||
trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID])
|
|
||||||
trade.direction = order.direction
|
|
||||||
trade.price = order.price
|
|
||||||
trade.volume = order.tradedVolume
|
|
||||||
trade.tradeTime = datetime.now().strftime('%H:%M:%S')
|
|
||||||
self.gateway.onTrade(trade)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def cancelOrder(self, cancelOrderReq): # type: (self, VtCancelOrderReq)->None
|
|
||||||
""""""
|
|
||||||
localID = cancelOrderReq.orderID
|
|
||||||
order = self.getOrderByLocalID(localID)
|
|
||||||
|
|
||||||
if self.isOrderPosted(order):
|
|
||||||
sysID = self.getSysIDForOrder(order)
|
|
||||||
req = {
|
|
||||||
'type': directionMap[order.direction],
|
|
||||||
'order_id': sysID,
|
|
||||||
'currency': cancelOrderReq.symbol
|
|
||||||
}
|
|
||||||
self.addReq('POST',
|
|
||||||
'/trade/cancel',
|
|
||||||
callback=lambda data, reqid: self.onCancelOrder(localID, data, reqid),
|
|
||||||
postdict=req)
|
|
||||||
else:
|
|
||||||
self.cancelDict[localID] = cancelOrderReq
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onCancelOrder(self, localID, data, reqid):
|
|
||||||
if self.checkError(u'撤单', data):
|
|
||||||
return
|
|
||||||
order = self.getOrderByLocalID(localID)
|
|
||||||
order.status = constant.STATUS_CANCELLED
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def qryContract(self):
|
|
||||||
""""""
|
|
||||||
contract = VtContractData()
|
|
||||||
contract.gatewayName = self.gatewayName
|
|
||||||
|
|
||||||
for symbol, tick in minimum_ticks.items():
|
|
||||||
contract.symbol = symbol
|
|
||||||
contract.exchange = constant.EXCHANGE_BITHUMB
|
|
||||||
contract.vtSymbol = '.'.join([contract.symbol, contract.exchange])
|
|
||||||
contract.name = contract.vtSymbol
|
|
||||||
contract.productClass = constant.PRODUCT_SPOT
|
|
||||||
contract.priceTick = tick
|
|
||||||
contract.size = 1
|
|
||||||
self.gateway.onContract(contract)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def qryPublicTick(self, symbol='ALL'):
|
|
||||||
""" symbol 可以是'BTC', 'ETC'等等电子货币符号;也可以使用'ALL',表示要获取所有货币的行情"""
|
|
||||||
url = '/public/ticker/' + symbol
|
|
||||||
if symbol.upper() == 'ALL':
|
|
||||||
self.addReq('GET', url, self.onQryMultiPublicTicker)
|
|
||||||
else:
|
|
||||||
self.addReq('GET', url,
|
|
||||||
callback=lambda data, reqid: self.onQrySinglePublicTicker(symbol, data, id))
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def qryPublicOrderBook(self, symbol='ALL'):
|
|
||||||
""" symbol 可以是'BTC', 'ETC'等等电子货币符号;也可以使用'ALL',表示要获取所有货币的行情"""
|
|
||||||
url = '/public/orderbook/' + symbol
|
|
||||||
if symbol.upper() == 'ALL':
|
|
||||||
self.addReq('GET', url, self.onQryMultiPublicOrderBook)
|
|
||||||
else:
|
|
||||||
self.addReq('GET', url,
|
|
||||||
callback=lambda data, reqid: self.onQrySinglePublicOrderBook(symbol, data, id))
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def qryPrivateTick(self, symbol, currency='CNY'):
|
|
||||||
""""""
|
|
||||||
req = {
|
|
||||||
'order_currency': symbol,
|
|
||||||
'payment_currency': currency,
|
|
||||||
}
|
|
||||||
self.addReq('POST', '/info/ticker', self.onQryPrivateTicker, postdict=req)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def qryPosition(self, symbol='ALL'):
|
|
||||||
""""""
|
|
||||||
req = {
|
|
||||||
'currency': symbol,
|
|
||||||
}
|
|
||||||
self.addReq('POST', '/info/balance', self.onQryPosition, postdict=req)
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onQryPosition(self, data, reqid): # type: (self, dict, int)->None
|
|
||||||
""""""
|
|
||||||
if self.checkError(u'查询持仓', data):
|
|
||||||
return
|
|
||||||
|
|
||||||
datas = data['data'] # type: dict
|
|
||||||
|
|
||||||
# 先分类一下
|
|
||||||
infos = defaultdict(dict) # type: dict[str, dict[str, str]]
|
|
||||||
for key, val in datas.items(): # type: str, str
|
|
||||||
split_position = key.rfind('_')
|
|
||||||
infoType, symbol = key[:split_position], key[split_position+1:]
|
|
||||||
infos[symbol.upper()][infoType] = val
|
|
||||||
|
|
||||||
for symbol in infos.keys():
|
|
||||||
info = infos[symbol]
|
|
||||||
if symbol == u'LAST': # 过滤掉xcoin_last,这个值表示的是最后一次交易量
|
|
||||||
continue
|
|
||||||
if symbol == u'KRW':
|
|
||||||
accountData = VtAccountData()
|
|
||||||
# todo: accountID必须从另一个API获取
|
|
||||||
# accountData.accountID =
|
|
||||||
accountData.balance = info['total']
|
|
||||||
accountData.available = info['available']
|
|
||||||
self.gateway.onAccount(accountData)
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
pos = VtPositionData()
|
|
||||||
pos.gatewayName = self.gatewayName
|
|
||||||
|
|
||||||
pos.symbol = symbol
|
|
||||||
pos.exchange = constant.EXCHANGE_BITHUMB
|
|
||||||
pos.vtSymbol = '.'.join([pos.symbol, pos.exchange])
|
|
||||||
pos.direction = constant.DIRECTION_NET
|
|
||||||
pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction])
|
|
||||||
pos.position = float(info['total'])
|
|
||||||
pos.frozen = float(info['in_use'])
|
|
||||||
|
|
||||||
self.gateway.onPosition(pos)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def parsePublicTickerData(self, symbol, info):
|
|
||||||
dt = datetime.now()
|
|
||||||
date = dt.strftime('%Y%m%d')
|
|
||||||
time = dt.strftime('%H:%M:%S')
|
|
||||||
|
|
||||||
tick = self.getTick(symbol)
|
|
||||||
|
|
||||||
tick.openPrice = float(info['opening_price'])
|
|
||||||
tick.highPrice = float(info['max_price'])
|
|
||||||
tick.lowPrice = float(info['min_price'])
|
|
||||||
tick.lastPrice = float(info['closing_price']) # todo: 也许应该是'buy_price'?
|
|
||||||
tick.volume = float(info['volume_1day'])
|
|
||||||
tick.datetime = datetime
|
|
||||||
tick.date = date
|
|
||||||
tick.time = time
|
|
||||||
|
|
||||||
# 只有订阅了深度行情才推送
|
|
||||||
if tick.bidPrice1:
|
|
||||||
self.gateway.onTick(tick)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onQrySinglePublicTicker(self, symbol, data, reqid):
|
|
||||||
if self.checkError(u'查询行情', data):
|
|
||||||
return
|
|
||||||
|
|
||||||
info = data['data']
|
|
||||||
self.parsePublicTickerData(symbol=symbol, info=info)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onQryMultiPublicTicker(self, data, reqid):
|
|
||||||
if self.checkError(u'查询行情', data):
|
|
||||||
return
|
|
||||||
|
|
||||||
for symbol, info in data['data'].items():
|
|
||||||
# 里面可能会出现一对:date: int这样的值,所以要过滤掉
|
|
||||||
if isinstance(info, dict):
|
|
||||||
self.parsePublicTickerData(symbol=symbol, info=info)
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def parsePublicOrderBookData(self, symbol, info):
|
|
||||||
dt = datetime.now()
|
|
||||||
date = dt.strftime('%Y%m%d')
|
|
||||||
time = dt.strftime('%H:%M:%S')
|
|
||||||
|
|
||||||
tick = self.getTick(symbol)
|
|
||||||
|
|
||||||
for i in range(5):
|
|
||||||
tick.__setattr__('askPrice' + str(i + 1), float(info['asks'][i]['price']))
|
|
||||||
tick.__setattr__('askVolume' + str(i + 1), float(info['asks'][i]['quantity']))
|
|
||||||
|
|
||||||
for i in range(5):
|
|
||||||
tick.__setattr__('bidPrice' + str(i + 1), float(info['bids'][i]['price']))
|
|
||||||
tick.__setattr__('bidVolume' + str(i + 1), float(info['bids'][i]['quantity']))
|
|
||||||
|
|
||||||
tick.datetime = datetime
|
|
||||||
tick.date = date
|
|
||||||
tick.time = time
|
|
||||||
|
|
||||||
# 只有订阅了深度行情才推送
|
|
||||||
if tick.bidPrice1:
|
|
||||||
self.gateway.onTick(tick)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onQrySinglePublicOrderBook(self, symbol, data, reqid):
|
|
||||||
if self.checkError(u'五档行情', data):
|
|
||||||
return
|
|
||||||
|
|
||||||
info = data['data']
|
|
||||||
self.parsePublicTickerData(symbol=symbol, info=info)
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onQryMultiPublicOrderBook(self, data, reqid):
|
|
||||||
if self.checkError(u'五档行情', data):
|
|
||||||
return
|
|
||||||
|
|
||||||
for symbol, info in data['data'].items():
|
|
||||||
self.parsePublicTickerData(symbol=symbol, info=info)
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def onQryPrivateTicker(self, data, reqid):
|
|
||||||
pass
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def getTick(self, symbol):
|
|
||||||
""""""
|
|
||||||
tick = self.tickDict.get(symbol, None) # type: VtTickData
|
|
||||||
|
|
||||||
if not tick:
|
|
||||||
tick = VtTickData()
|
|
||||||
tick.gatewayName = self.gatewayName
|
|
||||||
tick.symbol = symbol
|
|
||||||
tick.exchange = constant.EXCHANGE_BITHUMB
|
|
||||||
tick.vtSymbol = '.'.join([tick.symbol, tick.exchange])
|
|
||||||
self.tickDict[symbol] = tick
|
|
||||||
|
|
||||||
return tick
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def checkError(self, name, data):
|
|
||||||
""""""
|
|
||||||
status = data.get('status', None)
|
|
||||||
if status == u'0000':
|
|
||||||
return False
|
|
||||||
elif not status:
|
|
||||||
self.writeLog(u'%s触发错误:%s' % (name, u"未知的响应报文 : %s".format(data)))
|
|
||||||
return True
|
|
||||||
|
|
||||||
msg = data.get('message', u'unknown')
|
|
||||||
self.writeLog(u'%s触发错误:%s' % (name, msg))
|
|
||||||
return True
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def getOrderByLocalID(self, localID): # type: (str)->VtOrderData
|
|
||||||
"""如果没有该订单,这个函数会出错"""
|
|
||||||
return self.orders[localID]
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def getOrderByVtOrderID(self, vtOrderId): # type: (str)->VtOrderData
|
|
||||||
"""如果没有该订单,这个函数会出错"""
|
|
||||||
localID = vtOrderId[vtOrderId.rfind('.') + 1:]
|
|
||||||
return self.getOrderByLocalID(localID)
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def getOrderBySysID(self, sysID): # type: (str)->VtOrderData
|
|
||||||
return self.getOrderByLocalID(self.getLocalIDBySysID(sysID))
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def getLocalIDBySysID(self, sysID): # type: (str)->str
|
|
||||||
return self.sysLocalDict[sysID]
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def isOrderPosted(self, order): # type: (VtOrderData)->bool
|
|
||||||
"""检查服务器是否响应了一个下单请求,如果已经响应了返回True,否则False"""
|
|
||||||
return order.orderID in self.localSysDict
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def getSysIDForOrder(self, order): # type: (VtOrderData)->str
|
|
||||||
return self.localSysDict[order.orderID]
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
|
||||||
def hasSysID(self, sysID):
|
|
||||||
return sysID in self.sysLocalDict
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# default test secret:
|
|
||||||
API_KEY = '0c2f5621ac18d26d51ce640b25eb44f9'
|
|
||||||
API_SECRET = '62bb8b4e263476f443f8d3dbf0aad6bc'
|
|
||||||
|
|
||||||
api = BithumbRestApi()
|
|
||||||
api.init(apiKey=API_KEY, apiSecret=API_SECRET)
|
|
||||||
api.start(1)
|
|
||||||
|
|
||||||
eventEngine = EventEngine2()
|
|
||||||
rest = RestApi(BithumbGateway(eventEngine=eventEngine))
|
|
||||||
rest.connect(API_KEY, API_SECRET)
|
|
||||||
|
|
||||||
translateDict = {
|
|
||||||
u'\uac70\ub798 \uccb4\uacb0\ub0b4\uc5ed\uc774 '
|
|
||||||
u'\uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.': "交易记录不存在",
|
|
||||||
|
|
||||||
u'\uac70\ub798 \uc9c4\ud589\uc911\uc778 \ub0b4\uc5ed\uc774 '
|
|
||||||
u'\uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.': "没有正在进行的交易",
|
|
||||||
|
|
||||||
u'\ub9e4\uc218\uae08\uc561\uc774 \uc0ac\uc6a9\uac00\ub2a5 KRW'
|
|
||||||
u' \ub97c \ucd08\uacfc\ud558\uc600\uc2b5\ub2c8\ub2e4.': "购买金额超过可用KRW",
|
|
||||||
|
|
||||||
u'\ub9e4\uc218\uac74\uc758 \uc0c1\ud0dc\uac00 \uc9c4\ud589\uc911\uc774 \uc544\ub2d9\ub2c8\ub2e4. '
|
|
||||||
u'\ucde8\uc18c\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.': "购买查询的状态未在进行中。 它无法取消。",
|
|
||||||
|
|
||||||
u'\uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \ud654\ud3d0\uc785\ub2c8\ub2e4. [347]': "不支持该货币单位。[347]",
|
|
||||||
}
|
|
||||||
|
|
||||||
def printError(jsonObj):
|
|
||||||
if rest.checkError('', data=jsonObj):
|
|
||||||
print('error : ')
|
|
||||||
msg = jsonObj['message']
|
|
||||||
print(translateDict.get(msg, msg))
|
|
||||||
|
|
||||||
def manualCancelOrder(sysID):
|
|
||||||
def onTradeCancel(jsonObj, reqid):
|
|
||||||
print('onTradeCancel : \n{}'.format(jsonObj))
|
|
||||||
printError(jsonObj)
|
|
||||||
|
|
||||||
rest.addReq('POST', '/trade/cancel', onTradeCancel,
|
|
||||||
postdict={'type': 'bid', 'order_id': sysID, 'currency': 'XMR'})
|
|
||||||
|
|
||||||
def apiCancelOrder(localId):
|
|
||||||
cancelReq = VtCancelOrderReq()
|
|
||||||
cancelReq.symbol = order.symbol
|
|
||||||
cancelReq.orderID = localId
|
|
||||||
rest.cancelOrder(cancelReq)
|
|
||||||
|
|
||||||
# query tick
|
|
||||||
rest.qryPublicTick('BTC')
|
|
||||||
rest.qryPublicTick('ALL')
|
|
||||||
rest.qryPosition('BTC')
|
|
||||||
rest.qryPosition('ALL')
|
|
||||||
|
|
||||||
# send order
|
|
||||||
sendOrderReq = VtOrderReq()
|
|
||||||
sendOrderReq.symbol = 'XMR'
|
|
||||||
sendOrderReq.direction = constant.DIRECTION_LONG
|
|
||||||
sendOrderReq.volume = minimum_ticks['XMR']
|
|
||||||
# sendOrderReq.price = 700
|
|
||||||
# sendOrderReq.currency = 'CNY' # 不可用
|
|
||||||
# sendOrderReq.price = 16.6461
|
|
||||||
# sendOrderReq.currency = 'USD' # 不可用
|
|
||||||
sendOrderReq.price = 17500
|
|
||||||
sendOrderReq.currency = 'KRW'
|
|
||||||
|
|
||||||
vtOrderId = rest.sendOrder(sendOrderReq)
|
|
||||||
order = rest.getOrderByVtOrderID(vtOrderId)
|
|
||||||
|
|
||||||
# todo: order的状态表示不够清晰
|
|
||||||
while not rest.isOrderPosted(order):
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
sysID = rest.getSysIDForOrder(order)
|
|
||||||
print("sysID : ")
|
|
||||||
print(sysID)
|
|
||||||
|
|
||||||
def onOrders(jsonObj, reqid):
|
|
||||||
print('on_orders : \n{}'.format(jsonObj))
|
|
||||||
printError(jsonObj)
|
|
||||||
|
|
||||||
for detail in jsonObj['data']:
|
|
||||||
sysID = detail['order_id']
|
|
||||||
if rest.hasSysID(sysID):
|
|
||||||
apiCancelOrder(rest.getLocalIDBySysID(sysID))
|
|
||||||
else:
|
|
||||||
manualCancelOrder(sysID)
|
|
||||||
|
|
||||||
after = '1531926544794' # 2018-07-18T15:09:04.794Z
|
|
||||||
# rest.addReq('POST', '/info/orders', on_orders,
|
|
||||||
# postdict={'order_id': sysID, 'type': 'bid', 'after': after, 'currency': 'XMR'}) # got
|
|
||||||
#
|
|
||||||
# rest.addReq('POST', '/info/orders', on_orders,
|
|
||||||
# postdict={'order_id': sysID, 'type': 'bid', 'currency': 'XMR'}) # got
|
|
||||||
#
|
|
||||||
# rest.addReq('POST', '/info/orders', on_orders,
|
|
||||||
# postdict={'after': after, 'currency': 'XMR'}) # got
|
|
||||||
rest.addReq('POST', '/info/orders', onOrders, postdict={'currency': 'XMR'}) # got
|
|
||||||
|
|
||||||
# rest.addReq('POST', '/info/orders', on_orders,
|
|
||||||
# postdict={'order_id': sysID, 'type': 'bid', 'after': after}) # 没有正在进行的交易
|
|
||||||
#
|
|
||||||
# rest.addReq('POST', '/info/orders', on_orders, postdict={'after': after}) # 没有正在进行的交易
|
|
||||||
# rest.addReq('POST', '/info/orders', on_orders, postdict={}) # 没有正在进行的交易
|
|
||||||
# rest.addReq('POST', '/info/orders', on_orders, postdict={'currency': 'ALL'}) # 不支持该货币单位
|
|
||||||
|
|
||||||
raw_input()
|
|
@ -11,11 +11,11 @@ from time import sleep
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
from futuquant import (OpenQuoteContext, OpenHKTradeContext, OpenUSTradeContext,
|
from futu import (OpenQuoteContext, OpenHKTradeContext, OpenUSTradeContext,
|
||||||
RET_ERROR, RET_OK,
|
RET_ERROR, RET_OK,
|
||||||
TrdEnv, TrdSide, OrderType, OrderStatus, ModifyOrderOp,
|
TrdEnv, TrdSide, OrderType, OrderStatus, ModifyOrderOp,
|
||||||
StockQuoteHandlerBase, OrderBookHandlerBase,
|
StockQuoteHandlerBase, OrderBookHandlerBase,
|
||||||
TradeOrderHandlerBase, TradeDealHandlerBase)
|
TradeOrderHandlerBase, TradeDealHandlerBase)
|
||||||
|
|
||||||
from vnpy.trader.vtGateway import *
|
from vnpy.trader.vtGateway import *
|
||||||
from vnpy.trader.vtConstant import GATEWAYTYPE_INTERNATIONAL
|
from vnpy.trader.vtConstant import GATEWAYTYPE_INTERNATIONAL
|
||||||
|
@ -36,7 +36,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
def initUi(self):
|
def initUi(self):
|
||||||
"""初始化界面"""
|
"""初始化界面"""
|
||||||
self.setWindowTitle('VnTrader')
|
self.setWindowTitle('VN Trader')
|
||||||
self.initCentral()
|
self.initCentral()
|
||||||
self.initMenu()
|
self.initMenu()
|
||||||
self.initStatusBar()
|
self.initStatusBar()
|
||||||
|
Loading…
Reference in New Issue
Block a user