def __init__(self, api_key, secret):
     self.APIKey = api_key
     self.Secret = secret
     self.req_per_sec = 6
     self.req_time_log = RingBuffer(self.req_per_sec)
     self.lock = threading.RLock()
     socket.setdefaulttimeout(int(Config.get("BOT", "timeout", 30, 1, 180)))
Exemple #2
0
 def __init__(self, api_key, secret):
     self.APIKey = api_key
     self.Secret = secret
     self.req_per_sec = 6
     self.req_time_log = RingBuffer(self.req_per_sec)
     self.lock = threading.RLock()
     socket.setdefaulttimeout(30)
Exemple #3
0
 def __init__(self, file, logLimit, exchange=''):
     self.jsonOutputFile = file
     self.jsonOutput = {}
     self.clearStatusValues()
     self.jsonOutputLog = RingBuffer(logLimit)
     self.jsonOutput['exchange'] = exchange
     self.jsonOutput['label'] = Config.get("BOT", "label", "Lending Bot")
Exemple #4
0
 def __init__(self, cfg):
     self.cfg = cfg
     self.APIKey = self.cfg.get("API", "apikey", None)
     self.Secret = self.cfg.get("API", "secret", None)
     self.req_per_sec = 6
     self.req_time_log = RingBuffer(self.req_per_sec)
     self.lock = threading.RLock()
     socket.setdefaulttimeout(int(Config.get("BOT", "timeout", 30, 1, 180)))
 def __init__(self, cfg, log):
     super(Bitfinex, self).__init__(cfg, log)
     self.cfg = cfg
     self.log = log
     self.lock = threading.RLock()
     self.req_per_period = 1
     self.default_req_period = 1000  # milliseconds, 1000 = 60/min
     self.req_period = self.default_req_period
     self.req_time_log = RingBuffer(self.req_per_period)
     self.url = 'https://api.bitfinex.com'
     self.key = self.cfg.get("API", "apikey", None)
     self.secret = self.cfg.get("API", "secret", None)
     self.apiVersion = 'v1'
     self.symbols = []
     self.ticker = {}
     self.tickerTime = 0
     self.baseCurrencies = ['USD', 'BTC', 'ETH']
     self.all_currencies = self.cfg.get_all_currencies()
     self.usedCurrencies = []
     self.timeout = int(self.cfg.get("BOT", "timeout", 30, 1, 180))
     self.api_debug_log = self.cfg.getboolean("BOT", "api_debug_log")
     self.last_get_time = 0
     self.last_get_request = ''
     self.last_get_cache = ''
     # Initialize usedCurrencies
     _ = self.return_available_account_balances("lending")
Exemple #6
0
class JsonOutput(object):
    def __init__(self, file, logLimit, exchange=''):
        self.jsonOutputFile = file
        self.jsonOutput = {}
        self.clearStatusValues()
        self.jsonOutputLog = RingBuffer(logLimit)
        self.jsonOutput['exchange'] = exchange
        self.jsonOutput['label'] = Config.get("BOT", "label", "Lending Bot")

    def status(self, status, time, days_remaining_msg):
        self.jsonOutput["last_update"] = time + days_remaining_msg
        self.jsonOutput["last_status"] = status

    def printline(self, line):
        line = line.replace("\n", ' | ')
        self.jsonOutputLog.append(line)

    def writeJsonFile(self):
        with io.open(self.jsonOutputFile, 'w', encoding='utf-8') as f:
            self.jsonOutput["log"] = self.jsonOutputLog.get()
            f.write(json.dumps(self.jsonOutput, ensure_ascii=True, sort_keys=True))

    def addSectionLog(self, section, key, value):
        if section not in self.jsonOutput:
            self.jsonOutput[section] = {}
        if key not in self.jsonOutput[section]:
            self.jsonOutput[section][key] = {}
        self.jsonOutput[section][key] = value

    def statusValue(self, coin, key, value):
        if coin not in self.jsonOutputCoins:
            self.jsonOutputCoins[coin] = {}
        self.jsonOutputCoins[coin][key] = str(value)

    def clearStatusValues(self):
        self.jsonOutputCoins = {}
        self.jsonOutput["raw_data"] = self.jsonOutputCoins
        self.jsonOutputCurrency = {}
        self.jsonOutput["outputCurrency"] = self.jsonOutputCurrency

    def outputCurrency(self, key, value):
        self.jsonOutputCurrency[key] = str(value)
 def __init__(self, cfg, log):
     super(Bitfinex, self).__init__(cfg, log)
     self.cfg = cfg
     self.log = log
     self.lock = threading.RLock()
     self.req_per_min = 60
     self.req_period = 15 # seconds
     self.req_per_period = int(self.req_per_min / ( 60.0 / self.req_period))
     self.req_time_log = RingBuffer(self.req_per_period)
     self.url = 'https://api.bitfinex.com'
     self.key = self.cfg.get("API", "apikey", None)
     self.secret = self.cfg.get("API", "secret", None)
     self.apiVersion = 'v1'
     self.symbols = []
     self.ticker = {}
     self.tickerTime = 0
     self.usedCurrencies = []
     self.timeout = int(self.cfg.get("BOT", "timeout", 30, 1, 180))
     # Initialize usedCurrencies
     _ = self.return_available_account_balances("lending")
Exemple #8
0
 def __init__(self, cfg, log):
     super(Poloniex, self).__init__(cfg, log)
     self.cfg = cfg
     self.log = log
     self.APIKey = self.cfg.get("API", "apikey", None)
     self.Secret = self.cfg.get("API", "secret", None)
     self.req_per_period = 6
     self.default_req_period = 1000  # milliseconds
     self.req_period = self.default_req_period
     self.req_time_log = RingBuffer(self.req_per_period)
     self.lock = threading.RLock()
     socket.setdefaulttimeout(int(Config.get("BOT", "timeout", 30, 1, 180)))
     self.api_debug_log = self.cfg.getboolean("BOT", "api_debug_log")
class Bitfinex(ExchangeApi):
    def __init__(self, cfg, log):
        super(Bitfinex, self).__init__(cfg, log)
        self.cfg = cfg
        self.log = log
        self.lock = threading.RLock()
        self.req_per_min = 60
        self.req_period = 15 # seconds
        self.req_per_period = int(self.req_per_min / ( 60.0 / self.req_period))
        self.req_time_log = RingBuffer(self.req_per_period)
        self.url = 'https://api.bitfinex.com'
        self.key = self.cfg.get("API", "apikey", None)
        self.secret = self.cfg.get("API", "secret", None)
        self.apiVersion = 'v1'
        self.symbols = []
        self.ticker = {}
        self.tickerTime = 0
        self.usedCurrencies = []
        self.timeout = int(self.cfg.get("BOT", "timeout", 30, 1, 180))
        # Initialize usedCurrencies
        _ = self.return_available_account_balances("lending")

    @property
    def _nonce(self):
        """
        Returns a nonce
        Used in authentication
        """
        return str(int(round(time.time() * 1000)))

    @ExchangeApi.synchronized
    def limit_request_rate(self):
        now = time.time()
        # start checking only when request time log is full
        if len(self.req_time_log) == self.req_per_period:
            time_since_oldest_req = now - self.req_time_log[0]
            # check if oldest request is more than self.req_period ago
            if time_since_oldest_req < self.req_period:
                # print self.req_time_log.get()
                # uncomment to debug
                # print("Waiting {0} sec, {1} to keep api request rate".format(self.req_period - time_since_oldest_req,
                #       threading.current_thread()))
                # print("Req:{0} Oldest req:{1} Diff:{2} sec".format(now, self.req_time_log[0], time_since_oldest_req))
                self.req_time_log.append(now + self.req_period - time_since_oldest_req)
                time.sleep(self.req_period - time_since_oldest_req)
                return
            # uncomment to debug
            # else:
            #     print self.req_time_log.get()
            #     print("Not Waiting {0}".format(threading.current_thread()))
            #     print("Req:{0} Oldest req:{1}  Diff:{2} sec".format(now, self.req_time_log[0], time_since_oldest_req))
        # append current request time to the log, pushing out the 60th request time before it
        self.req_time_log.append(now)

    def _sign_payload(self, payload):
        j = json.dumps(payload)
        data = base64.standard_b64encode(j.encode('utf8'))

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

    @ExchangeApi.synchronized
    def _request(self, method, request, payload=None, verify=True):
        now = time.time()
        try:
            # keep the 60 request per minute limit
            self.limit_request_rate()

            r = {}
            url = '{}{}'.format(self.url, request)
            if method == 'get':
                r = requests.get(url, timeout=self.timeout, headers={'Connection':'close'})
            else:
                r = requests.post(url, headers=payload, verify=verify, timeout=self.timeout)

            if r.status_code != 200:
                if r.status_code == 502 or r.status_code in range(520, 527, 1):
                    raise ApiError('API Error ' + str(r.status_code) +
                                   ': The web server reported a bad gateway or gateway timeout error.')
                else:
                    raise ApiError('API Error ' + str(r.status_code) + ': ' + r.text)

            return r.json()

        except Exception as ex:
            ex.message = ex.message if ex.message else str(ex)
            ex.message = "{0} Requesting {1}".format(ex.message, self.url + request)
            raise ex

    def _post(self, command, payload=None, verify=True):
        payload = payload or {}
        payload['request'] = '/{}/{}'.format(self.apiVersion, command)
        payload['nonce'] = self._nonce
        signed_payload = self._sign_payload(payload)
        return self._request('post', payload['request'], signed_payload, verify)

    def _get(self, command):
        request = '/{}/{}'.format(self.apiVersion, command)
        return self._request('get', request)

    def _get_symbols(self):
        """
        A list of symbol names. Currently "btcusd", "ltcusd", "ltcbtc", ...
        https://bitfinex.readme.io/v1/reference#rest-public-symbols
        """
        if len(self.symbols) == 0:
            bfx_resp = self._get('symbols')
            all_currencies = self.cfg.get_all_currencies()
            for symbol in bfx_resp:
                base = symbol[3:].upper()
                curr = symbol[:3].upper()
                if base in ['USD', 'BTC'] and curr in all_currencies:
                    self.symbols.append(symbol)

        return self.symbols

    def return_open_loan_offers(self):
        """
        Returns active loan offers
        https://bitfinex.readme.io/v1/reference#rest-auth-offers
        """
        bfx_resp = self._post('offers')
        resp = Bitfinex2Poloniex.convertOpenLoanOffers(bfx_resp)

        return resp

    def return_loan_orders(self, currency, limit=0):
        command = ('lendbook/' + currency + '?limit_asks=' + str(limit) + '&limit_bids=' + str(limit))
        bfx_resp = self._get(command)
        resp = Bitfinex2Poloniex.convertLoanOrders(bfx_resp)

        return resp

    def return_active_loans(self):
        """
        Returns own active loan offers
        https://bitfinex.readme.io/v1/reference#rest-auth-offers
        """
        bfx_resp = self._post('credits')
        resp = Bitfinex2Poloniex.convertActiveLoans(bfx_resp)

        return resp

    def return_ticker(self):
        """
        The ticker is a high level overview of the state of the market
        https://bitfinex.readme.io/v1/reference#rest-public-ticker
        """
        t = int(time.time())
        if t - self.tickerTime < 60:
            return self.ticker

        set_ticker_time = True

        for symbol in self._get_symbols():
            base = symbol[3:].upper()
            curr = symbol[:3].upper()
            if base in ['BTC', 'USD'] and (curr == 'BTC' or curr in self.usedCurrencies):
                couple = (base + '_' + curr)
                couple_reverse = (curr + '_' + base)

                try:
                    ticker = self._get('pubticker/' + symbol)

                    if 'message' in ticker:
                        raise ApiError("Error: {} ({})".format(ticker['message'], symbol))

                    self.ticker[couple] = {
                        "last": ticker['last_price'],
                        "lowestAsk": ticker['ask'],
                        "highestBid": ticker['bid'],
                        "percentChange": "",
                        "baseVolume": str(float(ticker['volume']) * float(ticker['mid'])),
                        "quoteVolume": ticker['volume']
                    }
                    self.ticker[couple_reverse] = {
                        "last": 1 / float(self.ticker[couple]['last']),
                        "lowestAsk": 1 / float(self.ticker[couple]['lowestAsk']),
                        "highestBid": 1 / float(self.ticker[couple]['highestBid'])
                    }

                except Exception as ex:
                    self.log.log_error('Error retrieving ticker for {}: {}. Continue with next currency.'
                                       .format(symbol, ex.message))
                    set_ticker_time = False
                    continue

        if set_ticker_time and len(self.ticker) > 2:  # USD_BTC and BTC_USD are always in
            self.tickerTime = t

        return self.ticker

    def return_available_account_balances(self, account):
        """
        Returns own balances sorted by account
        https://bitfinex.readme.io/v1/reference#rest-auth-wallet-balances
        """
        bfx_resp = self._post('balances')
        balances = Bitfinex2Poloniex.convertAccountBalances(bfx_resp, account)

        if 'lending' in balances:
            for curr in balances['lending']:
                if curr not in self.usedCurrencies:
                    self.usedCurrencies.append(curr)

        return balances

    def cancel_loan_offer(self, currency, order_number):
        """
        Cancels an offer
        https://bitfinex.readme.io/v1/reference#rest-auth-cancel-offer
        """
        payload = {
            "offer_id": order_number,
        }

        bfx_resp = self._post('offer/cancel', payload)

        success = 0
        message = ''
        try:
            if bfx_resp['id'] == order_number:
                success = 1
                message = "Loan offer canceled ({:.4f} @ {:.4f}%).".format(float(bfx_resp['remaining_amount']),
                                                                           float(bfx_resp['rate']) / 365)
        except Exception as e:
            message = "Error canceling offer: ", str(e)
            success = 0

        return {"success": success, "message": message}

    def create_loan_offer(self, currency, amount, duration, auto_renew, lending_rate):
        """
        Creates a loan offer for a given currency.
        https://bitfinex.readme.io/v1/reference#rest-auth-new-offer
        """

        payload = {
            "currency": currency,
            "amount": str(amount),
            "rate": str(round(float(lending_rate),10) * 36500), 
            "period": int(duration),
            "direction": "lend"
        }

        try:
            bfx_resp = self._post('offer/new', payload)
            plx_resp = {"success": 0, "message": "Error", "orderID": 0}
            if bfx_resp['id']:
                plx_resp['orderId'] = bfx_resp['id']
                plx_resp['success'] = 1
                plx_resp['message'] = "Loan order placed."
            return plx_resp

        except Exception as e:
                msg = str(e)
                # "Invalid offer: incorrect amount, minimum is 50 dollar or equivalent in USD"
                if "Invalid offer: incorrect amount, minimum is 50" in msg:
                    usd_min = 50
                    cur_min = usd_min
                    if currency != 'USD':
                        cur_min = usd_min / float(self.return_ticker()['USD_' + currency]['lowestAsk'])

                    raise Exception("Error create_loan_offer: Amount must be at least " + str(cur_min) + " " + currency)
                else:
                    raise e

    def return_balances(self):
        """
        Returns balances of exchange wallet
        https://bitfinex.readme.io/v1/reference#rest-auth-wallet-balances
        """
        balances = self.return_available_account_balances('exchange')
        return balances['exchange']

    def transfer_balance(self, currency, amount, from_account, to_account):
        """
        Transfers values from one account/wallet to another
        https://bitfinex.readme.io/v1/reference#rest-auth-transfer-between-wallets
        """
        account_map = {
            'margin': 'trading',
            'lending': 'deposit',
            'exchange': 'exchange'
        }
        payload = {
            "currency": currency,
            "amount": amount,
            "walletfrom": account_map[from_account],
            "walletto": account_map[to_account]
        }

        bfx_resp = self._post('transfer', payload)
        plx_resp = {
            "status":  1 if bfx_resp[0]['status'] == "success" else 0,
            "message": bfx_resp[0]['message']
        }

        return plx_resp

    def return_lending_history(self, start, stop, limit=500):
        """
        Retrieves balance ledger entries. Search funding payments in it and returns
        it as history.
        https://bitfinex.readme.io/v1/reference#rest-auth-balance-history
        """
        history = []
        all_currencies = self.cfg.get_all_currencies()
        for curr in all_currencies:
            payload = {
                "currency": curr,
                "since": str(start),
                "until": str(stop),
                "limit": limit,
                "wallet": "deposit"
            }
            bfx_resp = self._post('history', payload)
            for entry in bfx_resp:
                if 'Margin Funding Payment' in entry['description']:
                    amount = float(entry['amount'])
                    history.append({
                        "id": int(float(entry['timestamp'])),
                        "currency": curr,
                        "rate": "0.0",
                        "amount": "0.0",
                        "duration": "0.0",
                        "interest": str(amount / 0.85),
                        "fee": str(amount-amount / 0.85),
                        "earned": str(amount),
                        "open": Bitfinex2Poloniex.convertTimestamp(entry['timestamp']),
                        "close": Bitfinex2Poloniex.convertTimestamp(entry['timestamp'])
                    })

        return history
Exemple #10
0
class Poloniex(ExchangeApi):
    def __init__(self, cfg):
        self.cfg = cfg
        self.APIKey = self.cfg.get("API", "apikey", None)
        self.Secret = self.cfg.get("API", "secret", None)
        self.req_per_sec = 6
        self.req_time_log = RingBuffer(self.req_per_sec)
        self.lock = threading.RLock()
        socket.setdefaulttimeout(int(Config.get("BOT", "timeout", 30, 1, 180)))

    @synchronized
    def limit_request_rate(self):
        now = time.time()
        # start checking only when request time log is full
        if len(self.req_time_log) == self.req_per_sec:
            time_since_oldest_req = now - self.req_time_log[0]
            # check if oldest request is more than 1sec ago
            if time_since_oldest_req < 1:
                # print self.req_time_log.get()
                # uncomment to debug
                # print "Waiting %s sec to keep api request rate" % str(1 - time_since_oldest_req)
                # print "Req: %d  6th Req: %d  Diff: %f sec" %(now, self.req_time_log[0], time_since_oldest_req)
                self.req_time_log.append(now + 1 - time_since_oldest_req)
                time.sleep(1 - time_since_oldest_req)
                return
            # uncomment to debug
            # else:
            #     print self.req_time_log.get()
            #     print "Req: %d  6th Req: %d  Diff: %f sec" % (now, self.req_time_log[0], time_since_oldest_req)
        # append current request time to the log, pushing out the 6th request time before it
        self.req_time_log.append(now)

    @synchronized
    def api_query(self, command, req=None):
        # keep the 6 request per sec limit
        self.limit_request_rate()

        if req is None:
            req = {}

        def _read_response(resp):
            data = json.loads(resp.read())
            if 'error' in data:
                raise ApiError(data['error'])
            return data

        try:
            if command == "returnTicker" or command == "return24hVolume":
                ret = urllib2.urlopen(
                    urllib2.Request('https://poloniex.com/public?command=' +
                                    command))
                return _read_response(ret)
            elif command == "returnOrderBook":
                ret = urllib2.urlopen(
                    urllib2.Request('https://poloniex.com/public?command=' +
                                    command + '&currencyPair=' +
                                    str(req['currencyPair'])))
                return _read_response(ret)
            elif command == "returnMarketTradeHistory":
                ret = urllib2.urlopen(
                    urllib2.Request('https://poloniex.com/public?command=' +
                                    "returnTradeHistory" + '&currencyPair=' +
                                    str(req['currencyPair'])))
                return _read_response(ret)
            elif command == "returnLoanOrders":
                req_url = ('https://poloniex.com/public?command=' +
                           "returnLoanOrders" + '&currency=' +
                           str(req['currency']))
                if req['limit'] > 0:
                    req_url += ('&limit=' + str(req['limit']))
                ret = urllib2.urlopen(urllib2.Request(req_url))
                return _read_response(ret)
            else:
                req['command'] = command
                req['nonce'] = int(time.time() * 1000)
                post_data = urllib.urlencode(req)

                sign = hmac.new(self.Secret, post_data,
                                hashlib.sha512).hexdigest()
                headers = {'Sign': sign, 'Key': self.APIKey}

                ret = urllib2.urlopen(
                    urllib2.Request('https://poloniex.com/tradingApi',
                                    post_data, headers))
                json_ret = _read_response(ret)
                return post_process(json_ret)
        except urllib2.HTTPError as ex:
            raw_polo_response = ex.read()
            try:
                data = json.loads(raw_polo_response)
                polo_error_msg = data['error']
            except Exception as ex:
                if ex.code == 502 or ex.code in range(520, 526, 1):
                    # 502 and 520-526 Bad Gateway so response is likely HTML from Cloudflare
                    polo_error_msg = ''
                else:
                    polo_error_msg = raw_polo_response
            ex.message = ex.message if ex.message else str(ex)
            ex.message = "{0} Requesting {1}.  Poloniex reports: '{2}'".format(
                ex.message, command, polo_error_msg)
            raise ex
        except Exception as ex:
            ex.message = ex.message if ex.message else str(ex)
            ex.message = "{0} Requesting {1}".format(ex.message, command)
            raise

    def return_ticker(self):
        return self.api_query("returnTicker")

    def return24h_volume(self):
        return self.api_query("return24hVolume")

    def return_order_book(self, currency_pair):
        return self.api_query("returnOrderBook",
                              {'currencyPair': currency_pair})

    def return_market_trade_history(self, currency_pair):
        return self.api_query("returnMarketTradeHistory",
                              {'currencyPair': currency_pair})

    def transfer_balance(self, currency, amount, from_account, to_account):
        return self.api_query(
            "transferBalance", {
                'currency': currency,
                'amount': amount,
                'fromAccount': from_account,
                'toAccount': to_account
            })

    # Returns all of your balances.
    # Outputs:
    # {"BTC":"0.59098578","LTC":"3.31117268", ... }
    def return_balances(self):
        return self.api_query('returnBalances')

    def return_available_account_balances(self, account):
        balances = self.api_query('returnAvailableAccountBalances',
                                  {"account": account})
        if isinstance(
                balances, list
        ):  # silly api wrapper, empty dict returns a list, which breaks the code later.
            balances = {}
        return balances

    # Returns your open orders for a given market, specified by the "currencyPair" POST parameter, e.g. "BTC_XCP"
    # Inputs:
    # currencyPair  The currency pair e.g. "BTC_XCP"
    # Outputs:
    # orderNumber   The order number
    # type          sell or buy
    # rate          Price the order is selling or buying at
    # Amount        Quantity of order
    # total         Total value of order (price * quantity)
    def return_open_orders(self, currency_pair):
        return self.api_query('returnOpenOrders',
                              {"currencyPair": currency_pair})

    def return_open_loan_offers(self):
        loan_offers = self.api_query('returnOpenLoanOffers')
        if isinstance(
                loan_offers, list
        ):  # silly api wrapper, empty dict returns a list, which breaks the code later.
            loan_offers = {}
        return loan_offers

    def return_active_loans(self):
        return self.api_query('returnActiveLoans')

    def return_lending_history(self, start, stop, limit=500):
        return self.api_query('returnLendingHistory', {
            'start': start,
            'end': stop,
            'limit': limit
        })

    # Returns your trade history for a given market, specified by the "currencyPair" POST parameter
    # Inputs:
    # currencyPair  The currency pair e.g. "BTC_XCP"
    # Outputs:
    # date          Date in the form: "2014-02-19 03:44:59"
    # rate          Price the order is selling or buying at
    # amount        Quantity of order
    # total         Total value of order (price * quantity)
    # type          sell or buy
    def return_trade_history(self, currency_pair):
        return self.api_query('returnTradeHistory',
                              {"currencyPair": currency_pair})

    # Places a buy order in a given market. Required POST parameters are "currencyPair", "rate", and "amount".
    # If successful, the method will return the order number.
    # Inputs:
    # currencyPair  The curreny pair
    # rate          price the order is buying at
    # amount        Amount of coins to buy
    # Outputs:
    # orderNumber   The order number
    def buy(self, currency_pair, rate, amount):
        return self.api_query('buy', {
            "currencyPair": currency_pair,
            "rate": rate,
            "amount": amount
        })

    # Places a sell order in a given market. Required POST parameters are "currencyPair", "rate", and "amount".
    # If successful, the method will return the order number.
    # Inputs:
    # currencyPair  The curreny pair
    # rate          price the order is selling at
    # amount        Amount of coins to sell
    # Outputs:
    # orderNumber   The order number
    def sell(self, currency_pair, rate, amount):
        return self.api_query('sell', {
            "currencyPair": currency_pair,
            "rate": rate,
            "amount": amount
        })

    def create_loan_offer(self, currency, amount, duration, auto_renew,
                          lending_rate):
        return self.api_query(
            'createLoanOffer', {
                "currency": currency,
                "amount": amount,
                "duration": duration,
                "autoRenew": auto_renew,
                "lendingRate": lending_rate,
            })

    # Cancels an order you have placed in a given market. Required POST parameters are "currencyPair" and "orderNumber".
    # Inputs:
    # currencyPair  The curreny pair
    # orderNumber   The order number to cancel
    # Outputs:
    # succes        1 or 0
    def cancel(self, currency_pair, order_number):
        return self.api_query('cancelOrder', {
            "currencyPair": currency_pair,
            "orderNumber": order_number
        })

    def cancel_loan_offer(self, currency, order_number):
        return self.api_query('cancelLoanOffer', {
            "currency": currency,
            "orderNumber": order_number
        })

    # Immediately places a withdrawal for a given currency, with no email confirmation.
    # In order to use this method, the withdrawal privilege must be enabled for your API key.
    # Required POST parameters are "currency", "amount", and "address". Sample output: {"response":"Withdrew 2398 NXT."}
    # Inputs:
    # currency      The currency to withdraw
    # amount        The amount of this coin to withdraw
    # address       The withdrawal address
    # Outputs:
    # response      Text containing message about the withdrawal
    def withdraw(self, currency, amount, address):
        return self.api_query('withdraw', {
            "currency": currency,
            "amount": amount,
            "address": address
        })

    def return_loan_orders(self, currency, limit=0):
        return self.api_query('returnLoanOrders', {
            "currency": currency,
            "limit": limit
        })

    # Toggles the auto renew setting for the specified orderNumber
    def toggle_auto_renew(self, order_number):
        return self.api_query('toggleAutoRenew', {"orderNumber": order_number})
class Poloniex:
    def __init__(self, api_key, secret):
        self.APIKey = api_key
        self.Secret = secret
        self.req_per_sec = 6
        self.req_time_log = RingBuffer(self.req_per_sec)
        self.lock = threading.RLock()
        socket.setdefaulttimeout(30)

    @synchronized
    def limit_request_rate(self):
        now = time.time()
        # start checking only when request time log is full
        if len(self.req_time_log) == self.req_per_sec:
            time_since_oldest_req = now - self.req_time_log[0]
            # check if oldest request is more than 1sec ago
            if time_since_oldest_req < 1:
                # print self.req_time_log.get()
                # uncomment to debug
                # print "Waiting %s sec to keep api request rate" % str(1 - time_since_oldest_req)
                # print "Req: %d  6th Req: %d  Diff: %f sec" %(now, self.req_time_log[0], time_since_oldest_req)
                self.req_time_log.append(now + 1 - time_since_oldest_req)
                time.sleep(1 - time_since_oldest_req)
                return
            # uncomment to debug
            # else:
            #     print self.req_time_log.get()
            #     print "Req: %d  6th Req: %d  Diff: %f sec" % (now, self.req_time_log[0], time_since_oldest_req)
        # append current request time to the log, pushing out the 6th request time before it
        self.req_time_log.append(now)

    @synchronized
    def api_query(self, command, req=None):
        # keep the 6 request per sec limit
        self.limit_request_rate()

        if req is None:
            req = {}

        def _read_response(resp):
            data = json.loads(resp.read())
            if 'error' in data:
                raise PoloniexApiError(data['error'])
            return data

        try:
            if command == "returnTicker" or command == "return24hVolume":
                ret = urllib2.urlopen(urllib2.Request('https://poloniex.com/public?command=' + command))
                return _read_response(ret)
            elif command == "returnOrderBook":
                ret = urllib2.urlopen(urllib2.Request(
                    'https://poloniex.com/public?command=' + command + '&currencyPair=' + str(req['currencyPair'])))
                return _read_response(ret)
            elif command == "returnMarketTradeHistory":
                ret = urllib2.urlopen(urllib2.Request(
                    'https://poloniex.com/public?command=' + "returnTradeHistory" + '&currencyPair=' + str(
                        req['currencyPair'])))
                return _read_response(ret)
            elif command == "returnLoanOrders":
                req_url = 'https://poloniex.com/public?command=' + "returnLoanOrders" + '&currency=' + str(req['currency'])
                if req['limit'] != '':
                    req_url += '&limit=' + str(req['limit'])
                ret = urllib2.urlopen(urllib2.Request(req_url))
                return _read_response(ret)
            else:
                req['command'] = command
                req['nonce'] = int(time.time() * 1000)
                post_data = urllib.urlencode(req)

                sign = hmac.new(self.Secret, post_data, hashlib.sha512).hexdigest()
                headers = {
                    'Sign': sign,
                    'Key': self.APIKey
                }

                ret = urllib2.urlopen(urllib2.Request('https://poloniex.com/tradingApi', post_data, headers))
                json_ret = _read_response(ret)
                return post_process(json_ret)
        except Exception as ex:
            ex.message = ex.message if ex.message else str(ex)
            ex.message = "{0} Requesting {1}".format(ex.message, command)
            raise

    def return_ticker(self):
        return self.api_query("returnTicker")

    def return24h_volume(self):
        return self.api_query("return24hVolume")

    def return_order_book(self, currency_pair):
        return self.api_query("returnOrderBook", {'currencyPair': currency_pair})

    def return_market_trade_history(self, currency_pair):
        return self.api_query("returnMarketTradeHistory", {'currencyPair': currency_pair})

    def transfer_balance(self, currency, amount, from_account, to_account):
        return self.api_query("transferBalance", {'currency': currency, 'amount': amount, 'fromAccount': from_account,
                                                  'toAccount': to_account})

    # Returns all of your balances.
    # Outputs: 
    # {"BTC":"0.59098578","LTC":"3.31117268", ... }
    def return_balances(self):
        return self.api_query('returnBalances')

    def return_available_account_balances(self, account):
        balances = self.api_query('returnAvailableAccountBalances', {"account": account})
        if isinstance(balances, list):  # silly api wrapper, empty dict returns a list, which breaks the code later.
            balances = {}
        return balances

    # Returns your open orders for a given market, specified by the "currencyPair" POST parameter, e.g. "BTC_XCP"
    # Inputs:
    # currencyPair  The currency pair e.g. "BTC_XCP"
    # Outputs: 
    # orderNumber   The order number
    # type          sell or buy
    # rate          Price the order is selling or buying at
    # Amount        Quantity of order
    # total         Total value of order (price * quantity)
    def return_open_orders(self, currency_pair):
        return self.api_query('returnOpenOrders', {"currencyPair": currency_pair})

    def return_open_loan_offers(self):
        loan_offers = self.api_query('returnOpenLoanOffers')
        if isinstance(loan_offers, list):  # silly api wrapper, empty dict returns a list, which breaks the code later.
            loan_offers = {}
        return loan_offers

    def return_active_loans(self):
        return self.api_query('returnActiveLoans')

    # Returns your trade history for a given market, specified by the "currencyPair" POST parameter
    # Inputs:
    # currencyPair  The currency pair e.g. "BTC_XCP"
    # Outputs: 
    # date          Date in the form: "2014-02-19 03:44:59"
    # rate          Price the order is selling or buying at
    # amount        Quantity of order
    # total         Total value of order (price * quantity)
    # type          sell or buy
    def return_trade_history(self, currency_pair):
        return self.api_query('returnTradeHistory', {"currencyPair": currency_pair})

    # Places a buy order in a given market. Required POST parameters are "currencyPair", "rate", and "amount".
    # If successful, the method will return the order number.
    # Inputs:
    # currencyPair  The curreny pair
    # rate          price the order is buying at
    # amount        Amount of coins to buy
    # Outputs: 
    # orderNumber   The order number
    def buy(self, currency_pair, rate, amount):
        return self.api_query('buy', {"currencyPair": currency_pair, "rate": rate, "amount": amount})

    # Places a sell order in a given market. Required POST parameters are "currencyPair", "rate", and "amount".
    # If successful, the method will return the order number.
    # Inputs:
    # currencyPair  The curreny pair
    # rate          price the order is selling at
    # amount        Amount of coins to sell
    # Outputs: 
    # orderNumber   The order number
    def sell(self, currency_pair, rate, amount):
        return self.api_query('sell', {"currencyPair": currency_pair, "rate": rate, "amount": amount})

    def create_loan_offer(self, currency, amount, duration, auto_renew, lending_rate):
        return self.api_query('createLoanOffer',
                              {"currency": currency, "amount": amount, "duration": duration, "autoRenew": auto_renew,
                               "lendingRate": lending_rate, })

    # Cancels an order you have placed in a given market. Required POST parameters are "currencyPair" and "orderNumber".
    # Inputs:
    # currencyPair  The curreny pair
    # orderNumber   The order number to cancel
    # Outputs: 
    # succes        1 or 0
    def cancel(self, currency_pair, order_number):
        return self.api_query('cancelOrder', {"currencyPair": currency_pair, "orderNumber": order_number})

    def cancel_loan_offer(self, currency, order_number):
        return self.api_query('cancelLoanOffer', {"currency": currency, "orderNumber": order_number})

    # Immediately places a withdrawal for a given currency, with no email confirmation.
    # In order to use this method, the withdrawal privilege must be enabled for your API key.
    # Required POST parameters are "currency", "amount", and "address". Sample output: {"response":"Withdrew 2398 NXT."}
    # Inputs:
    # currency      The currency to withdraw
    # amount        The amount of this coin to withdraw
    # address       The withdrawal address
    # Outputs: 
    # response      Text containing message about the withdrawal
    def withdraw(self, currency, amount, address):
        return self.api_query('withdraw', {"currency": currency, "amount": amount, "address": address})

    def return_loan_orders(self, currency, limit=''):
        return self.api_query('returnLoanOrders', {"currency": currency, "limit": limit})

    # Toggles the auto renew setting for the specified orderNumber
    def toggle_auto_renew(self, order_number):
        return self.api_query('toggleAutoRenew', {"orderNumber": order_number})