1. 接口地址

  1. 网站:

  2. API:

  3. API文档:

2. 签名认证

2.1. 申请创建 API Key

通过web端页面 创建API Key

  1. Access Key API 访问密钥

  2. Secret Key 签名认证加密所使用的密钥

2.2. 签名说明

API 请求在通过 internet 传输的过程中极有可能被篡改,为了确保请求未被更改,除公共接口(基础信息,行情数据)外的私有接口均必须使用您的 API Key 做签名认证,以校验参数或参数值在传输途中是否发生了更改。

一个合法的请求由以下几部分组成
  1. 方法请求地址:如 https://www.wbfutures.pro/api/v1/future/position

  2. API访问密钥(apiKey):您申请的 API Key 中的 Access Key

  3. 时间戳(apiExpires):您发出请求的时间戳,以秒为单位。如:1588262400

  4. 签名(signature):签名计算得出的值,用于确保签名有效和未被篡改

  5. 必选和可选参数:每个方法都有一组用于定义 API 调用的必需参数和可选参数。GET和POST请求的参数都不参与签名。

注意:apiKey,apiExpires, signature三个参数都在请求头中,,另外需要设置’content-type' : 'application/json'`,参考以下实例(对应各自键值)
// 请求头必须包含
var headers = {
  'content-type' : 'application/json',
  'apiExpires': expires, //UNIX时间戳以秒为单位校验一分钟以内合法
  'apiKey': AccessKey, // API 访问密钥(您获取的ApiKey中的AccessKey)
  'signature': signature // 签名
};

2.3. 签名步骤

假设您创建的API Key 如下:

Access Key = w3xxxxxx-19xxxxxx-25xxxxxx-3xxxx
Secret Key = aaxxxxxx-63xxxxxx-57xxxxxx-1xxxx

您发起请求时,

时间戳  = 1588262400
  • 在进行签名计算前,请先对参数规范化处理

  apiKey:w3xxxxxx-19xxxxxx-25xxxxxx-3xxxx
  apiExpires:1588262400
  • 按照ASCII码的顺序对apiKey, apiExpires 两个参数进行升序排序

  apiExpires:1588262400
  apiKey:w3xxxxxx-19xxxxxx-25xxxxxx-3xxxx
  • 排序后的参数按照“keyvalue”的格式拼接在一起

  apiExpires1588262400apiKeyw3xxxxxx-19xxxxxx-25xxxxxx-3xxxx
  • 将Secret Key拼接到上一步字符串末尾,作为签名字符串

  apiExpires1588262400apiKeyw3xxxxxx-19xxxxxx-25xxxxxx-3xxxxaaxxxxxx-63xxxxxx-57xxxxxx-1xxxx
  • 使用MD5对签名字符串求签名得到signature

  md5(apiExpires1588262400apiKeyw3xxxxxx-19xxxxxx-25xxxxxx-3xxxxaaxxxxxx-63xxxxxx-57xxxxxx-1xxxx)
  = q8xxxxxx-49xxxxxx-05xxxxxx-6xxxx
  • 最终签名认证请求的请求头,如下示例

  // 请求头必须包含
  var headers = {
    'content-type' : 'application/json',
    'apiExpires': 1588262400, //UNIX时间戳以秒为单位
    'apiKey': 'w3xxxxxx-19xxxxxx-25xxxxxx-3xxxx', // API访问密钥
    'signature': 'q8xxxxxx-49xxxxxx-05xxxxxx-6xxxx' // 签名
  };

2.4. 签名失败

  1. 检查 APIKey 是否有效,是否复制正确,是否有绑定 IP 白名单

  2. 检查时间戳是否是 UTC 当前时间戳,校验一分钟以内合法

  3. 检查参数签名数据格式是否按('apiExpires' + expires值 + 'apiKey' + accessKey值 + secretKey值)

  4. 检查编码utf-8

3. 代码实例

3.1. javascript

<script language="JavaScript">
    var request = require("request");
    var crypto = require("crypto");

    var accessKey = "78a24e1a77de5c7ac23b948a3f41eba4";
    var secretKey = "164220f29534db3384b9a89d0655a226";
    var path = "/api/v1/order?filter=%7B%22orderId%22%3A%2211548326910655928%22%7D";
    var expires = 1583473773431; //new Date().getTime();

    var signature = md5('apiExpires' + expires +"apiKey" + accessKey + secretKey);
    var headers = {
        "content-type": "application/json",
        Accept: "application/json",
        apiExpires: expires,
        apikey: accessKey,
        signature: signature
    };

    const requestOptions = {
        headers: headers,
        url: "xxxx.io" + path,
        method: verb,
        body: postBody
    };

    request(requestOptions, function(error, response, body) {
        if (error) {
            console.log(error);
        }
        console.log(body);
    });
</script>

3.2. Python

import time
import hashlib
import hmac
from urllib.parse import urlparse
# 签名是 md5('apiExpires' + expires +"apiKey" + accessKey + secretKey)的十六进制编码。
# verb 必须是大写的,url 是相对的,expires 必须是 unix 时间戳(以秒为单位)
# 并且数据(如果存在的话)必须是 JSON 格式,并且键值之间没有空格。
def generate_signature(accessKey, secretKey, expires):
    """Generate a request signature compatible with cloud."""

    print("Computing md5: %s" % verb + path + str(expires) + data)
    message = f'apiExpires{expires}apiKey{self.accessKey}{self.secretKey}'
    md5 = hashlib.md5()
    md5.update(sign.encode())
    signature = md5.hexdigest();
    # print(f'signature:{signature}')
    # signature = '6404770bb5334daea72d5e41722c4504'
    return signature

# expires = 1583473773431
# 或者你可以像以下这样生成:
# expires = int(round(time.time()) + 5)
# GET请求将参数json化,然后urlencode, 放在url参数后面(?filter=xxxxxxx), data为空字符串
# POST请求将参数json化成字符串(字符串不能有空格),放在data参数的位置
print(generate_signature('78a24e1a77de5c7ac23b948a3f41eba4', '164220f29534db3384b9a89d0655a226', expires))

4. 错误码

编码

注释

0

成功

1001

账户不存在

1002

合约不存在

1003

应用标识错误

1004

委托价格不合法

1005

委托数量不合法

1006

委托数量超过限制

1007

保证金可用不足

1008

可用数量不足

1009

账户被接管,禁止交易

1010

账户被接管,禁止转出资金

1011

合约禁止交易

1012

委托方向不合法

1013

委托类型不合法

1016

委托数量低于最小委托单位

1018

委托金额不合法

1019

委托不存在

1020

对手方没有订单

1022

该笔持仓可平仓数量不足

1023

账户被接管,禁止平仓

1027

合约已经存在

1028

委托笔数超过限制

1029

持仓数量超过限制

1031

当前合约存在与本次委托的保证金类型不同的持仓

1032

初始保证金率错误

1035

禁止转入转出保证金

1036

委托价格超过限制

1037

委托金额超过限制

1042

禁止逐仓开仓

1044

市价委托消耗了过多的流动性档位

1046

当前价格无法成为被动委托,订单将被撤销

5. 字段缩写对照表

为防止数据包过大,返回数据键值进行了压缩,参考下面对应关系

{
    contractId: v.ci,
    result: {
        messageType: v.mt,
        applId: v.ai,
        contractId: v.ci,
        symbol: v.sb,
        tradeDate: v.td,
        time: v.te,
        lastPrice: v.lp,
        matchQty: v.mq,
        numTrades: v.nt,
        openPrice: v.op,
        priceHigh: v.ph,
        priceLow: v.pl,
        historyPriceHigh: v.hph,
        historyPriceLow: v.hpl,
        totalTurnover: v.tt,
        totalVolume: v.tv,
        totalBidVol: v.tbv,
        totalAskVol: v.tav,
        prevPrice: v.pp,
        clearPrice: v.cp,
        posiVol: v.pv,
        priceChangeRadio: v.pcr,
        priceChange: v.pc,
        lastUpdateId: v.lui,
        contractStatus: v.cs,
        deliveryPrice: v.dp,
        fundingRate: v.fr,
        predictionFundingRate: v.pfr,
        premiumIndex: v.pi,
        predictionPremiumIndex: v.ppi,
        fairBasis: v.fb,
        tradingsignal: v.ts,
        indexPrice: v.ip,
        signalLevel: v.sl
    }
}

6. SDK

6.1. python

#!/usr/bin/python
# -*- coding: utf-8 -*-
#auther: Rexxar

import socketio
import os
import time
import hashlib
import hmac
import urllib
import threading
import requests
import json


class SDK():


    def __init__(self,config):
        '''初始化'''
        self.publicKey = config['apiKey']
        self.privateKey = config['secretKey']
        self.baseWsUrl = 'wss://ws.wbfutures.pro'
        self.baseRsUrl = 'https://www.wbfutures.pro/api'
        self.status = False  #websocket连接闸口

        self._setWebsocket() #连接websocket
        process = threading.Thread(target=self._ping) #建立心跳
        process.start()
        while not self.status:
            continue

    def _signature(self):
        '''签名'''
        expires = int(time.time()+5)   #timestamp
        sign = f'apiExpires{expires}apiKey{self.publicKey}{self.privateKey}'
        m = hashlib.md5()
        m.update(sign.encode())
        signature = m.hexdigest()
        return signature,expires


    def _getUrl(self,method,params=None):
        '''生成url'''
        baseRsUrl = self.baseRsUrl
        method,path = method.split(' ')
        req = {}
        signature,expires = self._signature() #拿到签名

        '''===判断参数结构==='''
        if (params is not None) and ('filter' in params):
            req = {'filter':json.dumps(params['filter'])}
        elif params is not None:
            for k in params:
                if params[k] is not None:
                    req[k] = params[k]
        req = urllib.parse.urlencode(req)

        '''===生成Url==='''
        if req=='':
            url = f'{baseRsUrl}{path}'
        else:
            url = f'{baseRsUrl}{path}?{req}'
        return url,signature,expires


    def _httpReq(self,method,params=None,body=None):
        '''请求'''
        url,signature,expires = self._getUrl(method,params)
        method = method.split(' ')[0]
        headers = {'content-type':'application/json',
                   'Accept':'application/json',
                   'apiExpires':str(expires),
                   'apiKey':self.publicKey,
                   'signature':signature}
        print(f'method:{method}')
        print(f'url:{url}')

        '''===不同请求方法==='''
        if method=='GET':
            res = requests.get(url,headers=headers)
        elif method=='POST':
            if body is not None:
                res = requests.post(url,headers=headers,data=body)
            else:
                res = requests.post(url,headers=headers)
        elif method=='DELETE':
            res = requests.delete(url,headers=headers)
        rsp = res.json()
        return rsp


    def _setWebsocket(self):
        '''配置websocket 自动重连'''
        sio = socketio.Client()
        @sio.event
        def connect():
            print('===WBF Websocket Server Connected!===')
            signature,expires = self._signature()
            auth = ['auth',{'header':{'type':1001},'body':{'apiKey':self.publicKey,'expires':expires,'signature':signature}}]
            print(auth)
            sio.emit(auth[0],auth[1]) #鉴权
        @sio.event
        def disconnect():
            print("===WBF Websocket Server Disconnected!===")
        @sio.on('auth')
        def rsp(data):
            situation = data['body']['msg']
            print(data)
            if situation=='success':
                print('===WBF Account Authentication Successfully===')
                self.status = True
            else:
                raise Exception(f"===WBF Account Authentication Failed!!===\nerror: {situation}")
        sio.connect(self.baseWsUrl) #连接websocket
        self.sio = sio #websocket实例


    def _ping(self):
        '''心跳发送 保持连接状态'''
        try:
            sio = self.sio
            t=0
            while t<=10:
                if t==10:
                    sio.emit('ping')
                    t=0
                time.sleep(1)
                t+=1
        except:
            time.sleep(5)


    ''' ==========================================================================='''
    '''============================ Websocket Function ============================'''
    '''============================================================================'''

    def subBase(self,func):
        '''订阅基本信息'''
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'match'}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('match',func)


    def subKline(self,symbol,period,func):
        '''订阅K线'''
        sio = self.sio
        periodDic={'1m':'60000','3m':'180000','5m':'300000',
                   '15m':'900000','30m':'1800000','1h':'3600000',
                   '2h':'7200000','4h':'14400000','6h':'21600000',
                   '12h':'43200000','1d':'86400000','1w':'604800000'}
        period = periodDic[period]
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'future_kline','params':{'symbols':[
               {'symbol':symbol,
                'ranges':[period]}]}}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('future_kline',func)


    def subLegalCoin(self,func):
        '''订阅法币'''
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'exchange'}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('exchange',func)


    def subMarket(self,func):
        '''订阅市场数据'''
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'future_market_stat'}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('future_market_stat',func)


    def subCoinPrice(self,func):
        '''订阅币价'''
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'coin_price'}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('coin_price',func)


    def subMarketQuote(self,symbol,func):
        '''订阅市场quote'''
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'future_all_indicator',
                'params':{'symbols':[{'symbol':symbol}]}}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('future_all_indicator',func)


    def subRealtime(self,func):
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'realtime'}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('realtime',func)


    def subDepth(self,symbol,func):
        '''订阅深度'''
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'future_snapshot_depth','params':{'symbols':[
               {'symbol':symbol}]}}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('future_snapshot_depth',func)


    def subQuote(self,symbol,func):
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'future_snapshot_indicator','params':{'symbols':[
               {'symbol':symbol}]}}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('future_snapshot_indicator',func)


    def subTick(self,symbol,func):
        '''订阅tick'''
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'future_tick','params':{'symbols':[
               {'symbol':symbol}]}}]}}
        print(req)
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('future_tick',func)


    def subNotice(self,func):
        sio = self.sio
        req = {'header':{'type':1003},
               'body':{'topics':[
               {'topic':'notice'}]}}
        print(req['body']['topics'][0])
        sio.emit('subscribe',req)
        sio.on('notice',func)


    ''' ==========================================================================='''
    '''============================ Restful Function ============================'''
    '''============================================================================'''

    def getCoinPrice(self):
        req = 'GET /api/v1/common/exchange/coins'
        data = self._httpReq(req)
        return data


    def getContract(self):
        req = 'GET /api/v1/future/queryContract'
        data = self._httpReq(req)
        return data


    def getCurrency(self):
        req = 'GET /api/v1/common/queryCurrency'
        data = self._httpReq(req)
        return data


    def getTradeCoin(self):
        req = 'GET /api/v1/common/exchange/list'
        data = self._httpReq(req)
        return data

    def getLegalCoin(self):
        req = 'GET /api/v1/common/exchange/list'
        data = self._httpReq(req)
        return data


    def getHistoryDelivery(self,type=2,pageNum=None,pageSize=None,contractId=None,sort=None):
        req = 'GET /api/v1/future/queryContractDeliveryList'
        data = self._httpReq(req,self._getUrl(req,params={'type':type,'pageNum':pageNum,
                                                          'pageSize':pageSize,'contractId':contractId,
                                                          'sort':sort}))
        return data


    def getAccount(self):
        req = 'GET /api/v1/future/user'
        data = self._httpReq(req,self._getUrl(req))
        return data


    def getBalance(self):
        '''获取资产'''
        req = 'GET /api/v1/future/margin'
        data = self._httpReq(req)
        return data


    def getMarginRate(self):
        req = 'GET /api/v1/future/queryAccountMarginRate'
        data = self._httpReq(req)
        return data


    def getUserParam(self,contractId):
        req = 'GET /api/v1/future/queryUserFutureParam'
        data = self._httpReq(req,params={'filter':{'contractId':contractId}})
        return data


    def getPosition(self):
        '''获取仓位'''
        req = 'GET /api/v1/future/position'
        data = self._httpReq(req)
        return data


    def getMargin(self,varietyId,contractId):
        req = 'GET /api/v1/future/queryVarietyMargin'
        data = self._httpReq(req,params={'filter':{'varietyId':varietyId,
                                         'contractId':contractId}})
        # data = self._httpReq(req,params={'varietyId':varietyId,'contractId':contractId})
        return data


    def makeOrder(self,side,quantity,price=None,orderType=1,positionEffect=1,marginType=1,marginRate=0,symbol=None,contractId=None,clientOrderId=None):
        '''下单'''
        req = 'POST /api/v1/future/order'
        body = {'side':side,'quantity':str(quantity),'orderType':orderType,'positionEffect':positionEffect,
                'marginType':marginType,'marginRate':str(marginRate)}

        extraKey = ['price','symbol','contractId']
        extra = [str(price),symbol,contractId]
        for i,e in enumerate(extra):
            if (e is not None) and (e != 'None'):
                body[extraKey[i]] = e
        print(body)
        data = self._httpReq(req,body=json.dumps(body,separators=(',',':')))
        return data


    def makeOrders(self,side,quantity,price,orderType=1,positionEffect=1,marginType=1,marginRate=0,symbol=None,contractId=None,clientOrderId=None):
        '''批量下单'''
        req = 'POST /api/v1/future/orders'
        body = {'orders':[{'side':side[i],'orderQty':str(quantity[i]),'orderType':orderType,'positionEffect':positionEffect,
                           'marginType':marginType,'orderPrice':str(price[i]),'contractId':contractId} for i in range(len(side))]}

        print(body)
        data = self._httpReq(req,body=json.dumps(body,separators=(',',':')))
        return data


    def cancelOrder(self,contractId,orderId):
        '''撤单'''
        req = 'DELETE /api/v1/future/order'
        data = self._httpReq(req,params={'filter':{'contractId':contractId,
                                                   'originalOrderId':str(orderId)}})
        return data

    def cancelAll(self):
        '''全撤'''
        req = 'DELETE /api/v1/future/order/all'
        data = self._httpReq(req)
        return data


    def queryOrder(self,orderId):
        '''查询订单'''
        req = 'GET /api/v1/future/order'
        data = self._httpReq(req,params={'filter':{'orderId':str(orderId)}})
        # data = self._httpReq(req,params={'orderId':str(orderId)})
        return data

    def queryOrders(self,orderIdList):
        req = 'GET /api/v1/future/orders'
        data = self._httpReq(req,params={'filter':{'orderId':[str(i) for i in orderIdList]}})
        return data


    def getOrderList(self):
        '''当前订单'''
        req = 'GET /api/v1/future/queryActiveOrder'
        data = self._httpReq(req)
        return data


    def getOrders(self):
        req = 'GET /api/v1/future/queryHisOrder'
        data = self._httpReq(req)
        return data


    def changeMargin(self,contractId,margin):
        req = 'POST /api/v1/future/position/transferMargin'
        body = {'contractId':contractId,'margin':str(margin)}
        # data = self._httpReq(req,params={'filter':body})
        data = self._httpReq(req,body=json.dumps(body,separators=(',',':')))
        return data


    def changeMarginType(self,contractId,initMarginRate,marginType):
        req = 'POST /api/v1/future/position/isolate'
        body = {'contractId':contractId,'initMarginRate':str(initMarginRate),'marginType':marginType}
        # data = self._httpReq(req,params={'filter':body})
        data = self._httpReq(req,body=json.dumps(body,separators=(',',':')))
        return data


    def transfer(self,currencyId,amount,transferType):
        req = 'POST /api/v1/future/transfer'
        body = {'currencyId':currencyId,'amount':amount,'transferType':transferType}
        data = self._httpReq(req,body=json.dumps(body,separators=(',',':')))
        return data


    def getForceLower(self):
        req = 'GET /api/v1/future/queryForceLower'
        data = self._httpReq(req)
        return data


    def getDeals(self):
        '''历史成交'''
        req = 'GET /api/v1/future/queryHisMatch'
        data = self._httpReq(req)
        return data


    def getMarketStats(self,contractId):
        req = 'GET /api​/v1​/futureQuot​/queryMarketStat'
        data = self._httpReq(req,params={'contractId':contractId})
        # data = self._httpReq(req,params={'filter':{'contractId':contractId}})
        return data

    def getQuote(self,contractId):
        req = 'GET /api/v1/futureQuot/querySnapshot'
        data = self._httpReq(req,params={'contractId':contractId})
        return data

    def getQuotes(self):
        req = 'GET /api/v1/futureQuot/queryIndicatorList'
        data = self._httpReq(req)
        return data

    def getKline(self,contractId,period):
        req = 'GET /api/v1/futureQuot/queryCandlestick'
        params = {'contractId':contractId,'range':period}
        print(params)
        data = self._httpReq(req,params=params)
        return data

    def getTick(self,contractId):
        req = 'GET /api/v1/futureQuot/queryTickTrade'
        data = self._httpReq(req,params={'contractId':contractId})
        return data


if __name__ == '__main__':
    config = {'apiKey':'*******',
              'secretKey':'******'}
    task = SDK(config)
    contractId = 100000

    '''======restful========'''
    data = task.getDeals()
    print(data)


    '''======websocket======'''
    # def handle(content):
    #     print(content)
    # task.subBase(handle)
    # task.subKline(contractId,'1m',handle)
    # task.subLegalCoin(handle)
    # task.subMarket(handle)
    # task.subCoinPrice(handle)
    # task.subMarketQuote(contractId,handle)
    # task.subRealtime(handle)
    # task.subDepth(contractId,handle)
    # task.subTick(contractId,handle)
    # task.subQuote(contractId,handle)
    # task.subNotice(handle)