commit
3af8f52760
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 用Python的交易员
|
||||
Copyright (c) 2015 Xiaoyou Chen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
108
README.md
108
README.md
@ -7,95 +7,100 @@
|
||||
|
||||
### 简介
|
||||
|
||||
vn.py是基于Python的开源量化交易程序开发框架,起源于国内私募的自主量化交易系统。2015年初项目启动时只是单纯的交易API接口的Python封装。随着业内关注度的上升和社区不断的贡献,目前已经成长为一套全功能的交易程序开发框架,用户群体也日渐多样化,包括私募基金、券商自营和资管、期货资管和子公司、高校研究机构和专业个人投资者等。
|
||||
|
||||
2018年中启动代号为vn.crypto的数字货币量化交易系统开发计划,目前已完成第一阶段的开发,提供针对数字货币交易所原生的REST/WebSocket API的高性能的Python接口封装设计、以及适合7x24小时长时间交易需求的算法交易AlgoTrading模块、面向币圈交易的前端应用示例CryptoTrader等,后续会进一步完善打造功能全面的数字货币量化交易平台。
|
||||
vn.py是基于Python的开源量化交易系统开发框架,起源于国内私募基金的自主交易系统。2015年1月项目正式发布,在开源社区4年持续不断的贡献下,已经从早期的交易API接口封装,一步步成长为一套全功能量化交易平台。随着业内关注度的上升,用户群体也日渐多样化,包括:私募基金、证券自营和资管、期货资管和子公司、高校研究机构、专业个人投资者等等。
|
||||
|
||||
---
|
||||
|
||||
### 项目结构
|
||||
|
||||
1. 丰富的Python交易API接口(vnpy.api),基本覆盖了国内外所有常规交易品种(股票、期货、期权、外汇、外盘、数字货币),具体包括:
|
||||
1. 全功能量化交易平台(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易应用。
|
||||
|
||||
- 传统金融
|
||||
* 覆盖国内外所有交易品种(股票、期货、期权、外汇、外盘、CFD、数字货币)的交易接口:
|
||||
|
||||
- CTP(ctp)
|
||||
* 国内市场
|
||||
|
||||
- 飞马(femas)
|
||||
* CTP(ctpGateway)
|
||||
|
||||
- 中泰证券XTP(xtp)
|
||||
* 飞马(femasGateway)
|
||||
|
||||
- 中信证券期权(cshshlp)
|
||||
* 中泰证券XTP(xtpGateway)
|
||||
|
||||
- 金仕达黄金(ksgold)
|
||||
* 中信证券期权(cshshlpGateway)
|
||||
|
||||
- 金仕达期权(ksotp)
|
||||
* 金仕达黄金(ksgoldGateway)
|
||||
|
||||
- 飞鼠(sgit)
|
||||
* 金仕达期权(ksotpGateway)
|
||||
|
||||
- 飞创(xspeed)
|
||||
* 飞鼠(sgitGateway)
|
||||
|
||||
- 飞创证券(sec)
|
||||
* 飞创(xspeedGateway)
|
||||
|
||||
- QDP(qdp)
|
||||
* 飞创证券(secGateway)
|
||||
|
||||
- 上海直达期货(shzd)
|
||||
* QDP(qdpGateway)
|
||||
|
||||
- Interactive Brokers(ib)
|
||||
* Wind行情(windGateway)
|
||||
|
||||
- 福汇(fxcm)
|
||||
* 海外市场
|
||||
|
||||
* 富途证券(futuGateway)
|
||||
|
||||
* 上海直达期货(shzdGateway)
|
||||
|
||||
* Interactive Brokers(ibGateway)
|
||||
|
||||
* 福汇(fxcmGateway)
|
||||
|
||||
|
||||
- 数字货币
|
||||
* 数字货币
|
||||
|
||||
- OKEX(okex)
|
||||
* OKEX(okexGateway)
|
||||
|
||||
- 火币(huobi)
|
||||
* OKEX合约(okexfGateway)
|
||||
|
||||
- 币安(binance)
|
||||
* 火币(huobiGateway)
|
||||
|
||||
- BitMEX (bitmex)
|
||||
* 币安(binanceGateway)
|
||||
|
||||
- Bitfinex (bitfinex)
|
||||
* BitMEX (bitmexGateway)
|
||||
|
||||
- Coinbase Pro (coinbase)
|
||||
* Bitfinex (bitfinexGateway)
|
||||
|
||||
- FCoin (fcoin)
|
||||
* Coinbase Pro (coinbaseGateway)
|
||||
|
||||
- BigOne (bigone)
|
||||
* FCoin (fcoinGateway)
|
||||
|
||||
- LBank(lbank)
|
||||
* BigOne (bigoneGateway)
|
||||
|
||||
- CCXT (ccxt)
|
||||
* LBank(lbankGateway)
|
||||
|
||||
* CCXT (ccxtGateway)
|
||||
|
||||
2. 简洁易用的事件驱动引擎(vnpy.event),作为事件驱动型交易程序的核心
|
||||
* 经过开源社区大量用户实盘检验,做到开箱即用的各类量化策略交易应用(包括逻辑层和界面层):
|
||||
|
||||
3. 支持服务器端数据推送的RPC框架(vnpy.rpc),用于实现多进程分布式架构的交易系统
|
||||
* CtaStrategy:CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略)
|
||||
|
||||
4. 开箱即用的量化交易平台(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易程序,应用举例:
|
||||
* SpreadTrading:价差交易模块,根据用户的配置自动实现价差组合的深度行情以及持仓变化计算,同时内置的交易算法SniperAlgo可以满足大部分到价成交策略的需求,用户也可以基于AlgoTemplate开发更复杂的价差算法
|
||||
|
||||
* 同时登录多个交易接口,在一套界面上监控多种市场的行情和多种资产账户的资金、持仓、委托、成交情况
|
||||
* OptionMaster:期权交易模块,强大的期权投资组合管理功能,结合基于Cython开发的高效期权定价模型,支持毫秒级别的整体希腊值持仓风险计算,用户可以基于期权交易引擎OmEngine快速开发各类复杂期权交易应用
|
||||
|
||||
* 支持跨市场套利(CTP期货和XTP证券)、境内外套利(CTP期货和IB外盘)、多市场数据整合实时预测走势(CTP的股指期货数据、IB的外盘A50数据、Wind的行业指数数据)等策略应用
|
||||
* AlgoTrading:算法交易模块,提供多种常用的智能交易算法:TWAP、Sniper、BestLimit、Iceberg、Arbitrage等等,支持数据库配置保存、CSV文件加载启动以及RPC跨进程算法交易服务
|
||||
|
||||
* CtaStrategy,CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略)
|
||||
* TradeCopy:复制交易模块,用户可以通过发布者Provider进程来对外提供交易策略信号(手动、策略均可),订阅者Subscriber进程根据收到的信号自动执行同步交易,简洁快速得实现一拖多账户交易功能
|
||||
|
||||
* SpreadTrading,价差交易模块,根据用户的配置自动实现价差组合的深度行情以及持仓变化计算,同时内置的交易算法SniperAlgo可以满足大部分到价成交策略的需求,用户也可以基于AlgoTemplate开发更复杂的价差算法
|
||||
* RiskManager:事前风控模块,负责在交易系统将任何交易请求发出到柜台前的一系列标准检查操作,支持用户自定义风控规则的扩展
|
||||
|
||||
* OptionMaster,期权交易模块,强大的期权投资组合管理功能,结合基于Cython开发的高效期权定价模型,支持毫秒级别的整体希腊值持仓风险计算,用户可以基于期权交易引擎OmEngine快速开发各类复杂期权交易应用
|
||||
* DataRecorder:实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化
|
||||
|
||||
* AlgoTrading,算法交易模块,提供多种常用的智能交易算法:TWAP、Sniper、BestLimit、Iceberg、Arbitrage等等,支持数据库配置保存、CSV文件加载启动以及RPC跨进程算法交易服务
|
||||
* RpcService:RPC跨进程调用服务,基于MainEngineProxy组件,用户可以如同开发单一进程应用搬开发多进程架构的复杂交易应用
|
||||
|
||||
* TradeCopy,复制交易模块,用户可以通过发布者Provider进程来对外提供交易策略信号(手动、策略均可),订阅者Subscriber进程根据收到的信号自动执行同步交易,简洁快速得实现一拖多账户交易功能
|
||||
* RtdService:EXCEL RTD服务组件,通过pyxll模块提供EXCEL表格系统对VN Trader系统内所有数据的访问
|
||||
|
||||
* RiskManager,前端风控模块,负责在交易系统将任何交易请求发出到柜台前的一系列标准检查操作,支持用户自定义风控规则的扩展
|
||||
2. Python交易API接口封装(vnpy.api),提供上述交易接口的底层对接实现
|
||||
|
||||
* DataRecorder,实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化
|
||||
3. 简洁易用的事件驱动引擎(vnpy.event),作为事件驱动型交易程序的核心
|
||||
|
||||
* RpcService,RPC跨进程调用服务,基于MainEngineProxy组件,用户可以如同开发单一进程应用搬开发多进程架构的复杂交易应用
|
||||
|
||||
* RtdService,EXCEL RTD服务组件,通过pyxll模块提供EXCEL表格系统对VN Trader系统内所有数据的访问
|
||||
4. 支持服务器端数据推送的RPC框架(vnpy.rpc),用于实现多进程分布式架构的交易系统
|
||||
|
||||
5. 数据相关的API接口(vnpy.data),用于构建和更新历史行情数据库,目前包括:
|
||||
|
||||
@ -103,7 +108,7 @@ vn.py是基于Python的开源量化交易程序开发框架,起源于国内私
|
||||
|
||||
6. 关于vn.py项目的应用演示(examples),对于新手而言可以从这里开始学习vn.py项目的使用方式
|
||||
|
||||
8. vn.py项目的Docker镜像(docker):
|
||||
7. vn.py项目的Docker镜像(docker):
|
||||
|
||||
* web docker,在Docker中启动基于Web交易的交易服务器WebTrader,在浏览器中实现CTA策略的运维操作
|
||||
|
||||
@ -111,7 +116,7 @@ vn.py是基于Python的开源量化交易程序开发框架,起源于国内私
|
||||
|
||||
9. [社区论坛](http://www.vnpy.com)和[知乎专栏](http://zhuanlan.zhihu.com/vn-py),内容包括vn.py项目的开发教程和Python在量化交易领域的应用研究等内容
|
||||
|
||||
10. 官方交流QQ群262656087,管理较严格(定期清除长期潜水的成员)
|
||||
10. 官方交流QQ群262656087,管理严格(定期清除长期潜水的成员),入群费将捐赠给vn.py社区基金
|
||||
|
||||
---
|
||||
### 环境准备
|
||||
@ -227,11 +232,7 @@ if __name__ == '__main__':
|
||||
|
||||
* [WingIDE](http://wingware.com/):非常好用的Python集成开发环境(作者就是用它写的vn.py)
|
||||
|
||||
* [Robomongo](https://robomongo.org/):MongoDB的图形化客户端,方便监控和修改数据
|
||||
|
||||
* [Sublime Text](http://www.sublimetext.com/):针对编程的文本编辑器,当然你也可以使用Vim或者Emacs
|
||||
|
||||
* [PyQtGraph](http://www.pyqtgraph.org/):适用于开发实时更新数据的图表,如Tick图、K线图、期权波动率曲线等(Matplotlib渲染开销太大,用于实盘绘图可能拖慢整个程序)
|
||||
* [Visual Studio Code](https://code.visualstudio.com/):针对编程的文本编辑器,方便阅读项目中的Python、C++、Markdown文件
|
||||
|
||||
* [Visual Studio 2013](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx):这个就不多说了(作者编译API封装用的是2013版本)
|
||||
|
||||
@ -259,14 +260,13 @@ vn.py使用github托管其源代码,如果希望贡献代码请使用github的
|
||||
---
|
||||
### 项目捐赠
|
||||
|
||||
过去的半年中陆续收到了许多用户的捐赠,在此深表感谢!所有的捐赠资金都投入到了vn.py社区基金中,用于支持vn.py项目的运作。最近主要的一个支出是相关文档编写,目前来看文档完成的速度和质量都显著超出预期。
|
||||
过去4年中收到过许多社区用户的捐赠,在此深表感谢!所有的捐赠资金都投入到了vn.py社区基金中,用于支持vn.py项目的运作。
|
||||
|
||||
先强调一下:**vn.py是开源项目,可以永久免费使用,并没有强制捐赠的要求!!!**
|
||||
|
||||
捐赠方式:支付宝3216630132@qq.com(*晓优)
|
||||
|
||||
|
||||
计划长期维护一份捐赠清单,所以请在留言中注明是项目捐赠以及捐赠人的名字(当然想匿名的用户就随意了)。
|
||||
长期维护捐赠清单,请在留言中注明是项目捐赠以及捐赠人的名字。
|
||||
|
||||
|
||||
---
|
||||
|
@ -1,302 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
'''
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import re
|
||||
import urllib
|
||||
import zlib
|
||||
|
||||
from vnpy.api.rest import Request, RestClient
|
||||
from vnpy.api.websocket import WebsocketClient
|
||||
from vnpy.trader.vtGateway import *
|
||||
|
||||
REST_HOST = 'https://api.huobipro.com'
|
||||
WEBSOCKET_MARKET_HOST = 'wss://api.huobi.pro/ws' # Global站行情
|
||||
WEBSOCKET_ASSETS_HOST = 'wss://api.huobi.pro/ws/v1' # 资产和订单
|
||||
WEBSOCKET_CONTRACT_HOST = 'wss://www.hbdm.com/ws' # 合约站行情
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _split_url(url):
|
||||
"""
|
||||
将url拆分为host和path
|
||||
:return: host, path
|
||||
"""
|
||||
m = re.match('\w+://([^/]*)(.*)', url)
|
||||
if m:
|
||||
return m.group(1), m.group(2)
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def createSignature(apiKey, method, host, path, secretKey):
|
||||
"""创建签名"""
|
||||
sortedParams = (
|
||||
("AccessKeyId", apiKey),
|
||||
("SignatureMethod", 'HmacSHA256'),
|
||||
("SignatureVersion", "2"),
|
||||
("Timestamp", datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S'))
|
||||
)
|
||||
encodeParams = urllib.urlencode(sortedParams)
|
||||
|
||||
payload = [method, host, path, encodeParams]
|
||||
payload = '\n'.join(payload)
|
||||
payload = payload.encode(encoding='UTF8')
|
||||
|
||||
secretKey = secretKey.encode(encoding='UTF8')
|
||||
|
||||
digest = hmac.new(secretKey, payload, digestmod=hashlib.sha256).digest()
|
||||
|
||||
signature = base64.b64encode(digest)
|
||||
params = dict(sortedParams)
|
||||
params["Signature"] = signature
|
||||
return params
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiRestApi(RestClient):
|
||||
|
||||
def __init__(self, gateway): # type: (VtGateway)->HuobiRestApi
|
||||
super(HuobiRestApi, self).__init__()
|
||||
self.gateway = gateway
|
||||
self.gatewayName = gateway.gatewayName
|
||||
|
||||
self.apiKey = ""
|
||||
self.apiSecret = ""
|
||||
self.signHost = ""
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def sign(self, request):
|
||||
request.headers = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36"}
|
||||
additionalParams = createSignature(self.apiKey,
|
||||
request.method,
|
||||
self.signHost,
|
||||
request.path,
|
||||
self.apiSecret)
|
||||
if not request.params:
|
||||
request.params = additionalParams
|
||||
else:
|
||||
request.params.update(additionalParams)
|
||||
if request.method == "POST":
|
||||
request.headers['Content-Type'] = 'application/json'
|
||||
return request
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def connect(self, apiKey, apiSecret, sessionCount=3):
|
||||
"""连接服务器"""
|
||||
self.apiKey = apiKey
|
||||
self.apiSecret = apiSecret
|
||||
|
||||
host, path = _split_url(REST_HOST)
|
||||
self.init(REST_HOST)
|
||||
self.signHost = host
|
||||
self.start(sessionCount)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def qeuryAccount(self):
|
||||
self.addRequest('GET', '/v1/account/accounts', self.onAccount)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onAccount(self, data, request): # type: (dict, Request)->None
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def cancelWithdraw(self, id):
|
||||
self.addRequest('POST',
|
||||
"/v1/dw/withdraw-virtual/" + str(id) + "/cancel",
|
||||
self.onWithdrawCanceled
|
||||
)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onWithdrawCanceled(self, data, request): # type: (dict, Request)->None
|
||||
pass
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiWebsocketApiBase(WebsocketClient):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self, gateway):
|
||||
"""Constructor"""
|
||||
super(HuobiWebsocketApiBase, self).__init__()
|
||||
|
||||
self.gateway = gateway
|
||||
self.gatewayName = gateway.gatewayName
|
||||
|
||||
self.apiKey = ''
|
||||
self.apiSecret = ''
|
||||
self.signHost = ''
|
||||
self.path = ''
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def connect(self, apiKey, apiSecret, url):
|
||||
""""""
|
||||
self.apiKey = apiKey
|
||||
self.apiSecret = apiSecret
|
||||
|
||||
host, path = _split_url(url)
|
||||
|
||||
self.init(url)
|
||||
self.signHost = host
|
||||
self.path = path
|
||||
self.start()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def login(self):
|
||||
params = {
|
||||
'op': 'auth',
|
||||
}
|
||||
params.update(
|
||||
createSignature(self.apiKey,
|
||||
'GET',
|
||||
self.signHost,
|
||||
self.path,
|
||||
self.apiSecret)
|
||||
)
|
||||
return self.sendPacket(params)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onLogin(self, packet):
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
@staticmethod
|
||||
def unpackData(data):
|
||||
return json.loads(zlib.decompress(data, 31))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onPacket(self, packet):
|
||||
"""
|
||||
这里我新增了一个onHuobiPacket的函数,也可以让子类重写这个函数,然后调用super.onPacket
|
||||
"""
|
||||
if 'ping' in packet:
|
||||
self.sendPacket({'pong': packet['ping']})
|
||||
return
|
||||
|
||||
# todo: use another error handing method
|
||||
if 'err-msg' in packet:
|
||||
return self.onHuobiErrorPacket(packet)
|
||||
|
||||
if "op" in packet and packet["op"] == "auth":
|
||||
return self.onLogin(packet)
|
||||
|
||||
self.onHuobiPacket(packet)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiPacket(self, packet): # type: (dict)->None
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiErrorPacket(self, packet): # type: (dict)->None
|
||||
print("error : {}".format(packet))
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiAssetsWebsocketApi(HuobiWebsocketApiBase):
|
||||
|
||||
def connect(self, apiKey, apiSecret, host=WEBSOCKET_ASSETS_HOST):
|
||||
"""
|
||||
这里我使用重写connect,添加了默认参数。这样写感觉~~不太好~~,不过目前想到的比较好的方式就是这样了
|
||||
虽然在Python中可以直接把这个connect()写成不接收host和path的形式,但是PyCharm会提示重载错误,所以不接收host和path似乎不太好?
|
||||
|
||||
我觉得最好的写法应该是这个函数不接收host和path。同时为了让PyCharm不提示重载错误(减少歧义),应该给
|
||||
HuobiWebsocketApiBase.connect起另外一个名字。
|
||||
"""
|
||||
return super(HuobiAssetsWebsocketApi, self). \
|
||||
connect(apiKey, apiSecret, host)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onConnected(self):
|
||||
self.login()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def subscribeAccount(self):
|
||||
"""
|
||||
:param symbol: str ethbtc, ltcbtc, etcbtc, bchbtc
|
||||
:param period: str 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year
|
||||
"""
|
||||
self.sendPacket({
|
||||
"op": "sub",
|
||||
"cid": "any thing you want",
|
||||
"topic": "accounts"
|
||||
})
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiPacket(self, packet): # type: (dict)->None
|
||||
if 'op' in packet:
|
||||
if packet['op'] == 'sub':
|
||||
timestamp = packet['ts']
|
||||
topic = packet['topic']
|
||||
"""
|
||||
"data": {
|
||||
"event": "order.match|order.place|order.refund|order.cancel|order.fee-refund|margin.transfer|margin.loan|margin.interest|margin.repay|other",
|
||||
"list": [
|
||||
{
|
||||
"account-id": 419013,
|
||||
"currency": "usdt",
|
||||
"type": "trade",
|
||||
"balance": "500009195917.4362872650"
|
||||
},
|
||||
{
|
||||
"account-id": 419013,
|
||||
"currency": "btc",
|
||||
"type": "frozen",
|
||||
"balance": "9786.6783000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiMarketWebsocketApi(HuobiWebsocketApiBase):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def connect(self, apiKey, apiSecret, host=WEBSOCKET_MARKET_HOST):
|
||||
"""
|
||||
这里我使用重写connect,添加了默认参数。这样写感觉~~不太好~~,不过目前想到的比较好的方式就是这样了
|
||||
虽然在Python中可以直接把这个connect()写成不接收host和path的形式,但是PyCharm会提示重载错误,所以不接收host和path似乎不太好?
|
||||
|
||||
我觉得最好的写法应该是这个函数不接收host和path。同时为了让PyCharm不提示重载错误(减少歧义),应该给
|
||||
HuobiWebsocketApiBase.connect起另外一个名字。
|
||||
"""
|
||||
return super(HuobiMarketWebsocketApi, self). \
|
||||
connect(apiKey, apiSecret, host)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def subscribeKLine(self, symbol, period): # type:(str, str)->None
|
||||
"""
|
||||
:param symbol: str ethbtc, ltcbtc, etcbtc, bchbtc
|
||||
:param period: str 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year
|
||||
:return:
|
||||
"""
|
||||
self.sendPacket({
|
||||
"sub": "market." + symbol + ".kline." + period,
|
||||
"id": "any thing you want"
|
||||
})
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiPacket(self, packet): # type: (dict)->None
|
||||
# code for test purpose only
|
||||
if 'ch' in packet:
|
||||
if packet['ch'] == 'market.btcusdt.kline.1min':
|
||||
timestamp = packet['ts']
|
||||
data = packet['tick']
|
||||
id = data['id']
|
||||
amount = data['amount']
|
||||
count = data['count']
|
||||
open = data['open']
|
||||
close = data['close']
|
||||
low = data['low']
|
||||
high = data['high']
|
||||
vol = data['vol']
|
||||
pass
|
@ -1,172 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import urllib
|
||||
|
||||
import time
|
||||
|
||||
from vnpy.api.rest import Request, RestClient
|
||||
from vnpy.api.websocket import WebsocketClient
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def paramsToDataV1(params):
|
||||
return urllib.urlencode(sorted(params.items()))
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def signV1(dataWithApiKey, apiSecret):
|
||||
"""
|
||||
usage:
|
||||
params = { ... , 'api_key': ...}
|
||||
data = paramsToData(params)
|
||||
signature = sign(data, apiSecret)
|
||||
data += "&sign" + signature
|
||||
|
||||
:param dataWithApiKey: sorted urlencoded args with apiKey
|
||||
:return: param 'sign' for okex api
|
||||
"""
|
||||
dataWithSecret = dataWithApiKey + "&secret_key=" + apiSecret
|
||||
return hashlib.md5(dataWithSecret.encode()).hexdigest().upper()
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def signV3(dataToSign, apiSecret):
|
||||
return base64.b64encode( hmac.new(apiSecret, dataToSign.encode(), hashlib.sha256).digest())
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesRestBaseV1(RestClient):
|
||||
host = 'https://www.okex.com/api/v1'
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self):
|
||||
super(OkexFuturesRestBaseV1, self).__init__()
|
||||
self.apiKey = None
|
||||
self.apiSecret = None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# noinspection PyMethodOverriding
|
||||
def init(self, apiKey, apiSecret, apiPassphrase):
|
||||
# type: (str, str, str) -> any
|
||||
super(OkexFuturesRestBaseV1, self).init(self.host)
|
||||
self.apiKey = apiKey
|
||||
self.apiSecret = apiSecret
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def sign(self, request): # type: (Request)->Request
|
||||
args = request.params or {}
|
||||
args.update(request.data or {})
|
||||
if 'sign' in args:
|
||||
args.pop('sign')
|
||||
if 'apiKey' not in args:
|
||||
args['api_key'] = self.apiKey
|
||||
data = paramsToDataV1(args)
|
||||
signature = signV1(data, self.apiSecret)
|
||||
data += "&sign=" + signature
|
||||
|
||||
request.headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
request.data = data
|
||||
return request
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesRestBaseV3(RestClient):
|
||||
"""
|
||||
Okex Rest API v3基础类
|
||||
"""
|
||||
host = 'https://www.okex.com'
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self):
|
||||
super(OkexFuturesRestBaseV3, self).__init__()
|
||||
self.apiKey = None
|
||||
self.apiSecret = None
|
||||
self.apiPassphrase = None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# noinspection PyMethodOverriding
|
||||
def init(self, apiKey, apiSecret, apiPassphrase):
|
||||
# type: (str, str, str) -> any
|
||||
super(OkexFuturesRestBaseV3, self).init(self.host)
|
||||
self.apiKey = apiKey
|
||||
self.apiSecret = apiSecret
|
||||
self.apiPassphrase = apiPassphrase
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def sign(self, request): # type: (Request)->Request
|
||||
timestamp = str(time.time())
|
||||
|
||||
data = json.dumps(request.data)
|
||||
request.data = data
|
||||
dataToSign = timestamp + request.method + request.path + data
|
||||
|
||||
signature = signV3(dataToSign, self.apiSecret)
|
||||
|
||||
request.headers = {
|
||||
'OK-ACCESS-KEY': self.apiKey,
|
||||
'OK-ACCESS-SIGN': signature,
|
||||
'OK-ACCESS-TIMESTAMP': timestamp,
|
||||
'OK-ACCESS-PASSPHRASE': self.apiPassphrase,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
return request
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesWebSocketBase(WebsocketClient):
|
||||
"""
|
||||
Okex期货websocket客户端
|
||||
实例化后使用init设置apiKey和secretKey(apiSecret)
|
||||
"""
|
||||
host = 'wss://real.okex.com:10440/websocket/okexapi?compress=true'
|
||||
|
||||
def __init__(self):
|
||||
super(OkexFuturesWebSocketBase, self).__init__()
|
||||
super(OkexFuturesWebSocketBase, self).init(OkexFuturesWebSocketBase.host)
|
||||
self.apiKey = None
|
||||
self.apiSecret = None
|
||||
self.apiPassphrase = None
|
||||
|
||||
self.autoLogin = True
|
||||
|
||||
self.onConnected = self._onConnected
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# noinspection PyMethodOverriding
|
||||
def init(self, apiKey, secretKey, apiPassphrase, autoLogin=True):
|
||||
|
||||
self.apiKey = apiKey
|
||||
self.apiSecret = secretKey
|
||||
self.apiPassphrase = apiPassphrase
|
||||
self.autoLogin = autoLogin
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def sendPacket(self, dictObj, authenticate=False):
|
||||
if authenticate:
|
||||
pass
|
||||
return super(OkexFuturesWebSocketBase, self).sendPacket(dictObj)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _login(self, ):
|
||||
timestamp = str(time.time())
|
||||
|
||||
data = timestamp + 'GET' + '/users/self/verify'
|
||||
signature = signV3(data, self.apiSecret)
|
||||
|
||||
self.sendPacket({
|
||||
"event": "login",
|
||||
"parameters": {
|
||||
"api_key": self.apiKey,
|
||||
"timestamp": timestamp,
|
||||
"passphrase": self.apiPassphrase,
|
||||
"sign": signature,
|
||||
}
|
||||
}, authenticate=False)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onConnected(self):
|
||||
if self.autoLogin:
|
||||
self._login()
|
@ -1,11 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from __future__ import absolute_import
|
||||
from vnpy.trader import vtConstant
|
||||
from .okexFuturesGateway import OkexFuturesGateway
|
||||
|
||||
gatewayClass = OkexFuturesGateway
|
||||
gatewayName = 'OKEXFUTURES'
|
||||
gatewayDisplayName = 'OKEXFUTURES'
|
||||
gatewayType = vtConstant.GATEWAYTYPE_BTC
|
||||
gatewayQryEnabled = True
|
@ -1,907 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
import sys
|
||||
import traceback
|
||||
import zlib
|
||||
from collections import defaultdict
|
||||
|
||||
from enum import Enum
|
||||
from typing import Dict, List
|
||||
|
||||
from vnpy.api.rest import Request
|
||||
from vnpy.trader.gateway.okexFuturesGateway.OkexFuturesBase import \
|
||||
OkexFuturesRestBaseV3, \
|
||||
OkexFuturesWebSocketBase
|
||||
from vnpy.trader.vtFunction import getJsonPath
|
||||
from vnpy.trader.vtGateway import *
|
||||
|
||||
|
||||
########################################################################
|
||||
class ApiError(Exception):
|
||||
"""Okex的API常常变动,当API发生奇葩的变动的时候,会发升这个异常"""
|
||||
pass
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesEasySymbol(object):
|
||||
BTC = 'btc'
|
||||
LTC = 'ltc'
|
||||
ETH = 'eth'
|
||||
ETC = 'etc'
|
||||
BCH = 'bch'
|
||||
EOS = 'eos'
|
||||
XRP = 'xrp'
|
||||
BTG = 'btg'
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesSymbol(object):
|
||||
BTC = 'btc_usd'
|
||||
LTC = 'ltc_usd'
|
||||
ETH = 'eth_usd'
|
||||
ETC = 'etc_usd'
|
||||
BCH = 'bch_usd'
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesPriceType(object):
|
||||
Buy = 'buy'
|
||||
Sell = 'sell'
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesContractType(object):
|
||||
ThisWeek = 'this_week'
|
||||
NextWeek = 'next_week'
|
||||
Quarter = 'quarter'
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesOrderType(object):
|
||||
OpenLong = '1'
|
||||
OpenShort = '2'
|
||||
CloseLong = '3'
|
||||
CloseShort = '4'
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesOrderStatus(object):
|
||||
NotTraded = 0
|
||||
PartialTraded = 1
|
||||
Finished = 2
|
||||
|
||||
|
||||
########################################################################
|
||||
class Order(object):
|
||||
_lastLocalId = 0
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self):
|
||||
Order._lastLocalId += 1
|
||||
self.localId = str(Order._lastLocalId)
|
||||
self.remoteId = None
|
||||
self.vtOrder = None # type: VtOrderData
|
||||
|
||||
|
||||
########################################################################
|
||||
class Symbol(object):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self):
|
||||
self.v3 = None # type: str # BTC_USD_1891201
|
||||
self.ui = None # type: str # BTC_USD_NEXTWEEK
|
||||
self.v1Symbol = None # type: str # btc_usd
|
||||
self.uiSymbol = None # type: str # btc_usd
|
||||
self.easySymbol = None # type: str # btc
|
||||
self.localContractTYpe = None # type: str # THISWEEK
|
||||
self.remoteContractType = None # type: str # this_week
|
||||
|
||||
|
||||
########################################################################
|
||||
class ChannelType(Enum):
|
||||
Login = 1
|
||||
ForecastPrice = 2
|
||||
Tick = 3
|
||||
Depth = 4
|
||||
Trade = 5
|
||||
Index = 6
|
||||
UserTrade = 7
|
||||
UserInfo = 8
|
||||
Position = 9
|
||||
Order = 10
|
||||
|
||||
|
||||
########################################################################
|
||||
class Channel(object):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self, type_, symbol=None, remoteContractType=None, extra_=None):
|
||||
self.type = type_
|
||||
self.symbol = symbol
|
||||
self.remoteContractType = remoteContractType
|
||||
self.extra = extra_
|
||||
|
||||
|
||||
########################################################################
|
||||
class OkexFuturesGateway(VtGateway):
|
||||
"""OKEX期货交易接口"""
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self, eventEngine, gatewayName='OKEXFUTURES'):
|
||||
"""Constructor"""
|
||||
super(OkexFuturesGateway, self).__init__(eventEngine, gatewayName)
|
||||
self.exchange = constant.EXCHANGE_OKEXFUTURE
|
||||
self.apiKey = None # type: str
|
||||
self.apiSecret = None # type: str
|
||||
self.apiPassphrase = None # type: str
|
||||
|
||||
self.restClient = OkexFuturesRestBaseV3()
|
||||
|
||||
self.webSocket = OkexFuturesWebSocketBase()
|
||||
self.webSocket.onPacket = self.onWebSocketPacket
|
||||
self.webSocket.unpackData = self.webSocketUnpackData
|
||||
|
||||
self.leverRate = 1
|
||||
self.symbols = []
|
||||
|
||||
self._symbolDict = {} # type: Dict[str, Symbol]
|
||||
|
||||
self.tradeID = 0
|
||||
self._orders = {} # type: Dict[str, Order]
|
||||
self._remoteIds = {} # type: Dict[str, Order]
|
||||
self._lastTicker = None # type: VtTickData
|
||||
self._utcOffset = datetime.now() - datetime.utcnow()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def readConfig(self):
|
||||
"""
|
||||
从json文件中读取设置,并将其内容返回为一个dict
|
||||
:一个一个return:
|
||||
"""
|
||||
fileName = self.gatewayName + '_connect.json'
|
||||
filePath = getJsonPath(fileName, __file__)
|
||||
|
||||
try:
|
||||
with open(filePath, 'rt') as f:
|
||||
return json.load(f)
|
||||
except IOError:
|
||||
log = VtLogData()
|
||||
log.gatewayName = self.gatewayName
|
||||
log.logContent = u'读取连接配置出错,请检查'
|
||||
# todo: pop a message box is better
|
||||
self.onLog(log)
|
||||
return None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def loadSetting(self):
|
||||
"""载入设置"""
|
||||
setting = self.readConfig()
|
||||
if setting:
|
||||
"""连接"""
|
||||
# 载入json文件
|
||||
try:
|
||||
# todo: check by predefined settings names and types
|
||||
# or check by validator
|
||||
self.apiKey = str(setting['apiKey'])
|
||||
self.apiSecret = str(setting['secretKey'])
|
||||
self.apiPassphrase = str(setting['passphrase'])
|
||||
self.leverRate = setting['leverRate']
|
||||
self.symbols = setting['symbols']
|
||||
except KeyError:
|
||||
log = VtLogData()
|
||||
log.gatewayName = self.gatewayName
|
||||
log.logContent = u'连接配置缺少字段,请检查'
|
||||
self.onLog(log)
|
||||
return
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def connect(self):
|
||||
"""连接"""
|
||||
self.loadSetting()
|
||||
self.restClient.init(self.apiKey, self.apiSecret, self.apiPassphrase)
|
||||
self.webSocket.init(self.apiKey, self.apiSecret, self.apiPassphrase)
|
||||
self.restClient.start()
|
||||
self.webSocket.start()
|
||||
|
||||
self.queryContracts()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def subscribe(self, subscribeReq): # type: (VtSubscribeReq)->None
|
||||
"""订阅行情"""
|
||||
s = self.parseSymbol(subscribeReq.symbol)
|
||||
remoteSymbol = s.v1Symbol.lower()
|
||||
remoteContractType = s.remoteContractType
|
||||
|
||||
# ticker
|
||||
self.webSocket.sendPacket({
|
||||
'event': 'addChannel',
|
||||
'channel':
|
||||
'ok_sub_futureusd_' + remoteSymbol.lower() + '_ticker_' + remoteContractType
|
||||
})
|
||||
|
||||
# depth
|
||||
self.webSocket.sendPacket({
|
||||
'event': 'addChannel',
|
||||
'channel':
|
||||
'ok_sub_futureusd_' + remoteSymbol.lower() + '_depth' + remoteContractType + '_5'
|
||||
})
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def subscribeUserTrade(self):
|
||||
self.webSocket.sendPacket({
|
||||
'event': 'addChannel',
|
||||
'channel': 'ok_sub_futureusd_trades'
|
||||
})
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _writeError(self, msg): # type: (str)->None
|
||||
e = VtErrorData()
|
||||
e.gatewayName = self.gatewayName
|
||||
e.errorMsg = msg
|
||||
self.onError(e)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _getOrderByLocalId(self, localId):
|
||||
"""从本地Id获取对应的内部Order对象"""
|
||||
if localId in self._orders:
|
||||
return self._orders[localId]
|
||||
return None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _getOrderByRemoteId(self, remoteId):
|
||||
"""从Api的OrderId获取对应的内部Order对象"""
|
||||
if remoteId in self._remoteIds:
|
||||
return self._remoteIds[remoteId]
|
||||
return None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _saveRemoteId(self, remoteId, myorder):
|
||||
"""将remoteId和队友的"""
|
||||
myorder.remoteId = remoteId
|
||||
self._remoteIds[remoteId] = myorder
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _generateLocalOrder(self, symbol, price, volume, direction, offset):
|
||||
myorder = Order()
|
||||
localId = myorder.localId
|
||||
self._orders[localId] = myorder
|
||||
myorder.vtOrder = VtOrderData.createFromGateway(self,
|
||||
localId,
|
||||
symbol,
|
||||
self.exchange,
|
||||
price,
|
||||
volume,
|
||||
direction,
|
||||
offset)
|
||||
return myorder
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def parseSymbol(self, symbol):
|
||||
return self._symbolDict[symbol]
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def sendOrder(self, vtRequest): # type: (VtOrderReq)->str
|
||||
"""发单"""
|
||||
symbol = self.parseSymbol(vtRequest.symbol).v3
|
||||
myorder = self._generateLocalOrder(symbol,
|
||||
vtRequest.price,
|
||||
vtRequest.volume,
|
||||
vtRequest.direction,
|
||||
vtRequest.offset)
|
||||
|
||||
orderType = _orderTypeMap[(vtRequest.direction, vtRequest.offset)] # 开多、开空、平多、平空
|
||||
|
||||
data = {
|
||||
'client_oid': None,
|
||||
'instrument_id': symbol,
|
||||
'type': orderType,
|
||||
'size': vtRequest.volume,
|
||||
'leverage': self.leverRate,
|
||||
}
|
||||
if vtRequest.priceType == constant.PRICETYPE_MARKETPRICE:
|
||||
data['match_price'] = '1'
|
||||
else:
|
||||
data['price'] = vtRequest.price
|
||||
|
||||
self.restClient.addRequest('POST', '/api/futures/v3/order',
|
||||
callback=self._onOrderSent,
|
||||
onFailed=self._onSendOrderFailed,
|
||||
data=data,
|
||||
extra=myorder
|
||||
)
|
||||
return myorder.vtOrder.vtOrderID
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def cancelOrder(self, vtCancel): # type: (VtCancelOrderReq)->None
|
||||
"""撤单"""
|
||||
myorder = self._getOrderByLocalId(vtCancel.orderID)
|
||||
assert myorder is not None, u"理论上是无法取消一个不存在的本地单的"
|
||||
|
||||
symbol = vtCancel.symbol
|
||||
remoteId = myorder.remoteId
|
||||
|
||||
path = '/api/futures/v3/cancel_order/' + symbol + '/' + remoteId
|
||||
self.restClient.addRequest('POST', path,
|
||||
callback=self._onOrderCanceled,
|
||||
onFailed=self._onCancelOrderFailed,
|
||||
data={
|
||||
'instrument_id': symbol,
|
||||
'order_id': remoteId
|
||||
},
|
||||
extra=myorder
|
||||
)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def queryContracts(self):
|
||||
return self.restClient.addRequest('GET', '/api/futures/v3/instruments',
|
||||
callback=self._onQueryContracts,
|
||||
)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def queryOrders(self, symbol, status): # type: (str, OkexFuturesOrderStatus)->None
|
||||
"""
|
||||
:param symbol:
|
||||
:param status: OkexFuturesOrderStatus
|
||||
:return:
|
||||
"""
|
||||
symbol = self.parseSymbol(symbol).v3
|
||||
path = '/api/futures/v3/orders/' + symbol
|
||||
self.restClient.addRequest("POST", path,
|
||||
data={
|
||||
'status': status,
|
||||
'instrument_id': symbol,
|
||||
# 'from': 0,
|
||||
# 'to': 2,
|
||||
# 'limit': 100,
|
||||
},
|
||||
callback=self._onQueryOrders,
|
||||
)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def qryAccount(self):
|
||||
"""查询账户资金
|
||||
Okex 的API变化太大,不单独实现API了,所有东西都封装在这里面
|
||||
"""
|
||||
return self.restClient.addRequest('GET', '/api/futures/v3/accounts',
|
||||
callback=self._onQryAccounts,
|
||||
)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onQryAccounts(self, data, _):
|
||||
if 'info' not in data:
|
||||
raise ApiError("unable to parse account data")
|
||||
|
||||
for easySymbol, detail in data['info'].items(): # type: str, dict
|
||||
acc = VtAccountData()
|
||||
acc.gatewayName = self.gatewayName
|
||||
acc.accountID = easySymbol
|
||||
acc.vtAccountID = acc.gatewayName + '.' + acc.accountID
|
||||
|
||||
acc.balance = detail.get('equity', 0)
|
||||
acc.available = detail['total_avail_balance']
|
||||
if 'contracts' in detail:
|
||||
keys = {'available_qty': 'available_qty',
|
||||
'fixed_balance': 'fixed_balance',
|
||||
'margin_for_unfilled': 'margin',
|
||||
'margin_frozen': 'margin',
|
||||
'realized_pnl': 'realized_pnl',
|
||||
'unrealized_pnl': 'unrealized_pnl'}
|
||||
for v in keys.values():
|
||||
detail[v] = 0.0
|
||||
for c in detail['contracts']:
|
||||
for k, v in keys.items():
|
||||
detail[v] += float(c[k])
|
||||
acc.margin = detail['margin']
|
||||
acc.positionProfit = data.get('unrealized_pnl', 0)
|
||||
acc.closeProfit = data.get('realized_pnl', 0)
|
||||
self.onAccount(acc)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def qryPosition(self):
|
||||
"""查询持仓"""
|
||||
return self.restClient.addRequest('GET', '/api/futures/v3/position',
|
||||
callback=self._onQueryPosition,
|
||||
)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def close(self):
|
||||
"""关闭"""
|
||||
self.restClient.stop()
|
||||
self.webSocket.stop()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onOrderSent(self, data, request): #type: (dict, Request)->None
|
||||
"""下单回调"""
|
||||
# errorCode = data['error_code'],
|
||||
# errorMessage = data['error_message'],
|
||||
myorder = request.extra # type: Order
|
||||
remoteId = data['order_id']
|
||||
if remoteId != '-1':
|
||||
myorder.remoteId = remoteId
|
||||
myorder.vtOrder.status = constant.STATUS_NOTTRADED
|
||||
self._saveRemoteId(myorder.remoteId, myorder)
|
||||
else:
|
||||
myorder.vtOrder.status = constant.STATUS_REJECTED
|
||||
self.onOrder(myorder.vtOrder)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onSendOrderFailed(self, _, request): # type:(int, Request)->None
|
||||
myorder = request.extra # type: Order
|
||||
myorder.vtOrder.status = constant.STATUS_REJECTED
|
||||
self.onOrder(myorder.vtOrder)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onOrderCanceled(self, data, request): #type: (dict, Request)->None
|
||||
myorder = request.extra # type: Order
|
||||
result = data['result']
|
||||
if result is True:
|
||||
myorder.vtOrder.status = constant.STATUS_CANCELLED
|
||||
self.onOrder(myorder.vtOrder)
|
||||
else:
|
||||
# todo: more detail about error
|
||||
print('failed to cancel order: ' + json.dumps(data))
|
||||
self._writeError('Failed to cancel order {}'.format(myorder.localId))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onCancelOrderFailed(self, _, request): # type:(int, Request)->None
|
||||
myorder = request.extra # type: Order
|
||||
self._writeError(u'Failed to cancel order {}'.format(myorder.localId))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onQueryContracts(self, data, _): #type: (dict, Request)->None
|
||||
|
||||
# 首先建立THISWEEK, NEXTWEEK, QUARTER和相应Contract的映射
|
||||
symbols = set()
|
||||
for contract in data:
|
||||
symbol = contract['instrument_id']
|
||||
symbols.add(symbol)
|
||||
|
||||
# 一般来说,一个币种对有三种到期日期不同的symbol。
|
||||
# 将这三种symbol按顺序排列,就能依次得到ThisWeek, NextWeek和Quarter三种symbol
|
||||
s2 = defaultdict(list)
|
||||
for symbol in sorted(symbols):
|
||||
easySymbol = symbol[:3]
|
||||
s2[easySymbol].append(symbol)
|
||||
|
||||
# 按顺序取出排列好的symbols,对应上ThisWeek, NextWeek和Quarter
|
||||
# 然后记录下来他们的几种symbols形式和相应的一些常量:
|
||||
# v1Symbol: BTC_USD_THISWEEK
|
||||
# v3Symbol: BTC_USD_181222
|
||||
# easySymbol: btc, eth, ...
|
||||
# remoteContractType: this_week, next_week, ...
|
||||
# localContractType: THISWEEK, NEXTWEEK, ...
|
||||
|
||||
symbolDict = {}
|
||||
for easySymbol, sortedSymbols in s2.items():
|
||||
if len(sortedSymbols) == 3:
|
||||
for contractType, v3symbol in zip(_contractTypeMap.keys(), sortedSymbols):
|
||||
uiSymbol = '{}_USD_{}'.format(easySymbol, contractType) # ETC_USD_THISWEEK
|
||||
remoteContractName = '{}{}'.format(easySymbol, v3symbol[-4:]) # ETC1201
|
||||
|
||||
s = Symbol()
|
||||
s.v1Symbol = '{}_{}'.format(easySymbol.lower(), "usd")
|
||||
s.v3 = v3symbol
|
||||
s.easySymbol = easySymbol
|
||||
s.remoteContractType = _contractTypeMap[contractType]
|
||||
s.localContractTYpe = contractType
|
||||
s.uiSymbol = uiSymbol
|
||||
|
||||
# normal map
|
||||
symbolDict[uiSymbol.upper()] = s
|
||||
symbolDict[uiSymbol.lower()] = s
|
||||
symbolDict[uiSymbol] = s
|
||||
symbolDict[v3symbol] = s
|
||||
|
||||
# switch between '-' and '_'
|
||||
symbolDict[uiSymbol.upper().replace('_', '-')] = s
|
||||
symbolDict[uiSymbol.lower().replace('_', '-')] = s
|
||||
symbolDict[uiSymbol.replace('_', '-')] = s
|
||||
symbolDict[v3symbol.replace('-', '_')] = s
|
||||
|
||||
# BTCUSD181228 BTCUSDTHISWEEK, btcusdthisweek
|
||||
symbolDict[v3symbol.upper().replace('-', '')] = s
|
||||
symbolDict[uiSymbol.upper().replace('_', '')] = s
|
||||
symbolDict[uiSymbol.lower().replace('_', '')] = s
|
||||
|
||||
symbolDict[remoteContractName.upper()] = s
|
||||
symbolDict[remoteContractName.lower()] = s
|
||||
|
||||
# unicode and str
|
||||
for k, v in symbolDict.items():
|
||||
self._symbolDict[str(k)] = v
|
||||
self._symbolDict[unicode(k)] = v
|
||||
|
||||
# 其次响应onContract,也是该函数的本职工作
|
||||
for contract in data:
|
||||
symbol = contract['instrument_id']
|
||||
size = contract['quote_increment'] if 'quote_increment' in contract else contract[
|
||||
'trade_increment'],
|
||||
vtContract = VtContractData.createFromGateway(
|
||||
gateway=self,
|
||||
exchange=self.exchange,
|
||||
symbol=symbol,
|
||||
productClass=constant.PRODUCT_FUTURES,
|
||||
priceTick=contract['tick_size'],
|
||||
size=size,
|
||||
name=symbol,
|
||||
expiryDate=contract['delivery'],
|
||||
underlyingSymbol=contract['underlying_index'],
|
||||
)
|
||||
self.onContract(vtContract)
|
||||
|
||||
# 最后订阅symbols,还有查询其他东西
|
||||
for symbol in self.symbols:
|
||||
s = self.parseSymbol(symbol)
|
||||
# noinspection PyTypeChecker
|
||||
req = VtSubscribeReq()
|
||||
req.symbol = s.v3
|
||||
self.subscribe(req)
|
||||
|
||||
# 查询账户啊,持仓啊,委托单啊之类的东西
|
||||
self.qryAccount()
|
||||
self.qryPosition()
|
||||
|
||||
# 查询所有未成交的委托
|
||||
# v3 API尚未支持该操作
|
||||
# for symbol in symbols:
|
||||
# # noinspection PyTypeChecker
|
||||
# self.queryOrders(symbol, OkexFuturesOrderStatus.NotTraded)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onQueryOrders(self, data, _): #type: (dict, Request)->None
|
||||
if data['result'] is True:
|
||||
for info in data['orders']:
|
||||
remoteId = info['order_id']
|
||||
tradedVolume = info['filled_qty']
|
||||
|
||||
myorder = self._getOrderByRemoteId(remoteId)
|
||||
if myorder:
|
||||
# 如果订单已经缓存在本地,则尝试更新订单状态
|
||||
|
||||
# 有新交易才推送更新
|
||||
if tradedVolume != myorder.vtOrder.tradedVolume:
|
||||
myorder.vtOrder.tradedVolume = tradedVolume
|
||||
myorder.vtOrder.status = constant.STATUS_PARTTRADED
|
||||
self.onOrder(myorder.vtOrder)
|
||||
else:
|
||||
# 本地无此订单的缓存(例如,用其他工具的下单)
|
||||
# 缓存该订单,并推送
|
||||
symbol = info['instrument_id']
|
||||
direction, offset = remoteOrderTypeToLocal(info['type'])
|
||||
myorder = self._generateLocalOrder(symbol,
|
||||
info['price'],
|
||||
info['size'],
|
||||
direction,
|
||||
offset)
|
||||
myorder.vtOrder.tradedVolume = tradedVolume
|
||||
myorder.remoteId = remoteId
|
||||
self._saveRemoteId(myorder.remoteId, myorder)
|
||||
self.onOrder(myorder.vtOrder)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _onQueryPosition(self, data, _): #type: (dict, Request)->None
|
||||
if 'holding' in data:
|
||||
posex = data['holding']
|
||||
elif 'position' in data:
|
||||
posex = data['position']
|
||||
else:
|
||||
raise ApiError("Failed to parse position data")
|
||||
for pos in posex:
|
||||
symbol = self.parseSymbol(pos['instrument_id']).uiSymbol
|
||||
|
||||
# 多头持仓
|
||||
vtPos = VtPositionData.createFromGateway(
|
||||
gateway=self,
|
||||
exchange=self.exchange,
|
||||
symbol=symbol,
|
||||
direction=constant.DIRECTION_NET,
|
||||
position=pos['long_qty'],
|
||||
price=pos['long_avg_cost'],
|
||||
)
|
||||
self.onPosition(vtPos)
|
||||
|
||||
# 多头持仓
|
||||
vtPos = VtPositionData.createFromGateway(
|
||||
gateway=self,
|
||||
exchange=self.exchange,
|
||||
symbol=symbol,
|
||||
direction=constant.DIRECTION_NET,
|
||||
position=pos['short_qty'],
|
||||
price=pos['short_avg_cost'],
|
||||
)
|
||||
self.onPosition(vtPos)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
@staticmethod
|
||||
def webSocketUnpackData(data):
|
||||
"""重载websocket.unpackData"""
|
||||
return json.loads(zlib.decompress(data, -zlib.MAX_WBITS))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onWebSocketPacket(self, packets):
|
||||
for packet in packets:
|
||||
channelName = None
|
||||
if 'channel' in packet:
|
||||
channelName = packet['channel']
|
||||
if not channelName or channelName == 'addChannel':
|
||||
return
|
||||
|
||||
data = packet['data']
|
||||
channel = parseChannel(channelName) # type: Channel
|
||||
if not channel:
|
||||
print("unknown websocket channel : ", json.dumps(packet, indent=2))
|
||||
return
|
||||
try:
|
||||
if channel.type == ChannelType.Tick:
|
||||
uiSymbol = remoteSymbolToLocal(channel.symbol,
|
||||
remoteContractTypeToLocal(
|
||||
channel.remoteContractType))
|
||||
if self._lastTicker is None:
|
||||
self._lastTicker = VtTickData.createFromGateway(
|
||||
gateway=self,
|
||||
symbol=uiSymbol,
|
||||
exchange=self.exchange,
|
||||
lastPrice=float(data['last']),
|
||||
lastVolume=float(data['vol']),
|
||||
highPrice=float(data['high']),
|
||||
lowPrice=float(data['low']),
|
||||
openInterest=float(data['hold_amount']),
|
||||
lowerLimit=float(data['limitLow']),
|
||||
upperLimit=float(data['limitHigh']),
|
||||
)
|
||||
else:
|
||||
self._lastTicker.lastPrice = float(data['last'])
|
||||
self._lastTicker.lastVolume = float(data['vol'])
|
||||
self._lastTicker.highPrice = float(data['high'])
|
||||
self._lastTicker.lowPrice = float(data['low'])
|
||||
self._lastTicker.openInterest = float(data['hold_amount'])
|
||||
self._lastTicker.lowerLimit = float(data['limitLow'])
|
||||
self._lastTicker.upperLimit = float(data['limitHigh'])
|
||||
self._lastTicker.datetime = datetime.now()
|
||||
self._lastTicker.date = self._lastTicker.datetime.strftime('%Y%m%d')
|
||||
self._lastTicker.time = self._lastTicker.datetime.strftime('%H:%M:%S')
|
||||
self.onTick(self._lastTicker)
|
||||
elif channel.type == ChannelType.Depth:
|
||||
|
||||
asks = data['asks']
|
||||
bids = data['bids']
|
||||
if self._lastTicker is not None:
|
||||
timestamp = float(data['timestamp'])
|
||||
ts = datetime.utcfromtimestamp(timestamp/1000) + self._utcOffset
|
||||
|
||||
self._lastTicker.askPrice1 = asks[0][0]
|
||||
self._lastTicker.askPrice2 = asks[1][0]
|
||||
self._lastTicker.askPrice3 = asks[2][0]
|
||||
self._lastTicker.askPrice4 = asks[3][0]
|
||||
self._lastTicker.askPrice5 = asks[4][0]
|
||||
self._lastTicker.askVolume1 = asks[0][1]
|
||||
self._lastTicker.askVolume2 = asks[1][1]
|
||||
self._lastTicker.askVolume3 = asks[2][1]
|
||||
self._lastTicker.askVolume4 = asks[3][1]
|
||||
self._lastTicker.askVolume5 = asks[4][1]
|
||||
|
||||
self._lastTicker.bidPrice1 = bids[0][0]
|
||||
self._lastTicker.bidPrice2 = bids[1][0]
|
||||
self._lastTicker.bidPrice3 = bids[2][0]
|
||||
self._lastTicker.bidPrice4 = bids[3][0]
|
||||
self._lastTicker.bidPrice5 = bids[4][0]
|
||||
self._lastTicker.bidVolume1 = bids[0][1]
|
||||
self._lastTicker.bidVolume2 = bids[1][1]
|
||||
self._lastTicker.bidVolume3 = bids[2][1]
|
||||
self._lastTicker.bidVolume4 = bids[3][1]
|
||||
self._lastTicker.bidVolume5 = bids[4][1]
|
||||
self._lastTicker.datetime = ts
|
||||
self._lastTicker.date = self._lastTicker.datetime.strftime('%Y%m%d')
|
||||
self._lastTicker.time = self._lastTicker.datetime.strftime('%H:%M:%S')
|
||||
self.onTick(self._lastTicker)
|
||||
elif channel.type == ChannelType.Position:
|
||||
symbol = data['symbol']
|
||||
positions = data['positions']
|
||||
for pos in positions:
|
||||
if pos['position'] == '1':
|
||||
direction = constant.DIRECTION_LONG
|
||||
else:
|
||||
direction = constant.DIRECTION_SHORT
|
||||
total = pos['hold_amount']
|
||||
usable = pos['eveningup']
|
||||
# margin = _tryGetValue(pos, 'margin', 'fixmargin')
|
||||
profit = _tryGetValue(pos, 'profitreal', 'realized')
|
||||
symbol = self.parseSymbol(pos['contract_name']).uiSymbol
|
||||
self.onPosition(VtPositionData.createFromGateway(
|
||||
gateway=self,
|
||||
exchange=self.exchange,
|
||||
symbol=symbol,
|
||||
direction=direction,
|
||||
position=total,
|
||||
frozen=total - usable,
|
||||
price=pos['avgprice'],
|
||||
profit=profit,
|
||||
))
|
||||
elif channel.type == ChannelType.UserInfo:
|
||||
# ws 的acc没有分货币,没法用
|
||||
pass
|
||||
elif channel.type == ChannelType.UserTrade:
|
||||
tradeID = str(self.tradeID)
|
||||
self.tradeID += 1
|
||||
order = self._getOrderByRemoteId(data['orderid'])
|
||||
if order:
|
||||
self.onTrade(VtTradeData.createFromOrderData(
|
||||
order=order.vtOrder,
|
||||
tradeID=tradeID,
|
||||
tradePrice=data['price'],
|
||||
|
||||
# todo: 这里应该填写的到底是order总共成交了的数量,还是该次trade成交的数量
|
||||
tradeVolume=data['deal_amount'],
|
||||
))
|
||||
else:
|
||||
# todo: 与order无关联的trade该如何处理?
|
||||
# uiSymbol = remoteSymbolToLocal(info.symbol,
|
||||
# remoteContractTypeToLocal(info.remoteContractType))
|
||||
pass
|
||||
except KeyError:
|
||||
print("WebSocket error: parsing {}:\n{}".format(channelName, data))
|
||||
traceback.print_exception(*sys.exc_info())
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# noinspection PyUnusedLocal
|
||||
def onApiError(self, exceptionType, exceptionValue, tb,
|
||||
request=None # type: Request
|
||||
):
|
||||
msg = traceback.format_exception(exceptionType, exceptionValue, tb)
|
||||
self._writeError(msg)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onApiFailed(self, _, request): # type:(int, Request)->None
|
||||
self._writeError(str(request))
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def setQryEnabled(self, _):
|
||||
"""dummy function"""
|
||||
pass
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def localOrderTypeToRemote(direction, offset): # type: (str, str)->str
|
||||
return _orderTypeMap[(direction, offset)]
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def remoteOrderTypeToLocal(orderType): # type: (str)->(str, str)
|
||||
"""
|
||||
:param orderType:
|
||||
:return: direction, offset
|
||||
"""
|
||||
return _orderTypeMapReverse[orderType]
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def localContractTypeToRemote(localContractType):
|
||||
return _contractTypeMap[localContractType]
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def remoteContractTypeToLocal(remoteContractType):
|
||||
return _contractTypeMapReverse[remoteContractType]
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def localSymbolToRemote(symbol): # type: (str)->(OkexFuturesSymbol, OkexFuturesContractType)
|
||||
"""
|
||||
:return: remoteSymbol, remoteContractType
|
||||
"""
|
||||
return _symbolsForUi[symbol]
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def remoteSymbolToLocal(remoteSymbol, localContractType):
|
||||
return remoteSymbol.upper() + '_' + localContractType
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def remotePrefixToRemoteContractType(prefix):
|
||||
return _prefixForRemoteContractType[prefix]
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def parseChannel(channel): # type: (str)->Channel
|
||||
if channel == 'login':
|
||||
return Channel(ChannelType.Login)
|
||||
|
||||
# 还未提供订阅的channel都注释掉
|
||||
# elif channel[4:12] == 'forecast': # eg: 'btc_forecast_price'
|
||||
# return SymbolChannel(ChannelType.ForecastPrice, channel[:3])
|
||||
|
||||
sp = channel.split('_')
|
||||
if sp[-1] == 'trades': # eg: 'ok_sub_futureusd_trades'
|
||||
return Channel(ChannelType.UserTrade)
|
||||
# if sp[-1] == 'userinfo': # eg: 'ok_sub_futureusd_btc_userinfo'
|
||||
# return Channel(ChannelType.UserInfo)
|
||||
# if sp[-1] == 'index': # eg: 'ok_sub_futureusd_btc_index'
|
||||
# return SymbolChannel(ChannelType.Index, channel[17:20])
|
||||
if sp[-1] == 'positions': # eg: 'ok_sub_futureusd_positions'
|
||||
return Channel(ChannelType.Position)
|
||||
|
||||
if sp[-1] == 'userinfo': # eg: 'ok_sub_futureusd_positions'
|
||||
return Channel(ChannelType.UserInfo)
|
||||
|
||||
lsp = len(sp)
|
||||
if sp[-1] == 'quarter':
|
||||
if lsp == 7:
|
||||
_, _, _, easySymbol, crash, typeName, contractTypePrefix = sp
|
||||
return Channel(ChannelType.Tick,
|
||||
easySymbol + '_' + crash,
|
||||
remotePrefixToRemoteContractType(contractTypePrefix))
|
||||
elif sp[-1] == 'week':
|
||||
if lsp == 8:
|
||||
_, _, _, easySymbol, crash, typeName, contractTypePrefix, _ = sp
|
||||
return Channel(ChannelType.Tick,
|
||||
easySymbol + '_' + crash,
|
||||
remotePrefixToRemoteContractType(contractTypePrefix))
|
||||
if sp[-1] == '5':
|
||||
if lsp == 7: # eg "ok_sub_futureusd_eth_usd_depthquarter_5"
|
||||
_, _, _, easySymbol, crash, typeName_contractTypePrefix, depth = sp
|
||||
return Channel(ChannelType.Depth, easySymbol + '_' + crash,
|
||||
remotePrefixToRemoteContractType(typeName_contractTypePrefix[5:]),
|
||||
depth)
|
||||
if lsp == 8: # eg "ok_sub_futureusd_eth_usd_depthnext_week_5"
|
||||
_, _, _, easySymbol, crash, typeName_contractTypePrefix, _, depth = sp
|
||||
return Channel(ChannelType.Depth, easySymbol + '_' + crash,
|
||||
remotePrefixToRemoteContractType(typeName_contractTypePrefix[5:]),
|
||||
depth)
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _tryGetValue(dict, *keys):
|
||||
"""尝试从字典中获取某些键中的一个"""
|
||||
for k in keys:
|
||||
if k in dict:
|
||||
return dict[k]
|
||||
return None
|
||||
|
||||
|
||||
_prefixForRemoteContractType = {v.split('_')[0]: v for k, v in
|
||||
OkexFuturesContractType.__dict__.items() if
|
||||
not k.startswith('_')}
|
||||
|
||||
_orderTypeMap = {
|
||||
(constant.DIRECTION_LONG, constant.OFFSET_OPEN): OkexFuturesOrderType.OpenLong,
|
||||
(constant.DIRECTION_SHORT, constant.OFFSET_OPEN): OkexFuturesOrderType.OpenShort,
|
||||
(constant.DIRECTION_LONG, constant.OFFSET_CLOSE): OkexFuturesOrderType.CloseLong,
|
||||
(constant.DIRECTION_SHORT, constant.OFFSET_CLOSE): OkexFuturesOrderType.CloseShort,
|
||||
}
|
||||
_orderTypeMapReverse = {v: k for k, v in _orderTypeMap.items()}
|
||||
|
||||
_contractTypeMap = {
|
||||
k.upper(): v for k, v in OkexFuturesContractType.__dict__.items() if not k.startswith('_')
|
||||
}
|
||||
_contractTypeMapReverse = {v: k for k, v in _contractTypeMap.items()}
|
||||
|
||||
_easySymbols = {
|
||||
v for k, v in OkexFuturesEasySymbol.__dict__.items() if not k.startswith('_')
|
||||
}
|
||||
|
||||
_remoteSymbols = {
|
||||
v for k, v in OkexFuturesSymbol.__dict__.items() if not k.startswith('_')
|
||||
}
|
||||
|
||||
# symbols for ui,
|
||||
# keys:给用户看的symbols : f"{internalSymbol}_{contractType}"
|
||||
# values: API接口使用的symbol和contractType字段
|
||||
_symbolsForUi = {(remoteSymbol.upper() + '_' + upperContractType.upper())
|
||||
: (remoteSymbol, remoteContractType)
|
||||
for remoteSymbol in _remoteSymbols
|
||||
for upperContractType, remoteContractType in
|
||||
_contractTypeMap.items()
|
||||
} # type: Dict[str, List[str, str]]
|
||||
_symbolsForUiReverse = {v: k for k, v in _symbolsForUi.items()}
|
||||
|
||||
_channel_for_subscribe = {
|
||||
'ok_sub_futureusd_' + easySymbol + '_ticker_' + remoteContractType
|
||||
: (easySymbol, remoteContractType)
|
||||
for easySymbol in _easySymbols
|
||||
for remoteContractType in _contractTypeMap.values()
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"exchange": "huobi",
|
||||
"accessKey": "",
|
||||
"secretKey": "",
|
||||
"symbols": ["btcusdt","ethusdt","ethbtc"]
|
||||
|
6
examples/VnTrader/HUOBI_connect.json
Normal file
6
examples/VnTrader/HUOBI_connect.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"exchange": "huobi",
|
||||
"accessKey": "",
|
||||
"secretKey": "",
|
||||
"symbols": ["btcusdt","ethusdt","ethbtc"]
|
||||
}
|
@ -25,7 +25,7 @@ from vnpy.trader.uiQt import createQApp
|
||||
from vnpy.trader.uiMainWindow import MainWindow
|
||||
|
||||
# 加载底层接口
|
||||
from vnpy.trader.gateway import (ctpGateway, ibGateway)
|
||||
from vnpy.trader.gateway import (ctpGateway, ibGateway, huobiGateway)
|
||||
|
||||
if system == 'Linux':
|
||||
from vnpy.trader.gateway import xtpGateway
|
||||
@ -54,6 +54,7 @@ def main():
|
||||
# 添加交易接口
|
||||
me.addGateway(ctpGateway)
|
||||
me.addGateway(ibGateway)
|
||||
me.addGateway(huobiGateway)
|
||||
|
||||
if system == 'Windows':
|
||||
me.addGateway(femasGateway)
|
||||
|
@ -48,6 +48,7 @@ class Request(object):
|
||||
statusCode = 'terminated'
|
||||
else:
|
||||
statusCode = self.response.status_code
|
||||
# todo: encoding error
|
||||
return ("reuqest : {} {} {} because {}: \n"
|
||||
"headers: {}\n"
|
||||
"params: {}\n"
|
||||
|
@ -1,8 +1,5 @@
|
||||
{
|
||||
"exchange": "hadax",
|
||||
"accessKey": "请在火币申请",
|
||||
"secretKey": "请在火币申请",
|
||||
"symbols": ["aaceth"],
|
||||
"proxyHost": "127.0.0.1",
|
||||
"proxyPort": 1080
|
||||
"accessKey": "",
|
||||
"secretKey": "",
|
||||
"symbols": ["btcusdt","ethusdt","ethbtc"]
|
||||
}
|
@ -8,4 +8,4 @@ gatewayClass = HuobiGateway
|
||||
gatewayName = 'HUOBI'
|
||||
gatewayDisplayName = u'火币'
|
||||
gatewayType = vtConstant.GATEWAYTYPE_BTC
|
||||
gatewayQryEnabled = True
|
||||
gatewayQryEnabled = False
|
File diff suppressed because it is too large
Load Diff
@ -197,6 +197,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def openAppByName(self, appName):
|
||||
""""""
|
||||
detail = [i for i in self.appDetailList if i['appName'] == appName][0]
|
||||
return self.openAppByDetail(detail)
|
||||
|
||||
@ -206,13 +207,15 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
:return 返回app的窗口
|
||||
"""
|
||||
appName = appDetail['appName']
|
||||
try:
|
||||
self.widgetDict[appName].show()
|
||||
except KeyError:
|
||||
appEngine = self.mainEngine.getApp(appName)
|
||||
self.widgetDict[appName] = appDetail['appWidget'](appEngine, self.eventEngine)
|
||||
self.widgetDict[appName].show()
|
||||
return self.widgetDict[appName]
|
||||
if appName not in self.widgetDict:
|
||||
self.widgetDict[appName] = appDetail['appWidget'](self.mainEngine.getApp(appName),
|
||||
self.eventEngine)
|
||||
app = self.widgetDict[appName] # type: QtWidgets.QWidget
|
||||
app.show()
|
||||
app.resize(app.size()) # 修正最大化后的空白
|
||||
app.raise_() # 移到前台
|
||||
app.activateWindow() # 移到前台并获取焦点
|
||||
return app
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def createOpenAppFunction(self, appDetail):
|
||||
|
Loading…
Reference in New Issue
Block a user