diff --git a/.gitignore b/.gitignore index 1dbc687..2e27875 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__/ # Distribution / packaging .Python env/ +venv/ build/ develop-eggs/ dist/ @@ -60,3 +61,19 @@ target/ #Ipython Notebook .ipynb_checkpoints + +.vscode + +venv/** + +geminipy.egg-info +geminipy/lab +geminipy/lab/** +geminipy/lab/__init__.py +geminipy/lab/check_accounts.py +geminipy/lab/clearing_lab.py +geminipy/lab/search_repos.py +# Pycharm +.idea +data +data/accounts_info.json diff --git a/README.md b/README.md index 833ab0b..81095cf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Geminipy -A library for the Gemini bitcoin exchange API +A library for the Gemini bitcoin exchange API. Easily communicate with the Gemini Bitcoin exchange API without having to deal with HTTP requests. @@ -7,14 +7,15 @@ having to deal with HTTP requests. Requirements ============ -Please get an API key from [https://exchange.gemini.com/settings/api](https://exchange.gemini.com/settings/api) +A Python 3 interpreter (versions < 3.8 not tested) +Please get your API keys from [https://exchange.gemini.com/settings/api](https://exchange.gemini.com/settings/api) Installation ============ ```shell -pip install geminipy +pip install "git+https://github.com/pl0mo/geminipy" ``` Example @@ -23,27 +24,21 @@ Example ```python from geminipy import Geminipy -# The connection defaults to the Gemini sandbox. -# Add 'live=True' to use the live exchange -con = Geminipy(api_key='your API key', secret_key='your secret key', live=True) +# Set live param to False to use Gemini testing (sandbox) enviroment. +api = Geminipy(apikey='your API key', secret='your secret key') # public request -symbols = con.symbols() - -# a Requests response is returned. -# So we can access the HTTP reponse code, -# the raw response content, or a json object -print symbols.status_code -print symbols.content -print symbols.json() +symbols = api.get_symbols() + +print(symbols) # authenticated request -order = con.new_order(amount='1', price='200',side='buy') +order = api.place_order('btcusd', amount=1, price=200.0, side='buy',) -print order.json() +print(order) #send a heartbeat -con.heartbeat() +api.get_heartbeat() ``` The required nonce is the current millisecond timestamp. diff --git a/changelog b/changelog index 274a0ad..33951d0 100644 --- a/changelog +++ b/changelog @@ -1,5 +1,24 @@ # Geminipy Changelog +# v0.0.6 +Code organization by creating "utils.py" and "core.py" files. +Some errors fixed on README examples. +Using findpackages in "setup.py" file. + +# v0.0.5 +Code update to python 3.6 or newer version. +Code simplified and refactored +Created new method "_requests" to unify all requests. +Code comments changed. +Method names changed for more friendly and autodoc ones. +New class Geminipy method "get_account_details" +New class Geminipy method "get_earn_balance" +New class Geminipy method "request_approved_address" +New class Geminipy method "remove_approved_address" +New class Geminipy method "get_notional_balances" +Changelog updated + + # v0.0.4 Added "fees" function for the fee and notional volume API endpoint diff --git a/geminipy/__init__.py b/geminipy/__init__.py index 20c0a3c..b51bf76 100644 --- a/geminipy/__init__.py +++ b/geminipy/__init__.py @@ -1,329 +1,13 @@ +# -*- coding:utf-8 -*- """ This module contains a class to make requests to the Gemini API. Author: Mike Marzigliano """ -import time -import json -import hmac -import base64 -import hashlib -import requests +from geminipy.core import Geminipy -class Geminipy(object): - """ - A class to make requests to the Gemini API. +# Geminipy = GeminiAPI - Make public or authenticated requests according to the API documentation: - https://docs.gemini.com/ - """ - - live_url = 'https://api.gemini.com' - sandbox_url = 'https://api.sandbox.gemini.com' - base_url = sandbox_url - api_key = '' - secret_key = '' - - def __init__(self, api_key='', secret_key='', live=False): - """ - Initialize the class. - - Arguments: - api_key -- your Gemini API key - secret_key -- your Gemini API secret key for signatures - live -- use the live API? otherwise, use the sandbox (default False) - """ - self.api_key = api_key - self.secret_key = secret_key - - if live: - self.base_url = self.live_url - - # public requests - def symbols(self): - """Send a request for all trading symbols, return the response.""" - url = self.base_url + '/v1/symbols' - - return requests.get(url) - - def pubticker(self, symbol='btcusd'): - """Send a request for latest ticker info, return the response.""" - url = self.base_url + '/v1/pubticker/' + symbol - - return requests.get(url) - - def book(self, symbol='btcusd', limit_bids=0, limit_asks=0): - """ - Send a request to get the public order book, return the response. - - Arguments: - symbol -- currency symbol (default 'btcusd') - limit_bids -- limit the number of bids returned (default 0) - limit_asks -- limit the number of asks returned (default 0) - """ - url = self.base_url + '/v1/book/' + symbol - params = { - 'limit_bids': limit_bids, - 'limit_asks': limit_asks - } - - return requests.get(url, params) - - def trades(self, symbol='btcusd', since=0, limit_trades=50, - include_breaks=0): - """ - Send a request to get all public trades, return the response. - - Arguments: - symbol -- currency symbol (default 'btcusd') - since -- only return trades after this unix timestamp (default 0) - limit_trades -- maximum number of trades to return (default 50). - include_breaks -- whether to display broken trades (default False) - """ - url = self.base_url + '/v1/trades/' + symbol - params = { - 'since': since, - 'limit_trades': limit_trades, - 'include_breaks': include_breaks - } - - return requests.get(url, params) - - def auction(self, symbol='btcusd'): - """Send a request for latest auction info, return the response.""" - url = self.base_url + '/v1/auction/' + symbol - - return requests.get(url) - - def auction_history(self, symbol='btcusd', since=0, - limit_auction_results=50, include_indicative=1): - """ - Send a request for auction history info, return the response. - - Arguments: - symbol -- currency symbol (default 'btcusd') - since -- only return auction events after this timestamp (default 0) - limit_auction_results -- maximum number of auction events to return - (default 50). - include_indicative -- whether to include publication of indicative - prices and quantities. (default True) - """ - url = self.base_url + '/v1/auction/' + symbol + '/history' - params = { - 'since': since, - 'limit_auction_results': limit_auction_results, - 'include_indicative': include_indicative - } - - return requests.get(url, params) - - # authenticated requests - def new_order(self, amount, price, side, client_order_id=None, - symbol='btcusd', type='exchange limit', options=None): - """ - Send a request to place an order, return the response. - - Arguments: - amount -- quoted decimal amount of BTC to purchase - price -- quoted decimal amount of USD to spend per BTC - side -- 'buy' or 'sell' - client_order_id -- an optional client-specified order id (default None) - symbol -- currency symbol (default 'btcusd') - type -- the order type (default 'exchange limit') - """ - request = '/v1/order/new' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce(), - 'symbol': symbol, - 'amount': amount, - 'price': price, - 'side': side, - 'type': type - } - - if client_order_id is not None: - params['client_order_id'] = client_order_id - - if options is not None: - params['options'] = options - - return requests.post(url, headers=self.prepare(params)) - - def cancel_order(self, order_id): - """ - Send a request to cancel an order, return the response. - - Arguments: - order_id - the order id to cancel - """ - request = '/v1/order/cancel' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce(), - 'order_id': order_id - } - - return requests.post(url, headers=self.prepare(params)) - - def cancel_session(self): - """Send a request to cancel all session orders, return the response.""" - request = '/v1/order/cancel/session' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - return requests.post(url, headers=self.prepare(params)) - - def cancel_all(self): - """Send a request to cancel all orders, return the response.""" - request = '/v1/order/cancel/all' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - return requests.post(url, headers=self.prepare(params)) - - def order_status(self, order_id): - """ - Send a request to get an order status, return the response. - - Arguments: - order_id -- the order id to get information on - """ - request = '/v1/order/status' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce(), - 'order_id': order_id - } - - return requests.post(url, headers=self.prepare(params)) - - def active_orders(self): - """Send a request to get active orders, return the response.""" - request = '/v1/orders' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - return requests.post(url, headers=self.prepare(params)) - - def past_trades(self, symbol='btcusd', limit_trades=50, timestamp=0): - """ - Send a trade history request, return the response. - - Arguements: - symbol -- currency symbol (default 'btcusd') - limit_trades -- maximum number of trades to return (default 50) - timestamp -- only return trades after this unix timestamp (default 0) - """ - request = '/v1/mytrades' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce(), - 'symbol': symbol, - 'limit_trades': limit_trades, - 'timestamp': timestamp - } - - return requests.post(url, headers=self.prepare(params)) - - def tradevolume(self): - """Send a request to get your trade volume, return the response.""" - request = '/v1/tradevolume' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - return requests.post(url, headers=self.prepare(params)) - - def balances(self): - """Send an account balance request, return the response.""" - request = '/v1/balances' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - return requests.post(url, headers=self.prepare(params)) - - def newAddress(self, currency='btc', label=''): - """ - Send a request for a new cryptocurrency deposit address - with an optional label. Return the response. - - Arguements: - currency -- a Gemini supported cryptocurrency (btc, eth) - label -- optional label for the deposit address - """ - request = '/v1/deposit/' + currency + '/newAddress' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - if label != '': - params['label'] = label - - return requests.post(url, headers=self.prepare(params)) - - def fees(self): - """Send a request to get fee and notional volume, return the response.""" - request = '/v1/notionalvolume' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - return requests.post(url, headers=self.prepare(params)) - - def heartbeat(self): - """Send a heartbeat message, return the response.""" - request = '/v1/heartbeat' - url = self.base_url + request - params = { - 'request': request, - 'nonce': self.get_nonce() - } - - return requests.post(url, headers=self.prepare(params)) - - def get_nonce(self): - """Return the current millisecond timestamp as the nonce.""" - return int(round(time.time() * 1000)) - - def prepare(self, params): - """ - Prepare, return the required HTTP headers. - - Base 64 encode the parameters, sign it with the secret key, - create the HTTP headers, return the whole payload. - - Arguments: - params -- a dictionary of parameters - """ - jsonparams = json.dumps(params) - payload = base64.b64encode(jsonparams.encode()) - signature = hmac.new(self.secret_key.encode(), payload, - hashlib.sha384).hexdigest() - - return {'X-GEMINI-APIKEY': self.api_key, - 'X-GEMINI-PAYLOAD': payload, - 'X-GEMINI-SIGNATURE': signature} +__package__ = 'geminipy' +__all__ = ['Geminipy'] diff --git a/geminipy/constants.py b/geminipy/constants.py new file mode 100644 index 0000000..23ff529 --- /dev/null +++ b/geminipy/constants.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +SYMBOLS = [ + '1inchusd', + 'aaveusd', + 'alcxusd', + 'ampusd', + 'ankrusd', + 'axsusd', + 'balusd', + 'batbtc', + 'bateth', + 'batusd', + 'bchbtc', + 'bcheth', + 'bchusd', + 'bntusd', + 'bondusd', + 'btcdai', + 'btceur', + 'btcgbp', + 'btcgusd', + 'btcsgd', + 'btcusd', + 'compusd', + 'crvusd', + 'ctxusd', + 'cubeusd', + 'daiusd', + 'dogebtc', + 'dogeeth' + 'dogeusd', + 'efilfil', + 'enjusd', + 'ethbtc', + 'ethdai', + 'etheur', + 'ethgbp', + 'ethgusd', + 'ethsgd', + 'ethusd', + 'filusd', + 'ftmusd', + 'grtusd', + 'gusdusd', + 'injusd', + 'kncusd', + 'linkbtc', + 'linketh', + 'linkusd', + 'lptusd', + 'lrcusd', + 'ltcbch', + 'ltcbtc', + 'ltceth', + 'ltcusd', + 'lunausd', + 'manausd', + 'maticusd', + 'mco2usd', + 'mirusd', + 'mkrusd', + 'oxtbtc', + 'oxteth', + 'oxtusd', + 'paxgusd', + 'renusd', + 'sandusd', + 'sklusd', + 'slpusd', + 'snxusd', + 'storjusd', + 'sushiusd', + 'umausd', + 'uniusd', + 'ustusd', + 'xtzusd', + 'yfiusd', + 'zecbch', + 'zecbtc', + 'zeceth', + 'zecltc', + 'zecusd', + 'zrxusd' +] + diff --git a/geminipy/core.py b/geminipy/core.py new file mode 100644 index 0000000..457ae76 --- /dev/null +++ b/geminipy/core.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import base64 +import hashlib +import hmac +import json +import time + +import warnings +from datetime import datetime as dt + +import requests + +from geminipy.model import Networks +from geminipy.utils import _get_params + +warnings.simplefilter('ignore') + +class Client: + """Gemini API wrapper. + + Make public or authenticated requests according to the API documentation: + https://docs.gemini.com/ + """ + def __init__( + self, + apikey=None, + secret=None, + live=True, + nonce_mult=1000.0, + api_version=1, + timeout=15, + proxy=None): + + """Initialize the class. + + :param str apikey: your Gemini API key. + :param str secret: your Gemini API secret key for signatures. + :param float nonce_mult: a custom multiplier for nonce value. + """ + # self._api_version = api_version + if live: + self._url = f'https://api.gemini.com' + else: + self._url = f'https://api.sandbox.gemini.com' + + self._api_version = f'v{api_version:d}' + self._proxy = {'http': proxy} if proxy else None + self._timeout = timeout + self._url = f'{self._url}/{self._api_version}' + self._apikey = apikey + self._nonce_mult = nonce_mult + self._secret = secret.encode() + self._headers = { + 'Content-Type': 'text/plain', + 'Content-Length': '0', + 'X-GEMINI-APIKEY': self._apikey, + 'Cache-Control': 'no-cache' + } + + + # ================ Class private methods ================ + + def _request(self, end_point, method=None, params=None): + method = str(method or 'get').lower() + + request_args = { + 'url': f'{self._url}/{end_point}', + 'method': method, + 'timeout': self._timeout, + 'headers': self._headers + } + + + params = dict(params or {}) + + if method in ('post',): + params.update( + request=f'/{self._api_version}/{end_point}', + nonce=self._nonce + ) + json_params = json.dumps(params).encode() + payload = base64.b64encode(json_params) + signature = hmac.new(self._secret, payload, hashlib.sha384) + request_args.update( + headers={ + 'X-GEMINI-PAYLOAD': payload, + 'X-GEMINI-SIGNATURE': signature.hexdigest(), + **request_args['headers'] + } + ) + else: + request_args.update(params=params) + + if self._proxy: + request_args.update(proxies=self._proxy) + + if response.ok: + return response.json() + else: + return self._error_handler(response, end_point, method, params) + + def _error_handler(self, response, end_point, method=None, params=None): + try: + data = response.json() + if data: + if 'reason' in data: + if 'InvalidNonce' in data['reason']: + time.sleep(1.5) + if self._nonce_mult >= 1000.0 ** 2: + self._nonce_mult *= 10.0 + else: + self._nonce_mult **= 2 + return self._request(end_point, method, params) + return {'error': data} + else: + return { + 'error': { + 'code': response.status_code, + 'reason': response.reason, + 'content': response.text + } + } + except json.JSONDecodeError: + return { + 'error': { + 'code': response.status_code, + 'reason': response.reason, + 'content': response.text + } + } + except (requests.RequestException, requests.ConnectionError): + response.raise_for_status() + return {'error': 'Unknown error.'} + + @property + def _nonce(self): + """Return the current millisecond timestamp as the nonce. + + :return: current millisecond timestamp as the nonce + """ + return f'{time.time() * self._nonce_mult:.0f}' + + +class GeminiPublic(Client): + + # ================ PUBLIC END POINTS ================ + def get_symbols(self): + """Send a request to get trading symbols info.""" + return self._request('symbols') + + def get_ticker(self, symbol): + """Send a request to get a trading symbol ticker info. + + :param symbol: symbol id to query (example: "btcusd") + :return: trading symbol ticker info supplied by server. + """ + return self._request(f'pubticker/{symbol}') + + def get_orderbook(self, symbol, limit_bids=0, limit_asks=0): + """Send a request to get a symbol order book data. + + :param symbol: symbol id to query (example: "btcusd") + :param limit_bids: limit the number of bids returned (default 0) + :param limit_asks: limit the number of asks returned (default 0) + :return: symbol order book info supplied by server. + """ + params = _get_params(locals(), 'symbol') + return self._request(f'book/{symbol}', params=params) + + def get_trades(self, symbol, since=0, limit_trades=50, include_breaks=0): + """Send a request to get a symbol public trades. + + :param str symbol: symbol id to query (example: "btcusd") + :param int since: return trades after this unix epoch (default 0) + :param limit_trades: max number of trades to return (default 50). + :param int include_breaks: whether to display broken trades (default 0) + :return: supplied symbol public trades info supplied by server. + """ + params = _get_params(locals(), 'symbol') + return self._request(f'trades/{symbol}', params=params) + + def get_auction(self, symbol): + """Send a request to get a symbol latest auction info. + + :param str symbol: symbol id to query (example: "btcusd") + :return: supplied symbol latest auction supplied by server. + """ + return self._request(f'auction/{symbol}') + + def get_auction_history(self, symbol, since=0, limit_auction_results=50, include_indicative=1): + """Send a request to get a symbol auction history. + + :param str symbol: symbol id to query (example: "btcusd") + :param since: only return auction events after this timestamp. + :param limit_auction_results: max number of auction events to return. + :param include_indicative: set to 0 to not include publication of indicative info. + :return: + """ + params = _get_params(locals(), 'symbol') + return self._request(f'auction/{symbol}/history', params=params) + + +class Geminipy(GeminiPublic): + """Gemini API wrapper. + + Make public or authenticated requests according to the API documentation: + https://docs.gemini.com/ + """ + + # ================ AUTHENTICATED ENDPOINTS ================ + + + def place_order(self, side, symbol, amount, price=None, client_order_id=None, type=None, options=None, account=None): + """Send a request to place an order. + + :param str side: 'buy' or 'sell' + :param str symbol: symbol id to query (example: "btcusd") + :param float amount: symbol amount to order. + :param float price: order price. + :param client_order_id: an optional custom order indetifier. + :param type: order type (default 'exchange limit') + :param options: + return: + """ + account = account or 'primary' + params = _get_params(locals(), 'amount', 'price') + params.update(amount=f'{amount}', price=f'{price}') + return self._request('order/new', 'post', params) + + def cancel_order(self, order_id): + """Send a request to cancel an order. + + :param str order_id: order id to cancel. + :return: + """ + params = _get_params(locals()) + return self._request('order/cancel', 'post', params) + + def cancel_session(self): + """Send a request to cancel all session orders. + + :return: + """ + return self._request('order/cancel/session', 'post') + + def cancel_all(self): + """Send a request to cancel all orders. + + :return: + """ + return self._request('order/cancel/all', 'post') + + def get_order_status(self, order_id): + """Send a request to get an order current status. + + :param str order_id: the order id to get information on. + :return: + """ + params = _get_params(locals()) + return self._request('order/status', 'post', params) + + def get_active_orders(self): + """Send a request to get active orders. + + :return: + """ + return self._request('orders', 'post') + + def get_past_trades(self, symbol, limit_trades=50, timestamp=0): + """Send a symbol trades history request. + + :param str symbol: symbol id to query (example: "btcusd") + :param limit_trades: max number of trades to return (default 50) + :param timestamp: return trades after supplied unix epoch (default 0) + :return: + """ + params = _get_params(locals()) + return self._request('mytrades', 'post', params) + + def get_payment_methods(self, account=None): + return self._request('payments/methods', 'post', params={'account': account or 'primary'}) + + def get_trade_volume(self): + """Send a request to get your trade volume.""" + return self._request('tradevolume', 'post') + + def get_balances(self, account=None, precision=8): + """Send an account balance request. + + :param account: + :param precision: + :return: + """ + result = self._request('balances', 'post', params={'account': account or 'primary'}) + if result and isinstance(result, list): + return {v.pop('currency'): dict(type=v['type'], amount=round(float(v['amount']), precision), available=round(float(v['available']), precision), withdrawable=round(float(v['availableForWithdrawal']), precision)) for v in result if v and 'amount' in v and round(float(v['amount']), precision) > 0.0} + elif result and isinstance(result, dict) and 'error' in result: + return result + else: + return {'error': f'Unknown error: {str(result)}'} + + def get_approved_addresses(self, network, label=None, account=None): + """Get an approved addresses request for supplied network. + + :param str network: accepted vales -> bitcoin, ethereum, bitcoincash, litecoin, zcash, filecoin, dogecoin, tezos + :param str label: account name (example: primary) + :return: + """ + account = account or 'primary' + params = _get_params(locals(), 'network') + return self._request(f'approvedAddresses/account/{Networks.get_network(network)}', 'post', params) + + def get_deposit_addresses(self, network, account=None): + params = _get_params(locals(), 'network') + return self._request(f'addresses/{Networks.get_network(network)}', 'post', params) + + def new_deposit_address(self, currency, label=None, account=None): + """Send a request to generate a new cryptocurrency deposit address. + + :param str currency: crypto currency ID (example: btc) + :param str label: optional label for the deposit address + :return: + """ + params = _get_params(locals(), 'currency') + return self._request(f'deposit/{currency}/newAddress', 'post', params) + + def get_notional_balances(self, currency, account=None): + """Get approved addresses for supplied network and account (if supplied) + + :param str currency: currency to get balance for. + :param str account: account name (example: primary) + :return: + """ + params = _get_params(locals(), 'currency') + return self._request(f'notionalbalances/{currency}', 'post', params) + + def remove_approved_address(self, network, address, account=None): + """Get approved addresses for supplied network and account (if supplied) + + :param str network: accepted values: bitcoin, ethereum, bitcoincash, litecoin, zcash, filecoin, dogecoin, tezos + :param str address: address to remove. + :param str account: account name (example: primary) + :return: + """ + params = _get_params(locals(), 'network') + return self._request(f'approvedAddresses/account/{Networks.get_network(network)}/remove', 'post', params) + + def request_approved_address(self, network, address, label=None, account=None): + """Get approved addresses for supplied network and account (if supplied) + + :param str network: accepted values: bitcoin, ethereum, bitcoincash, litecoin, zcash, filecoin, dogecoin, tezos + :param str address: address to remove. + :param str label: a label to identify the supplied address. + :param str account: account name (example: primary) + :return: + """ + params = _get_params(locals(), 'network') + return self._request(f'approvedAddresses/{network}/request', 'post', params) + + def get_fees(self): + """Send a request to get fees and notional volume.""" + self._request('notionalvolume', 'post') + + def get_earn_balance(self, account=None): + """Send an account earn balance request. + + :param str account: account name (example: primary) + :return: + """ + params = _get_params(locals()) + return self._request('balances/earn', 'post', params) + + def get_account_details(self, account=None): + """Send a request to get account details related to supplied API keys. + + :return: + """ + + return self._request('account', 'post', params={'account': account or 'primary'}) + + def get_account_list(self): + """ + + :param account; + """ + return self._request('account/list', 'post') + + @property + def heartbeat(self): + """Send a heartbeat message. + + :return: + """ + return self._request('heartbeat', 'post') + + + # ID '6211ee5a-0c87-4deb-a216-3380c7beb14e' + def get_transfers(self, currency=None, limit_transfers=50, show_completed_deposit_advances=False, account=None): + """Returns all the past transfers associated with the API. + + """ + params = _get_params(locals()) + return self._request('transfers', 'post', params) + + def internal_transfers(self, currency, sourceAccount, targetAccount, amount, clientTransferId=None): + params = _get_params(locals(), 'currency') + return self._request(f'account/transfer/{currency}', 'post', params) + + def get_role(self): + """ + + :return: + """ + return self._request('roles', 'post') + + def withdraw(self, currency, address, amount, account=None): + params = {'address': address, 'amount': f'{amount}'} + if account: + params.update(account=account) + return self._request(f'withdraw/{str(currency).lower()}', 'post', params) + + def clearing_new(self, counterparty_id, expires_in_hrs, symbol, amount, price, side, account=None): + """ + + :param counterparty_id: + :param int expires_in_hrs: + :param str symbol: + :param float amount: + :param flpat price: + :param str side: + :param str account: + :return : + """ + params = { + 'counterparty_id': counterparty_id, + 'expires_in_hrs': expires_in_hrs, + 'symbol': symbol, + 'amount': amount, + 'price': price, + 'side': side + } + if account: + params.update(account=account) + result = self._request('clearing/new', 'post', params) + return result + + def clearing_broker_new(self, source_counterparty_id, target_counterparty_id, expires_in_hrs: int, symbol, amount, price, side, account=None): + params = { + 'source_counterparty_id': source_counterparty_id, + 'target_counterparty_id': target_counterparty_id, + 'expires_in_hrs': expires_in_hrs, + 'symbol': symbol, + 'amount': amount, + 'price': price, + 'side': side + } + if account: + params.update(account=account) + result = self._request('clearing/broker/new', 'post', params) + return result + + def clearing_confirm(self, clearing_id, symbol, amount, price, side, account=None): + params = { + 'clearing_id': clearing_id, + 'symbol': symbol, + 'amount': amount, + 'price': price, + 'side': side, + 'account': account or 'primary' + } + result = self._request('clearing/confirm', 'post', params) + return result + + def clearing_order_status(self, clearing_id, account=None): + params = { + 'clearing_id': clearing_id, + } + if account: + params.update(account=account) + result = self._request('clearing/status', 'post', params) + return result + + def clearing_cancel_order(self, clearing_id, account=None): + params = { + 'clearing_id': clearing_id, + } + if account: + params.update(account=account) + result = self._request('clearing/cancel', 'post', params) + return result diff --git a/geminipy/model.py b/geminipy/model.py new file mode 100644 index 0000000..be5ae6c --- /dev/null +++ b/geminipy/model.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +class Networks: + _NETWORKS = { + 'gusd': 'gusd', + 'btc': 'bitcoin', + 'eth': 'ethereum', + 'bch': 'bitcoincash', + 'ltc': 'litecoin', + 'zec': 'zcash', + 'fil': 'filecoin', + 'doge': 'dogecoin', + 'xtz': 'tezos' + } + + @classmethod + def get_network(cls, term): + term = str(term).lower() + if term in cls._NETWORKS or term in cls._NETWORKS.values(): + return cls._NETWORKS[term] if term in cls._NETWORKS else term + else: + raise ValueError(f'Network for coin "{term} is not supported.') + + @classmethod + def get_coin_network(cls, network): + network = str(network).lower() + if network not in cls._NETWORKS.values(): + raise ValueError(f'Network "{network}" is not supported.') + for k, v in cls._NETWORKS.items(): + if network == v: + return k + + @classmethod + def get_networks(cls): + return list(cls._NETWORKS.values()) diff --git a/geminipy/utils.py b/geminipy/utils.py new file mode 100644 index 0000000..527b0f2 --- /dev/null +++ b/geminipy/utils.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from typing import Any, Dict + + +def _get_params(params: Dict, *exclude) -> Dict[str, Any]: diff --git a/setup.py b/setup.py index c9d0387..b890eda 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,22 @@ +# -*- coding:utf-8 -*- from setuptools import setup +from pathlib import Path -with open('README.md') as f: - readme = f.read().replace('```', '') +readme = Path('README.md') +if readme.is_file(): + readme = readme.read_text().replace('```', '') +else: + readme = '' setup( name='geminipy', - version='0.0.4', + version='0.0.6', packages=['geminipy'], - url='https://github.com/geminipy/geminipy', + url='https://github.com/pl0mo/geminipy', license='GNU GPL', author='Mike Marzigliano', author_email='marzig76@gmail.com', zip_safe=False, long_description=readme, - description='API client for Gemini', + description='API client for Gemini' )