Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >Bitex -用于Python的加密货币交换API框架-第2轮

Bitex -用于Python的加密货币交换API框架-第2轮
EN

Code Review用户
提问于 2016-12-05 02:21:11
回答 1查看 1.2K关注 0票数 11

这是对我以前的评论请求的跟进。

BitEx是我已经研究了9个多月的Python模块,作为一个附带项目。它是6个月前在GitHub上发布的,随着我接近我的1.0版本,我想借此机会在这里展示我的代码,以便纠正它。

它解决并提供给

的东西

它的目的是消除深入到REST密码交换的血淋淋的细节的需要,并为所有受支持的API提供一个统一和直观的接口。它负责身份验证过程,并为交换中所有常用的方法提供了标准化的方法集(具有相同的方法签名)(轮询订单簿和代码、下订单和取消订单等),以及所有其他特定方法(或我有时间实现的方法)。

本质上,它是两个子包:bitex.api是负责通过requests模块设置http请求以及处理身份验证细节的后端。它可以被看作是requests的包装器,在技术上完全可以单独用于向交换发送和接收数据。

另一个是bitex.interfaces,它为所有已实现的交换提供了上述同构、标准化的方法。除了提供相同的方法签名外,它还旨在标准化方法的返回值。由于不同的exchange之间的差异很大,这些方法通过在bitex.formattersreturn_json装饰器中找到的格式化程序来处理数据格式化。它依赖于bitex.api

为什么我要把这个提交给审查

自从我开始这个项目以来,我已经重写了几次基本代码。我花了很长时间才弄清楚如何布局结构(这主要是由于我在过去一年作为软件开发学徒时的学习曲线)。

然而,在过去的两个月里,我对目前的结构变得相当喜欢和自豪,并认为它是相当体面的--因此,我可以对其进行公开审计。

我在如何从我的评论中获得最好的价值上读过元问题,最初决定对我的代码进行三轮评论:

  1. 圆:代码样式 (已完成),PEP8,可读性,丙酮-度
  2. 圆:重构选项和当前布局的评估,特别是API类的sign()方法、return_json()装饰器和格式化程序功能的使用。
  3. 圆:缺陷,代码和逻辑的改进,bug等。

审查第2轮:重构和布局

我特别担心bitex.api子模块。sign()方法很难概括,因为输入变化很大,迫使我把所有的东西都传递给它们。我想不出一个更明智的解决办法。

bitex.interfaces中,创建一个BaseInterface类似乎是个好主意,因为我有几种方法,它们出现在所有接口-- query_public()query_private()和标准化方法中。然而,第二次一瞥,这个mixin类并没有使sense.This产生效果,因为在我看来,它似乎是为了拥有一个mixin类而引入一个类,因为无论如何,在大多数交换中,我都必须重写一些方法,从而产生了几乎相同的代码--只有很少的改进。

最后,使用和设计bitex.formatters来格式化通过@return_json装饰器从bitex.interfaces类返回的数据(到目前为止,这个名称可能有点不太好,因为它不再仅仅返回requests.response JSON值)。我的方法可以接受吗?它似乎是最简单的解决方案(而不是直接将每个Interface类的格式弄得乱七八糟)。

bitex.api

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# Import Built-Ins
import logging
import requests
import time
# Import Third-Party

# Import Homebrew


log = logging.getLogger(__name__)


class RESTAPI:

    def __init__(self, uri, api_version='', key='', secret=''):
        """
        Base Class for REST API connections.
        """
        self.key = key
        self.secret = secret
        self.uri = uri
        self.apiversion = api_version
        self.req_methods = {'POST': requests.post, 'PUT': requests.put,
                            'GET': requests.get, 'DELETE': requests.delete,
                            'PATCH': requests.patch}
        log.debug("Initialized RESTAPI for URI: %s; "
                  "Will request on API version: %s" %
                  (self.uri, self.apiversion))

    def load_key(self, path):
        """
        Load key and secret from file.
        """
        with open(path, 'r') as f:
            self.key = f.readline().strip()
            self.secret = f.readline().strip()

    def nonce(self):
        return str(int(1000 * time.time()))

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        """
        Dummy Signature creation method. Override this in child.
        URL is required to be returned, as some Signatures use the url for
        sig generation, and api calls made must match the address exactly.
        """
        url = self.uri

        return url, {'params': {'test_param': "authenticated_chimichanga"}}

    def query(self, method_verb, endpoint, authenticate=False,
              *args, **kwargs):
        """
        Queries exchange using given data. Defaults to unauthenticated query.
        """
        request_method = self.req_methods[method_verb]

        if self.apiversion:
            endpoint_path = '/' + self.apiversion + '/' + endpoint
        else:
            endpoint_path = '/' + endpoint

        url = self.uri + endpoint_path
        if authenticate:  # sign off kwargs and url before sending request
            url, request_kwargs = self.sign(url, endpoint, endpoint_path,
                                            method_verb, *args, **kwargs)
        else:
            request_kwargs = kwargs
        log.debug("Making request to: %s, kwargs: %s" % (url, request_kwargs))
        r = request_method(url, timeout=5, **request_kwargs)
        log.debug("Made %s request made to %s, with headers %s and body %s. "
                  "Status code %s" %
                  (r.request.method, r.request.url, r.request.headers,
                   r.request.body, r.status_code))
        return r

bitex.api.rest

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# Import Built-ins
import logging
import json
import hashlib
import hmac
import base64
import time
import urllib
import urllib.parse
from requests.auth import AuthBase

# Import Third-Party

# Import Homebrew
from bitex.api.api import RESTAPI


log = logging.getLogger(__name__)


class BitfinexREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='v1',
                 url='https://api.bitfinex.com'):
        super(BitfinexREST, self).__init__(url, api_version=api_version,
                                           key=key, secret=secret)

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        try:
            req = kwargs['params']
        except KeyError:
            req = {}
        req['request'] = endpoint_path
        req['nonce'] = self.nonce()

        js = json.dumps(req)
        data = base64.standard_b64encode(js.encode('utf8'))

        h = hmac.new(self.secret.encode('utf8'), data, hashlib.sha384)
        signature = h.hexdigest()
        headers = {"X-BFX-APIKEY": self.key,
                   "X-BFX-SIGNATURE": signature,
                   "X-BFX-PAYLOAD": data}

        return url, {'headers': headers}


class BitstampREST(RESTAPI):
    def __init__(self, user_id='', key='', secret='', api_version='',
                 url='https://www.bitstamp.net/api'):
        self.id = user_id
        super(BitstampREST, self).__init__(url, api_version=api_version,
                                           key=key, secret=secret)

    def load_key(self, path):
        """
        Load key and secret from file.
        """
        with open(path, 'r') as f:
            self.id = f.readline().strip()
            self.key = f.readline().strip()
            self.secret = f.readline().strip()

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()
        message = nonce + self.id + self.key

        signature = hmac.new(self.secret.encode(), message.encode(),
                             hashlib.sha256)
        signature = signature.hexdigest().upper()

        try:
            req = kwargs['params']
        except KeyError:
            req = {}
        req['key'] = self.key
        req['nonce'] = nonce
        req['signature'] = signature
        return url, {'data': req}


class BittrexREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='v1.1',
                 url='https://bittrex.com/api'):
        super(BittrexREST, self).__init__(url, api_version=api_version, key=key,
                                          secret=secret)

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):

        try:
            params = kwargs['params']
        except KeyError:
            params = {}

        nonce = self.nonce()

        req_string = endpoint_path + '?apikey=' + self.key + "&nonce=" + nonce + '&'
        req_string += urllib.parse.urlencode(params)
        headers = {"apisign": hmac.new(self.secret.encode('utf-8'),
                                       (self.uri + req_string).encode('utf-8'),
                                       hashlib.sha512).hexdigest()}

        return self.uri + req_string, {'headers': headers, 'params': {}}


class CoincheckREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='api',
                 url='https://coincheck.com'):
        super(CoincheckREST, self).__init__(url, api_version=api_version,
                                            key=key, secret=secret)

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):

        nonce = self.nonce()
        try:
            params = kwargs['params']
        except KeyError:
            params = {}

        params = json.dumps(params)
        # sig = nonce + url + req
        data = (nonce + endpoint_path + params).encode('utf-8')
        h = hmac.new(self.secret.encode('utf8'), data, hashlib.sha256)
        signature = h.hexdigest()
        headers = {"ACCESS-KEY": self.key,
                   "ACCESS-NONCE": nonce,
                   "ACCESS-SIGNATURE": signature}

        return url, {'headers': headers}


class GdaxAuth(AuthBase):
    def __init__(self, api_key, secret_key, passphrase):
        self.api_key = api_key.encode('utf-8')
        self.secret_key = secret_key.encode('utf-8')
        self.passphrase = passphrase.encode('utf-8')

    def __call__(self, request):
        timestamp = str(time.time())
        message = (timestamp + request.method + request.path_url +
                   (request.body or ''))
        hmac_key = base64.b64decode(self.secret_key)
        signature = hmac.new(hmac_key, message.encode('utf-8'), hashlib.sha256)
        signature_b64 = base64.b64encode(signature.digest())

        request.headers.update({
            'CB-ACCESS-SIGN': signature_b64,
            'CB-ACCESS-TIMESTAMP': timestamp,
            'CB-ACCESS-KEY': self.api_key,
            'CB-ACCESS-PASSPHRASE': self.passphrase,
            'Content-Type': 'application/json'
        })
        return request


class GDAXRest(RESTAPI):
    def __init__(self, passphrase='', key='', secret='', api_version='',
                 url='https://api.gdax.com'):
        self.passphrase = passphrase
        super(GDAXRest, self).__init__(url, api_version=api_version, key=key,
                                       secret=secret)

    def load_key(self, path):
        """
        Load key and secret from file.
        """
        with open(path, 'r') as f:
            self.passphrase = f.readline().strip()
            self.key = f.readline().strip()
            self.secret = f.readline().strip()

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        auth = GdaxAuth(self.key, self.secret, self.passphrase)
        try:
            js = kwargs['params']
        except KeyError:
            js = {}

        return url, {'json': js, 'auth': auth}


class KrakenREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='0',
                 url='https://api.kraken.com'):
        super(KrakenREST, self).__init__(url, api_version=api_version,
                                         key=key, secret=secret)

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        try:
            req = kwargs['params']
        except KeyError:
            req = {}

        req['nonce'] = self.nonce()
        postdata = urllib.parse.urlencode(req)

        # Unicode-objects must be encoded before hashing
        encoded = (str(req['nonce']) + postdata).encode('utf-8')
        message = (endpoint_path.encode('utf-8') +
                   hashlib.sha256(encoded).digest())

        signature = hmac.new(base64.b64decode(self.secret),
                             message, hashlib.sha512)
        sigdigest = base64.b64encode(signature.digest())

        headers = {
            'API-Key': self.key,
            'API-Sign': sigdigest.decode('utf-8')
        }

        return url, {'data': req, 'headers': headers}


class ItbitREST(RESTAPI):
    def __init__(self, user_id = '', key='', secret='', api_version='v1',
                 url='https://api.itbit.com'):
        self.userId = user_id
        super(ItbitREST, self).__init__(url, api_version=api_version,
                                 key=key, secret=secret)

    def load_key(self, path):
        """
        Load user id, key and secret from file.
        """
        with open(path, 'r') as f:
            self.userId = f.readline().strip()
            self.clientKey = f.readline().strip()
            self.secret = f.readline().strip()

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        try:
            params = kwargs['params']
        except KeyError:
            params = {}

        verb = method_verb

        if verb in ('PUT', 'POST'):
            body = params
        else:
            body = {}

        timestamp = self.nonce()
        nonce = self.nonce()

        message = json.dumps([verb, url, body, nonce, timestamp],
                             separators=(',', ':'))
        sha256_hash = hashlib.sha256()
        nonced_message = nonce + message
        sha256_hash.update(nonced_message.encode('utf8'))
        hash_digest = sha256_hash.digest()
        hmac_digest = hmac.new(self.secret.encode('utf-8'),
                               url.encode('utf-8') + hash_digest,
                               hashlib.sha512).digest()
        signature = base64.b64encode(hmac_digest)

        auth_headers = {
            'Authorization': self.key + ':' + signature.decode('utf8'),
            'X-Auth-Timestamp': timestamp,
            'X-Auth-Nonce': nonce,
            'Content-Type': 'application/json'
        }
        return url, {'headers': auth_headers}


class OKCoinREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='v1',
                 url='https://www.okcoin.com/api'):
        super(OKCoinREST, self).__init__(url, api_version=api_version,
                                         key=key,
                                         secret=secret)

    def sign(self,url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()

        # sig = nonce + url + req
        data = (nonce + url).encode()

        h = hmac.new(self.secret.encode('utf8'), data, hashlib.sha256)
        signature = h.hexdigest()
        headers = {"ACCESS-KEY":       self.key,
                   "ACCESS-NONCE":     nonce,
                   "ACCESS-SIGNATURE": signature}

        return url, {'headers': headers}


class BTCERest(RESTAPI):
    def __init__(self, key='', secret='', api_version='3',
                 url='https://btc-e.com/api'):
        super(BTCERest, self).__init__(url, api_version=api_version, key=key,
                                         secret=secret)

    def sign(self, url, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()
        try:
            params = kwargs['params']
        except KeyError:
            params = {}
        post_params = params
        post_params.update({'nonce': nonce, 'method': endpoint.split('/', 1)[1]})
        post_params = urllib.parse.urlencode(post_params)

        signature = hmac.new(self.secret.encode('utf-8'),
                             post_params.encode('utf-8'), hashlib.sha512)
        headers = {'Key': self.key, 'Sign': signature.hexdigest(),
                   "Content-type": "application/x-www-form-urlencoded"}

        # split by tapi str to gain clean url;
        url = url.split('/tapi', 1)[0] + '/tapi'

        return url, {'headers': headers, 'params': params}


class CCEXRest(RESTAPI):
    def __init__(self, key='', secret='', api_version='',
                 url='https://c-cex.com/t'):
        super(CCEXRest, self).__init__(url, api_version=api_version, key=key,
                                         secret=secret)

    def sign(self, uri, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()
        try:
            params = kwargs['params']
        except KeyError:
            params = {}

        params['apikey'] = self.key
        params['nonce'] = nonce
        post_params = params
        post_params.update({'nonce': nonce, 'method': endpoint})
        post_params = urllib.parse.urlencode(post_params)

        url = uri + post_params

        sig = hmac.new(url, self.secret, hashlib.sha512)
        headers = {'apisign': sig}

        return url, {'headers': headers}


class CryptopiaREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='',
                 url='https://www.cryptopia.co.nz/api'):
        super(CryptopiaREST, self).__init__(url, api_version=api_version, key=key,
                                         secret=secret)

    def sign(self, uri, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()
        try:
            params = kwargs['params']
        except KeyError:
            params = {}


        post_data = json.dumps(params)
        md5 = base64.b64encode(hashlib.md5().updated(post_data).digest())

        sig = self.key + 'POST' + urllib.parse.quote_plus(uri).lower() + nonce + md5
        hmac_sig = base64.b64encode(hmac.new(base64.b64decode(self.secret),
                                              sig, hashlib.sha256).digest())
        header_data = 'amx' + self.key + ':' + hmac_sig + ':' + nonce
        headers = {'Authorization': header_data,
                   'Content-Type': 'application/json; charset=utf-8'}

        return uri, {'headers': headers, 'data': post_data}


class GeminiREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='v1',
                 url='https://api.gemini.com'):
        super(GeminiREST, self).__init__(url, api_version=api_version, key=key,
                                         secret=secret)

    def sign(self, uri, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()
        try:
            params = kwargs['params']
        except KeyError:
            params = {}
        payload = params
        payload['nonce'] = nonce
        payload['request'] = endpoint_path
        payload = base64.b64encode(json.dumps(payload))
        sig = hmac.new(self.secret, payload, hashlib.sha384).hexdigest()
        headers = {'X-GEMINI-APIKEY': self.key,
                   'X-GEMINI-PAYLOAD': payload,
                   'X-GEMINI-SIGNATURE': sig}
        return uri, {'headers': headers}


class YunbiREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='v2',
                 url='https://yunbi.com/api'):
        super(YunbiREST, self).__init__(url, api_version=api_version, key=key,
                                         secret=secret)

    def sign(self, uri, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()
        try:
            params = kwargs['params']
        except KeyError:
            params = {}
        params['tonce'] = nonce
        params['access_key'] = self.key
        post_params = urllib.parse.urlencode(params)
        msg = '%s|%s|%s' % (method_verb, endpoint_path, post_params)

        sig = hmac.new(self.secret, msg, hashlib.sha256).hexdigest()
        uri += post_params + '&signature=' + sig

        return uri, {}


class RockTradingREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='v1',
                 url='https://api.therocktrading.com'):
        super(RockTradingREST, self).__init__(url, api_version=api_version,
                                        key=key,
                                        secret=secret)

    def sign(self, uri, endpoint, endpoint_path, method_verb, *args, **kwargs):
        nonce = self.nonce()
        try:
            params = kwargs['params']
        except KeyError:
            params = {}
        payload = params
        payload['nonce'] = int(nonce)
        payload['request'] = endpoint_path

        msg = nonce + uri
        sig = hmac.new(self.secret.encode(), msg.encode(), hashlib.sha384).hexdigest()
        headers = {'X-TRT-APIKEY': self.key,
                   'X-TRT-Nonce': nonce,
                   'X-TRT-SIGNATURE': sig, 'Content-Type': 'application/json'}
        return uri, {'headers': headers}


class PoloniexREST(RESTAPI):
    def __init__(self, key='', secret='', api_version='',
                 url='https://poloniex.com'):
        super(PoloniexREST, self).__init__(url, api_version=api_version,
                                           key=key, secret=secret)

    def sign(self, uri, endpoint, endpoint_path, method_verb, *args, **kwargs):
        try:
            params = kwargs['params']
        except KeyError:
            params = {}
        params['nonce'] = self.nonce()
        payload = params

        msg = urllib.parse.urlencode(payload).encode('utf-8')
        sig = hmac.new(self.secret.encode('utf-8'), msg, hashlib.sha512).hexdigest()
        headers = {'Key': self.key, 'Sign': sig}
        return uri, {'headers': headers, 'data': params}

bitex.interfaces

bitex.interfaces.kraken

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"""
https:/kraken.com/help/api
"""

# Import Built-Ins
import logging

# Import Third-Party

# Import Homebrew
from bitex.api.rest import KrakenREST
from bitex.utils import return_json
from bitex.formatters.kraken import cancel, trade, order_book

# Init Logging Facilities
log = logging.getLogger(__name__)


class Kraken(KrakenREST):
    def __init__(self, key='', secret='', key_file=''):
        super(Kraken, self).__init__(key, secret)
        if key_file:
            self.load_key(key_file)

    def make_params(self, *pairs, **kwargs):
        q = {'pair': ','.join(pairs)}
        q.update(kwargs)
        return q

    def public_query(self, endpoint, **kwargs):
        path = 'public/' + endpoint
        return self.query('GET', path, **kwargs)

    def private_query(self, endpoint, **kwargs):
        path = 'private/' + endpoint
        return self.query('POST', path, authenticate=True, **kwargs)

    """
    BitEx Standardized Methods
    """

    @return_json(None)
    def ticker(self, *pairs):
        q = self.make_params(*pairs)
        return self.public_query('Ticker', params=q)

    @return_json(order_book)
    def order_book(self, pair, **kwargs):
        q = self.make_params(pair, **kwargs)
        return self.public_query('Depth', params=q)

    @return_json(None)
    def trades(self, pair, **kwargs):
        q = self.make_params(pair, **kwargs)
        return self.public_query('Trades', params=q)

    def _add_order(self, pair, side, price, amount, **kwargs):
        q = {'pair': pair, 'type': side, 'price': price,
             'ordertype': 'limit', 'volume': amount,
             'trading_agreement': 'agree'}
        q.update(kwargs)
        return self.private_query('AddOrder', params=q)

    @return_json(trade)
    def bid(self, pair, price, amount, **kwargs):
        return self._add_order(pair, 'buy', price, amount, **kwargs)

    @return_json(trade)
    def ask(self, pair, price, amount, **kwargs):
        return self._add_order(pair, 'sell', price, amount, **kwargs)

    @return_json(cancel)
    def cancel_order(self, order_id, **kwargs):
        q = {'txid': order_id}
        q.update(kwargs)
        return self.private_query('CancelOrder', params=q)

    @return_json(None)
    def order_info(self, *txids, **kwargs):
        if len(txids) > 1:
            q = {'txid': txids}
        elif txids:
            txid, *_ = txids
            q = {'txid': txid}
        else:
            q = {}
        q.update(kwargs)
        return self.private_query('QueryOrders', params=q)

    @return_json(None)
    def balance(self, **kwargs):
        return self.private_query('Balance')

    @return_json(None)
    def withdraw(self, _type, source_wallet, amount, tar_addr, **kwargs):
        raise NotImplementedError()

    @return_json(None)
    def deposit_address(self, **kwargs):
        raise NotImplementedError()

    """
    Exchange Specific Methods
    """

    @return_json(None)
    def time(self):
        return self.public_query('Time')

    @return_json(None)
    def assets(self, **kwargs):
        return self.public_query('Assets', params=kwargs)

    @return_json(None)
    def pairs(self, **kwargs):
        return self.public_query('AssetPairs', params=kwargs)

    @return_json(None)
    def ohlc(self, pair, **kwargs):
        q = self.make_params(pair, **kwargs)
        return self.public_query('OHLC', params=q)

    @return_json(None)
    def spread(self, pair, **kwargs):
        q = self.make_params(pair, **kwargs)
        return self.public_query('Spread', params=q)

    @return_json(None)
    def orders(self, **kwargs):
        q = kwargs
        return self.private_query('OpenOrders', params=q)

    @return_json(None)
    def closed_orders(self, **kwargs):
        q = kwargs
        return self.private_query('ClosedOrders', params=q)

    @return_json(None)
    def trade_history(self, **kwargs):
        q = kwargs
        return self.private_query('TradesHistory', params=q)

    @return_json(None)
    def fees(self, pair=None):
        q = {'fee-info': True}

        if pair:
            q['pair'] = pair
        return self.private_query('TradeVolume', params=q)

bitex.interfaces.bitfinex

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"""
http://docs.bitfinex.com/
"""

# Import Built-Ins
import logging

# Import Third-Party

# Import Homebrew
from bitex.api.rest import BitfinexREST
from bitex.utils import return_json
from bitex.formatters.bitfinex import trade, cancel, order_status
# Init Logging Facilities
log = logging.getLogger(__name__)


class Bitfinex(BitfinexREST):
    def __init__(self, key='', secret='', key_file=''):
        super(Bitfinex, self).__init__(key, secret)
        if key_file:
            self.load_key(key_file)

    def public_query(self, endpoint, **kwargs):
        return self.query('GET', endpoint, **kwargs)

    def private_query(self, endpoint, **kwargs):
        return self.query('POST', endpoint, authenticate=True, **kwargs)

    """
    BitEx Standardized Methods
    """
    @return_json(None)
    def order_book(self, pair, **kwargs):
        return self.public_query('book/%s' % pair, params=kwargs)

    @return_json(None)
    def ticker(self, pair, **kwargs):
        return self.public_query('pubticker/%s' % pair, params=kwargs)

    @return_json(None)
    def trades(self, pair, **kwargs):
        return self.public_query('trades/%s' % pair, params=kwargs)

    def _place_order(self, pair, amount, price, side, replace, **kwargs):
        q = {'symbol': pair, 'amount': amount, 'price': price, 'side': side,
             'type': 'exchange limit'}
        q.update(kwargs)
        if replace:
            return self.private_query('order/cancel/replace', params=q)
        else:
            return self.private_query('order/new', params=q)

    @return_json(trade)
    def bid(self, pair, price, amount, replace=False, **kwargs):
        return self._place_order(pair, amount, price, 'buy', replace=replace,
                                 **kwargs)

    @return_json(trade)
    def ask(self, pair, price, amount, replace=False, **kwargs):
        return self._place_order(pair, str(amount), str(price), 'sell',
                                 replace=replace, **kwargs)

    @return_json(cancel)
    def cancel_order(self, order_id, all=False, **kwargs):

        q = {'order_id': int(order_id)}
        q.update(kwargs)
        if not all:
            return self.private_query('order/cancel', params=q)
        else:
            endpoint = 'order/cancel/all'
            return self.private_query(endpoint)

    @return_json(order_status)
    def order(self, order_id, **kwargs):
        q = {'order_id': order_id}
        q.update(kwargs)
        return self.private_query('order/status', params=q)

    @return_json(None)
    def balance(self, **kwargs):
        return self.private_query('balances', params=kwargs)

    @return_json(None)
    def withdraw(self, _type, source_wallet, amount, tar_addr, **kwargs):
        q = {'withdraw_type': _type, 'walletselected': source_wallet,
             'amount': amount, 'address': tar_addr}
        q.update(kwargs)
        return self.private_query('withdraw', params=q)

    @return_json(None)
    def deposit_address(self, **kwargs):
        q = {'method': currency, 'wallet_name': target_wallet}
        q.update(kwargs)
        return self.private_query('deposit/new', params=kwargs)

    """
    Exchange Specific Methods
    """

    @return_json(None)
    def statistics(self, pair):
        return self.public_query('stats/%s' % pair)

    @return_json(None)
    def funding_book(self, currency, **kwargs):
        return self.public_query('lendbook/%s' % currency, params=kwargs)

    @return_json(None)
    def lends(self, currency, **kwargs):
        return self.public_query('lends/%s' % currency, params=kwargs)

    @return_json(None)
    def pairs(self, details=False):
        if details:
            return self.public_query('symbols_details')
        else:
            return self.public_query('symbols')

    @return_json(None)
    def fees(self):
        return self.private_query('account_infos')

    @return_json(None)
    def orders(self):
        return self.private_query('orders')

    @return_json(None)
    def balance_history(self, currency, **kwargs):
        q = {'currency': currency}
        q.update(kwargs)
        return self.private_query('history/movements', params=q)

    @return_json(None)
    def trade_history(self, pair, since, **kwargs):
        q = {'symbol': pair, 'timestamp': since}
        q.update(kwargs)
    return self.private_query('mytrades', params=q)

bitex.interfaces.gdax

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"""
https://docs.gdax.com/
"""

# Import Built-Ins
import logging

# Import Third-Party

# Import Homebrew
from bitex.api.rest import GDAXRest
from bitex.utils import return_json

# Init Logging Facilities
log = logging.getLogger(__name__)


class GDAX(GDAXRest):
    def __init__(self, key='', secret='', key_file=''):
        super(GDAX, self).__init__(key, secret)
        if key_file:
            self.load_key(key_file)

    def public_query(self, endpoint, **kwargs):
        return self.query('GET', endpoint, **kwargs)

    def private_query(self, endpoint, method_verb='POST', **kwargs):
        return self.query(method_verb, endpoint, authenticate=True, **kwargs)

    """
    BitEx Standardized Methods
    """

    @return_json(None)
    def ticker(self, pair, **kwargs):
        return self.public_query('products/%s/ticker' % pair, params=kwargs)

    @return_json(None)
    def order_book(self, pair, **kwargs):
        return self.public_query('products/%s/book' % pair, params=kwargs)

    @return_json(None)
    def trades(self, pair, **kwargs):
        return self.public_query('products/%s/trades' % pair, params=kwargs)

    @return_json(None)
    def bid(self, pair, price, size, **kwargs):
        q = {'side': 'buy', 'type': 'market', 'product_id': pair,
             'price': price, 'size': size}
        q.update(kwargs)
        return self.private_query('orders', params=q)

    @return_json(None)
    def ask(self, pair, price, amount, **kwargs):
        q = {'side': 'sell', 'type': 'market', 'product_id': pair,
             'price': price, 'size': size}
        q.update(kwargs)
        return self.private_query('orders', params=q)

    @return_json(None)
    def cancel_order(self, order_id, all=False, **kwargs):

        if not all:
            return self.private_query('orders/%s' % order_id,
                                      method_verb='DELETE', params=kwargs)
        else:
            return self.private_query('orders', method_verb='DELETE',
                                      params=kwargs)

    @return_json(None)
    def order(self, order_id, **kwargs):
        return self.private_query('orders/%s' % order_id, method_verb='GET',
                                  params=kwargs)

    @return_json(None)
    def balance(self, **kwargs):
        return self.private_query('accounts', method_verb='GET', params=kwargs)

    @return_json(None)
    def withdraw(self, _type, source_wallet, amount, tar_addr, **kwargs):
        raise NotImplementedError()

    @return_json(None)
    def deposit_address(self, **kwargs):
        raise NotImplementedError()

    """
    Exchange Specific Methods
    """

    @return_json
    def time(self):
        return self.public_query('time')

    @return_json(None)
    def currencies(self):
        return self.public_query('currencies')

    @return_json(None)
    def pairs(self):
        return self.public_query('products')

    @return_json(None)
    def ohlc(self, pair, **kwargs):
        return self.public_query('products/%s/candles' % pair, params=kwargs)

    @return_json(None)
    def stats(self, pair, **kwargs):
        return self.public_query('products/%s/stats' % pair, params=kwargs)

bitex.utils

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# Import Built-Ins
import logging
import json
import requests
# Import Third-Party

# Import Homebrew

# Init Logging Facilities
log = logging.getLogger(__name__)


def return_json(formatter=None):
    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                r = func(*args, **kwargs)
            except Exception as e:
                log.error("return_json(): Error during call to "
                          "%s(%s, %s) %s" % (func.__name__, args, kwargs, e))
                raise

            try:
                r.raise_for_status()
            except requests.HTTPError as e:
                log.error("return_json: HTTPError for url %s: "
                          "%s" % (r.request.url, e))
                return None, r

            try:
                data = r.json()
            except json.JSONDecodeError:
                log.error('return_json: Error while parsing json. '
                          'Request url was: %s, result is: '
                          '%s' % (r.request.url, r.text))
                return None, r
            except Exception as e:
                log.error("return_json(): Unexpected error while parsing json "
                          "from %s: %s" % (r.request.url, e))
                raise

            # Apply formatter and return
            if formatter is not None:
                return formatter(data, *args, **kwargs), r
            else:
                return data, r
        return wrapper
return decorator

您还可以在其GitHub存储库中找到代码。我省略了一些接口类--我提供的三个类与它们所提供的类一样多样。

GitHub仓库 (dev分支)

EN

回答 1

Code Review用户

回答已采纳

发布于 2017-01-12 14:27:43

这里有很多代码,所以我只想回顾一下bitex.api。你会发现这里有很多可以供一次审查的。也许在“代码评审”中的其他评审人员会检查您的其他代码。

  1. 没有模块文档字符串。这个模块的目的是什么?里面有什么?
  2. import requests位于“内置”部分,但据我所知,这个模块不是内置到Python中的,因此它应该位于“第三方”部分。
  3. RESTAPI的名称可以改进。这个类的实例是代表API客户端还是API服务器?名字没有说清楚。
  4. RESTAPI类没有docstring。这个类的实例代表什么?你能举几个例子说明一下它的用法吗?在对post的介绍中,有一些文本可以用作docstring的起点。
  5. 当您记录消息时,不要使用%操作符来格式化消息,而是分别传递格式字符串和格式参数如文件所述。(这为记录器提供了更大的灵活性,例如,如果日志记录被禁止,那么消息可能永远不需要格式化。)
  6. __init__方法的docstring需要记录该方法,而不是整个类。所以应该开始“创建RESTAPI对象.”然后继续记录论点的意义。
  7. 这很小,但是为什么参数用下划线拼写api_version,而属性拼写为apiversion却没有?
  8. 使用不安全的keysecret的默认参数是有风险的。有一种风险是,由于疏忽,load_key方法可能不会被调用(特别是因为没有文档来解释何时需要调用它)。这将使API变得不安全。如果代码在默认情况下是安全的,那么情况要好得多。因此,我宁愿使用无效的默认参数。
  9. self.req_methods每次都是相同的,所以它应该是全局常量或类属性,而不是实例属性。
  10. 可以避免self.req_methods结构中的重复,例如:#支持的HTTP谓词。REQ_METHODS ={谓词:getattr(请求,verb.lower()),用于动词},但实际上,我认为这些数据结构是不必要的。而不是: request_method = self.req_methods方法_动词 #。R= request_method(url,timeout=5,**request_kwargs)为什么不使用requests.request?R= requests.request(method_verb,url,timeout=5,**request_kwargs)
  11. load_key的docstring应该解释path参数的含义。(这是一个文本文件的路径,其前两行分别是密钥和秘密。)
  12. open的第二个参数默认为'r',因此可以省略这一点。
  13. 方法nonce没有docstring。
  14. 仅以time.time的输出为基础是有风险的,因为这可能会倒退:>>>导入时间>>> time.get_clock_info(' time ').monotonic False,这意味着nonces可能会重复。
  15. sign的docstring并不解释方法应该做什么。
  16. 默认情况下,sign方法是不安全的。很容易忘记推翻它,这样就不会有什么问题,但是所有的签名都是假的。在默认情况下最好是安全的。一个很好的方法是使用来自abc (抽象基类)模块的工具:从abc导入ABCMeta,抽象方法类RESTAPI(metaclass=ABCMeta):#.@抽象方法def签名(self,.):#.现在,在不重写RESTAPI方法的情况下从sign继承的任何尝试都会引发异常。
  17. query方法的docstring并不解释参数的含义或返回的内容。
  18. timeout=5关键字参数是硬编码的。如果有人需要查询比此更长的超时时间的API,该怎么办?该值应该是query函数的关键字参数(或者可能是类或对象的属性)。
票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/148983

复制
相关文章
Python 文件复制&按目录树结构拷贝&批量删除目录及其子目录下的文件
#!/usr/bin/env/ python # -*- coding:utf-8 -*- __author__ = 'shouke' import os import subprocess # 复制文件或目录到指定目录(非自身目录) def copy_dir_or_file(src, dest): if not os.path.exists(dest): print('目标路径:%s 不存在' % dest) return [False, '目标路径:%s 不存在' % dest] elif not os.path.isdir(dest): print('目标路径:%s 不为目录' % dest) return [False, '目标路径:%s 不为目录' % dest] elif src.replace('/', '\\').rstrip('\\') == dest.replace('/', '\\').rstrip('\\'): print('源路径和目标路径相同,无需复制') return [True,'源路径和目标路径相同,不需要复制'] if not os.path.exists(src): print('源路径:%s 不存在' % src) return [False, '源路径:%s 不存在' % src] # /E 复制目录和子目录,包括空的 /Y 无需确认,自动覆盖已有文件 args = 'xcopy /YE ' + os.path.normpath(src) + ' ' + os.path.normpath(dest) # 注意:xcopy不支持 d:/xxx,只支持 d:\xxxx,所以要转换 try: with subprocess.Popen(args, shell=True, universal_newlines = True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: output = proc.communicate() print('复制文件操作输出:%s' % str(output)) if not output[1]: print('复制目标文件|目录(%s) 到目标目录(%s)成功' % (src, dest)) return [True,'复制成功'] else: print('复制目标文件|目录(%s) 到目标目录(%s)失败:%s' % (src, dest, output[1])) return [False,'复制目标文件|目录(%s) 到目标目录(%s)失败:%s' % (src, dest, output[1])] except Exception as e: print('复制目标文件|目录(%s) 到目标目录(%s)失败 %s' % (src, dest, e)) return [False, '复制目标文件|目录(%s) 到目标目录(%s)失败 %s' % (src, dest, e)] # 删除指定目录及其子目录下的所有子文件,不删除目录 def delete_file(dirpath): if not os.path.exists(dirpath): print('要删除的目标路径:%s 不存在' % dirpath) return [False, '要删除的目标路径:%s 不存在' % dirpath] elif not os.path.isdir(dirpath): print('要删除的目标路径:%s 不为目录' % dirpath) return [False, '要删除的目标路径:%s 不为目录' % dirpath] # 注意:同xcopy命令,del也不支持 d:/xxxx,Linux/Unix路径的写法,只支持d:\xxx windows路径的写法 args = 'del /F/S/Q ' + os.path.normpath(dirpath) # /F 强制删除只读文件。 /S 删除所有子目录中的指定的文件。 /Q 安静模式。删除前,不要求确认 try: with subprocess.Popen(args, shell=True, universal_newlines = True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
授客
2019/09/11
1.9K0
使用Python批量复制源目录下的所有Excel文件复制到目标目录中
前几天在Python白银群【由恒远】问了一个Python自动化办公处理的问题,这里拿出来给大家分享下。
Python进阶者
2023/09/02
5290
使用Python批量复制源目录下的所有Excel文件复制到目标目录中
编写一个程序,将 d: \ java 目录下的所有.java 文件复制到d: \ jad 目录下,并 将原来文件的扩展名从.java 改为.jad
自己写完之后,对照网上的代码进行了优化,涉及大量的文件操作,作为文件操作的摸版,可以借鉴里面的函数细节
粲然忧生
2022/08/02
1K0
java文件夹复制到指定目录
版权声明:本文为博主原创文章,未经博主允许不得转载。 最近一个项目需要文件夹复制 废话不说上代码 public class FileUtils { public static void copy(File orig, File dest) { // 用于改后缀后复制 BufferedReader buf = null; PrintWriter out = null; try { buf = new BufferedReader(new InputStrea
DencyCheng
2018/11/05
4.1K0
.gitignore过滤当前目录下的文件夹,不影响其他目录
最近在使用git时遇到一个问题, push到远程机器上时某个log文件夹丢失了,本地查找是有的,git status也显示clean:
梦飞
2022/06/23
1.3K0
msbuild 修改 VisualStudio 文件复制到输出目录的路径
在默认的 VisualStudio 可以右击任意的文件,让这个文件在编译时复制到输出目录,但是这个选项将会在复制到输出目录时带上这个文件所在 VisualStudio 的文件夹结构。本文告诉大家几个方法让 VisualStudio 的文件可以在编译时输出到自定义的任意路径
林德熙
2020/02/17
3.6K1
文件、目录_文件目录表
文件是一种抽象机制,它提供了一种方式用来存储信息以及在后面进行读取。可能任何一种机制最重要的特性就是管理对象的命名方式。
全栈程序员站长
2022/09/20
2.3K0
文件、目录_文件目录表
玩转并理解linux中的文件/目录的rwx权限
linux是一个相对安全的系统, 其中的权限更是无处不在。 在本文中, 我们来谈谈linux中的文件/目录的rwx权限。 为了简便起见, 我们仅仅以文件owner的rwx为例。
全栈程序员站长
2022/09/02
2.4K0
python 中目录、文件
2) open("test.txt",w)           直接打开一个文件,如果文件不存在则创建文件
py3study
2020/01/10
1.3K0
java 把文件从一个目录复制到另一个目录
方法一:简单粗暴,直接使用copy(),如果目标存在,先使用delete()删除,再复制;
崔笑颜
2020/06/08
1.9K0
PHP对目录下的子目录及文件进行压缩并解压
创建压缩类文件 zip.php <?php class Zip{ /** * Zip a folder (include itself). * Usage: * Zi
Action
2021/05/07
1.8K0
PHP对目录下的子目录及文件进行压缩并解压
Python 查看目录中的文件
一些关于文件的操作 例如,实现查看目录内容的功能。类似Linux下的tree命令。 统计目录下指定后缀文件的行数。
AnRFDev
2021/02/01
3.3K0
golang把文件复制到另一个目录
//本程序 主要功能是把A文件夹下的文件与B目录下文件对比,如果找到就覆盖到B相应的目录下。 // 用法: merge A目录 B目录 // merge.go package main import ( "flag" "fmt" "os" "path/filepath" "strings" "time" "github.com/Unknwon/com" ) const ( IsDirectory =
李海彬
2018/03/27
1.2K0
golang把文件复制到另一个目录
//本程序 主要功能是把A文件夹下的文件与B目录下文件对比,如果找到就覆盖到B相应的目录下。 // 用法: merge A目录 B目录 // merge.go package main import ( "flag" "fmt" "os" "path/filepath" "strings" "time" "github.com/Unknwon/com" ) const ( IsDirectory =
李海彬
2018/03/27
2.1K0
在 Linux 中永久并安全删除文件和目录的方法
引言 在大多数情况下,我们习惯于使用 Delete 键、垃圾箱或 rm 命令从我们的计算机中删除文件,但这不是永久安全地从硬盘中(或任何存储介质)删除文件的方法。 该文件只是对用户隐藏,它驻留在硬盘上的某个地方。它有可能被数据窃贼、执法取证或其它方式来恢复。 假设文件包含密级或机密内容,例如安全系统的用户名和密码,具有必要知识和技能的攻击者可以轻松地恢复删除文件的副本并访问这些用户凭证(你可以猜测到这种情况的后果)。 在本文中,我们将解释一些命令行工具,用于永久并安全地删除 Linux 中的文件。 1.
小小科
2018/05/04
4.6K0
在 Linux 中永久并安全删除文件和目录的方法
/etc/fstab文件_将etc目录挂载到
第一列到六列是设备或分区 挂载点 文件类型 default是挂载类型 dump 系统故障dump内存信息到硬盘 fsck 检查磁盘坏道等
全栈程序员站长
2022/09/20
8890
Nodejs中读取文件目录中的所有文件
关于Nodejs中的文件系统即File System可以参考官方Node.js v12.18.1的文档File system
ccf19881030
2020/06/28
14.8K0
linux下文件数、目录数、文件名长度的各种限制
以下测试都是在没有优化或修改内核的前提下测试的结果 1. 测试目的:ext3文件系统下filename最大字符长度   测试平台:RHEL5U3_x64   测试过程: LENTH=`for i in {1..255};do for x in a;do echo -n $x;done;done` touch $LENTH 当增加到256时,touch报错,File name too long linux系统下ext3文件系统内给文件/目录命名,最长只能支持127个中文字符,英文则可以支持255个字符 2. 测试目的:ext3文件系统下一级子目录的个数限制   测试平台:RHEL5U3_x64   测试过程: [root@fileserver maxdir]# for i in {1..32000};do mkdir $i;done mkdir: cannot create directory `31999': Too many links mkdir: cannot create directory `32000': Too many links ext3文件系统一级子目录的个数为31998(个)。 Linux为了cpu的搜索效率而规定的,要想改变数目大概要重新编译内核.  3. 测试目的:ext3文件系统下单个目录里的最大文件数   测试平台: RHEL5U3_x64   测试过程:   单个目录下的最大文件数似乎没什么特别限制,也是受限于所在文件系统的inode数限制:   df -i或者使用tune2fs -l /dev/sdaX或者dumpe2fs -h /dev/sdaX查看可用inode数,后两个命令    输出结果是一样的,但是跟df所得出的可用inode数会有些误差,至今不明白什么原因。   网上常用两种解决办法:   1) 重新mkfs,ext3默认block大小4096 Bytes,block设置小一些inode数设置大一些   2) 使用loopback文件系统临时解决:       在/usr中(也可以在别处)创建一个大文件,然后做成loopback文件系统,将原来的文件移到这个       文件系统中,并将它mount到/usr下合适的位置。这样可以大大减少你/usr中的文件数目。但是系统       性能会有点损失。 4. 测试目的: 打开文件数限制(文件句柄、文件描述符)   测试平台: RHEL5U3_x64   ulimit -n 65535设置,或者/etc/security/limit.conf里设置用户打开文件数、进程数、CPU等
一见
2019/03/14
5.5K0
java遍历文件夹下所有图片_遍历指定文件夹下的所有图片,并复制到指定目录下…
importjava.awt.image.BufferedImage;importjava.io.File;importjava.io.IOException;importjava.util.ArrayList;importjava.util.List;importjavax.imageio.ImageIO;public classCopy
全栈程序员站长
2022/09/05
2.8K0
点击加载更多

相似问题

无法将cuda:0设备类型张量转换为numpy

16

cuda张量的索引列表给出错误-“无法将cuda:0设备类型张量转换为numpy”

12

将张量转换为numpy数组

10

将Numpy Arrray转换为张量

266

如何将张量的numpy数组转换为张量?

10
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文