diff --git a/libcryptomarket/api/poloniex_api.py b/libcryptomarket/api/poloniex_api.py index 1b9c883..a6ac77d 100644 --- a/libcryptomarket/api/poloniex_api.py +++ b/libcryptomarket/api/poloniex_api.py @@ -1,5 +1,11 @@ #!/bin/python import requests +import hmac +import hashlib +import urllib +from functools import partial + +from libcryptomarket.api.rest_api_connector import RestApiConnector API_URL = "https://poloniex.com/public?command=" VALID_PERIODS = [300, 900, 1800, 7200, 14400, 86400] @@ -58,3 +64,116 @@ def get_return_order_book(currency_pair, depth=10): raise ValueError("Query error from Poloniex API ({0})".format(rjson)) return r.json() + + +class PoloniexApi(RestApiConnector): + """Poloniex API. + """ + + URL = 'https://poloniex.com/tradingApi' + + AVAILABLE_TRADING_API = [ + "returnBalances", + "returnCompleteBalances", + "returnDepositAddresses", + "generateNewAddress", + "returnDepositsWithdrawals", + "returnOpenOrders", + "returnTradeHistory", + "returnOrderTrades", + "buy", + "sell", + "cancelOrder", + "moveOrder", + "withdraw", + "returnFeeInfo", + "returnAvailableAccountBalances", + "returnTradableBalances", + "transferBalance", + "returnMarginAccountSummary", + "marginBuy", + "marginSell", + "getMarginPosition", + "closeMarginPosition", + "createLoanOffer", + "cancelLoanOffer", + "returnOpenLoanOffers", + "returnActiveLoans", + "returnLendingHistory", + "toggleAutoRenew", + ] + + def __init__(self, public_key, private_key, logger=None): + """Constructor. + + :param public_key: Public key. + :param private_key: Private key. + """ + RestApiConnector.__init__(self, url=PoloniexApi.URL, logger=logger) + + self.__public_key = public_key + self.__private_key = private_key + + def __getattr__(self, name): + """Get attribute. + """ + if name in PoloniexApi.AVAILABLE_TRADING_API: + return partial(self._request, command=name, http_method="POST") + else: + raise AttributeError("Trading method ({0}) ".format(name) + + "is not defined.") + + def _request(self, command, **kwargs): + """Send request. + + :param kwargs: Named arguments. + """ + kwargs["command"] = command + kwargs["nonce"] = self._generate_nonce() + + return self._send_request( + command="", + http_method="POST", + public_key=self.__public_key, + private_key=self.__private_key, + params=None, + data=kwargs) + + def _generate_auth(self, public_key, private_key): + """Generate authentication. + + :param public_key: Public key. + :param private_key: Private key. + """ + return None + + def _generate_headers(self, command, http_method, params, data, + public_key, private_key): + """Generate headers. + + :param command: Command. + :param http_method: HTTP method, for example GET. + :param params: Parameters. + :param data: Data. + :param public_key: Public key. + :param private_key: Private key. + """ + signature = hmac.new(private_key.encode(), + urllib.parse.urlencode(data).encode(), + digestmod=hashlib.sha512).hexdigest() + header = { + 'Key': public_key, + 'Sign': signature + } + + return header + + def _format_data(self, data): + """Format the data to exchange desirable format. + + :param data: Data. + """ + if data is None: + return "" + else: + return data diff --git a/libcryptomarket/api/rest_api_connector.py b/libcryptomarket/api/rest_api_connector.py new file mode 100644 index 0000000..39323e8 --- /dev/null +++ b/libcryptomarket/api/rest_api_connector.py @@ -0,0 +1,105 @@ +#!/bin/python +import requests +import urllib +import json +from time import time + + +class RestApiConnector(object): + """REST API connector. + """ + + def __init__(self, url, logger=None): + """Constructor. + + :param url: URL address. + :param logger: Logger. Default is None. + """ + self._logger = logger + self._url = url + + @classmethod + def _generate_nonce(cls): + """Generate an increasing unique number. + """ + return int(round(time() * 1000)) + + def _generate_headers(self, command, http_method, params, data, + public_key, private_key): + """Generate headers. + + :param command: Command. + :param http_method: HTTP method, for example GET. + :param params: Parameters. + :param data: Data. + :param public_key: Public key. + :param private_key: Private key. + """ + raise NotImplementedError("Not yet implemented.") + + def _generate_auth(self, public_key, private_key): + """Generate authentication. + + :param public_key: Public key. + :param private_key: Private key. + """ + raise NotImplementedError("Not yet implemented.") + + def _format_data(self, data): + """Format the data to exchange desirable format. + + :param data: Data. + """ + raise NotImplementedError("Not yet implemented.") + + def _send_request(self, command, http_method, public_key, private_key, + params=None, data=None): + """Send request. + + :param command: API command. + :param http_method: Http method. + :param api_key: API key. + :param params: Input parameters, which will be parsed + as "?key1=value1...". + :param data: Data. + :return: JSON object. + """ + http_method = http_method.upper() + if http_method == "DELETE": + R = requests.delete + elif http_method == "GET": + R = requests.get + elif http_method == "POST": + R = requests.post + else: + raise ValueError("Http method must be either DELETE, GET or " + "POST.") + + url = urllib.parse.urljoin(self._url, command) + data = self._format_data(data) + headers = self._generate_headers(command, http_method, params, + data, public_key, private_key) + auth = self._generate_auth(public_key, private_key) + + if self._logger is not None: + self._logger.info(">>> OUT:\n%s" % json.dumps({ + "Method": http_method, + "Url": url, + "Params": params, + "Data": data, + "Headers": headers + })) + + if auth is None: + response = R(url, params=params, data=data, headers=headers) + else: + response = R(url, params=params, data=data, headers=headers, + auth=auth) + + if self._logger is not None: + self._logger.info("<<< IN:\n%s" % json.dumps({ + "Status code": response.status_code, + "Text": response.text + })) + + return response