From 46afeb57d49324586fd7fba1031a40ad8a948c34 Mon Sep 17 00:00:00 2001 From: msincenselee Date: Sun, 18 Mar 2018 23:15:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Eokex=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/api/okex/README.md | 41 + vnpy/api/okex/__init__.py | 3 + vnpy/api/okex/okexData.py | 705 +++++ vnpy/api/okex/test.py | 51 + vnpy/api/okex/vnokex.py | 735 +++++ .../gateway/okexGateway/OKEX_connect.json | 6 + vnpy/trader/gateway/okexGateway/__init__.py | 11 + .../trader/gateway/okexGateway/okexGateway.py | 2664 +++++++++++++++++ 8 files changed, 4216 insertions(+) create mode 100644 vnpy/api/okex/README.md create mode 100644 vnpy/api/okex/__init__.py create mode 100644 vnpy/api/okex/okexData.py create mode 100644 vnpy/api/okex/test.py create mode 100644 vnpy/api/okex/vnokex.py create mode 100644 vnpy/trader/gateway/okexGateway/OKEX_connect.json create mode 100644 vnpy/trader/gateway/okexGateway/__init__.py create mode 100644 vnpy/trader/gateway/okexGateway/okexGateway.py diff --git a/vnpy/api/okex/README.md b/vnpy/api/okex/README.md new file mode 100644 index 00000000..b7c733db --- /dev/null +++ b/vnpy/api/okex/README.md @@ -0,0 +1,41 @@ +### 简介 + +OKEX的比特币交易接口,基于Websocket API开发,实现了以下功能: + +1. 发送、撤销委托 + +2. 查询委托、持仓、资金、成交历史 + +3. 实时行情、成交、资金更新的推送 + +币币交易WebSocket服务连接地址:wss://real.okex.com:10441/websocket + +发送请求 +请求数据格式为: +{'event':'addChannel','channel':'channelValue','parameters':{'api_key':'value1','sign':'value2'}} +其中 +event: addChannel(注册请求数据)/removeChannel(注销请求数据) +channel: OKEx提供请求数据类型 +parameters: 参数为选填参数,其中api_key为用户申请的APIKEY,sign为签名字符串,签名规则参照请求说明 +binary: 参数为选填参数,是否为压缩数据。1 压缩数据;0 原始数据;默认0 + +例如: websocket.send("{'event':'addChannel','channel':'ok_sub_spot_usd_btc_ticker','binary','1'}") +websocket.send("[{'event':'addChannel','channel':'ok_sub_spot_usd_btc_ticker'},{'event':'addChannel','channel':'ok_sub_spot_usd_btc_depth'},{'event':'addChannel','channel':'ok_sub_spot_usd_btc_trades'}]"),支持批量注册 + +服务器响应 +返回数据格式为: [{"channel":"channel","success":"","errorcode":"","data":{}}, {"channel":"channel","success":"","errorcode":1,"data":{}}] +其中 +channel: 请求的数据类型 +result: true成功,false失败(用于WebSocket 交易API) data: 返回结果数据 +errorcode: 错误码(用于WebSocket 交易API) + +推送过程说明 +为保证推送的及时性及减少流量,行情数据(ticker)和委托深度(depth)这两种数据类型只会在数据发生变化的情况下才会推送数据,交易记录(trades)是推送从上次推送到本次推送产生的增量数据。 + +如何判断连接是否断开 +OKEx通过心跳机制解决这个问题。客户端每30秒发送一次心跳数据:{'event':'ping'},服务器会响应客户端:{"event":"pong"}以此来表明客户端和服务端保持正常连接。如果客户端未接到服务端响应的心跳数据则需要客户端重新建立连接。 + +### API信息 + +链接:[https://github.com/okcoin-okex/OKEx.com-api-docs/](https://github.com/okcoin-okex/OKEx.com-api-docs/) + diff --git a/vnpy/api/okex/__init__.py b/vnpy/api/okex/__init__.py new file mode 100644 index 00000000..cf2a383b --- /dev/null +++ b/vnpy/api/okex/__init__.py @@ -0,0 +1,3 @@ +# encoding: UTF-8 + +from vnpy.api.okex.vnokex import WsSpotApi, WsFuturesApi,SPOT_SYMBOL, CONTRACT_SYMBOL, SPOT_CURRENCY,CONTRACT_TYPE \ No newline at end of file diff --git a/vnpy/api/okex/okexData.py b/vnpy/api/okex/okexData.py new file mode 100644 index 00000000..9eb73ec0 --- /dev/null +++ b/vnpy/api/okex/okexData.py @@ -0,0 +1,705 @@ +# encoding: UTF-8 + +# 币币最小交易量 +# key: symbol_pair, value: min_size +# ----------------------------------------- +SPOT_TRADE_SIZE_DICT = {} + +SPOT_TRADE_SIZE_DICT['eth_usdt'] = 0.0001 +SPOT_TRADE_SIZE_DICT['btc_usdt'] = 0.0001 +SPOT_TRADE_SIZE_DICT['bch_btc'] = 0.001 +SPOT_TRADE_SIZE_DICT['bt2_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['etc_eth'] = 0.01 +SPOT_TRADE_SIZE_DICT['btg_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['ltc_usdt'] = 0.001 +SPOT_TRADE_SIZE_DICT['etc_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['bch_usdt'] = 0.001 +SPOT_TRADE_SIZE_DICT['qtum_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['qtum_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['qtum_eth'] = 0.01 +SPOT_TRADE_SIZE_DICT['neo_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['gas_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['hsr_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['neo_eth'] = 0.01 +SPOT_TRADE_SIZE_DICT['gas_eth'] = 0.01 +SPOT_TRADE_SIZE_DICT['hsr_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['neo_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['gas_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['hsr_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['dash_btc'] = 0.001 +SPOT_TRADE_SIZE_DICT['xrp_btc'] = 1 +SPOT_TRADE_SIZE_DICT['zec_btc'] = 0.001 +SPOT_TRADE_SIZE_DICT['dash_eth'] = 0.001 +SPOT_TRADE_SIZE_DICT['xrp_eth'] = 1 +SPOT_TRADE_SIZE_DICT['zec_eth'] = 0.001 +SPOT_TRADE_SIZE_DICT['dash_usdt'] = 0.001 +SPOT_TRADE_SIZE_DICT['xrp_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['zec_usdt'] = 0.001 +SPOT_TRADE_SIZE_DICT['iota_btc'] = 1 +SPOT_TRADE_SIZE_DICT['xuc_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['iota_eth'] = 1 +SPOT_TRADE_SIZE_DICT['xuc_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['iota_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['xuc_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['eos_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['omg_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['eos_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['omg_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['eos_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['omg_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['act_btc'] = 1 +SPOT_TRADE_SIZE_DICT['btm_btc'] = 1 +SPOT_TRADE_SIZE_DICT['act_eth'] = 1 +SPOT_TRADE_SIZE_DICT['btm_eth'] = 1 +SPOT_TRADE_SIZE_DICT['act_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['btm_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['bcd_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['bcd_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['storj_btc'] = 1 +SPOT_TRADE_SIZE_DICT['snt_btc'] = 10 +SPOT_TRADE_SIZE_DICT['storj_eth'] = 1 +SPOT_TRADE_SIZE_DICT['snt_eth'] = 10 +SPOT_TRADE_SIZE_DICT['storj_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['snt_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['pay_btc'] = 1 +SPOT_TRADE_SIZE_DICT['dgd_btc'] = 0.001 +SPOT_TRADE_SIZE_DICT['gnt_btc'] = 1 +SPOT_TRADE_SIZE_DICT['pay_eth'] = 1 +SPOT_TRADE_SIZE_DICT['dgd_eth'] = 0.001 +SPOT_TRADE_SIZE_DICT['gnt_eth'] = 1 +SPOT_TRADE_SIZE_DICT['pay_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['dgd_usdt'] = 0.001 +SPOT_TRADE_SIZE_DICT['gnt_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['lrc_btc'] = 1 +SPOT_TRADE_SIZE_DICT['nuls_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['mco_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['lrc_eth'] = 1 +SPOT_TRADE_SIZE_DICT['nuls_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['mco_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['lrc_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['nuls_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['mco_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['btg_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['cmt_btc'] = 10 +SPOT_TRADE_SIZE_DICT['itc_btc'] = 1 +SPOT_TRADE_SIZE_DICT['cmt_eth'] = 10 +SPOT_TRADE_SIZE_DICT['itc_eth'] = 1 +SPOT_TRADE_SIZE_DICT['cmt_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['itc_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['sbtc_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['pra_btc'] = 1 +SPOT_TRADE_SIZE_DICT['san_btc'] = 1 +SPOT_TRADE_SIZE_DICT['edo_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['avt_btc'] = 1 +SPOT_TRADE_SIZE_DICT['pra_eth'] = 1 +SPOT_TRADE_SIZE_DICT['san_eth'] = 1 +SPOT_TRADE_SIZE_DICT['edo_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['avt_eth'] = 1 +SPOT_TRADE_SIZE_DICT['pra_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['san_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['edo_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['avt_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['ltc_bch'] = 0.001 +SPOT_TRADE_SIZE_DICT['ltc_eth'] = 0.001 +SPOT_TRADE_SIZE_DICT['etc_bch'] = 0.01 +SPOT_TRADE_SIZE_DICT['btg_bch'] = 0.01 +SPOT_TRADE_SIZE_DICT['avt_bch'] = 1 +SPOT_TRADE_SIZE_DICT['act_bch'] = 1 +SPOT_TRADE_SIZE_DICT['cmt_bch'] = 10 +SPOT_TRADE_SIZE_DICT['dgd_bch'] = 0.001 +SPOT_TRADE_SIZE_DICT['dash_bch'] = 0.001 +SPOT_TRADE_SIZE_DICT['eos_bch'] = 0.1 +SPOT_TRADE_SIZE_DICT['edo_bch'] = 0.1 +SPOT_TRADE_SIZE_DICT['ctr_btc'] = 1 +SPOT_TRADE_SIZE_DICT['link_btc'] = 1 +SPOT_TRADE_SIZE_DICT['salt_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['1st_btc'] = 1 +SPOT_TRADE_SIZE_DICT['wtc_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['sngls_btc'] = 10 +SPOT_TRADE_SIZE_DICT['snm_btc'] = 10 +SPOT_TRADE_SIZE_DICT['zrx_btc'] = 1 +SPOT_TRADE_SIZE_DICT['bnt_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['cvc_btc'] = 1 +SPOT_TRADE_SIZE_DICT['ctr_eth'] = 1 +SPOT_TRADE_SIZE_DICT['link_eth'] = 1 +SPOT_TRADE_SIZE_DICT['salt_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['1st_eth'] = 1 +SPOT_TRADE_SIZE_DICT['wtc_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['sngls_eth'] = 10 +SPOT_TRADE_SIZE_DICT['snm_eth'] = 10 +SPOT_TRADE_SIZE_DICT['zrx_eth'] = 1 +SPOT_TRADE_SIZE_DICT['bnt_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['cvc_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ctr_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['link_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['salt_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['1st_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['wtc_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['sngls_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['snm_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['zrx_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['bnt_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['cvc_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['bcd_bch'] = 0.1 +SPOT_TRADE_SIZE_DICT['sbtc_bch'] = 0.1 +SPOT_TRADE_SIZE_DICT['bcx_btc'] = 10 +SPOT_TRADE_SIZE_DICT['bcx_bch'] = 10 +SPOT_TRADE_SIZE_DICT['mana_btc'] = 10 +SPOT_TRADE_SIZE_DICT['rcn_btc'] = 10 +SPOT_TRADE_SIZE_DICT['mana_eth'] = 10 +SPOT_TRADE_SIZE_DICT['mana_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['vee_btc'] = 10 +SPOT_TRADE_SIZE_DICT['vee_eth'] = 10 +SPOT_TRADE_SIZE_DICT['vee_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['tnb_btc'] = 10 +SPOT_TRADE_SIZE_DICT['tnb_eth'] = 10 +SPOT_TRADE_SIZE_DICT['tnb_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['amm_btc'] = 1 +SPOT_TRADE_SIZE_DICT['amm_eth'] = 1 +SPOT_TRADE_SIZE_DICT['amm_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['knc_btc'] = 1 +SPOT_TRADE_SIZE_DICT['knc_eth'] = 1 +SPOT_TRADE_SIZE_DICT['knc_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['dat_eth'] = 10 +SPOT_TRADE_SIZE_DICT['dat_btc'] = 10 +SPOT_TRADE_SIZE_DICT['dat_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['gnx_btc'] = 1 +SPOT_TRADE_SIZE_DICT['gnx_eth'] = 1 +SPOT_TRADE_SIZE_DICT['gnx_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['icx_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['icx_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['icx_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['ark_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['ark_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['ark_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['yoyo_btc'] = 10 +SPOT_TRADE_SIZE_DICT['yoyo_eth'] = 10 +SPOT_TRADE_SIZE_DICT['yoyo_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['qvt_btc'] = 1 +SPOT_TRADE_SIZE_DICT['qvt_eth'] = 1 +SPOT_TRADE_SIZE_DICT['qvt_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['elf_eth'] = 1 +SPOT_TRADE_SIZE_DICT['elf_btc'] = 1 +SPOT_TRADE_SIZE_DICT['elf_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['ast_btc'] = 1 +SPOT_TRADE_SIZE_DICT['ast_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ast_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['sub_btc'] = 1 +SPOT_TRADE_SIZE_DICT['sub_eth'] = 1 +SPOT_TRADE_SIZE_DICT['sub_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['dnt_btc'] = 10 +SPOT_TRADE_SIZE_DICT['dnt_eth'] = 10 +SPOT_TRADE_SIZE_DICT['dnt_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['fun_btc'] = 10 +SPOT_TRADE_SIZE_DICT['fun_eth'] = 10 +SPOT_TRADE_SIZE_DICT['fun_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ace_btc'] = 10 +SPOT_TRADE_SIZE_DICT['ace_eth'] = 10 +SPOT_TRADE_SIZE_DICT['ace_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['trx_btc'] = 10 +SPOT_TRADE_SIZE_DICT['trx_eth'] = 10 +SPOT_TRADE_SIZE_DICT['trx_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['evx_btc'] = 1 +SPOT_TRADE_SIZE_DICT['evx_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['evx_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['mda_btc'] = 1 +SPOT_TRADE_SIZE_DICT['mda_eth'] = 1 +SPOT_TRADE_SIZE_DICT['mda_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['mth_btc'] = 10 +SPOT_TRADE_SIZE_DICT['mth_eth'] = 10 +SPOT_TRADE_SIZE_DICT['mth_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['mtl_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['mtl_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['mtl_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['xem_btc'] = 1 +SPOT_TRADE_SIZE_DICT['xem_eth'] = 1 +SPOT_TRADE_SIZE_DICT['xem_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['icn_btc'] = 1 +SPOT_TRADE_SIZE_DICT['icn_eth'] = 1 +SPOT_TRADE_SIZE_DICT['icn_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['eng_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['req_btc'] = 1 +SPOT_TRADE_SIZE_DICT['oax_btc'] = 1 +SPOT_TRADE_SIZE_DICT['dgb_btc'] = 10 +SPOT_TRADE_SIZE_DICT['ppt_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['dgb_eth'] = 10 +SPOT_TRADE_SIZE_DICT['dgb_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ppt_eth'] = 0.01 +SPOT_TRADE_SIZE_DICT['ppt_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['oax_eth'] = 1 +SPOT_TRADE_SIZE_DICT['oax_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['req_eth'] = 1 +SPOT_TRADE_SIZE_DICT['req_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['eng_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['eng_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['rcn_eth'] = 10 +SPOT_TRADE_SIZE_DICT['rcn_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['swftc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['swftc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['swftc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['rdn_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['rdn_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['rdn_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['xmr_btc'] = 0.001 +SPOT_TRADE_SIZE_DICT['xmr_eth'] = 0.001 +SPOT_TRADE_SIZE_DICT['xmr_usdt'] = 0.001 +SPOT_TRADE_SIZE_DICT['xlm_btc'] = 1 +SPOT_TRADE_SIZE_DICT['xlm_eth'] = 1 +SPOT_TRADE_SIZE_DICT['xlm_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['kcash_btc'] = 10 +SPOT_TRADE_SIZE_DICT['kcash_eth'] = 10 +SPOT_TRADE_SIZE_DICT['kcash_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['mdt_btc'] = 10 +SPOT_TRADE_SIZE_DICT['mdt_eth'] = 10 +SPOT_TRADE_SIZE_DICT['mdt_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['nas_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['nas_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['nas_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['rnt_btc'] = 10 +SPOT_TRADE_SIZE_DICT['rnt_eth'] = 10 +SPOT_TRADE_SIZE_DICT['rnt_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['wrc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['wrc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['wrc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ukg_btc'] = 1 +SPOT_TRADE_SIZE_DICT['ukg_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ukg_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['ugc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['ugc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['ugc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['dpy_btc'] = 1 +SPOT_TRADE_SIZE_DICT['dpy_eth'] = 1 +SPOT_TRADE_SIZE_DICT['dpy_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['read_btc'] = 10 +SPOT_TRADE_SIZE_DICT['read_eth'] = 10 +SPOT_TRADE_SIZE_DICT['read_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ssc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['ssc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['ssc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['aac_btc'] = 10 +SPOT_TRADE_SIZE_DICT['smt_btc'] = 10 +SPOT_TRADE_SIZE_DICT['fair_btc'] = 10 +SPOT_TRADE_SIZE_DICT['aac_eth'] = 10 +SPOT_TRADE_SIZE_DICT['smt_eth'] = 10 +SPOT_TRADE_SIZE_DICT['aac_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['smt_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['fair_eth'] = 10 +SPOT_TRADE_SIZE_DICT['fair_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ubtc_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['ubtc_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['ubtc_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['cag_btc'] = 1 +SPOT_TRADE_SIZE_DICT['cag_eth'] = 1 +SPOT_TRADE_SIZE_DICT['cag_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['dna_btc'] = 1 +SPOT_TRADE_SIZE_DICT['lend_btc'] = 10 +SPOT_TRADE_SIZE_DICT['lend_eth'] = 10 +SPOT_TRADE_SIZE_DICT['dna_eth'] = 1 +SPOT_TRADE_SIZE_DICT['lend_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['dna_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['rct_btc'] = 10 +SPOT_TRADE_SIZE_DICT['rct_eth'] = 10 +SPOT_TRADE_SIZE_DICT['rct_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['bch_eth'] = 0.001 +SPOT_TRADE_SIZE_DICT['show_btc'] = 10 +SPOT_TRADE_SIZE_DICT['show_eth'] = 10 +SPOT_TRADE_SIZE_DICT['show_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['vib_btc'] = 1 +SPOT_TRADE_SIZE_DICT['vib_eth'] = 1 +SPOT_TRADE_SIZE_DICT['vib_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['mot_btc'] = 1 +SPOT_TRADE_SIZE_DICT['mot_eth'] = 1 +SPOT_TRADE_SIZE_DICT['mot_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['utk_btc'] = 10 +SPOT_TRADE_SIZE_DICT['utk_eth'] = 10 +SPOT_TRADE_SIZE_DICT['utk_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['mag_btc'] = 10 +SPOT_TRADE_SIZE_DICT['mag_eth'] = 10 +SPOT_TRADE_SIZE_DICT['mag_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['topc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['topc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['brd_btc'] = 1 +SPOT_TRADE_SIZE_DICT['topc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['qun_btc'] = 10 +SPOT_TRADE_SIZE_DICT['brd_eth'] = 1 +SPOT_TRADE_SIZE_DICT['qun_eth'] = 10 +SPOT_TRADE_SIZE_DICT['qun_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['brd_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['viu_btc'] = 10 +SPOT_TRADE_SIZE_DICT['viu_eth'] = 10 +SPOT_TRADE_SIZE_DICT['ost_btc'] = 1 +SPOT_TRADE_SIZE_DICT['viu_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ost_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ost_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['aidoc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['aidoc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['aidoc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['int_btc'] = 10 +SPOT_TRADE_SIZE_DICT['la_btc'] = 1 +SPOT_TRADE_SIZE_DICT['la_eth'] = 1 +SPOT_TRADE_SIZE_DICT['la_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['int_eth'] = 10 +SPOT_TRADE_SIZE_DICT['int_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ipc_btc'] = 1 +SPOT_TRADE_SIZE_DICT['ipc_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ipc_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['ngc_btc'] = 1 +SPOT_TRADE_SIZE_DICT['ngc_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ngc_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['tio_btc'] = 1 +SPOT_TRADE_SIZE_DICT['tio_eth'] = 1 +SPOT_TRADE_SIZE_DICT['tio_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['iost_btc'] = 10 +SPOT_TRADE_SIZE_DICT['iost_eth'] = 10 +SPOT_TRADE_SIZE_DICT['iost_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['poe_btc'] = 10 +SPOT_TRADE_SIZE_DICT['poe_eth'] = 10 +SPOT_TRADE_SIZE_DICT['poe_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['mof_btc'] = 1 +SPOT_TRADE_SIZE_DICT['yee_btc'] = 10 +SPOT_TRADE_SIZE_DICT['mof_eth'] = 1 +SPOT_TRADE_SIZE_DICT['yee_eth'] = 10 +SPOT_TRADE_SIZE_DICT['yee_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['mof_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['ins_btc'] = 1 +SPOT_TRADE_SIZE_DICT['ins_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ins_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['tct_btc'] = 10 +SPOT_TRADE_SIZE_DICT['tct_eth'] = 10 +SPOT_TRADE_SIZE_DICT['tct_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['atl_btc'] = 1 +SPOT_TRADE_SIZE_DICT['atl_eth'] = 1 +SPOT_TRADE_SIZE_DICT['atl_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['theta_btc'] = 10 +SPOT_TRADE_SIZE_DICT['theta_eth'] = 10 +SPOT_TRADE_SIZE_DICT['lev_btc'] = 10 +SPOT_TRADE_SIZE_DICT['theta_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['lev_eth'] = 10 +SPOT_TRADE_SIZE_DICT['lev_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['stc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['spf_btc'] = 1 +SPOT_TRADE_SIZE_DICT['stc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['spf_eth'] = 1 +SPOT_TRADE_SIZE_DICT['spf_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['stc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ref_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['ref_eth'] = 0.01 +SPOT_TRADE_SIZE_DICT['ref_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['snc_btc'] = 1 +SPOT_TRADE_SIZE_DICT['snc_eth'] = 1 +SPOT_TRADE_SIZE_DICT['snc_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['pst_btc'] = 1 +SPOT_TRADE_SIZE_DICT['pst_eth'] = 1 +SPOT_TRADE_SIZE_DICT['pst_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['can_btc'] = 10 +SPOT_TRADE_SIZE_DICT['can_eth'] = 10 +SPOT_TRADE_SIZE_DICT['can_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['hot_btc'] = 10 +SPOT_TRADE_SIZE_DICT['mkr_btc'] = 0.001 +SPOT_TRADE_SIZE_DICT['mkr_eth'] = 0.001 +SPOT_TRADE_SIZE_DICT['mkr_usdt'] = 0.001 +SPOT_TRADE_SIZE_DICT['hot_eth'] = 10 +SPOT_TRADE_SIZE_DICT['key_btc'] = 10 +SPOT_TRADE_SIZE_DICT['hot_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['key_eth'] = 10 +SPOT_TRADE_SIZE_DICT['key_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['light_btc'] = 10 +SPOT_TRADE_SIZE_DICT['light_eth'] = 10 +SPOT_TRADE_SIZE_DICT['light_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['true_btc'] = 1 +SPOT_TRADE_SIZE_DICT['true_eth'] = 1 +SPOT_TRADE_SIZE_DICT['true_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['of_btc'] = 10 +SPOT_TRADE_SIZE_DICT['of_eth'] = 10 +SPOT_TRADE_SIZE_DICT['of_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['soc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['soc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['soc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['wbtc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['dent_btc'] = 10 +SPOT_TRADE_SIZE_DICT['dent_eth'] = 10 +SPOT_TRADE_SIZE_DICT['dent_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['zen_btc'] = 0.01 +SPOT_TRADE_SIZE_DICT['zen_eth'] = 0.01 +SPOT_TRADE_SIZE_DICT['zen_usdt'] = 0.01 +SPOT_TRADE_SIZE_DICT['hmc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['hmc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['hmc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['zip_btc'] = 10 +SPOT_TRADE_SIZE_DICT['zip_eth'] = 10 +SPOT_TRADE_SIZE_DICT['zip_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['nano_btc'] = 0.1 +SPOT_TRADE_SIZE_DICT['nano_eth'] = 0.1 +SPOT_TRADE_SIZE_DICT['nano_usdt'] = 0.1 +SPOT_TRADE_SIZE_DICT['cic_btc'] = 10 +SPOT_TRADE_SIZE_DICT['cic_eth'] = 10 +SPOT_TRADE_SIZE_DICT['cic_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['gto_btc'] = 1 +SPOT_TRADE_SIZE_DICT['gto_eth'] = 1 +SPOT_TRADE_SIZE_DICT['gto_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['chat_btc'] = 10 +SPOT_TRADE_SIZE_DICT['chat_eth'] = 10 +SPOT_TRADE_SIZE_DICT['chat_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['insur_btc'] = 10 +SPOT_TRADE_SIZE_DICT['insur_eth'] = 10 +SPOT_TRADE_SIZE_DICT['insur_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['cbt_btc'] = 10 +SPOT_TRADE_SIZE_DICT['cbt_eth'] = 10 +SPOT_TRADE_SIZE_DICT['cbt_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['r_btc'] = 1 +SPOT_TRADE_SIZE_DICT['r_eth'] = 1 +SPOT_TRADE_SIZE_DICT['r_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['uct_btc'] = 10 +SPOT_TRADE_SIZE_DICT['uct_eth'] = 1 +SPOT_TRADE_SIZE_DICT['uct_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['bec_btc'] = 1 +SPOT_TRADE_SIZE_DICT['bec_eth'] = 1 +SPOT_TRADE_SIZE_DICT['bec_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['mith_btc'] = 1 +SPOT_TRADE_SIZE_DICT['mith_eth'] = 1 +SPOT_TRADE_SIZE_DICT['mith_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['abt_btc'] = 1 +SPOT_TRADE_SIZE_DICT['abt_eth'] = 1 +SPOT_TRADE_SIZE_DICT['abt_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['bkx_btc'] = 1 +SPOT_TRADE_SIZE_DICT['bkx_eth'] = 1 +SPOT_TRADE_SIZE_DICT['bkx_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['gtc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['gtc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['gtc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['auto_btc'] = 10 +SPOT_TRADE_SIZE_DICT['auto_eth'] = 10 +SPOT_TRADE_SIZE_DICT['auto_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['gsc_btc'] = 10 +SPOT_TRADE_SIZE_DICT['gsc_eth'] = 10 +SPOT_TRADE_SIZE_DICT['gsc_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['rfr_btc'] = 10 +SPOT_TRADE_SIZE_DICT['rfr_eth'] = 10 +SPOT_TRADE_SIZE_DICT['rfr_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['trio_btc'] = 10 +SPOT_TRADE_SIZE_DICT['trio_eth'] = 10 +SPOT_TRADE_SIZE_DICT['trio_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['tra_btc'] = 10 +SPOT_TRADE_SIZE_DICT['tra_eth'] = 10 +SPOT_TRADE_SIZE_DICT['tra_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ren_btc'] = 10 +SPOT_TRADE_SIZE_DICT['ren_eth'] = 10 +SPOT_TRADE_SIZE_DICT['ren_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['wfee_btc'] = 10 +SPOT_TRADE_SIZE_DICT['wfee_eth'] = 10 +SPOT_TRADE_SIZE_DICT['wfee_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['dadi_btc'] = 1 +SPOT_TRADE_SIZE_DICT['dadi_eth'] = 1 +SPOT_TRADE_SIZE_DICT['dadi_usdt'] = 1 +SPOT_TRADE_SIZE_DICT['enj_btc'] = 10 +SPOT_TRADE_SIZE_DICT['enj_eth'] = 10 +SPOT_TRADE_SIZE_DICT['enj_usdt'] = 10 +SPOT_TRADE_SIZE_DICT['ont_btc'] = 1 +SPOT_TRADE_SIZE_DICT['ont_eth'] = 1 +SPOT_TRADE_SIZE_DICT['ont_usdt'] = 1 + +# ------------------------------------- +SPOT_ERROR_DICT = {} +SPOT_ERROR_DICT['1000'] = u'必选参数不能为空' +SPOT_ERROR_DICT['1000'] = u'用户请求频率过快,超过该接口允许的限额' +SPOT_ERROR_DICT['1000'] = u'系统错误' +SPOT_ERROR_DICT['1000'] = u'请求失败' +SPOT_ERROR_DICT['1000'] = u'SecretKey不存在' +SPOT_ERROR_DICT['1000'] = u'Api_key不存在' +SPOT_ERROR_DICT['1000'] = u'签名不匹配' +SPOT_ERROR_DICT['1000'] = u'非法参数' +SPOT_ERROR_DICT['1000'] = u'订单不存在' +SPOT_ERROR_DICT['1001'] = u'余额不足' +SPOT_ERROR_DICT['1001'] = u'买卖的数量小于BTC/LTC最小买卖额度' +SPOT_ERROR_DICT['1001'] = u'当前网站暂时只支持btc_usdltc_usd' +SPOT_ERROR_DICT['1001'] = u'此接口只支持https请求' +SPOT_ERROR_DICT['1001'] = u'下单价格不得≤0或≥' +SPOT_ERROR_DICT['1001'] = u'下单价格与最新成交价偏差过大' +SPOT_ERROR_DICT['1001'] = u'币数量不足' +SPOT_ERROR_DICT['1001'] = u'API鉴权失败' +SPOT_ERROR_DICT['1001'] = u'借入不能小于最低限额[SD:100,BTC:0.1,LTC:1]' +SPOT_ERROR_DICT['1001'] = u'页面没有同意借贷协议' +SPOT_ERROR_DICT['1002'] = u'费率不能大于1%' +SPOT_ERROR_DICT['1002'] = u'费率不能小于0.01%' +SPOT_ERROR_DICT['1002'] = u'获取最新成交价错误' +SPOT_ERROR_DICT['1002'] = u'可借金额不足' +SPOT_ERROR_DICT['1002'] = u'额度已满,暂时无法借款' +SPOT_ERROR_DICT['1002'] = u'借款(含预约借款)及保证金部分不能提出' +SPOT_ERROR_DICT['1002'] = u'修改敏感提币验证信息,24小时内不允许提现' +SPOT_ERROR_DICT['1002'] = u'提币金额已超过今日提币限额' +SPOT_ERROR_DICT['1002'] = u'账户有借款,请撤消借款或者还清借款后提币' +SPOT_ERROR_DICT['1003'] = u'存在BTC/LTC充值,该部分等值金额需6个网络确认后方能提出' +SPOT_ERROR_DICT['1003'] = u'未绑定手机或谷歌验证' +SPOT_ERROR_DICT['1003'] = u'服务费大于最大网络手续费' +SPOT_ERROR_DICT['1003'] = u'服务费小于最低网络手续费' +SPOT_ERROR_DICT['1003'] = u'可用BTC/LTC不足' +SPOT_ERROR_DICT['1003'] = u'提币数量小于最小提币数量' +SPOT_ERROR_DICT['1003'] = u'交易密码未设置' +SPOT_ERROR_DICT['1004'] = u'取消提币失败' +SPOT_ERROR_DICT['1004'] = u'提币地址不存在或未认证' +SPOT_ERROR_DICT['1004'] = u'交易密码错误' +SPOT_ERROR_DICT['1004'] = u'合约权益错误,提币失败' +SPOT_ERROR_DICT['1004'] = u'取消借款失败' +SPOT_ERROR_DICT['1004'] = u'当前为子账户,此功能未开放' +SPOT_ERROR_DICT['1004'] = u'提币信息不存在' +SPOT_ERROR_DICT['1004'] = u'小额委托(<0.15BTC)的未成交委托数量不得大于50个' +SPOT_ERROR_DICT['1005'] = u'重复撤单' +SPOT_ERROR_DICT['1005'] = u'提币受限' +SPOT_ERROR_DICT['1006'] = u'美元充值后的48小时内,该部分资产不能提出' +SPOT_ERROR_DICT['1010'] = u'账户被冻结' +SPOT_ERROR_DICT['1010'] = u'订单类型错误' +SPOT_ERROR_DICT['1010'] = u'不是本用户的订单' +SPOT_ERROR_DICT['1010'] = u'私密订单密钥错误' +SPOT_ERROR_DICT['1021'] = u'非开放API' +SPOT_ERROR_DICT['1002'] = u'交易金额大于余额' +SPOT_ERROR_DICT['1003'] = u'交易金额小于最小交易值' +SPOT_ERROR_DICT['1004'] = u'交易金额小于0' +SPOT_ERROR_DICT['1007'] = u'没有交易市场信息' +SPOT_ERROR_DICT['1008'] = u'没有最新行情信息' +SPOT_ERROR_DICT['1009'] = u'没有订单' +SPOT_ERROR_DICT['1010'] = u'撤销订单与原订单用户不一致' +SPOT_ERROR_DICT['1011'] = u'没有查询到该用户' +SPOT_ERROR_DICT['1013'] = u'没有订单类型' +SPOT_ERROR_DICT['1014'] = u'没有登录' +SPOT_ERROR_DICT['1015'] = u'没有获取到行情深度信息' +SPOT_ERROR_DICT['1017'] = u'日期参数错误' +SPOT_ERROR_DICT['1018'] = u'下单失败' +SPOT_ERROR_DICT['1019'] = u'撤销订单失败' +SPOT_ERROR_DICT['1024'] = u'币种不存在' +SPOT_ERROR_DICT['1025'] = u'没有K线类型' +SPOT_ERROR_DICT['1026'] = u'没有基准币数量' +SPOT_ERROR_DICT['1027'] = u'参数不合法可能超出限制' +SPOT_ERROR_DICT['1028'] = u'保留小数位失败' +SPOT_ERROR_DICT['1029'] = u'正在准备中' +SPOT_ERROR_DICT['1030'] = u'有融资融币无法进行交易' +SPOT_ERROR_DICT['1031'] = u'转账余额不足' +SPOT_ERROR_DICT['1032'] = u'该币种不能转账' +SPOT_ERROR_DICT['1035'] = u'密码不合法' +SPOT_ERROR_DICT['1036'] = u'谷歌验证码不合法' +SPOT_ERROR_DICT['1037'] = u'谷歌验证码不正确' +SPOT_ERROR_DICT['1038'] = u'谷歌验证码重复使用' +SPOT_ERROR_DICT['1039'] = u'短信验证码输错限制' +SPOT_ERROR_DICT['1040'] = u'短信验证码不合法' +SPOT_ERROR_DICT['1041'] = u'短信验证码不正确' +SPOT_ERROR_DICT['1042'] = u'谷歌验证码输错限制' +SPOT_ERROR_DICT['1043'] = u'登陆密码不允许与交易密码一致' +SPOT_ERROR_DICT['1044'] = u'原密码错误' +SPOT_ERROR_DICT['1045'] = u'未设置二次验证' +SPOT_ERROR_DICT['1046'] = u'原密码未输入' +SPOT_ERROR_DICT['1048'] = u'用户被冻结' +SPOT_ERROR_DICT['1050'] = u'订单已撤销或者撤单中' +SPOT_ERROR_DICT['1051'] = u'订单已完成交易' +SPOT_ERROR_DICT['1201'] = u'账号零时删除' +SPOT_ERROR_DICT['1202'] = u'账号不存在' +SPOT_ERROR_DICT['1203'] = u'转账金额大于余额' +SPOT_ERROR_DICT['1204'] = u'不同种币种不能转账' +SPOT_ERROR_DICT['1205'] = u'账号不存在主从关系' +SPOT_ERROR_DICT['1206'] = u'提现用户被冻结' +SPOT_ERROR_DICT['1207'] = u'不支持转账' +SPOT_ERROR_DICT['1208'] = u'没有该转账用户' +SPOT_ERROR_DICT['1209'] = u'当前api不可用' +SPOT_ERROR_DICT['1216'] = u'市价交易暂停,请选择限价交易' +SPOT_ERROR_DICT['1217'] = u'您的委托价格超过最新成交价的±5%,存在风险,请重新下单' +SPOT_ERROR_DICT['1218'] = u'下单失败,请稍后再试' + +# -------------------------------------- +FUTURES_ERROR_DICT = {} + +FUTURES_ERROR_DICT['10000'] = u'必填参数为空' +FUTURES_ERROR_DICT['10001'] = u'参数错误' +FUTURES_ERROR_DICT['10002'] = u'验证失败' +FUTURES_ERROR_DICT['10003'] = u'该连接已经请求了其他用户的实时交易数据' +FUTURES_ERROR_DICT['10004'] = u'该连接没有请求此用户的实时交易数据' +FUTURES_ERROR_DICT['10005'] = u'api_key或者sign不合法' +FUTURES_ERROR_DICT['10008'] = u'非法参数' +FUTURES_ERROR_DICT['10009'] = u'订单不存在' +FUTURES_ERROR_DICT['10010'] = u'余额不足' +FUTURES_ERROR_DICT['10011'] = u'卖的数量小于BTC/LTC最小买卖额度' +FUTURES_ERROR_DICT['10012'] = u'当前网站暂时只支持btc_usdltc_usd' +FUTURES_ERROR_DICT['10014'] = u'下单价格不得≤0或≥1000000' +FUTURES_ERROR_DICT['10015'] = u'暂不支持此channel订阅' +FUTURES_ERROR_DICT['10016'] = u'币数量不足' +FUTURES_ERROR_DICT['10017'] = u'WebSocket鉴权失败' +FUTURES_ERROR_DICT['10100'] = u'用户被冻结' +FUTURES_ERROR_DICT['10049'] = u'小额委托(<0.15BTC)的未成交委托数量不得大于50个' +FUTURES_ERROR_DICT['10216'] = u'非开放API' +FUTURES_ERROR_DICT['20001'] = u'用户不存在' +FUTURES_ERROR_DICT['20002'] = u'用户被冻结' +FUTURES_ERROR_DICT['20003'] = u'用户被爆仓冻结' +FUTURES_ERROR_DICT['20004'] = u'合约账户被冻结' +FUTURES_ERROR_DICT['20005'] = u'用户合约账户不存在' +FUTURES_ERROR_DICT['20006'] = u'必填参数为空' +FUTURES_ERROR_DICT['20007'] = u'参数错误' +FUTURES_ERROR_DICT['20008'] = u'合约账户余额为空' +FUTURES_ERROR_DICT['20009'] = u'虚拟合约状态错误' +FUTURES_ERROR_DICT['20010'] = u'合约风险率信息不存在' +FUTURES_ERROR_DICT['20011'] = u'开仓前保证金率超过90%' +FUTURES_ERROR_DICT['20012'] = u'开仓后保证金率超过90%' +FUTURES_ERROR_DICT['20013'] = u'暂无对手价' +FUTURES_ERROR_DICT['20014'] = u'系统错误' +FUTURES_ERROR_DICT['20015'] = u'订单信息不存在' +FUTURES_ERROR_DICT['20016'] = u'平仓数量是否大于同方向可用持仓数量' +FUTURES_ERROR_DICT['20017'] = u'非本人操作' +FUTURES_ERROR_DICT['20018'] = u'下单价格高于前一分钟的105%或低于95%' +FUTURES_ERROR_DICT['20019'] = u'该IP限制不能请求该资源' +FUTURES_ERROR_DICT['20020'] = u'密钥不存在' +FUTURES_ERROR_DICT['20021'] = u'指数信息不存在' +FUTURES_ERROR_DICT['20022'] = u'接口调用错误' +FUTURES_ERROR_DICT['20023'] = u'逐仓用户' +FUTURES_ERROR_DICT['20024'] = u'sign签名不匹配' +FUTURES_ERROR_DICT['20025'] = u'杠杆比率错误' +FUTURES_ERROR_DICT['20100'] = u'请求超时' +FUTURES_ERROR_DICT['20101'] = u'数据格式无效' +FUTURES_ERROR_DICT['20102'] = u'登录无效' +FUTURES_ERROR_DICT['20103'] = u'数据事件类型无效' +FUTURES_ERROR_DICT['20104'] = u'数据订阅类型无效' +FUTURES_ERROR_DICT['20107'] = u'JSON格式错误' +FUTURES_ERROR_DICT['20115'] = u'quote参数未匹配到' +FUTURES_ERROR_DICT['20116'] = u'参数不匹配' +FUTURES_ERROR_DICT['1002'] = u'交易金额大于余额' +FUTURES_ERROR_DICT['1003'] = u'交易金额小于最小交易值' +FUTURES_ERROR_DICT['1004'] = u'交易金额小于0' +FUTURES_ERROR_DICT['1007'] = u'没有交易市场信息' +FUTURES_ERROR_DICT['1008'] = u'没有最新行情信息' +FUTURES_ERROR_DICT['1009'] = u'没有订单' +FUTURES_ERROR_DICT['1010'] = u'撤销订单与原订单用户不一致' +FUTURES_ERROR_DICT['1011'] = u'没有查询到该用户' +FUTURES_ERROR_DICT['1013'] = u'没有订单类型' +FUTURES_ERROR_DICT['1014'] = u'没有登录' +FUTURES_ERROR_DICT['1015'] = u'没有获取到行情深度信息' +FUTURES_ERROR_DICT['1017'] = u'日期参数错误' +FUTURES_ERROR_DICT['1018'] = u'下单失败' +FUTURES_ERROR_DICT['1019'] = u'撤销订单失败' +FUTURES_ERROR_DICT['1024'] = u'币种不存在' +FUTURES_ERROR_DICT['1025'] = u'没有K线类型' +FUTURES_ERROR_DICT['1026'] = u'没有基准币数量' +FUTURES_ERROR_DICT['1027'] = u'参数不合法可能超出限制' +FUTURES_ERROR_DICT['1028'] = u'保留小数位失败' +FUTURES_ERROR_DICT['1029'] = u'正在准备中' +FUTURES_ERROR_DICT['1030'] = u'有融资融币无法进行交易' +FUTURES_ERROR_DICT['1031'] = u'转账余额不足' +FUTURES_ERROR_DICT['1032'] = u'该币种不能转账' +FUTURES_ERROR_DICT['1035'] = u'密码不合法' +FUTURES_ERROR_DICT['1036'] = u'谷歌验证码不合法' +FUTURES_ERROR_DICT['1037'] = u'谷歌验证码不正确' +FUTURES_ERROR_DICT['1038'] = u'谷歌验证码重复使用' +FUTURES_ERROR_DICT['1039'] = u'短信验证码输错限制' +FUTURES_ERROR_DICT['1040'] = u'短信验证码不合法' +FUTURES_ERROR_DICT['1041'] = u'短信验证码不正确' +FUTURES_ERROR_DICT['1042'] = u'谷歌验证码输错限制' +FUTURES_ERROR_DICT['1043'] = u'登陆密码不允许与交易密码一致' +FUTURES_ERROR_DICT['1044'] = u'原密码错误' +FUTURES_ERROR_DICT['1045'] = u'未设置二次验证' +FUTURES_ERROR_DICT['1046'] = u'原密码未输入' +FUTURES_ERROR_DICT['1048'] = u'用户被冻结' +FUTURES_ERROR_DICT['1050'] = u'订单已撤销或者撤销中' +FUTURES_ERROR_DICT['1051'] = u'订单已完成交易' +FUTURES_ERROR_DICT['1201'] = u'账号零时删除' +FUTURES_ERROR_DICT['1202'] = u'账号不存在' +FUTURES_ERROR_DICT['1203'] = u'转账金额大于余额' +FUTURES_ERROR_DICT['1204'] = u'不同种币种不能转账' +FUTURES_ERROR_DICT['1205'] = u'账号不存在主从关系' +FUTURES_ERROR_DICT['1206'] = u'提现用户被冻结' +FUTURES_ERROR_DICT['1207'] = u'不支持转账' +FUTURES_ERROR_DICT['1208'] = u'没有该转账用户' +FUTURES_ERROR_DICT['1209'] = u'当前api不可用' \ No newline at end of file diff --git a/vnpy/api/okex/test.py b/vnpy/api/okex/test.py new file mode 100644 index 00000000..fe99c9a6 --- /dev/null +++ b/vnpy/api/okex/test.py @@ -0,0 +1,51 @@ +# encoding: UTF-8 + +from vnokex import * + +# 在OkCoin网站申请这两个Key,分别对应用户名和密码 +apiKey = '你的apiKey' +secretKey = '你的secretKey' + +# 创建API对象 +api = OkexSpotApi() + +api.connect(apiKey, secretKey, True) + +sleep(3) + +api.login() +api.subscribeSpotTicker("bch_btc") +api.subscribeSpotDepth("bch_btc") +api.subscribeSpotDepth("bch_btc", 5) +api.subscribeSpotDeals("bch_btc") +api.subscribeSpotKlines("bch_btc","30min") + +#api.spotTrade("etc_usdt","sell", "50" , "0.01") +#api.spotCancelOrder("etc_btc","44274138") +#api.spotUserInfo() +#api.spotOrderInfo("etc_btc", 44284731) + +# api = OkexFuturesApi() +# api.connect(apiKey, secretKey, True) + +# sleep(3) +#api.subsribeFutureTicker("btc","this_week") +#api.subscribeFutureKline("btc","this_week", "30min") +#api.subscribeFutureDepth("btc","this_week") +#api.subscribeFutureDepth("btc","this_week", 5) +#api.subscribeFutureTrades("btc","this_week") +#api.subscribeFutureIndex("btc") +#api.subscribeFutureForecast_price("btc") + +#api.login() +#api.futureTrade( "etc_usd", "this_week" ,"1" , 20 , 1 , _match_price = '0' , _lever_rate = '10') # 14245727693 +#api.futureCancelOrder("etc_usd","14245727693" , "this_week") +#api.futureUserInfo() +#api.futureOrderInfo("etc_usd" , "14245727693" , "this_week" , '1', '1' , '10') +# api.subscribeFutureTrades() + +''' +合约账户信息、 持仓信息等,在登录后都会自动推送。。。官方文档这样写的,还没实际验证过 +''' + +input() \ No newline at end of file diff --git a/vnpy/api/okex/vnokex.py b/vnpy/api/okex/vnokex.py new file mode 100644 index 00000000..95d0a2f9 --- /dev/null +++ b/vnpy/api/okex/vnokex.py @@ -0,0 +1,735 @@ +# encoding: UTF-8 + +import hashlib +import zlib +import json +from time import sleep +from threading import Thread +import traceback +import websocket +import requests +import sys + +# API文档 https://github.com/okcoin-okex/OKEx.com-api-docs + +from vnpy.api.okex.okexData import SPOT_TRADE_SIZE_DICT,SPOT_ERROR_DICT, FUTURES_ERROR_DICT + +# OKEX网站 +OKEX_USD_SPOT = 'wss://real.okex.com:10441/websocket' # OKEX (币币交易)现货地址 +OKEX_USD_CONTRACT = 'wss://real.okex.com:10440/websocket/okexapi' # OKEX 期货地址 + +OKEX_CONTRACT_HOST = 'https://www.okex.com/api/v1/future_hold_amount.do?symbol=%s_usd&contract_type=%s' # 合约持仓查询地址 + +SPOT_CURRENCY = ["usdt", + "btc", + "ltc", + "eth", + "etc", + "bch"] + +SPOT_SYMBOL = ["ltc_btc", + "eth_btc", + "etc_btc", + "bch_btc", + "btc_usdt", + "eth_usdt", + "ltc_usdt", + "etc_usdt", + "bch_usdt", + "etc_eth", + "bt1_btc", + "bt2_btc", + "btg_btc", + "qtum_btc", + "hsr_btc", + "neo_btc", + "gas_btc", + "qtum_usdt", + "hsr_usdt", + "neo_usdt", + "gas_usdt"] + +KLINE_PERIOD = ["1min","3min","5min","15min","30min","1hour","2hour","4hour","6hour","12hour","day","3day","week"] + +CONTRACT_SYMBOL = ["btc","ltc","eth","etc","bch","eos","xrp","btg"] + +# 合约类型 : 当周,下周,季度 +CONTRACT_TYPE = ["this_week", "next_week", "quarter"] + + +######################################################################## +class OkexApi(object): + """交易接口""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.host = '' # 服务器 + self.apiKey = '' # 用户名 + self.secretKey = '' # 密码 + + self.ws = None # websocket应用对象 现货对象 + self.thread = None # 初始化线程 + + #---------------------------------------------------------------------- + def reconnect(self): + """重新连接""" + # 首先关闭之前的连接 + self.close() + + # 再执行重连任务 + self.ws = websocket.WebSocketApp(self.host, + on_message=self.onMessage, + on_error=self.onError, + on_close=self.onClose, + on_open=self.onOpen) + + self.thread = Thread(target=self.ws.run_forever) + self.thread.start() + + #---------------------------------------------------------------------- + def connect(self, apiKey, secretKey, trace=False): + """ + 连接网关 + :param apiKey : 申请的API key + :param secretKey: 签名key + :param trace : 是否开启websocket的日志跟踪功能,输出到StreamHandler + :return: + """ + # 更新websocket服务器地址/API key/ + self.host = OKEX_USD_SPOT + self.apiKey = apiKey + self.secretKey = secretKey + + # 是否开启日志 + websocket.enableTrace(trace) + + # 创建websocket,绑定本地回调函数 onMessage/onError/onClose/onOpen + self.ws = websocket.WebSocketApp(self.host, + on_message=self.onMessage, + on_error=self.onError, + on_close=self.onClose, + on_open=self.onOpen) + + self.thread = Thread(target=self.ws.run_forever) + self.thread.start() + + #---------------------------------------------------------------------- + def readData(self, evt): + """ + 解码推送收到的数据 + :param evt: + :return: + """ + data = json.loads(evt) + return data + + #---------------------------------------------------------------------- + def close(self): + """ + 关闭接口 + :return: + """ + + if self.thread and self.thread.isAlive(): + print(u'vnokex.close') + self.ws.close() + self.thread.join() + + #---------------------------------------------------------------------- + def onMessage(self, ws, evt): + """ + 信息推送事件 + :param ws: 接口 + :param evt: 事件 + :return: + """ + print(u'vnokex.onMessage:{}'.format(evt)) + + #---------------------------------------------------------------------- + def onError(self, ws, evt): + """ + 接口错误推送事件 + :param ws: + :param evt: + :return: + """ + print(u'vnokex.onApiError:{}'.format(evt)) + + + #---------------------------------------------------------------------- + def onClose(self, ws): + """ + 接口断开事件 + :param ws: + :return: + """ + print(u'vnokex.onClose') + + #---------------------------------------------------------------------- + def onOpen(self, ws): + """ + 接口打开事件 + :param ws: + :return: + """ + print(u'vnokex.onOpen') + + #---------------------------------------------------------------------- + def generateSign(self, params): + """生成签名""" + print(u'vnokex.generateSign') + l = [] + for key in sorted(params.keys()): + l.append('%s=%s' %(key, params[key])) + l.append('secret_key=%s' %self.secretKey) + sign = '&'.join(l) + return hashlib.md5(sign.encode('utf-8')).hexdigest().upper() + + #---------------------------------------------------------------------- + def sendTradingRequest(self, channel, params): + """发送交易请求""" + print(u'vnokex.sendTradingRequest') + # 在参数字典中加上api_key和签名字段 + params['api_key'] = self.apiKey + params['sign'] = self.generateSign(params) + + # 生成请求 + d = {} + d['event'] = 'addChannel' + d['channel'] = channel + d['parameters'] = params + + # 使用json打包并发送 + j = json.dumps(d) + + # 若触发异常则重连 + try: + self.ws.send(j) + except websocket.WebSocketConnectionClosedException as ex: + print(u'vnokex.sendTradingRequest Exception:{}'.format(str(ex)),file=sys.stderr) + + #---------------------------------------------------------------------- + def sendDataRequest(self, channel): + """发送数据请求""" + print(u'vnokex.sendDataRequest') + d = {} + d['event'] = 'addChannel' + d['channel'] = channel + j = json.dumps(d) + + # 若触发异常则重连 + try: + self.ws.send(j) + except websocket.WebSocketConnectionClosedException as ex: + print(u'vnokex.sendDataRequest Exception:{},{}'.format(str(ex),traceback.format_exc()), file=sys.stderr) + except Exception as ex: + print(u'vnokex.sendDataRequest Exception:{},{}'.format(str(ex),traceback.format_exc()), file=sys.stderr) + + def sendHeartBeat(self): + """ + 发送心跳 + :return: + """ + d = {'event': 'ping'} + # 若触发异常则重连 + try: + print(u'vnokex.sendHeartBeat') + j = json.dumps(d) + self.ws.send(j) + except websocket.WebSocketConnectionClosedException as ex: + print(u'vnokex.sendHeartBeat Exception:{}'.format(str(ex)), file=sys.stderr) + + #---------------------------------------------------------------------- + def login(self): + """ + 登录 + :return: True/False + """ + print(u'vnokex.login()') + params = {} + params['api_key'] = self.apiKey + params['sign'] = self.generateSign(params) + + # 生成请求 + d = {} + d['event'] = 'login' + d['parameters'] = params + + # 使用json打包并发送 + j = json.dumps(d) + + # 若触发异常则重连 + try: + self.ws.send(j) + return True + except websocket.WebSocketConnectionClosedException as ex: + print(u'vnokex.login exception:{},{}'.format(str(ex), traceback.format_exc()), file=sys.stderr) + return False + + +######################################################################## +class WsSpotApi(OkexApi): + """现货交易接口""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + super(WsSpotApi, self).__init__() + + #---------------------------------------------------------------------- + def subscribeSpotTicker(self, symbol): + print(u'vnokex.subscribeSpotTicker:{}'.format(symbol)) + + """订阅现货的Tick""" + channel = 'ok_sub_spot_%s_ticker' %symbol + self.sendDataRequest(channel) + + #---------------------------------------------------------------------- + def subscribeSpotDepth(self, symbol, depth=0): + """订阅现货的深度""" + print(u'vnokex.subscribeSpotDepth:{}'.format(symbol)) + + channel = 'ok_sub_spot_%s_depth' %symbol + if depth: + channel = channel + '_' + str(depth) + self.sendDataRequest(channel) + + #---------------------------------------------------------------------- + def subscribeSpotDeals(self, symbol): + """ + 订阅成交记录 + :param symbol: + :return: + """ + print(u'vnokex.subscribeSpotDeals:{}'.format(symbol)) + channel = 'ok_sub_spot_%s_deals' %symbol + self.sendDataRequest(channel) + + #---------------------------------------------------------------------- + def subscribeSpotKlines(self, symbol, period): + """ + 订阅K线数据 + :param symbol: + :param period: + :return: + """ + print(u'vnokex.subscribeSpotKlines:{} {}'.format(symbol,period)) + channel = 'ok_sub_spot_%s_kline_%s' %(symbol, period) + self.sendDataRequest(channel) + + #---------------------------------------------------------------------- + def spotTrade(self, symbol, type_, price, amount): + """现货委托""" + print(u'vnokex.stopTrade:symbol:{} type:{} price:{} amount:{}'.format(symbol, type_, price,amount)) + + params = {} + params['symbol'] = str(symbol) + params['type'] = str(type_) + params['price'] = str(price) + params['amount'] = str(amount) + + channel = 'ok_spot_order' + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def spotCancelOrder(self, symbol, orderid): + """ + 现货撤单指令 + :param symbol:现货合约 + :param orderid: + :return: + """ + print(u'vnokex.spotCancelOrder:{} orderid:{}'.format(symbol, orderid)) + params = {} + params['symbol'] = str(symbol) + params['order_id'] = str(orderid) + + channel = 'ok_spot_cancel_order' + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def spotUserInfo(self): + """ + 查询现货账户" + :return: + """"" + print(u'vnokex.spotUserInfo()') + channel = 'ok_spot_userinfo' + self.sendTradingRequest(channel, {}) + + #---------------------------------------------------------------------- + def spotOrderInfo(self, symbol, orderid): + """ + 查询现货委托信息 + :param symbol: 现货合约 + :param orderid: 委托编号 + :return: + """ + print(u'vnokex.spotOrderInfo:{},orderid:{}'.format(symbol,orderid)) + + params = {} + params['symbol'] = str(symbol) + params['order_id'] = str(orderid) + + channel = 'ok_spot_orderinfo' + + self.sendTradingRequest(channel, params) + + +######################################################################## +''' +OKEX 合约接口 + +[{ + "channel": "btc_forecast_price", + "timestamp":"1490341322021", + "data": "998.8" +}] +data(string): 预估交割价格 +timestamp(string): 时间戳 +操作说明 +无需订阅,交割前一小时自动返回 + +这段数据,交割前会自动返回 +''' + + +class WsFuturesApi(object): + # ---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' # 用户名 + self.secretKey = '' # 密码 + + self.ws = None # websocket应用对象 期货对象 + self.active = False # 还存活 + + self.use_lever_rate = 10 + + self.trace = False + + # ----------------------------------------------------------------------- + def connect(self, apiKey, secretKey, trace=False): + self.host = OKEX_USD_CONTRACT + self.apiKey = apiKey + self.secretKey = secretKey + self.trace = trace + + websocket.enableTrace(trace) + + self.ws = websocket.WebSocketApp(self.host, + on_message=self.onMessage, + on_error=self.onError, + on_close=self.onClose, + on_open=self.onOpen) + + self.thread = Thread(target=self.ws.run_forever, args=(None, None, 60, 30)) + self.thread.start() + + # ---------------------------------------------------------------------- + def http_get_request(self, url, params, add_to_headers=None, TIMEOUT=5): + headers = { + "Content-type": "application/x-www-form-urlencoded", + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0' + } + if add_to_headers: + headers.update(add_to_headers) + #postdata = urllib.urlencode(params) + try: + # response = requests.get(url, postdata, headers=headers, timeout=TIMEOUT) + response = requests.get(url, headers=headers, timeout=TIMEOUT) + if response.status_code == 200: + return response.json() + else: + return {"status": "fail"} + except Exception as e: + print(u'httpGet failed :{}'.format(str(e)), file=sys.stderr) + return {"status": "fail", "msg": e} + + # ---------------------------------------------------------------------- + def getContractName(self, symbol_no_usd, contract_type): + try: + url = OKEX_CONTRACT_HOST % (symbol_no_usd, contract_type) + data = self.http_get_request(url, {}) + + if type(data) == type([]) and len(data) > 0: + d1 = data[0] + contract_name = d1["contract_name"] + return contract_name + + except Exception as ex: + print(u'OkexContractApi.getContractName exception:{},{}'.format(str(ex),traceback.format_exc()), file=sys.stderr) + return None + + def sendHeartBeat(self): + """ + 发送心跳 + :return: + """ + d = {'event': 'ping'} + # 若触发异常则重连 + try: + print(u'vnokex.futuresApi.sendHeartBeat') + j = json.dumps(d) + self.ws.send(j) + except websocket.WebSocketConnectionClosedException as ex: + print(u'vnokex.futuresApi.sendHeartBeat Exception:{}'.format(str(ex)), file=sys.stderr) + + # ---------------------------------------------------------------------- + def close(self): + """关闭接口""" + if self.thread and self.thread.isAlive(): + self.ws.close() + self.thread.join() + + # ---------------------------------------------------------------------- + def readData(self, evt): + """解压缩推送收到的数据""" + # # 创建解压器 + # decompress = zlib.decompressobj(-zlib.MAX_WBITS) + + # # 将原始数据解压成字符串 + # inflated = decompress.decompress(evt) + decompress.flush() + + # 通过json解析字符串 + # data = json.loads(inflated) + data = json.loads(evt) + + return data + + # ---------------------------------------------------------------------- + def onMessage(self, ws, evt): + """信息推送""" + print(evt) + + # ---------------------------------------------------------------------- + def onError(self, ws, evt): + """错误推送""" + print('OkexContractApi.onError:{}'.format(evt)) + + # ---------------------------------------------------------------------- + def onClose(self, ws): + """接口断开""" + print('OkexContractApi.onClose') + + # ---------------------------------------------------------------------- + def onOpen(self, ws): + """接口打开""" + print('OkexContractApi.onOpen') + + def subsribeFutureTicker(self, symbol, contract_type): + req = "{'event':'addChannel','channel':'ok_sub_futureusd_%s_ticker_%s'}" % (symbol, contract_type) + self.ws.send(req) + + def subscribeFutureKline(self, symbol, contract_type, time_period): + req = "{'event':'addChannel','channel':'ok_sub_futureusd_%s_kline_%s_%s'}" % ( + symbol, contract_type, time_period) + self.ws.send(req) + + def subscribeFutureDepth(self, symbol, contract_type): + req = "{'event':'addChannel','channel':'ok_sub_future_%s_depth_%s_usd'}" % (symbol, contract_type) + self.ws.send(req) + + def subscribeFutureDepth20(self, symbol, contract_type): + req = "{'event':'addChannel','channel':'ok_sub_futureusd_%s_depth_%s_20'}" % (symbol, contract_type) + self.ws.send(req) + + def subscribeFutureTrades(self, symbol, contract_type): + req = "{'event':'addChannel','channel':'ok_sub_futureusd_%s_trade_%s'}" % (symbol, contract_type) + self.ws.send(req) + + def subscribeFutureIndex(self, symbol): + req = "{'event':'addChannel','channel':'ok_sub_futureusd_%s_index'}" % (symbol) + self.ws.send(req) + + # ---------------------------------------------------------------------- + def generateSign(self, params): + """生成签名""" + l = [] + for key in sorted(params.keys()): + l.append('%s=%s' % (key, params[key])) + l.append('secret_key=%s' % self.secretKey) + sign = '&'.join(l) + return hashlib.md5(sign.encode('utf-8')).hexdigest().upper() + + # ---------------------------------------------------------------------- + def sendTradingRequest(self, channel, params): + """发送交易请求""" + # 在参数字典中加上api_key和签名字段 + params['api_key'] = self.apiKey + params['sign'] = self.generateSign(params) + + # 生成请求 + d = {} + d['event'] = 'addChannel' + d['channel'] = channel + d['parameters'] = params + + # 使用json打包并发送 + j = json.dumps(d) + + # 若触发异常则重连 + try: + self.ws.send(j) + except websocket.WebSocketConnectionClosedException as ex: + print(u'OkexContractApi.sendTradingRequest exception:{},{}'.format(str(ex),traceback.format_exc()), file=sys.stderr) + except Exception as ex: + print(u'OkexContractApi.sendTradingRequest exception:{},{}'.format(str(ex), traceback.format_exc()), file=sys.stderr) + + # ---------------------------------------------------------------------- + + def login(self): + """ + 登录 + :return: + """ + params = {} + params['api_key'] = self.apiKey + params['sign'] = self.generateSign(params) + + # 生成请求 + d = {} + d['event'] = 'login' + d['parameters'] = params + + # 使用json打包并发送 + j = json.dumps(d) + + # 若触发异常则重连 + try: + self.ws.send(j) + except websocket.WebSocketConnectionClosedException as ex: + print(u'OkexContractApi.login exception:{},{}'.format(str(ex), traceback.format_exc()), file=sys.stderr) + except Exception as ex: + print(u'OkexContractApi.login exception:{},{}'.format(str(ex), traceback.format_exc()), file=sys.stderr) + + # ---------------------------------------------------------------------- + + def futureSubscribeIndex(self, symbol): + """ + 订阅期货合约指数行情 + :param symbol: 合约 + :return: + """ + channel = 'ok_sub_futureusd_%s_index' % symbol + self.sendTradingRequest(channel, {}) + + # ---------------------------------------------------------------------- + def futureAllIndexSymbol(self): + """ + 订阅所有的期货合约指数行情 + :return: + """ + for symbol in CONTRACT_SYMBOL: + self.futureSubscribeIndex(symbol) + + # ---------------------------------------------------------------------- + def futureTrade(self, symbol_pair, contract_type, type_, price, amount, _match_price='0', _lever_rate=None): + """期货委托""" + params = {} + params['symbol'] = str(symbol_pair) + params['contract_type'] = str(contract_type) + params['price'] = str(price) + params['amount'] = str(amount) + params['type'] = type_ # 1:开多 2:开空 3:平多 4:平空 + params['match_price'] = _match_price # 是否为对手价: 0:不是 1:是 当取值为1时,price无效 + + if _lever_rate != None: + params['lever_rate'] = _lever_rate + else: + params['lever_rate'] = str(self.use_lever_rate) + + channel = 'ok_futureusd_trade' + + self.sendTradingRequest(channel, params) + + # ---------------------------------------------------------------------- + def futureCancelOrder(self, symbol_pair, orderid, contract_type): + """ + 期货撤单指令 + :param symbol_pair: 合约对 + :param orderid: 委托单编号 + :param contract_type: 合约类型 + :return: + """ + params = {} + params['symbol'] = str(symbol_pair) + params['order_id'] = str(orderid) + params['contract_type'] = str(contract_type) + + channel = 'ok_futureusd_cancel_order' + + self.sendTradingRequest(channel, params) + + # ---------------------------------------------------------------------- + def futureUserInfo(self): + """查询期货账户""" + channel = 'ok_futureusd_userinfo' + self.sendTradingRequest(channel, {}) + + # ---------------------------------------------------------------------- + # def futureSubUserInfo(self): + # channel = 'ok_sub_futureusd_userinfo' + # self.sendTradingRequest(channel, {}) + + # ---------------------------------------------------------------------- + def futureOrderInfo(self, symbol_pair, order_id, contract_type, status, current_page, page_length=50): + """ + 发出查询期货委托 + :param symbol_pair: 合约对 + :param order_id: 委托单编号 + :param contract_type: 合约类型 + :param status: 状态 + :param current_page:当前页 + :param page_length: 每页长度 + :return: + """ + params = {} + params['symbol'] = str(symbol_pair) + params['order_id'] = str(order_id) + params['contract_type'] = str(contract_type) + params['status'] = str(status) + params['current_page'] = str(current_page) + params['page_length'] = str(page_length) + + channel = 'ok_futureusd_orderinfo' + + self.sendTradingRequest(channel, params) + + # ---------------------------------------------------------------------- + def futureAllUnfinishedOrderInfo(self): + """ + 订阅所有未完成的委托单信息 + :return: + """ + for symbol in CONTRACT_SYMBOL: + symbol_usd = symbol + "_usd" + for contract_type in CONTRACT_TYPE: + # orderid = -1, + self.futureOrderInfo(symbol_usd, -1, contract_type, 1, 1, 50) + + # ---------------------------------------------------------------------- + def subscribeFutureTrades(self): + """ + 订阅期货成交回报 + :return: + """ + channel = 'ok_sub_futureusd_trades' + self.sendTradingRequest(channel, {}) + + # ---------------------------------------------------------------------- + def subscribeFutureUserInfo(self): + """订阅期货账户信息""" + channel = 'ok_sub_futureusd_userinfo' + + self.sendTradingRequest(channel, {}) + + # ---------------------------------------------------------------------- + def subscribeFuturePositions(self): + """订阅期货持仓信息""" + channel = 'ok_sub_futureusd_positions' + + self.sendTradingRequest(channel, {}) \ No newline at end of file diff --git a/vnpy/trader/gateway/okexGateway/OKEX_connect.json b/vnpy/trader/gateway/okexGateway/OKEX_connect.json new file mode 100644 index 00000000..a0224e4c --- /dev/null +++ b/vnpy/trader/gateway/okexGateway/OKEX_connect.json @@ -0,0 +1,6 @@ +{ + "apiKey": "你的apiKey", + "secretKey": "你的secretKey", + "trace": false, + "leverage": 10 +} \ No newline at end of file diff --git a/vnpy/trader/gateway/okexGateway/__init__.py b/vnpy/trader/gateway/okexGateway/__init__.py new file mode 100644 index 00000000..7e68e043 --- /dev/null +++ b/vnpy/trader/gateway/okexGateway/__init__.py @@ -0,0 +1,11 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from vnpy.trader.gateway.okexGateway.okexGateway import OkexGateway + +gatewayClass = OkexGateway +gatewayName = 'OKEX' +gatewayDisplayName = u'OKEX' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = True + diff --git a/vnpy/trader/gateway/okexGateway/okexGateway.py b/vnpy/trader/gateway/okexGateway/okexGateway.py new file mode 100644 index 00000000..6dbee407 --- /dev/null +++ b/vnpy/trader/gateway/okexGateway/okexGateway.py @@ -0,0 +1,2664 @@ +# encoding: UTF-8 + +''' +vnpy.api.okex的gateway接入 +注意: +1. 目前仅支持USD现货交易 +''' + +import os +import json +from datetime import datetime +from time import sleep +from copy import copy +from threading import Condition +from queue import Queue +from threading import Thread +from time import sleep +import traceback + +from vnpy.api.okex import WsSpotApi, WsFuturesApi, SPOT_SYMBOL, CONTRACT_SYMBOL, CONTRACT_TYPE, SPOT_CURRENCY +from vnpy.api.okex.okexData import SPOT_TRADE_SIZE_DICT, SPOT_ERROR_DICT, FUTURES_ERROR_DICT + +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath +from vnpy.trader.vtConstant import EXCHANGE_OKEX, DIRECTION_NET, PRODUCT_SPOT, DIRECTION_LONG, DIRECTION_SHORT, PRICETYPE_LIMITPRICE, PRICETYPE_MARKETPRICE, OFFSET_OPEN, OFFSET_CLOSE +from vnpy.trader.vtConstant import STATUS_CANCELLED, STATUS_NOTTRADED, STATUS_PARTTRADED, STATUS_ALLTRADED, STATUS_UNKNOWN, PRODUCT_FUTURES +from vnpy.trader.vtObject import VtErrorData + +# 价格类型映射 +# 买卖类型: 限价单(buy/sell) 市价单(buy_market/sell_market) +priceTypeMap = {} +priceTypeMap['buy'] = (DIRECTION_LONG, PRICETYPE_LIMITPRICE) +priceTypeMap['buy_market'] = (DIRECTION_LONG, PRICETYPE_MARKETPRICE) +priceTypeMap['sell'] = (DIRECTION_SHORT, PRICETYPE_LIMITPRICE) +priceTypeMap['sell_market'] = (DIRECTION_SHORT, PRICETYPE_MARKETPRICE) +priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} + +priceContractOffsetTypeMap = {} +priceContractOffsetTypeMap['1'] = (DIRECTION_LONG , OFFSET_OPEN) +priceContractOffsetTypeMap['2'] = (DIRECTION_SHORT , OFFSET_OPEN ) +priceContractOffsetTypeMap['3'] = (DIRECTION_SHORT , OFFSET_CLOSE ) +priceContractOffsetTypeMap['4'] = (DIRECTION_LONG , OFFSET_CLOSE) +priceContractTypeMapReverse = {v: k for k, v in priceContractOffsetTypeMap.items()} + +# 委托状态印射 +statusMap = {} +statusMap[-1] = STATUS_CANCELLED # 撤单 +statusMap[0] = STATUS_NOTTRADED # 未成交 +statusMap[1] = STATUS_PARTTRADED # 部分成交 +statusMap[2] = STATUS_ALLTRADED # 全部成交 +statusMap[4] = STATUS_UNKNOWN # 未知状态 + +EVENT_OKEX_INDEX_FUTURE = "eFuture_Index_OKEX" + +######################################################################## +class OkexGateway(VtGateway): + """OKEX交易接口""" + + # ---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName='OKEX'): + """Constructor""" + super(OkexGateway, self).__init__(eventEngine, gatewayName) + + self.api_spot = OkexSpotApi(self) # 现货交易接口 + self.api_futures = OkexFuturesApi(self) # 期货交易接口 + + self.leverage = 0 + self.spot_connected = False # 现货交易接口连接状态 + self.futures_connected = False # 期货交易接口连接状态 + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 2 # 查询触发点 + self.hbCount = 0 # 心跳触发倒计时 + self.hbTrigger = 30 # 心跳触发点 + + # gateway 配置文件 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + # 消息调试 + self.log_message = False + + # ---------------------------------------------------------------------- + def connect(self): + """连接""" + # 载入json文件 + try: + f = open(self.filePath, 'r') + except IOError: + self.writeError(u'OkexGateway.connect:读取连接配置{}出错,请检查'.format(self.filePath)) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + secretKey = str(setting['secretKey']) + trace = setting['trace'] + self.leverage = setting['leverage'] if 'leverage' in setting else 0 + spot_connect = setting['spot_connect'] + futures_connect = setting['futures_connect'] + self.log_message = setting['log_message'] if 'log_message' in setting else False + + except KeyError: + self.writeError(u'OkexGateway.connect:连接配置缺少字段,请检查') + return + + if spot_connect: + self.api_spot.active = True + self.api_spot.connect(apiKey, secretKey, trace) + self.writeLog(u'connect okex ws spot api') + + if futures_connect: + self.api_futures.active = True + self.api_futures.connect(apiKey, secretKey, trace) + self.writeLog(u'connect okex ws contract api') + + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'接口初始化成功' + self.onLog(log) + + # 启动查询 + self.initQuery() + #self.startQuery() + + def writeLog(self, content): + """ + 记录日志文件 + :param content: + :return: + """ + if self.logger: + self.logger.info(content) + + def writeError(self, content, error_id = 0): + """ + 发送错误通知/记录日志文件 + :param content: + :return: + """ + error = VtErrorData() + error.gatewayName = self.gatewayName + error.errorID = error_id + error.errorMsg = content + self.onError(error) + + if self.logger: + self.logger.error(content) + + # ---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """ + 订阅行情 + :param subscribeReq: VtSubscribeReq, + :return: + """ + try: + if subscribeReq.symbol in SPOT_SYMBOL: + if self.api_spot and self.spot_connected: + self.api_spot.subscribe(subscribeReq) + else: + self.writeError(u'现货接口未创建/未连接,无法调用subscribe') + + else: + if self.api_futures and self.futures_connected: + self.api_futures.subscribe(subscribeReq) + else: + self.writeError(u'期货接口未创建/未连接,无法调用subscribe') + + except Exception as ex: + self.onError(u'OkexGateway.subscribe 异常,请检查日志') + self.writeLog(u'OkexGateway.subscribe Exception :{},{}'.format(str(ex), traceback.format_exc())) + + # ---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + if orderReq.symbol in SPOT_SYMBOL: + if self.api_spot and self.spot_connected: + return self.api_spot.spotSendOrder(orderReq) + else: + self.writeError(u'现货接口未创建/连接,无法调用sendOrder') + return '' + else: + if self.api_futures and self.futures_connected: + return self.api_futures.futureSendOrder(orderReq) + else: + self.writeError(u'期货接口未创建/连接,无法调用sendOrder') + return '' + # ---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + if self.spot_connected: + self.api_spot.spotCancel(cancelOrderReq) + + if self.futures_connected: + self.api_futures.futureCancel(cancelOrderReq) + + # ---------------------------------------------------------------------- + def qryAccount(self): + """查询账户资金""" + if self.spot_connected: + self.api_spot.spotUserInfo() + + if self.futures_connected: + self.api_futures.futureUserInfo() + + # ---------------------------------------------------------------------- + def qryOrderInfo(self): + #if self.spot_connected: + # self.api_spot.spotAllOrders() + + if self.futures_connected: + self.api_futures.futureAllUnfinishedOrderInfo() + + # ---------------------------------------------------------------------- + def qryPosition(self): + """查询持仓""" + pass + + # ---------------------------------------------------------------------- + def close(self): + """关闭""" + if self.spot_connected: + self.api_spot.active = False + self.api_spot.close() + + if self.futures_connected: + self.api_futures.active = False + self.api_futures.close() + + # ---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + # self.qryFunctionList = [self.qryAccount, self.qryOrderInfo] + self.qryFunctionList = [self.qryOrderInfo] + # self.qryFunctionList = [] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 2 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + # ---------------------------------------------------------------------- + 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 + + def heartbeat(self,event): + """ + 心跳 + :return: + """ + self.hbCount += 1 + + if self.hbCount < self.hbTrigger: + return + + # 清空倒计时 + self.hbCount = 0 + # 发送心跳请求 + if self.api_spot.active and self.spot_connected: + self.api_spot.sendHeartBeat() + + if self.api_futures.active and self.futures_connected: + self.api_futures.sendHeartBeat() + + # ---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + self.eventEngine.register(EVENT_TIMER, self.heartbeat) + + # ---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + #---------------------------------------------------------------------- + def onFutureIndexPush(self, push_dic): + """ + 合约指数更新事件 + :param push_dic: + :return: + """ + event1 = Event(type_=EVENT_OKEX_INDEX_FUTURE) + event1.dict_['data'] = push_dic + self.eventEngine.put(event1) + +######################################################################## +class OkexSpotApi(WsSpotApi): + """okex的API实现""" + + # ---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(WsSpotApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + self.active = False # 若为True则会在断线后自动重连 + + self.cbDict = {} # 回调函数字典 + self.tickDict = {} # 缓存最新tick字典 + self.orderDict = {} # 委托单字典 + + self.channelSymbolMap = {} # 请求数据类型与合约的映射字典 + + self.localNo = 0 # 本地委托号 + self.localNoQueue = Queue() # 未收到系统委托号的本地委托号队列 + self.localNoDict = {} # key为本地委托号,value为系统委托号 + self.orderIdDict = {} # key为系统委托号,value为本地委托号 + self.cancelDict = {} # key为本地委托号,value为撤单请求 + + self.recordOrderId_BefVolume = {} # 记录的之前处理的量 + + self.cache_some_order = {} + self.tradeID = 0 + + # 已登记的品种对队列 + self.registerSymbolPairArray = set([]) + + # 初始化回调函数 + self.initCallback() + + ''' + 登录后,每次订单执行撤销后又这样的 推送。。不知道干啥的。先过滤掉了 + {u'binary': 1, u'product': u'spot', u'type': u'order', u'base': u'etc' +, u'quote': u'usdt', u'data': {u'status': -1, u'orderType': 0, u'price': u'25.4050', u'modifyTime': +1512288275000L, u'userId': 6548935, u'createTime': 1512288275000L, u'source': 0, u'quoteSize': u'0.0 +0000000', u'executedValue': u'0.00000000', u'id': 62877909, u'filledSize': u'0.00000000', u'side': 1 +, u'size': u'0.01000000'}} + ''' + + # ---------------------------------------------------------------------- + def onMessage(self, ws, evt): + """ + 响应信息处理,包括心跳响应、请求响应、数据推送 + :param ws: websocket接口 + :param evt: 消息体 + :return: + """ + # str => json + ws_data = self.readData(evt) + + if self.gateway.log_message: + self.gateway.writeLog(u'SpotApi.onMessage:{}'.format(ws_data)) + + # 消息是dict格式 + if isinstance(ws_data, dict): + if 'event' in ws_data: + #处理心跳得 + if ws_data['event'] == 'pong': + self.gateway.writeLog(u'heart beat response {}'.format(datetime.now())) + return + else: + self.gateway.writeLog(u'其他 event:{}'.format(ws_data)) + else: + self.gateway.writeLog(u'其他数据:{}'.format(ws_data)) + + # 消息是list格式 + if isinstance(ws_data, list): + for data in ws_data: + channel_value = data.get('channel', None) + if channel_value == None: + continue + + # 登录请求恢复 + if channel_value == 'login': + login_data = data.get('data', {}) + result = login_data.get('result', False) + if result: + self.gateway.writeLog(u'Spot login success: {}'.format(datetime.now())) + self.gateway.spot_connected = True + else: + self.gateway.writeError(u'SpotApi login fail: {},data:{}'.format(datetime.now(), data)) + continue + + # 功能订阅得回复 + elif channel_value == 'addChannel': + channel_data = data.get('data', {}) + result = channel_data.get('result', False) + channel_value = channel_data["channel"] + if result: + self.gateway.writeLog(u'功能订阅请求:{} 成功:'.format(channel_value)) + continue + + + # 其他回调/数据推送 + callback = self.cbDict.get(channel_value) + if callback: + try: + callback(data) + except Exception as ex: + self.gateway.writeError(u'Spot {}回调函数处理异常,请查日志'.format(channel_value)) + self.gateway.writeLog(u'Spot onMessage call back {} exception:{},{}'.format(channel_value, str(ex),traceback.format_exc())) + else: + self.gateway.writeError(u'Spot unkonw msg:{}'.format(data)) + + # ---------------------------------------------------------------------- + def onError(self, ws, evt): + """Api方法重载,错误推送""" + error = VtErrorData() + error.gatewayName = self.gatewayName + error.errorID = 0 + error.errorMsg = str(evt) + self.gateway.onError(error) + + # ---------------------------------------------------------------------- + def onErrorMsg(self, data): + error = VtErrorData() + error.gatewayName = self.gatewayName + if 'data' in data and 'error_code' in data['data']: + error_code =data['data']['error_code'] + error.errorMsg = u'SpotApi 出错:{}'.format(SPOT_ERROR_DICT.get(error_code)) + error.errorID = error_code + else: + error.errorMsg = str(data) + self.gateway.onError(error) + + # ---------------------------------------------------------------------- + def onClose(self, ws): + """ + 断开接口 + :param ws: websocket + :return: + """ + # 如果尚未连上,则忽略该次断开提示 + if not self.gateway.spot_connected: + self.gateway.writeLog(u'spot接口未连接,忽略断开提示') + return + + self.gateway.spot_connected = False + self.gateway.writeLog(u'Spot服务器连接断开') + + # 重新连接 + if self.active: + def reconnect(): + while not self.gateway.connected: + self.gateway.writeLog(u'等待10秒后重新连接') + sleep(10) + if not self.gateway.connected: + self.reconnect() + + t = Thread(target=reconnect) + t.start() + + # ---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """ + 订阅行情 + :param subscribeReq: + :return: + """ + self.gateway.writeLog(u'SpotApi.subscribe({})'.format(subscribeReq.symbol)) + + # 检查:带有网关特征的品种对symbol, 如:eth_usdt.OKEX + symbol_pair_gateway = subscribeReq.symbol + arr = symbol_pair_gateway.split('.') + # 提取品种对 eth_usdt + symbol_pair = arr[0] + + # 若品种对未登记过 + if symbol_pair not in self.registerSymbolPairArray: + # 添加登记品种对 + self.registerSymbolPairArray.add(symbol_pair) + # 发起品种行情订阅请求 + self.subscribeSingleSymbol(symbol_pair) + # 查询该品种对的订单 + self.spotOrderInfo(symbol_pair, '-1') + + # ---------------------------------------------------------------------- + def subscribeSingleSymbol(self, symbol): + """ + 订阅合约(对) + :param symbol:合约(对) + :return: + """ + self.gateway.writeLog(u'SpotApi.subscribeSingleSymbol({})'.format(symbol)) + if symbol in SPOT_TRADE_SIZE_DICT: + # 订阅行情数据 + self.subscribeSpotTicker(symbol) + # 订阅委托深度 + self.subscribeSpotDepth(symbol, depth=5) + # 订阅该合约的委托成交回报(所有人,不是你得账号) + #self.subscribeSpotDeals(symbol) + else: + self.gateway.writeError(u'SpotApi:订阅失败,{}不在SPOT_TRADE_SIZE_DICT中,'.format(symbol)) + + # ---------------------------------------------------------------------- + def spotAllOrders(self): + """ + 查询所有委托清单 + :return: + """ + self.gateway.writeLog('SpotApi.spotAllOrders()') + + # 根据已登记的品种对清单,逐一发委托查询 + for symbol in self.registerSymbolPairArray: + if symbol in SPOT_TRADE_SIZE_DICT: + self.spotOrderInfo(symbol, '-1') + + # 对已经发送委托,根据orderid,发出查询请求 + for orderId in self.orderIdDict.keys(): + order = self.orderDict.get(orderId, None) + if order != None: + symbol_pair = (order.symbol.split('.'))[0] + self.spotOrderInfo(symbol_pair, orderId) + + # ---------------------------------------------------------------------- + def onOpen(self, ws): + """ + ws连接成功事件回调函数 + :param ws: + :return: + """ + self.gateway.spot_connected = True + self.gateway.writeLog(u'Spot服务器连接成功') + + # 登录 + self.login() + + # 连接后查询现货账户和委托数据 + self.spotUserInfo() + + # 尝试订阅一个合约对 + self.subscribeSingleSymbol("etc_usdt") + self.subscribeSingleSymbol("btc_usdt") + self.subscribeSingleSymbol("eth_usdt") + self.subscribeSingleSymbol("ltc_usdt") + + self.gateway.writeLog(u'SpotApi初始化合约信息') + + for symbol in SPOT_TRADE_SIZE_DICT: + # self.subscribeSpotTicker(symbol) + # self.subscribeSpotDepth5(symbol) + # self.subscribeSpotDeals(symbol) + + # Ticker数据 + self.channelSymbolMap["ok_sub_spot_%s_ticker" % symbol] = symbol + # 盘口的深度 + self.channelSymbolMap["ok_sub_spot_%s_depth_5" % symbol] = symbol + # 所有人的交易数据 + self.channelSymbolMap["ok_sub_spot_%s_deals" % symbol] = symbol + + # 更新至数据引擎的合约信息 + contract = VtContractData() + contract.gatewayName = self.gatewayName + contract.symbol = symbol #'.'.join([contract.symbol, contract.exchange]) + contract.exchange = EXCHANGE_OKEX + contract.vtSymbol = symbol #'.'.join([contract.symbol, contract.exchange]) + contract.name = u'现货%s' % symbol + contract.size = 1 # 现货是1:1 + contract.priceTick = 0.0001 + contract.volumeTick = SPOT_TRADE_SIZE_DICT[symbol] + contract.productClass = PRODUCT_SPOT + self.gateway.onContract(contract) + + ''' + [{ + "channel":"ok_sub_spot_bch_btc_deals", + "data":[["1001","2463.86","0.052","16:34:07","ask"]] + }] + ''' + + # ---------------------------------------------------------------------- + def onSpotSubDeals(self, ws_data): + """ + 当前现货委托成交信息回报 + :param ws_data: + :return: + """ + self.gateway.writeLog(u'SpotApi.onSpotSubDeals:{}'.format(ws_data)) + + # ---------------------------------------------------------------------- + def initCallback(self): + """ + 初始化回调函数 + :return: + """ + # USD_SPOT 把每一个币币交易对的回调函数登记在字典中 + for symbol_pair in SPOT_TRADE_SIZE_DICT: + self.cbDict["ok_sub_spot_%s_ticker" % symbol_pair] = self.onTicker # 该币对的行情数据回报 + self.cbDict["ok_sub_spot_%s_depth_5" % symbol_pair] = self.onDepth # 该币对的委托深度回报 + self.cbDict["ok_sub_spot_%s_deals" % symbol_pair] = self.onSpotSubDeals # 该币对的成交记录回报 + + self.cbDict["ok_sub_spot_%s_order" % symbol_pair] = self.onSpotSubOrder # 该币对的交易更新回报 + self.cbDict["ok_sub_spot_%s_balance" % symbol_pair] = self.onSpotBalance # 该币对的账号信息回报 + + self.cbDict['ok_spot_userinfo'] = self.onSpotUserInfo # 合约账户信息回报 + self.cbDict['ok_spot_orderinfo'] = self.onSpotOrderInfo # 委托查询信息回报 + + # 下面这两个好像废弃了 + # self.cbDict['ok_sub_spot_userinfo'] = self.onSpotSubUserInfo + # self.cbDict['ok_sub_spot_trades'] = self.onSpotSubTrades + + self.cbDict['ok_spot_order'] = self.onSpotOrder # 交易委托回报 + self.cbDict['ok_spot_cancel_order'] = self.onSpotCancelOrder # 交易撤单回报 + + # ---------------------------------------------------------------------- + """ + ok_sub_spot_X_ticker 订阅行情数据 + websocket.send("{'event':'addChannel','channel':'ok_sub_spot_X_ticker'}"); + + X值为币对,如ltc_btc + # Request + {'event':'addChannel','channel':'ok_sub_spot_bch_btc_ticker'} + # Response + [ + { + "binary": 0, + "channel": "ok_sub_spot_bch_btc_ticker", + "data": { + "high": "10000", + "vol": "185.03743858", + "last": "111", + "low": "0.00000001", + "buy": "115", + "change": "101", + "sell": "115", + "dayLow": "0.00000001", + "dayHigh": "10000", + "timestamp": 1500444626000 + } + } + ] + 返回值说明 + + buy(double): 买一价 + high(double): 最高价 + last(double): 最新成交价 + low(double): 最低价 + sell(double): 卖一价 + timestamp(long):时间戳 + vol(double): 成交量(最近的24小时) + """ + + def onTicker(self, ws_data): + """ + tick行情数据回报 + :param ws_data: dict + :return: + """ + channel = ws_data.get('channel') + data = ws_data.get('data', {}) + # 检查channel/data + if channel is None and len(data) ==0: + return + + try: + symbol = self.channelSymbolMap[channel] + + if symbol not in self.tickDict: + tick = VtTickData() + tick.exchange = EXCHANGE_OKEX + tick.symbol = symbol # '.'.join([symbol, tick.exchange]) + tick.vtSymbol = symbol #'.'.join([symbol, tick.exchange]) + + tick.gatewayName = self.gatewayName + self.tickDict[symbol] = tick + else: + tick = self.tickDict[symbol] + + tick.highPrice = float(data['high']) + tick.lowPrice = float(data['low']) + tick.lastPrice = float(data['last']) + tick.volume = float(data['vol'].replace(',', '')) + # 这里不发出onTick event,留待深度行情发出 + except Exception as ex: + self.gateway.writeError(u'SpotApi.onTicker异常') + self.gateway.writeLog('SpotApi.onTicker channel {} exception:{},{} '.format(channel,str(ex), traceback.format_exc())) + + # ---------------------------------------------------------------------- + """ + ok_sub_spot_X_depth 订阅币币市场深度(200增量数据返回) + websocket.send("{'event':'addChannel','channel':'ok_sub_spot_X_depth'}"); + + X值为币对,如ltc_btc + + 示例 + + # Request + {'event':'addChannel','channel':'ok_sub_spot_bch_btc_depth'} + # Response + [ + { + "binary": 0, + "channel": "ok_sub_spot_bch_btc_depth", + "data": { + "asks": [], + "bids": [ + [ + "115", + "1" + ], + [ + "114", + "1" + ], + [ + "1E-8", + "0.0008792" + ] + ], + "timestamp": 1504529236946 + } + } + ] + 返回值说明 + + bids([string, string]):买方深度 + asks([string, string]):卖方深度 + timestamp(string):服务器时间戳 + 使用描述 + + 第一次返回全量数据,根据接下来数据对第一次返回数据进行如下操作:删除(量为0时);修改(价格相同量不同);增加(价格不存在)。 + + 3、ok_sub_spot_X_depth_Y 订阅市场深度 + websocket.send("{'event':'addChannel','channel':'ok_sub_spot_X_depth_Y'}"); + + X值为币对,如ltc_btc + + Y值为获取深度条数,如5,10,20 + + 示例 + + # Request + {'event':'addChannel','channel':'ok_sub_spot_bch_btc_depth_5'} + # Response + [ + { + "binary": 0, + "channel": "ok_sub_spot_bch_btc_depth_5", + "data": { + "asks": [], + "bids": [ + [ + "115", + "1" + ], + [ + "114", + "1" + ], + [ + "1E-8", + "0.0008792" + ] + ], + "timestamp": 1504529432367 + } + } + ] + 返回值说明 + + bids([string, string]):买方深度 + asks([string, string]):卖方深度 + timestamp(long):服务器时间戳 + """ + def onDepth(self, ws_data): + """ + 5档深度行情数据回报 + :param ws_data: + :return: + """ + channel = ws_data.get('channel') + data = ws_data.get('data', {}) + # 检查channel/data + if channel is None and len(data) == 0: + return + + symbol = self.channelSymbolMap.get(channel) + if symbol == None: + return + + # 更新tick + if symbol not in self.tickDict: + tick = VtTickData() + tick.symbol = symbol + tick.vtSymbol = symbol + tick.gatewayName = self.gatewayName + self.tickDict[symbol] = tick + else: + tick = self.tickDict[symbol] + + tick.bidPrice1, tick.bidVolume1 = data['bids'][0] + tick.bidPrice2, tick.bidVolume2 = data['bids'][1] + tick.bidPrice3, tick.bidVolume3 = data['bids'][2] + tick.bidPrice4, tick.bidVolume4 = data['bids'][3] + tick.bidPrice5, tick.bidVolume5 = data['bids'][4] + + tick.askPrice1, tick.askVolume1 = data['asks'][-1] + tick.askPrice2, tick.askVolume2 = data['asks'][-2] + tick.askPrice3, tick.askVolume3 = data['asks'][-3] + tick.askPrice4, tick.askVolume4 = data['asks'][-4] + tick.askPrice5, tick.askVolume5 = data['asks'][-5] + + tick.date, tick.time = self.generateDateTime(data['timestamp']) + # print "Depth", tick.date, tick.time + + # 推送tick事件 + newtick = copy(tick) + self.gateway.onTick(newtick) + + def onSpotBalance(self, ws_data): + """ + 现货账号更新回报 + 交易发生金额变动之后会触发这个回报 + :param data: + :return: + """ + # print data + data = ws_data.get('data') + if data is None: + self.gateway.writeError(u'SpotApi.onSpotBalance, no data in ws_data') + return + + data_info = data.get('info') + if data_info is None: + self.gateway.writeError(u'SpotApi.onSpotBalance, no info in data') + return + + data_info_free = data_info.get('free', {}) # 可用资产 + data_info_freezed = data_info.get('freezed') # 冻结资产 + + if data_info_freezed is None or not isinstance(data_info_freezed, dict): + self.gateway.writeError(u'SpotApi.onSpotBalance, no freezed in data_info') + self.gateway.writeLog(ws_data) + return + + # 只更新freezed内容 + for symbol, position in data_info_freezed.items(): + pos = VtPositionData() + pos.gatewayName = self.gatewayName + pos.symbol = symbol #+ "." + EXCHANGE_OKEX + pos.vtSymbol = symbol #+ "." + EXCHANGE_OKEX + pos.direction = DIRECTION_NET + pos.frozen = float(position) + pos.position = pos.frozen + float(data_info_free.get(symbol, 0)) + self.gateway.onPosition(pos) + + """ + [{"binary":0,"channel":"ok_spot_userinfo","data":{"result":true,"info":{"funds":{"borrow":{"dgd":"0" +,"bcd":"0","bcc":"0","bch":"0","hsr":"0","xuc":"0","omg":"0","eos":"0","qtum":"0","btc":"0","act":"0 +","bcs":"0","btg":"0","etc":"0","eth":"0","usdt":"0","gas":"0","zec":"0","neo":"0","ltc":"0","bt1":" +0","bt2":"0","iota":"0","pay":"0","storj":"0","gnt":"0","snt":"0","dash":"0"},"free":{"dgd":"0","bcd +":"0","bcc":"0","bch":"0","hsr":"0","xuc":"3","omg":"0","eos":"0","qtum":"0","btc":"0.00266884258369 +","act":"0","bcs":"0","btg":"0","etc":"7.9909635","eth":"0","usdt":"0","gas":"0","zec":"0","neo":"0" +,"ltc":"0","bt1":"0","bt2":"0","iota":"0","pay":"0","storj":"0","gnt":"0","snt":"0","dash":"0"},"fre +ezed":{"dgd":"0","bcd":"0","bcc":"0","bch":"0","hsr":"0","xuc":"0","omg":"0","eos":"0","qtum":"0","b +tc":"0","act":"0","bcs":"0","btg":"0","etc":"0","eth":"0","usdt":"0","gas":"0","zec":"0","neo":"0"," +ltc":"0","bt1":"0","bt2":"0","iota":"0","pay":"0","storj":"0","gnt":"0","snt":"0","dash":"0"}}}}}] + +{u'binary': 0, + u'data': {u'info': + {u'funds': + {u'freezed': + {u'zec': u'0', u'usdt': u'0', u'btg': u'0' + , u'btc': u'0', u'bt1': u'0', u'neo': u'0', u'pay': u'0', u'storj': u'0', u'iota': u'0', u'omg': u'0 + ', u'dgd': u'0', u'bt2': u'0', u'xuc': u'0', u'gas': u'0', u'hsr': u'0', u'snt': u'0', u'dash': u'0' + , u'bch': u'0', u'gnt': u'0', u'bcd': u'0', u'qtum': u'0', u'bcc': u'0', u'eos': u'0', u'etc': u'0', + u'act': u'0', u'eth': u'0', u'ltc': u'0', u'bcs': u'0'}, u'borrow': {u'zec': u'0', u'usdt': u'0', u + 'btg': u'0', u'btc': u'0', u'bt1': u'0', u'neo': u'0', u'pay': u'0', u'storj': u'0', u'iota': u'0', + u'omg': u'0', u'dgd': u'0', u'bt2': u'0', u'xuc': u'0', u'gas': u'0', u'hsr': u'0', u'snt': u'0', u' + dash': u'0', u'bch': u'0', u'gnt': u'0', u'bcd': u'0', u'qtum': u'0', u'bcc': u'0', u'eos': u'0', u' + etc': u'0', u'act': u'0', u'eth': u'0', u'ltc': u'0', u'bcs': u'0'}, + u'free': + {u'zec': u'0', u'usdt': u'0', u'btg': u'0', u'btc': u'0.00266884258369', u'bt1': u'0', u'neo': u'0', + u'pay': u'0', u'storj': u'0', u'iota': u'0', u'omg': u'0', u'dgd': u'0', u'bt2': u'0', u'xuc': u'3', + u'gas': u'0', u'hsr': u'0', u'snt': u'0', u'dash': u'0', u'bch': u'0', u'gnt': u'0', u'bcd': u'0', + u'qtum': u'0', u'bcc': u'0', u'eos': u'0', u'etc': u'7.9909635', u'act': u'0', u'eth': u'0', + u'ltc': u'0', u'bcs': u'0'} + } + }, u'result': True}, + u'channel': u'ok_spot_userinfo'} + """ + + # ---------------------------------------------------------------------- + def onSpotUserInfo(self, ws_data): + """现货账户资金推送""" + if 'data' not in ws_data or 'info' not in ws_data['data'] or 'funds' not in ws_data['data']['info']: + self.gateway.writeError(u'SpotApi.onSpotUserInfo 数据不完整,请检查日志') + self.gateway.writeLog(ws_data) + return + + data = ws_data.get('data') + data_info = data.get('info') + data_info_funds = data_info.get('funds') + + # 持仓信息 + for symbol in SPOT_CURRENCY: + #for symbol in ['btc', 'ltc', 'eth', self.currency]: + # for symbol in : + if symbol in data_info_funds['free']: + pos = VtPositionData() + pos.gatewayName = self.gatewayName + pos.symbol = symbol # + "." + EXCHANGE_OKEX + pos.vtSymbol = symbol #+ "." + EXCHANGE_OKEX + pos.vtPositionName = symbol + pos.direction = DIRECTION_NET + + pos.frozen = float(data_info_funds['freezed'][symbol]) + pos.position = pos.frozen + float(data_info_funds['free'][symbol]) + + self.gateway.onPosition(pos) + + # 账户资金 + account = VtAccountData() + account.gatewayName = self.gatewayName + account.accountID = self.gatewayName + account.vtAccountID = account.accountID + account.balance = 0.0 + # account.balance = float(funds['asset']['net']) + self.gateway.onAccount(account) + + # ---------------------------------------------------------------------- + def onSpotSubUserInfo(self, ws_data): + """ + 现货子账户资金推送 + :param ws_data: + :return: + """ + if 'data' not in ws_data or 'info' not in ws_data['data'] or 'funds' not in ws_data['data']['info']: + self.gateway.writeError(u'SpotApi.onSpotSubUserInfo 数据不完整,请检查日志') + self.gateway.writeLog(ws_data) + return + + data = ws_data.get('data') + data_info = data.get('info') + + # 持仓信息 + # for symbol in ['btc', 'ltc','eth', self.currency]: + for symbol in SPOT_CURRENCY: + if symbol in data_info['free']: + pos = VtPositionData() + pos.gatewayName = self.gatewayName + pos.symbol = symbol # + "." + EXCHANGE_OKEX + pos.vtSymbol = symbol #+ "." + EXCHANGE_OKEX + pos.vtPositionName = symbol + pos.direction = DIRECTION_NET + + pos.frozen = float(data_info['freezed'][symbol]) + pos.position = pos.frozen + float(data_info['free'][symbol]) + + self.gateway.onPosition(pos) + + """ + 委托回报数据 + { + "base": "bch", + "binary": 0, + "channel": "ok_sub_spot_bch_btc_order", + "data": { + "symbol": "bch_btc", + "tradeAmount": "1.00000000", + "createdDate": "1504530228987", + "orderId": 6191, + "completedTradeAmount": "0.00000000", + "averagePrice": "0", + "tradePrice": "0.00000000", + "tradeType": "buy", + "status": 0, + "tradeUnitPrice": "113.00000000" + }, + "product": "spot", + "quote": "btc", + "type": "balance" + } + + {u'binary': 0, u'data': {u'orderId': 62870564, u'status': 0, u'tradeType': u'sell', u'tradeUnitPrice +': u'25.3500', u'symbol': u'etc_usdt', u'tradePrice': u'0.0000', u'createdDate': u'1512287172393', u +'averagePrice': u'0', u'tradeAmount': u'0.01000000', u'completedTradeAmount': u'0.00000000'}, u'chan +nel': u'ok_sub_spot_etc_usdt_order'} + + 返回值说明: + createdDate(string):创建日期 + orderId(long):订单id + tradeType(string):交易类型(buy:买入;sell:卖出;buy_market:按市价买入;sell_market:按市价卖出) + sigTradeAmount(string):单笔成交数量 + sigTradePrice(string):单笔成交价格 + tradeAmount(string):委托数量(市价卖代表要卖总数量;限价单代表委托数量) + tradeUnitPrice(string):委托价格(市价买单代表购买总金额; 限价单代表委托价格) + symbol(string):交易币对,如ltc_btc + completedTradeAmount(string):已完成成交量 + tradePrice(string):成交金额 + averagePrice(string):平均成交价 + unTrade(string):当按市场价买币时表示剩余金额,其他情况表示此笔交易剩余买/卖币的数量 + status(int):-1已撤销,0等待成交,1部分成交,2完全成交,4撤单处理中 + """ + + # ---------------------------------------------------------------------- + def onSpotSubOrder(self, ws_data): + """ + 交易委托更新回报(发生部分成交/全部成交/拒单/撤销时,API推送的回报) + :param ws_data:ws推送的委托更新数据 + :return: + """ + data = ws_data("data") + if data is None: return + + # 本地和系统委托号 + orderId = str(data['orderId']) + + localNo = self.orderIdDict.get(orderId, None) + if localNo == None: + self.gateway.writeError(u'订单与本地委托无关联,不处理') + self.gateway.writeLog(data) + return + + # 委托信息 + if orderId not in self.orderDict: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = data['symbol'] # '.'.join([rawData['symbol'], EXCHANGE_OKEX]) + order.vtSymbol = order.symbol + + order.orderID = localNo + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.price = float(data['tradeUnitPrice']) + order.totalVolume = float(data['tradeAmount']) + order.direction, priceType = priceTypeMap[data['tradeType']] + + self.orderDict[orderId] = order + else: + order = self.orderDict[orderId] + + order.tradedVolume = float(data['completedTradeAmount']) + order.status = statusMap[data['status']] + + # 推送onOrder event + self.gateway.onOrder(copy(order)) + + bef_volume = self.recordOrderId_BefVolume.get(orderId, 0.0) + now_volume = float(data['completedTradeAmount']) - bef_volume + + if now_volume > 0.000001: + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = order.symbol + trade.vtSymbol = order.symbol + + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.orderID = localNo + trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) + + trade.price = float(data['tradeUnitPrice']) + trade.volume = float(now_volume) + + trade.direction, priceType = priceTypeMap[data['tradeType']] + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + + # 推送onTrade事件 + self.gateway.onTrade(trade) + + # ---------------------------------------------------------------------- + """ 委托信息查询回调得例子 + { + "binary": 0, + "channel": "ok_spot_orderinfo", + "data": { + "result": true, + "orders": [ + { + "symbol": "bch_btc", + "amount": "0.10000000", + "price": "1.00000000", + "avg_price": 0, + "create_date": 1504529828000, + "type": "buy", + "deal_amount": 0, + "order_id": 6189, + "status": -1 + } + ] + } + } + """ + + def onSpotOrderInfo(self, ws_data): + """ + 所有委托信息查询响应 + :param ws_data: + :return: + """ + + # 获取channel的数据 + data = ws_data.get('data', {}) + # 获取所有返回的委托单清单 + orders = data.get('orders', []) + + for data_order in orders: + self.localNo += 1 # 本地序列号自增长 + localNo = str(self.localNo) # int =》 str + + order_id = data_order.get('order_id') # 服务端的委托单编号 + if order_id is None: + self.gateway.writeError(u'SpotApi.onSpotOrderInfo,取order_id/orderId异常,请查日志') + self.gateway.writeLog(u'data_order:{}'.format(data_order)) + continue + + orderId = str(order_id) # int =》 str + + self.localNoDict[localNo] = orderId # 建立本地序列号与服务端序列号的相互映射 + self.orderIdDict[orderId] = localNo + + if orderId not in self.orderDict: # 不在本地委托单缓存时,添加 + order = VtOrderData() + order.gatewayName = self.gatewayName + + # order.symbol = spotSymbolMap[d['symbol']] + order.symbol = data_order["symbol"] # '.'.join([d["symbol"], EXCHANGE_OKEX]) + order.vtSymbol = order.symbol + + order.orderID = localNo + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.price = data_order['price'] + order.totalVolume = data_order['amount'] + order.direction, priceType = priceTypeMap[data_order['type']] + + self.orderDict[orderId] = order + else: + order = self.orderDict[orderId] # 使用本地缓存 + + order.tradedVolume = data_order['deal_amount'] # 更新成交数量 + order.status = statusMap[data_order['status']] # 更新成交状态 + + self.gateway.onOrder(copy(order)) # 提交onOrder事件 + + # ---------------------------------------------------------------------- + """ onSpotOrder的回报 ws_data 例子 + { + "binary": 0, + "channel": "ok_spot_order", + "data": { + "result": true, + "order_id": 6189 + } + } + """ + def onSpotOrder(self, ws_data): + """ + 交易委托回报 + :param ws_data: + :return: + """ + data = ws_data.get('data', {}) + + if 'error_code' in data: + error_id = data.get('error_code') + self.gateway.writeError(u'SpotApi.onSpotOrder 委托返回错误:{}'.format(SPOT_ERROR_DICT.get(error_id)), error_id=error_id) + self.gateway.writeLog(ws_data) + return + + orderId = data.get('order_id') + + if orderId is None: + self.gateway.writeError(u'SpotApi.onSpotOrder 委托返回中,没有orderid') + self.gateway.writeLog(ws_data) + return + + # 从本地编号Queue中,FIFO,提取最早的localNo + localNo = self.localNoQueue.get_nowait() + if localNo is None: + self.gateway.writeError(u'SpotApi.onSportOrder,未找到本地LocalNo,检查日志') + self.gateway.writeLog(ws_data) + return + + self.localNoDict[localNo] = str(orderId) + self.orderIdDict[str(orderId)] = localNo + + # ---------------------------------------------------------------------- + """ + onSpotCancelOrder得回报 ws_data例子 + { + "binary": 0, + "channel": "ok_spot_cancel_order", + "data": { + "result": true, + "order_id": "125433027" + } + } + """ + def onSpotCancelOrder(self, ws_data): + """ + 撤单请求回报 + :param ws_data: + :return: + """ + data = ws_data.get('data', {}) + + if 'error_code' in data: + error_id = data.get('error_code') + self.gateway.writeError(u'SpotApi.onSpotCancelOrder 委托返回错误:{}'.format(SPOT_ERROR_DICT.get(error_id)), error_id=error_id) + self.gateway.writeLog(ws_data) + return + + orderId = data.get('order_id') + if orderId is None: + self.gateway.writeError(u'SpotApi.onSpotCancelOrder 委托返回中,没有orderid') + self.gateway.writeLog(ws_data) + return + + orderId = str(orderId) + localNo = self.orderIdDict[orderId] + + if orderId not in self.orderDict: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = data['symbol'] # '.'.join([rawData['symbol'], EXCHANGE_OKEX]) + order.vtSymbol = order.symbol + + order.orderID = localNo + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + self.orderDict[orderId] = order + else: + order = self.orderDict[orderId] + + order.status = STATUS_CANCELLED + self.gateway.onOrder(order) + + del self.orderDict[orderId] + del self.orderIdDict[orderId] + del self.localNoDict[localNo] + + + # ---------------------------------------------------------------------- + def spotSendOrder(self, req): + """发委托单""" + # 取得币币配对symbol_pair,如果上层在symbol后添加 .OKEX,需要去除 + symbol = (req.symbol.split('.'))[0] + + # 获取匹配okex的订单类型 + type_ = priceTypeMapReverse[(req.direction, req.priceType)] + + # 本地委托号加1,并将对应字符串保存到队列中,返回基于本地委托号的vtOrderID + self.localNo += 1 + self.localNoQueue.put(str(self.localNo)) + vtOrderID = '.'.join([self.gatewayName, str(self.localNo)]) + + # 调用ws api发送委托 + self.spotTrade(symbol, type_, str(req.price), str(req.volume)) + self.gateway.writeLog('SpotSendOrder:symbol:{},Type:{},price:{},volume:{}'.format(symbol, type_, str(req.price), str(req.volume))) + + return vtOrderID + + # ---------------------------------------------------------------------- + def spotCancel(self, req): + """撤单""" + # symbol = spotSymbolMapReverse[req.symbol][:4] + symbol = (req.symbol.split('.'))[0] + localNo = req.orderID + + if localNo in self.localNoDict: + orderID = self.localNoDict[localNo] + self.spotCancelOrder(symbol, orderID) + else: + # 如果在系统委托号返回前客户就发送了撤单请求,则保存 + # 在cancelDict字典中,等待返回后执行撤单任务 + self.cancelDict[localNo] = req + + # ---------------------------------------------------------------------- + def generateDateTime(self, s): + """生成时间""" + dt = datetime.fromtimestamp(float(s) / 1e3) + time = dt.strftime("%H:%M:%S.%f") + date = dt.strftime("%Y%m%d") + return date, time + + +######################################################################## +class OkexFuturesApi(WsFuturesApi): + """okex的合约API实现""" + + # ---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(WsFuturesApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.active = False # 若为True则会在断线后自动重连 + + self.cbDict = {} + self.tickDict = {} + self.orderDict = {} + + self.channelSymbolMap = {} + self.localNo = 0 # 本地委托号 + self.localNoQueue = Queue() # 未收到系统委托号的本地委托号队列 + self.localNoDict = {} # key为本地委托号,value为系统委托号 + self.orderIdDict = {} # key为系统委托号,value为本地委托号 + self.cancelDict = {} # key为本地委托号,value为撤单请求 + + self.recordOrderId_BefVolume = {} # 记录的之前处理的量 + + self.cache_some_order = {} + self.tradeID = 0 + + self.registerSymbolPairArray = set([]) + + self._use_leverage = "10" + + self.bids_depth_dict = {} + self.asks_depth_dict = {} + + self.contract_name_dict = {} + + self.contractIdToSymbol = {} + + # ---------------------------------------------------------------------- + def setLeverage(self, __leverage): + self._use_leverage = __leverage + + # ---------------------------------------------------------------------- + def onMessage(self, ws, evt): + """信息推送""" + # str => json + ws_data = self.readData(evt) + + if self.gateway.log_message: + self.gateway.writeLog(u'SpotApi.onMessage:{}'.format(ws_data)) + + # 返回dict + if isinstance(ws_data, dict): + if 'event' in ws_data: + # 处理心跳得 + if ws_data['event'] == 'pong': + self.writeLog(u'heart beat response {}'.format(datetime.now())) + return + else: + self.writeLog(u'其他 event:{}'.format(ws_data)) + else: + self.writeLog(u'其他数据:{}'.format(ws_data)) + + # 返回list + if isinstance(ws_data, list): + for data in ws_data: + channel_value = data['channel'] if 'channel' in data else None + if channel_value == None: + continue + + # 登录请求恢复 + if channel_value == 'login': + if 'data' in data: + login_data = data['data'] + result = login_data['result'] if 'result' in login_data else False + if result: + self.writeLog(u'login success: {}'.format(datetime.now())) + self.gateway.futures_connected = True + else: + print(u'login fail: {},data:{}'.format(datetime.now(), data)) + continue + + # 功能请求回复 + elif channel_value == "addChannel": + if 'data' in data: + channel_data = data['data'] + result = channel_data['result'] if 'result' in channel_data else False + channel_value = channel_data["channel"] + if result: + self.writeLog(u'请求:{} 成功:'.format(channel_value)) + continue + else: + print(u'not data in :addChannel:{}'.format(data)) + + # 其他回调/数据推送 + callback = self.cbDict.get(channel_value) + if callback: + try: + callback(data) + except Exception as ex: + print(u'onMessage call back {} exception:{},{}'.format(channel_value, str(ex), + traceback.format_exc())) + else: + print(u'unkonw msg:{}'.format(data)) + + # ---------------------------------------------------------------------- + def onError(self, ws, evt): + """错误推送""" + error = VtErrorData() + error.gatewayName = self.gatewayName + error.errorMsg = str(evt) + self.gateway.onError(error) + + # #---------------------------------------------------------------------- + def onErrorMsg(self, data): + error = VtErrorData() + error.gatewayName = self.gatewayName + error_code = str(data["data"]["error_code"]) + error.errorID = error_code + error.errorMsg = u'FutureApi Error:{}'.format(FUTURES_ERROR_DICT.get(error_code)) + self.gateway.onError(error) + + # ---------------------------------------------------------------------- + def reconnect(self): + while not self.gateway.connected_contract: + self.writeLog(u'okex Api_contract 等待10秒后重新连接') + self.connect_Contract(self.apiKey, self.secretKey, self.trace) + sleep(10) + if not self.gateway.connected_contract: + self.reconnect() + + # ---------------------------------------------------------------------- + def onClose(self, ws): + """接口断开""" + # 如果尚未连上,则忽略该次断开提示 + if not self.gateway.connected_contract: + return + + self.gateway.connected_contract = False + self.writeLog(u'服务器连接断开') + + # 重新连接 + if self.active: + t = Thread(target=self.reconnect) + t.start() + + # ---------------------------------------------------------------------- + def dealSymbolFunc(self, symbol): + """ + 分解委托单symbol + :param symbol: + :return: + """ + arr = symbol.split('.') + symbol_pair = arr[0] + (symbol, contract_type, leverage) = symbol_pair.split(':') + symbol = symbol.replace("_usd", "") + + return (symbol_pair, symbol, contract_type, leverage) + + # ---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """ + 订阅行情 + :param subscribeReq: + :return: + """ + # 分解出 合约对/合约/合约类型/杠杆倍数 + (symbol_pair, symbol, contract_type, leverage) = self.dealSymbolFunc(subscribeReq.symbol) + + if symbol_pair not in self.registerSymbolPairArray: + # 登记合约对(btc_usd ltc_usd eth_usd etc_usd bch_usd) + self.registerSymbolPairArray.add(symbol_pair) + # 订阅行情 + self.subscribeSingleSymbol(symbol, contract_type, leverage) + + # ---------------------------------------------------------------------- + def subscribeSingleSymbol(self, symbol, contract_type, leverage): + """ + 订阅行情 + :param symbol: 合约,如btc,eth,etc等 + :param contract_type: 合约类型,当周,下周,季度 + :param leverage:杠杆倍数 + :return: + """ + self.gateway.writeLog(u'FuturesApi.subscribeSingleSymbol:symbol:{},cotract_type:{},leverage:{}'.format(symbol, contract_type, leverage)) + if symbol in CONTRACT_SYMBOL: + # 订阅tick行情 + self.subsribeFutureTicker(symbol, contract_type) + # 订阅深度行情 + self.subscribeFutureDepth(symbol, contract_type) + # 查询该合约的委托清单 + self.futureOrderInfo(symbol + "_usd", -1, contract_type, status="1", current_page=1, page_length=50) + # self.subscribeFutureTrades(symbol) + + # ---------------------------------------------------------------------- + def futureAllOrders(self): + """ + 发出查询所有合约委托查询 + :return: + """ + for symbol in self.registerSymbolPairArray: + # 根据已订阅的合约清单,逐一合约发出 + (symbol_pair, symbol, contract_type, leverage) = self.dealSymbolFunc(symbol) + self.futureOrderInfo(symbol + "_usd", -1, contract_type, status="1", current_page=1, page_length=50) + + + for orderId in self.orderIdDict.keys(): + # 根据缓存的委托单id,逐一发出委托查询 + order = self.orderDict.get(orderId, None) + if order != None: + symbol_pair, symbol, contract_type, leverage = self.dealSymbolFunc(order.symbol) + self.futureOrderInfo(symbol + "_usd", orderId, contract_type, status="1", current_page=1, + page_length=50) + + # ---------------------------------------------------------------------- + def onOpen(self, ws): + """连接成功""" + self.gateway.connected_contract = True + self.writeLog(u'服务器OKEX合约连接成功') + + self.initCallback() + + for symbol in CONTRACT_SYMBOL: + self.channelSymbolMap[ + "ok_sub_futureusd_%s_index" % symbol] = symbol + "_usd:%s:" + self._use_leverage # + "." + EXCHANGE_OKEX + + for use_contract_type in CONTRACT_TYPE: + use_symbol_name = symbol + "_usd:%s:%s" % (use_contract_type, self._use_leverage) + # Ticker数据 + self.channelSymbolMap["ok_sub_futureusd_%s_ticker_%s" % (symbol, use_contract_type)] = use_symbol_name + # 盘口的深度 + self.channelSymbolMap["ok_sub_future_%s_depth_%s_usd" % (symbol, use_contract_type)] = use_symbol_name + # 所有人的交易数据 + self.channelSymbolMap["ok_sub_futureusd_%s_trade_%s" % (symbol, use_contract_type)] = use_symbol_name + + contract = VtContractData() + contract.gatewayName = self.gatewayName + contract.symbol = use_symbol_name # + "." + EXCHANGE_OKEX + contract.exchange = EXCHANGE_OKEX + contract.vtSymbol = contract.symbol + contract.name = u'期货%s_%s_%s' % (symbol, use_contract_type, self._use_leverage) + contract.size = 0.00001 + contract.priceTick = 0.00001 + contract.productClass = PRODUCT_FUTURES + self.gateway.onContract(contract) + + # print contract.vtSymbol , contract.name + + quanyi_vtSymbol = symbol + "_usd_future_qy" # + "."+ EXCHANGE_OKEX + contract = VtContractData() + contract.gatewayName = self.gatewayName + contract.symbol = quanyi_vtSymbol + contract.exchange = EXCHANGE_OKEX + contract.vtSymbol = contract.symbol + contract.name = u'期货权益%s' % (symbol) + contract.size = 0.00001 + contract.priceTick = 0.00001 + contract.productClass = PRODUCT_FUTURES + self.gateway.onContract(contract) + + self.login() + # 连接后查询账户和委托数据 + self.futureUserInfo() + self.futureAllUnfinishedOrderInfo() + self.futureAllIndexSymbol() + + # print "test" + self.subscribeSingleSymbol("btc", "this_week", "10") + self.subscribeSingleSymbol("etc", "this_week", "10") + self.subscribeSingleSymbol("eos", "this_week", "10") + self.subscribeSingleSymbol("xrp", "this_week", "10") + self.subscribeSingleSymbol("btg", "this_week", "10") + + # ---------------------------------------------------------------------- + def writeLog(self, content): + """快速记录日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + # ---------------------------------------------------------------------- + def LoopforceGetContractDict(self, unFishedSet): + if len(unFishedSet) > 0: + new_unfishedSet = set([]) + for symbol, use_contract_type in unFishedSet: + t_contract_name = self.getContractName(symbol, use_contract_type) + if t_contract_name != None: + self.contract_name_dict[t_contract_name] = {"symbol": symbol, "contract_type": use_contract_type} + else: + new_unfishedSet.add((symbol, use_contract_type)) + + self.LoopforceGetContractDict(new_unfishedSet) + + # ---------------------------------------------------------------------- + def initCallback(self): + """初始化回调函数""" + # USD_CONTRACT + unfinished_contract_set = set([]) + for symbol in CONTRACT_SYMBOL: + self.cbDict["ok_sub_futureusd_%s_index" % (symbol)] = self.onFutureIndexInfo + for use_contract_type in CONTRACT_TYPE: + self.cbDict["ok_sub_futureusd_%s_ticker_%s" % (symbol, use_contract_type)] = self.onTicker + self.cbDict["ok_sub_future_%s_depth_%s_usd" % (symbol, use_contract_type)] = self.onDepth + self.cbDict["ok_sub_futureusd_%s_trade_%s" % (symbol, use_contract_type)] = self.onTrade + + t_contract_name = self.getContractName(symbol, use_contract_type) + if t_contract_name != None: + self.contract_name_dict[t_contract_name] = {"symbol": symbol, "contract_type": use_contract_type} + else: + unfinished_contract_set.add((symbol, use_contract_type)) + + self.LoopforceGetContractDict(unfinished_contract_set) + + # print self.contract_name_dict.keys() + + self.cbDict['ok_futureusd_trade'] = self.onFutureOrder + self.cbDict['ok_futureusd_cancel_order'] = self.onFutureOrderCancel + + self.cbDict['ok_futureusd_userinfo'] = self.onFutureUserInfo + self.cbDict['ok_futureusd_orderinfo'] = self.onFutureOrderInfo + + self.cbDict['ok_sub_futureusd_trades'] = self.onFutureSubTrades + self.cbDict['ok_sub_futureusd_userinfo'] = self.onFutureSubUserinfo + + self.cbDict['ok_sub_futureusd_positions'] = self.onFutureSubPositions + + # ---------------------------------------------------------------------- + ''' + 推送 币币交易指数过去 + websocket.send("{'event':'addChannel','channel':'ok_sub_futureusd_X_index'}"); + + ① X值为:btc, ltc, eth, etc, bch + # Request + {'event':'addChannel','channel':'ok_sub_futureusd_btc_index'} + # Response + [ + { + "data": { + "timestamp": "1490341322021", + "futureIndex": "998.0" + }, + "channel": "ok_sub_futureusd_btc_index" + } + ] + 返回值说明 + + futureIndex(string): 指数 + timestamp(string): 时间戳 + ''' + + def onFutureIndexInfo(self, ws_data): + """ + 指数合约推送 + :param ws_data: + :return: + """ + data = ws_data.get('data') + + if data is None: + return + + channel = ws_data['channel'] + + # 获取日期/时间 + t_date, t_time = self.generateDateTime(float(data["timestamp"])) + # 获取指数值 + float_index = float(data["futureIndex"]) + + # 根据channnel,获取合约 + symbol_pattern = self.channelSymbolMap[channel] + for use_contract_type in CONTRACT_TYPE: + # 合约, + symbol = symbol_pattern % (use_contract_type) + push_dic = {"symbol": symbol, "date": t_date, "time": t_time, "index": float_index} + # 调用gateway,推送event事件 + self.gateway.onFutureIndexPush(push_dic) + + # ---------------------------------------------------------------------- + """ + 期货行情推送 例子 + { + "data": { + "limitHigh": "1030.3", + "vol": 276406, + "last": 998.05, + "sell": 998.05, + "buy": 997.61, + "unitAmount": 100, + "hold_amount": 180178, + "contractId": 20170324034, + "high": 1049.18, + "low": 973.15, + "limitLow": "968.1" + }, + "channel": "ok_sub_futureusd_btc_ticker_this_week" + } + """ + + def onTicker(self, ws_data): + """ + 期货行情推送 + :param ws_data: + :return: + """ + data = ws_data.get('data', {}) + if len(data) == 0: + return + + channel = ws_data.get('channel') + if channel is None: + return + + try: + symbol = self.channelSymbolMap[channel] + # 首次到达tick,记录在tickDict缓存 + if symbol not in self.tickDict: + tick = VtTickData() + tick.exchange = EXCHANGE_OKEX + tick.symbol = symbol # '.'.join([symbol, tick.exchange]) + tick.vtSymbol = tick.symbol + + tick.gatewayName = self.gatewayName + self.tickDict[symbol] = tick + + # 创建symbol对应得深度行情字典缓存 + self.bids_depth_dict[symbol] = {} + self.asks_depth_dict[symbol] = {} + else: + tick = self.tickDict[symbol] + + # 更新 + tick.highPrice = float(data['high']) + tick.lowPrice = float(data['low']) + tick.lastPrice = float(data['last']) + tick.volume = float(str(data['vol']).replace(',', '')) + + self.contractIdToSymbol[str(data["contractId"])] = tick.symbol + # 待深度行情推送onTick + except Exception as ex: + self.gateway.writeError(u'ContractApi.onTicker exception:{}'.format(str(ex))) + self.gateway.writeLog(ws_data) + self.gateway.writeLog(u'ContractApi.onTicker exception:{},{}'.format(str(ex), traceback.format_exc())) + + # ---------------------------------------------------------------------- + """ + { + "data": { + "timestamp": 1490337551299, + "asks": [ + [ + "996.72", + "20.0", + "2.0065", + "85.654", + "852.0" + ] + ], + "bids": [ + [ + "991.67", + "6.0", + "0.605", + "0.605", + "6.0" + ] + }, + "channel": "ok_sub_futureusd_btc_depth_this_week" + } + + timestamp(long): 服务器时间戳 + asks(array):卖单深度 数组索引(string) 0 价格, 1 量(张), 2 量(币) 3, 累计量(币) 4,累积量(张) + bids(array):买单深度 数组索引(string) 0 价格, 1 量(张), 2 量(币) 3, 累计量(币) 4,累积量(张) + 使用描述: + 1,第一次返回全量数据 + 2,根据接下来数据对第一次返回数据进行,如下操作 + 删除(量为0时) + 修改(价格相同量不同) + 增加(价格不存在) + {u'binary': 0, u'data': {u'timestamp': 1515745170254L, u'bids': [[33.824, 6, 1.7 +738, 1.7738, 6], [33.781, 6, 1.7761, 7.6905, 26], [33.775, 6, 1.7764, 9.4669, 32 +], [33.741, 0, 11.2622, 20.1331, 68], [33.692, 6, 1.7808, 991.3629, 3341], [33.6 +35, 7, 2.0811, 1213.9536, 4090], [33.633, 0, 2.3786, 1223.1365, 4121], [33.629, +0, 1.1894, 1224.3259, 4125], [33.62, 14, 4.1641, 1218.1177, 4104], [33.617, 0, 1 +.4873, 1225.8132, 4130], [33.614, 7, 2.0824, 1221.6875, 4116], [33.601, 0, 1.785 +6, 1344.853, 4530], [33.591, 4, 1.1907, 1343.4074, 4525], [33.581, 16, 4.7645, 1 +351.7451, 4553], [33.579, 6, 1.7868, 1353.5319, 4559], [33.552, 6, 1.7882, 1360. +0871, 4581], [33.543, 0, 1.4906, 1386.5663, 4670], [33.54, 136, 40.5485, 1423.88 +44, 4795], [33.539, 9, 2.6834, 1426.5678, 4804], [33.53, 50, 14.912, 1441.4798, +4854], [33.525, 5, 1.4914, 1442.9712, 4859], [33.522, 5, 1.4915, 1444.4627, 4864 +], [33.511, 10, 2.984, 1448.6402, 4878], [33.502, 0, 2.6864, 1409.2357, 4746], [ +33.479, 4, 1.1947, 2353.4177, 7909], [25.245, 0, 9.9029, 21190.5178, 68253], [25 +.235, 0, 10.3031, 21200.8209, 68279], [25.225, 0, 4.3607, 21205.1816, 68290], [2 +5.215, 0, 3.9658, 21209.1474, 68300], [25.205, 0, 3.9674, 21213.1148, 68310]], u +'asks': [[43.493, 0, 3.2189, 21250.6634, 74286], [43.473, 0, 4.8305, 21247.4445, + 74272], [43.463, 0, 5.5219, 21242.614, 74251], [43.453, 0, 5.5232, 21237.0921, +74227], [43.433, 0, 3.6838, 21231.5689, 74203], [34.238, 500, 146.0365, 5495.506 +5, 18746], [34.18, 5, 1.4628, 3523.3267, 12000], [34.159, 6, 1.7564, 3521.8639, +11995], [34.137, 5, 1.4646, 3020.1076, 10282], [34.107, 6, 1.7591, 2013.2213, 68 +46], [34.105, 4, 1.1728, 2011.4622, 6840], [34.09, 4, 1.1733, 1345.1823, 4568], +[34.072, 500, 146.748, 1324.3485, 4497], [34.07, 6, 1.761, 1177.6005, 3997], [34 +.068, 683, 200.4813, 1175.8395, 3991], [34.063, 0, 1.1742, 944.5044, 3203], [34. +039, 15, 4.4067, 975.3582, 3308], [34.025, 0, 1.4695, 943.3302, 3199], [34.024, +7, 2.0573, 970.9515, 3293], [34.014, 0, 1.4699, 941.8607, 3194], [33.992, 5, 1.4 +709, 815.0705, 2763], [33.977, 0, 1.4715, 784.8013, 2660], [33.976, 0, 1.4716, 7 +83.3298, 2655], [33.971, 0, 2.3549, 781.8582, 2650], [33.968, 0, 2.0607, 779.503 +3, 2642], [33.965, 0, 1.1776, 777.4426, 2635], [33.96, 0, 2.0612, 776.265, 2631] +, [33.953, 200, 58.9049, 811.8338, 2752], [33.909, 13, 3.8337, 294.4836, 997], [ +33.908, 0, 7.0779, 315.7585, 1069], [33.905, 27, 7.9634, 286.5209, 970], [33.899 +, 8, 2.3599, 261.4503, 885], [33.895, 9, 2.6552, 259.0904, 877], [33.894, 0, 4.4 +255, 273.2863, 925], [33.893, 13, 3.8356, 256.4352, 868], [33.891, 0, 4.1308, 26 +8.8608, 910], [33.884, 6, 1.7707, 252.5996, 855], [33.878, 13, 3.8372, 250.8289, + 849], [33.825, 0, 17.7383, 17.7383, 60]]}, u'channel': u'ok_sub_future_etc_dept +h_this_week_usd'} + """ + def onDepth(self, ws_data): + """ + 期货深度行情推送 + :param ws_data: + :return: + """ + channel = ws_data.get('channel') + data = ws_data.get('data', {}) + if channel is None or len(data) == 0: + return + + symbol = self.channelSymbolMap.get(channel) + if symbol is None: + return + + try: + if symbol not in self.tickDict: + tick = VtTickData() + tick.symbol = symbol + tick.vtSymbol = symbol + tick.gatewayName = self.gatewayName + self.tickDict[symbol] = tick + + self.bids_depth_dict[symbol] = {} + self.asks_depth_dict[symbol] = {} + else: + tick = self.tickDict[symbol] + + tick_bids_depth = self.bids_depth_dict[symbol] + tick_asks_depth = self.asks_depth_dict[symbol] + + # 更新bids得价格深度 + for inf in data.get('bids', []): + price1, vol1, vol2, acc_vol1, acc_vol2 = inf + if abs(float(vol1)) < 0.00001: + del tick_bids_depth[price1] + else: + tick_bids_depth[price1] = float(vol1) + + try: + # 根据bidPrice价格排序 + arr = sorted(tick_bids_depth.items(), key=lambda x: x[0]) + + # 取后五个 + tick.bidPrice1, tick.bidVolume1 = arr[-1] + tick.bidPrice2, tick.bidVolume2 = arr[-2] + tick.bidPrice3, tick.bidVolume3 = arr[-3] + tick.bidPrice4, tick.bidVolume4 = arr[-4] + tick.bidPrice5, tick.bidVolume5 = arr[-5] + except Exception as ex: + self.writeLog(u'ContractApi.onDepth exception:{},{}'.format(str(ex), traceback.format_exc())) + + for inf in data.get('asks', []): + price1, vol1, vol2, acc_vol1, acc_vol2 = inf + if abs(float(vol1)) < 0.00001: + del tick_asks_depth[price1] + else: + tick_asks_depth[price1] = float(vol1) + try: + # 根据ask价格排序 + arr = sorted(tick_asks_depth.items(), key=lambda x: x[0]) + # 取前五个 + tick.askPrice1, tick.askVolume1 = arr[0] + tick.askPrice2, tick.askVolume2 = arr[1] + tick.askPrice3, tick.askVolume3 = arr[2] + tick.askPrice4, tick.askVolume4 = arr[3] + tick.askPrice5, tick.askVolume5 = arr[4] + + except Exception as ex: + self.writeLog(u'ContractApi.onDepth exception:{},{}'.format(str(ex), traceback.format_exc())) + + tick.date, tick.time = self.generateDateTime(data['timestamp']) + + # 推送onTick事件 + newtick = copy(tick) + self.gateway.onTick(newtick) + + except Exception as ex: + self.writeLog(u'ContractApi.onDepth exception:{},{}'.format(str(ex), traceback.format_exc())) + + # ---------------------------------------------------------------------- + """ + [ + { + "data":{ + "result":true, + "order_id":5017287829 + }, + "channel":"ok_futureusd_trade" + } + ] + + real data + {u'binary': 0, u'data': {u'order_id': 230874269033472L, u'result': True}, u'channel': u'ok_futureusd_trade'} + """ + + def onTrade(self, ws_data): + """ + 委托全部成交回报 + :param ws_data: + :return: + """ + self.writeLog(u'onTrade {}'.format(ws_data)) + + # ---------------------------------------------------------------------- + + """ + { + "data":{ + "result":true, + "order_id":"5017402127" + }, + "channel":"ok_futureusd_order" + } + """ + def onFutureOrder(self, ws_data): + """ + 委托下单请求响应, + :param ws_data: 出错代码,或者委托成功得order_id + :return: + """ + + data = ws_data.get('data', {}) + error_code = data.get('error_code') + if error_code is not None: + self.gateway.writeError(u'委托返回错误:{}'.format(FUTURES_ERROR_DICT.get(error_code)), error_id=error_code) + self.gateway.writeLog(ws_data) + return + orderId = data.get('order_id') + + if orderId is None: + self.gateway.writeError(u'SpotApi.onFutureOrder 委托返回中,没有orderid') + self.gateway.writeLog(ws_data) + return + + orderId = str(orderId) + + # 从本地编号Queue中,FIFO,提取最早的localNo + localNo = self.localNoQueue.get_nowait() + if localNo is None: + self.gateway.writeError(u'SpotApi.onSportOrder,未找到本地LocalNo,检查日志') + self.gateway.writeLog(ws_data) + return + + self.localNoDict[localNo] = orderId + self.orderIdDict[orderId] = localNo + + # ---------------------------------------------------------------------- + """ + { + "data":{ + "result":true, + "order_id":"5017402127" + }, + "channel":"ok_futureusd_cancel_order" + } + """ + def onFutureOrderCancel(self, ws_data): + """ + 委托撤单的响应" + :param ws_data: + :return: + """"" + data = ws_data.get('data', {}) + + if 'error_code' in data: + error_id = data.get('error_code', 0) + + self.gateway.writeError(u'SpotApi.onFutureOrderCancel 委托返回错误:{}'.format(FUTURES_ERROR_DICT.get(error_code)), error_id=error_id) + self.gateway.writeLog(ws_data) + return + + orderId = data.get('order_id') + if orderId is None: + self.gateway.writeError(u'SpotApi.onFutureOrderCancel 委托返回中,没有orderid') + self.gateway.writeLog(ws_data) + return + orderId = str(orderId) + # 获取本地委托流水号 + localNo = self.orderIdDict[orderId] + + # 发送onOrder事件 + order = self.orderDict[orderId] + order.status = STATUS_CANCELLED + self.gateway.onOrder(order) + + # 删除本地委托号与orderid的绑定 + del self.orderDict[orderId] + del self.orderIdDict[orderId] + del self.localNoDict[localNo] + + + ''' + 逐仓返回: + [{ + "channel": "ok_futureusd_userinfo", + "data": { + "info": { + "btc": { + "balance": 0.00000673, + "contracts": [{ + "available": 0.00000673, + "balance": 0, + "bond": 0, + "contract_id": 20150327013, + "contract_type": "quarter", + "freeze": 0, + "profit": 0, + "unprofit": 0 + }], + "rights": 0.00000673 + }, + "ltc": { + "balance": 0.00007773, + "contracts": [{ + "available": 16.5915, + "balance": 0, + "bond": 0.70871722, + "contract_type": "this_week", + "contractid": 20150130115, + "freeze": 0, + "profit": 17.30020414, + "unprofit": -1.8707 + }, { + "available": 0.00007773, + "balance": 0.03188496, + "bond": 0, + "contract_type": "quarter", + "contractid": 20150327116, + "freeze": 0, + "profit": -0.03188496, + "unprofit": 0 + }], + "rights": 0.00007773 + } + }, + "result": true + } + }] + balance(double): 账户余额 + symbol(string):币种 + keep_deposit(double):保证金 + profit_real(double):已实现盈亏 + unit_amount(int):合约价值 + 逐仓信息 + balance(double):账户余额 + available(double):合约可用 + balance(double):合约余额 + bond(double):固定保证金 + contract_id(long):合约ID + contract_type(string):合约类别 + freeze(double):冻结 + profit(double):已实现盈亏 + unprofit(double):未实现盈亏 + rights(double):账户权益 + 全仓返回: + [{ + "channel": "ok_futureusd_userinfo", + "data": { + "info": { + "btc": { + "account_rights": 1, + "keep_deposit": 0, + "profit_real": 3.33, + "profit_unreal": 0, + "risk_rate": 10000 + }, + "ltc": { + "account_rights": 2, + "keep_deposit": 2.22, + "profit_real": 3.33, + "profit_unreal": 2, + "risk_rate": 10000 + } + }, + "result": true + } + }] + + 实际日志 + {u'binary': 0, u'data': {u'info': {u'ltc': {u'contracts': [], u'balance': 0, u'rights': 0}, u'bch': +{u'contracts': [], u'balance': 0, u'rights': 0}, u'eos': {u'contracts': [], u'balance': 0, u'rights' +: 0}, u'etc': {u'contracts': [], u'balance': 4.98, u'rights': 4.98}, u'btg': {u'contracts': [], u'ba +lance': 0, u'rights': 0}, u'btc': {u'contracts': [], u'balance': 0, u'rights': 0}, u'eth': {u'contra +cts': [], u'balance': 0, u'rights': 0}, u'xrp': {u'contracts': [], u'balance': 0, u'rights': 0}}, u' +result': True}, u'channel': u'ok_futureusd_userinfo'} + +{u'contracts': [], u'balance': 0, u'rights': 0}, u'eos': {u'contracts': [], u'balance': 0, u'rights' +: 0}, u'etc': {u'contracts': [{u'available': 4.92279753, u'contract_id': 201802160040063L, u'profit' +: -0.01702246, u'unprofit': 0.0005, u'freeze': 0, u'contract_type': u'this_week', u'balance': 0.0572 +0247, u'bond': 0.04018001}], u'balance': 4.92279753, u'rights': 4.96357399}, u'btg': {u'contracts': +[], u'balance': 0, u'rights': 0}, u'btc': {u'contracts': [], u'balance': 0, u'rights': 0}, u'eth': { +u'contracts': [], u'balance': 0, u'rights': 0}, u'xrp': {u'contracts': [], u'balance': 0, u'rights': + 0}}, u'result': True}, u'channel': u'ok_futureusd_userinfo'} + + + u'etc': {u'contracts': [{u'available': 4.92279753, u'contract_id': 201802160040063L, u'profit' +: -0.01702246, u'unprofit': 0.0005, u'freeze': 0, u'contract_type': u'this_week', u'balance': 0.0572 +0247, u'bond': 0.04018001}] + ''' + + # ---------------------------------------------------------------------- + def onFutureUserInfo(self, ws_data): + """ + 合约账户信息推送(账户权益/已实现盈亏/未实现盈亏/可用/已用/冻结) + :param ws_data: + :return: + """ + data = ws_data.get('data', {}) + if len(data) == 0: + self.writeLog(u'FuturesApi.onFutureUserInfo:not data in:{}'.format(ws_data)) + return + + info = data.get("info", {}) + if len(data) == 0: + self.writeLog(u'FuturesApi.onFutureUserInfo:not info in:{}'.format(ws_data)) + return + + for symbol, s_inf in info.items(): + if "account_rights" in s_inf.keys(): + # 说明是 全仓返回 + account = VtAccountData() + account.gatewayName = self.gatewayName + account.accountID = self.gatewayName + account.vtAccountID = account.accountID + account.balance = float(s_inf["account_rights"]) + self.gateway.onAccount(account) + else: + # 说明是逐仓返回 + t_contracts = s_inf["contracts"] + t_balance = float(s_inf["balance"]) + t_rights = float(s_inf["rights"]) + + if t_balance > 0 or t_rights > 0: + account = VtAccountData() + account.gatewayName = self.gatewayName + account.accountID = symbol + "_usd_future_qy" # + "." + EXCHANGE_OKEX + account.vtAccountID = account.accountID + account.balance = t_rights + self.gateway.onAccount(account) + + # ---------------------------------------------------------------------- + """ 所有委托查询回报 ws_data + { + "data":{ + "result":true, + "orders":[ + { + "fee":0, + "amount":1, + "price":2, + "contract_name":"LTC0331", + "symbol":"ltc_usd", + "create_date":1490347972000, + "status":-1, + "lever_rate":10, + "deal_amount":0, + "price_avg":0, + "type":1, + "order_id":5017402127, + "unit_amount":10 + } + ] + }, + "channel":"ok_futureusd_orderinfo" + } + """ + def onFutureOrderInfo(self, ws_data): + """ + 所有委托信息查询响应 + :param ws_data: + :return: + """ + error_code = ws_data.get('error_code') + if error_code is not None: + self.gateway.writeError(u'期货委托查询报错:{}'.format(FUTURES_ERROR_DICT.get(error_code)), error_id=error_code) + self.gateway.writeLog(u'FutureApi.onFutureOrderInfo:{}'.format(ws_data)) + return + + data = ws_data.get("data", {}) + + for d in data.get('orders', []): + orderId = str(d['order_id']) + localNo = str(self.localNo) + + if orderId not in self.orderIdDict: + # orderId 不在本地缓存中,需要增加一个绑定关系 + while str(self.localNo) in self.localNoDict: + self.localNo += 1 + localNo = str(self.localNo) + # 绑定localNo 与 orderId + self.localNoDict[localNo] = orderId + self.orderIdDict[orderId] = localNo + self.writeLog(u'onFutureOrderInfo add orderid: local:{}<=>okex:{}'.format(localNo,orderId)) + else: + # orderid在本地缓存中, + localNo = self.orderIdDict[orderId] + # 检验 localNo是否在本地缓存,没有则补充 + if localNo not in self.localNoDict: + self.localNoDict[localNo] = orderId + self.writeLog(u'onFutureOrderInfo update orderid: local:{}<=>okex:{}'.format(localNo, orderId)) + + # order新增或更新在orderDict + if orderId not in self.orderDict: + order = VtOrderData() + order.gatewayName = self.gatewayName + contract_name = d["contract_name"] + dic_info = self.contract_name_dict[contract_name] + + use_contract_type = dic_info["contract_type"] + order.symbol = '.'.join([d["symbol"] + ":" + use_contract_type + ":" + str(self._use_leverage)]) #, EXCHANGE_OKEX]) + + order.vtSymbol = order.symbol + order.orderID = self.orderIdDict[orderId] # 更新orderId为本地的序列号 + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.price = d['price'] + order.totalVolume = d['amount'] + order.direction, offset = priceContractOffsetTypeMap[str(d['type'])] + + self.orderDict[orderId] = order + self.gateway.writeLog(u'新增本地orderDict缓存,okex orderId:{},order.orderid:{}'.format(orderId,order.orderID)) + else: + order = self.orderDict[orderId] + + order.tradedVolume = d['deal_amount'] + order.status = statusMap[int(d['status'])] + # 推送到OnOrder中 + self.gateway.onOrder(copy(order)) + + ''' + [{ + "data":{ + amount:1 + contract_id:20170331115, + contract_name:"LTC0331", + contract_type:"this_week", + create_date:1490583736324, + create_date_str:"2017-03-27 11:02:16", + deal_amount:0, + fee:0, + lever_rate:20, + orderid:5058491146, + price:0.145, + price_avg:0, + status:0, + system_type:0, + type:1, + unit_amount:10, + user_id:101 + }, + "channel":"ok_sub_futureusd_trades" + } + ] +实际数据 + ok_sub_futureusd_trades +{u'binary': 0, u'data': {u'orderid': 230874269033472L, u'contract_name': u'ETC0216', u'fee': 0.0, u' +user_id': 6548935, u'contract_id': 201802160040063L, u'price': 24.555, u'create_date_str': u'2018-02 +-10 18:34:21', u'amount': 1.0, u'status': 0, u'system_type': 0, u'unit_amount': 10.0, u'price_avg': +0.0, u'contract_type': u'this_week', u'create_date': 1518258861775L, u'lever_rate': 10.0, u'type': 1 +, u'deal_amount': 0.0}, u'channel': u'ok_sub_futureusd_trades'} +ok_sub_futureusd_trades +{u'binary': 0, u'data': {u'orderid': 230874269033472L, u'contract_name': u'ETC0216', u'fee': 0.0, u' +user_id': 6548935, u'contract_id': 201802160040063L, u'price': 24.555, u'create_date_str': u'2018-02 +-10 18:34:21', u'amount': 1.0, u'status': 0, u'system_type': 0, u'unit_amount': 10.0, u'price_avg': +0.0, u'contract_type': u'this_week', u'create_date': 1518258861775L, u'lever_rate': 10.0, u'type': 1 +, u'deal_amount': 0.0}, u'channel': u'ok_sub_futureusd_trades'} + ''' + + # ---------------------------------------------------------------------- + def onFutureSubTrades(self, ws_data): + """ + 交易信息回报 + :param ws_data: + :return: + """ + error_code = ws_data.get('error_code') + if error_code is not None: + self.gateway.writeError(u'期货交易报错:{}'.format(FUTURES_ERROR_DICT.get(error_code)), error_id=error_code) + self.gateway.writeLog(u'FutureApi.onFutureSubTrades:{}'.format(ws_data)) + return + + data = ws_data.get("data") + if data is None: + self.gateway.writeError(u'期货交易报错:No data') + self.gateway.writeLog(u'FutureApi.onFutureSubTrades: not data {}'.format(ws_data)) + return + + orderId = str(data["orderid"]) # okex的委托编号 + use_contract_type = data["contract_type"] # 合约类型 + + localNo = self.orderIdDict.get(orderId, None) # 本地委托编号 + + if localNo == None: + self.gateway.writeError(u'期货交易回报,非本地发出:orderid={}'.format(orderId)) + self.gateway.writeLog(u'FutureApi.onFutureSubTrades: not localNo,bypassing this trade {}'.format(ws_data)) + return + + # 委托信息,如果不在本地缓存,创建一个 + if orderId not in self.orderDict: + order = VtOrderData() + order.gatewayName = self.gatewayName + contract_name = data.get("contract_name") + contract_info = self.contract_name_dict.get(contract_name) + if contract_info is None: + self.gateway.writeError(u'期货交易回报,找不到合约的登记信息') + self.gateway.writeLog(u'no contract_name:{} in self.contract_name_dict'.format(contract_name)) + return + + symbol = contract_info["symbol"] + "_usd" # 自动添加_usd结尾 + order.symbol = ':'.join([symbol, use_contract_type, str(self._use_leverage)]) + order.vtSymbol = order.symbol + order.orderID = localNo + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + order.price = float(data['price']) + order.totalVolume = float(data['amount']) + order.direction, order.offset = priceContractOffsetTypeMap[str(data['type'])] + order.tradedVolume = float(data['deal_amount']) + order.status = statusMap[data['status']] + self.orderDict[orderId] = order + self.gateway.writeLog(u'新增order,orderid:{},symbol:{},data:{}'.format(order.orderID,order.symbol),data) + else: + # 更新成交数量/状态 + order = self.orderDict[orderId] + self.gateway.writeLog(u'orderid:{},tradedVolume:{}=>{},status:{}=>{}' + .format(order.orderId, + order.tradedVolume, float(data['deal_amount']), + order.status, statusMap[data['status']])) + order.tradedVolume = float(data['deal_amount']) + order.status = statusMap[data['status']] + + self.gateway.onOrder(copy(order)) + + # 判断成交数量是否有变化,有,则发出onTrade事件 + bef_volume = self.recordOrderId_BefVolume.get(orderId, 0.0) + now_volume = float(data['deal_amount']) - bef_volume + + if now_volume > 0.000001: + self.recordOrderId_BefVolume[orderId] = float(data['deal_amount']) + trade = VtTradeData() + trade.gatewayName = self.gatewayName + trade.symbol = order.symbol + trade.vtSymbol = order.symbol + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + trade.orderID = localNo + trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) + + trade.price = float(data['price']) + trade.volume = float(now_volume) + + trade.direction, trade.offset = priceContractOffsetTypeMap[str(data['type'])] + + trade.tradeTime = (data["create_date_str"].split(' '))[1] # u'2018-02-10 18:34:21 + + self.gateway.onTrade(trade) + + ''' + # Response + OKEX的合约是OKEX推出的以BTC/LTC等币种进行结算的虚拟合约产品,每一张合约分别代表100美元的BTC,或10美元的其他币种(LTC,ETH等) + +逐仓信息 + [{ + "data":{ + "balance":10.16491751, + "symbol":"ltc_usd", + "contracts":[ + { + "bond":0.50922987, + "balance":0.50922987, + "profit":0, + "freeze":1.72413792, + "contract_id":20170331115, + "available":6.51526374 + }, + { + "bond":0, + "balance":0, + "profit":0, + "freeze":1.64942529, + "contract_id":20170407135, + "available":6.51526374 + }, + { + "bond":0, + "balance":0, + "profit":0, + "freeze":0.27609056, + "contract_id":20170630116, + "available":6.51526374 + } + ] + }, + "channel":"ok_sub_futureusd_userinfo" + }] +全仓信息 +[{ + "data":{ + "balance":0.2567337, + "symbol":"btc_usd", + "keep_deposit":0.0025, + "profit_real":-0.00139596, + "unit_amount":100 + }, + "channel":"ok_sub_futureusd_userinfo" +}] + +信息 +{u'binary': 0, u'data': {u'contracts': [{u'available': 4.96199982, u'contract_id': 201802160040063L, + u'profit': -0.01800018, u'freeze': 0.0, u'balance': 0.01800018, u'bond': 0.0}], u'symbol': u'etc_us +d', u'balance': 4.96199982}, u'channel': u'ok_sub_futureusd_userinfo'} + ''' + + # ---------------------------------------------------------------------- + def onFutureSubUserinfo(self, ws_data): + """ + 子账户持仓信息回报 + :param ws_data: + :return: + """ + u_inf = ws_data['data'] + self.gateway.writeLog(u'onFutureSubUserinfo:{}'.format(ws_data)) + if "account_rights" in u_inf.keys(): + # 说明是 全仓返回 + pass + # account = VtAccountData() + # account.gatewayName = self.gatewayName + # account.accountID = self.gatewayName + # account.vtAccountID = account.accountID + # account.balance = float(u_inf["account_rights"]) + # self.gateway.onAccount(account) + else: + pass + # 感觉这里不是很有用。。。 遂放弃 + + # 说明是逐仓返回 + + # t_contracts = u_inf["contracts"] + # t_symbol = u_inf["symbol"].replace('_usd',"") + # for one_contract in t_contracts: + # account = VtAccountData() + # account.gatewayName = self.gatewayName + # account.accountID = self.gatewayName + + # pos = VtPositionData() + # pos.gatewayName = self.gatewayName + # #pos.symbol = t_symbol + "_usd:%s:%s.%s" % ( one_contract["contract_type"], self._use_leverage,EXCHANGE_OKEX) + # pos.symbol = self.contractIdToSymbol[one_contract["contract_id"]] + # pos.vtSymbol = pos.symbol + # pos.direction = DIRECTION_NET + # pos.frozen = float(one_contract["freeze"]) + # pos.position = pos.frozen + float(one_contract["balance"]) + + # self.gateway.onPosition(pos) + + ''' + [{ + "data":{ + "positions":[ + { + "position":"1", + "contract_name":"BTC0630", + "costprice":"994.89453079", + "bondfreez":"0.0025", + "avgprice":"994.89453079", + "contract_id":20170630013, + "position_id":27782857, + "eveningup":"0", + "hold_amount":"0", + "margin":0, + "realized":0 + }, + { + "position":"2", + "contract_name":"BTC0630", + "costprice":"1073.56", + "bondfreez":"0.0025", + "avgprice":"1073.56", + "contract_id":20170630013, + "position_id":27782857, + "eveningup":"0", + "hold_amount":"0", + "margin":0, + "realized":0 + } + ], + "symbol":"btc_usd", + "user_id":101 + }, + "channel":"ok_sub_futureusd_positions" +}] +逐仓返回 +[{ + "data":{ + "positions":[ + { + "position":"1", + "profitreal":"0.0", + "contract_name":"LTC0407", + "costprice":"0.0", + "bondfreez":"1.64942529", + "forcedprice":"0.0", + "avgprice":"0.0", + "lever_rate":10, + "fixmargin":0, + "contract_id":20170407135, + "balance":"0.0", + "position_id":27864057, + "eveningup":"0.0", + "hold_amount":"0.0" + }, + { + "position":"2", + "profitreal":"0.0", + "contract_name":"LTC0407", + "costprice":"0.0", + "bondfreez":"1.64942529", + "forcedprice":"0.0", + "avgprice":"0.0", + "lever_rate":10, + "fixmargin":0, + "contract_id":20170407135, + "balance":"0.0", + "position_id":27864057, + "eveningup":"0.0", + "hold_amount":"0.0" + } + "symbol":"ltc_usd", + "user_id":101 + }] + +实际返回的数据 + +{u'binary': 0, u'data': {u'positions': [{u'contract_name': u'ETC0216', u'balance': 0.01606774, u'con +tract_id': 201802160040063L, u'fixmargin': 0.0, u'position_id': 73790127, u'avgprice': 24.46, u'even +ingup': 0.0, u'profitreal': -0.01606774, u'hold_amount': 0.0, u'costprice': 24.46, u'position': 1, u +'lever_rate': 10, u'bondfreez': 0.04085301, u'forcedprice': 0.0}, {u'contract_name': u'ETC0216', u'b +alance': 0.01606774, u'contract_id': 201802160040063L, u'fixmargin': 0.0, u'position_id': 73790127, +u'avgprice': 0.0, u'eveningup': 0.0, u'profitreal': -0.01606774, u'hold_amount': 0.0, u'costprice': +0.0, u'position': 2, u'lever_rate': 10, u'bondfreez': 0.04085301, u'forcedprice': 0.0}, {u'contract_ +name': u'ETC0216', u'balance': 0.01606774, u'contract_id': 201802160040063L, u'fixmargin': 0.0, u'po +sition_id': 73790127, u'avgprice': 0.0, u'eveningup': 0.0, u'profitreal': -0.01606774, u'hold_amount +': 0.0, u'costprice': 0.0, u'position': 1, u'lever_rate': 20, u'bondfreez': 0.04085301, u'forcedpric +e': 0.0}, {u'contract_name': u'ETC0216', u'balance': 0.01606774, u'contract_id': 201802160040063L, u +'fixmargin': 0.0, u'position_id': 73790127, u'avgprice': 0.0, u'eveningup': 0.0, u'profitreal': -0.0 +1606774, u'hold_amount': 0.0, u'costprice': 0.0, u'position': 2, u'lever_rate': 20, u'bondfreez': 0. +04085301, u'forcedprice': 0.0}], u'symbol': u'etc_usd', u'user_id': 6548935}, u'channel': u'ok_sub_f +utureusd_positions'} + ''' + + # ---------------------------------------------------------------------- + def onFutureSubPositions(self, ws_data): + """期货子账号仓位推送""" + # 这个逐仓才是实际的仓位 !! + + data = ws_data.get("data") + if data is None: + self.gateway.writeError(u'onFutureSubPositions: no data in :{}'.format(ws_data)) + return + + symbol = data.get('symbol') + user_id = data.get('user_id') + + positions = data["positions"] + for inf in positions: + if 'fixmargin' in inf.keys(): + contract_name = inf["contract_name"] + position_leverage = str(inf["lever_rate"]) + + # print contract_name , position_leverage , self._use_leverage + if int(position_leverage) == int(self._use_leverage): + dic_inf = self.contract_name_dict[contract_name] + use_contract_type = dic_inf["contract_type"] + + # print dic_inf + pos = VtPositionData() + pos.gatewayName = self.gatewayName + + if int(inf["position"]) == 1: + pos.direction = DIRECTION_LONG + else: + pos.direction = DIRECTION_SHORT + + pos.symbol = symbol + ":" + use_contract_type + ":" + position_leverage # + "." + EXCHANGE_OKEX + pos.vtSymbol = pos.symbol + pos.vtPositionName = pos.symbol + "." + pos.direction + + pos.frozen = float(inf["hold_amount"]) - float(inf['eveningup']) + pos.position = float(inf["hold_amount"]) + pos.positionProfit = float(inf["profitreal"]) + + # print inf , pos.symbol + self.gateway.onPosition(pos) + else: + self.gateway.writeError(u'no fixmargin in positions info') + self.gateway.writeLog(u'{}'.format(ws_data)) + + + # ---------------------------------------------------------------------- + def futureSendOrder(self, req): + """ + 发委托单 + :param req: + :return: + """ + symbol_pair, symbol, contract_type, leverage = self.dealSymbolFunc(req.symbol) + + # print symbol_pair , symbol, contract_type , leverage + type_ = priceContractTypeMapReverse[(req.direction, req.offset)] + + # 本地委托号加1,并将对应字符串保存到队列中,返回基于本地委托号的vtOrderID + self.localNo += 1 + self.localNoQueue.put(str(self.localNo)) + vtOrderID = '.'.join([self.gatewayName, str(self.localNo)]) + + # print symbol + "_usd", contract_type , type_ , str(req.price), str(req.volume) + self.futureTrade(symbol + "_usd", contract_type, type_, str(req.price), str(req.volume), + _lever_rate=self._use_leverage) + + return vtOrderID + + # ---------------------------------------------------------------------- + def futureCancel(self, req): + """ + 发出撤单请求 + :param req: + :return: + """ + symbol_pair, symbol, contract_type, leverage = self.dealSymbolFunc(req.symbol) + + localNo = req.orderID + + if localNo in self.localNoDict: + # 找出okex的委托流水号 + orderID = self.localNoDict[localNo] + # 调用接口发出撤单请求 + self.futureCancelOrder(symbol + "_usd", orderID, contract_type) + else: + # 如果在系统委托号返回前客户就发送了撤单请求,则保存 + # 在cancelDict字典中,等待返回后执行撤单任务 + self.cancelDict[localNo] = req + + # ---------------------------------------------------------------------- + def generateDateTime(self, s): + """生成时间""" + dt = datetime.fromtimestamp(float(s) / 1e3) + time = dt.strftime("%H:%M:%S.%f") + date = dt.strftime("%Y%m%d") + return date, time +