def __init__(self, api_key=None, api_secret=None, base_api_uri=None, api_version=None): if not all((api_key, api_secret)): from stocklook.utils.security import Credentials creds = Credentials(allow_input=True) if api_key is None: try: api_key = creds.data['COINBASE_KEY'] except KeyError: warn("Set dict(stocklook.config.config) with " "COINBASE_KEY to avoid inputting it manually.") # This will be input manually if not available. api_secret = creds.get(creds.COINBASE, username=api_key, api=False) if api_key is None: api_key = creds.data[creds.COINBASE] CBClient.__init__(self, api_key=api_key, api_secret=api_secret, base_api_uri=base_api_uri, api_version=api_version) self._pmt_methods = None self._accounts = None
def _set_credentials(self): """ Sets gdax credentials via secure keyring storage. :return: """ from stocklook.utils.security import Credentials key = self.api_key creds = Credentials(allow_input=True) # Check GDAX_KEY in config if key is None: try: key = creds.data['GDAX_KEY'] except KeyError: warn("Set dict(stocklook.config.config) with " "GDAX_KEY to avoid inputting it manually.") # This will be input manually if not available. secret, phrase = creds.get_api(creds.GDAX, key=key) if key is None: # Well now the key will be made available. key = creds.data[creds.GDAX] self.api_key = key self.api_secret = secret self.api_passphrase = phrase
def _set_credentials(self): """ Sets gdax credentials via secure keyring storage. :return: """ c = Credentials() c.configure_object_vars(self, c.GDAX, 'api_key', ['api_secret', 'api_passphrase'])
def __init__(self, email=None, password=None, **kwargs): self.email = email self._kwargs = kwargs self._smtp = None self.password = password if not all((email, password)): c = Credentials() c.configure_object_vars( self, c.GMAIL, 'email', ['password']) self._kwargs['password'] = self.password
def __init__(self, key=None, secret=None): self.key = key self.secret = secret self.public = ['GetCurrencies', 'GetTradePairs', 'GetMarkets', 'GetMarket', 'GetMarketHistory', 'GetMarketOrders', 'GetMarketOrderGroups'] self.private = ['GetBalance', 'GetDepositAddress', 'GetOpenOrders', 'GetTradeHistory', 'GetTransactions', 'SubmitTrade', 'CancelTrade', 'SubmitTip', 'SubmitWithdraw', 'SubmitTransfer'] if not all((key, secret)): c = Credentials() c.configure_object_vars( self, c.CRYPTOPIA, 'key', ['secret'])
class EmailSender: """ Wraps yagmail with stocklook.utils.security.Credentials for secure access/storage of credentials. You should make a free gmail account to use this and set the GMAIL_EMAIL via stocklook.config.update_config(dict). """ Credentials.register_config_object_mapping(Credentials.GMAIL, {GMAIL_EMAIL: 'email', GMAIL_PASSWORD: '******'}) def __init__(self, email=None, password=None, **kwargs): self.email = email self._kwargs = kwargs self._smtp = None self.password = password if not all((email, password)): c = Credentials() c.configure_object_vars( self, c.GMAIL, 'email', ['password']) self._kwargs['password'] = self.password @property def smtp(self): if self._smtp is None: from yagmail import SMTP self._smtp = SMTP(self.email, **self._kwargs) return self._smtp
def set_credentials(self): pw = self._kwargs.get('password', None) if self.email is None: from ..config import config self.email = config.get('GMAIL_EMAIL', None) if pw is None: pw = config.get('GMAIL_PASSWORD', None) if pw is not None: self._kwargs['password'] = pw if not self.email or not pw: # We'll retrieve one or both from secure storage. from .security import Credentials c = Credentials(allow_input=True) pw = c.get(c.GMAIL, username=self.email, api=False) self._kwargs['password'] = pw self.email = c.data[c.GMAIL]
def __init__(self, api_key=None, api_secret=None, base_api_uri=None, api_version=None): self.api_key = None self.api_secret = None if not all((api_key, api_secret)): from stocklook.utils.security import Credentials creds = Credentials(allow_input=True) creds.configure_object_vars(self, creds.COINBASE, 'api_key', ['api_secret']) CBClient.__init__(self, api_key=api_key, api_secret=api_secret, base_api_uri=base_api_uri, api_version=api_version) self._pmt_methods = None self._accounts = None
def __init__(self, base_url=None, symbol=None, apiKey=None, apiSecret=None, orderIDPrefix='mm_bitmex_', shouldWSAuth=True, postOnly=False, timeout=7): """Init connector.""" self.logger = logging.getLogger('root') self.base_url = base_url self.symbol = symbol self.postOnly = postOnly self.apiKey = apiKey self.apiSecret = apiSecret if not all((apiKey, apiSecret)): c = Credentials() c.configure_object_vars(self, c.BITMEX, 'apiKey', ['apiSecret']) if len(orderIDPrefix) > 13: raise ValueError( "settings.ORDERID_PREFIX must be at most 13 characters long!") self.orderIDPrefix = orderIDPrefix self.retries = 0 # initialize counter # Prepare HTTPS session self.session = requests.Session() # These headers are always sent self.session.headers.update( {'user-agent': 'liquidbot-' + constants.VERSION}) self.session.headers.update({'content-type': 'application/json'}) self.session.headers.update({'accept': 'application/json'}) # Create websocket for streaming data self.ws = BitMEXWebsocket() self.ws.connect(base_url, symbol, shouldAuth=shouldWSAuth) self.timeout = timeout
class EmailSender: """ Wraps yagmail with stocklook.utils.security.Credentials for secure access/storage of credentials. You should make a free gmail account to use this and set the GMAIL_EMAIL via stocklook.config.update_config(dict). """ Credentials.register_config_object_mapping(Credentials.GMAIL, {GMAIL_EMAIL: 'email', GMAIL_PASSWORD: '******'}) def __init__(self, email=None, password=None, **kwargs): self.email = email self._kwargs = kwargs self._smtp = None self.password = password @property def smtp(self): if self._smtp is None: from yagmail import SMTP self.set_credentials() self._smtp = SMTP(self.email, **self._kwargs) return self._smtp def set_credentials(self): pw = self._kwargs.get('password', None) if self.email is None: from ..config import config self.email = config.get('GMAIL_EMAIL', None) if pw is None: pw = config.get('GMAIL_PASSWORD', None) if pw is not None: self._kwargs['password'] = pw if not self.email or not pw: # We'll retrieve one or both from secure storage. from .security import Credentials c = Credentials(allow_input=True) pw = c.get(c.GMAIL, username=self.email, api=False) self._kwargs['password'] = pw self.email = c.data[c.GMAIL]
class Gdax: """ The main Gdax exchange class exposes the majority of methods for calling the API. """ API_URL = 'https://api.gdax.com/' API_URL_TESTING = 'https://public.sandbox.gdax.com' Credentials.register_config_object_mapping( Credentials.GDAX, { GDAX_KEY: 'api_key', GDAX_SECRET: 'api_secret', GDAX_PASSPHRASE: 'api_passphrase' }) def __init__(self, key=None, secret=None, passphrase=None, wallet_auth=None, coinbase_client=None): """ The main interface to the Gdax Private API. Most of the API data gets broken down into other objects like GdaxAccount, GdaxProduct, GdaxDatabase, GdaxOrderSystem, etc. Most of these objects are managed/accessed via this interface. To get Gdax API credentials you must do the following (9/9/2017): 1) Go to https://www.gdax.com/settings/api 2) Select permissions for view, trade, and manage 3) Select "Create API Key" 4) Use the Key, Secret, and Passphrase :param key: (str, default None) None searches the dictionary located within stocklook.config.config under key 'GDAX_KEY' if not provided. :param secret: (str, default None) None searches the dictionary located within stocklook.config.config under key 'GDAX_SECRET' if not provided. :param passphrase: (str, default None) None searches the dictionary located within stocklook.config.config under key 'GDAX_PASSPHRASE' if not provided. :param wallet_auth: (requests.auth.Authbase, default None) None defaults to a gdax.api.CoinbaseExchangeAuth object. :param coinbase_client: (stocklook.crypto.coinbase_api.CoinbaseClient) An optionally pre-configured CoinbaseClient. """ self.api_key = key self.api_secret = secret self.api_passphrase = passphrase self._accounts = dict( ) # 'GdaxAccount.currency': stocklook.crypto.gdax.account.GdaxAccount self._orders = dict( ) # 'GdaxOrder.id': stocklook.crypto.gdax.order.GdaxOrder self._products = dict( ) # 'GdaxProduct.product': stocklook.crypto.gdax.product.GdaxProduct self._timeout_times = dict( ) # 'Gdax.caller_method_or_property' : datetime.datetime self._wallet_auth = wallet_auth self._coinbase_client = coinbase_client self.base_url = self.API_URL self.timeout_intervals = dict(accounts=120, ) self._coinbase_accounts = None self._db = None if not all([key, secret, passphrase]): self._set_credentials() def _set_credentials(self): """ Sets gdax credentials via secure keyring storage. :return: """ c = Credentials() c.configure_object_vars(self, c.GDAX, 'api_key', ['api_secret', 'api_passphrase']) def set_testing_url(self): """ Sets base API url to the gdax sandbox for testing purposes. :return: None """ self.base_url = self.API_URL_TESTING def set_production_url(self): """ Sets base API url to gdax production address. :return: None """ self.base_url = self.API_URL @property def accounts(self) -> dict: """ Dynamically compiled dictionary gets filled with BTC, USD, ETH, & LTC GdaxAccount objects when first accessed, caching data within the Gdax._accounts dictionary. :return: """ timed_out = timeout_check('accounts', t_data=self._timeout_times, seconds=self.timeout_intervals['accounts']) if not self._accounts or timed_out: self.sync_accounts() return self._accounts @property def coinbase_client(self): """ Returns stocklook.crypto.coinbase_api.CoinbaseClient If :param coinbase_client was not provided on Gdax.__init__, a new CoinbaseClient object will be created and cached. CoinbaseClient will use dict:stocklook.config.config['COINBASE_KEY'] and secret cached via keyring and will ask for user input if those aren't available. :return: """ if self._coinbase_client is None: from stocklook.crypto.coinbase_api import CoinbaseClient self._coinbase_client = CoinbaseClient() return self._coinbase_client @property def coinbase_accounts(self): """ Coinbase accounts associated to Gdax account like {currency: account_dict} """ if self._coinbase_accounts is None: self._coinbase_accounts = { ac['currency']: ac for ac in self.get_coinbase_accounts() } return self._coinbase_accounts @property def db(self): """ Generates/returns a default GdaxDatabase when first accessed. To make a custom GdaxDatabase use the Gdax.get_database() method and you can supply GdaxDatabase.__init__ kwargs there. :return: GdaxDatabase """ if self._db is None: self.get_database() return self._db def get_account(self, currency) -> GdaxAccount: """ Returns a GdaxAccount based on the given currency. :param currency: (str) BTC, LTC, USD, ETH :return: """ return self.accounts[currency] def sync_accounts(self): """ Iterates through Gdax.get_accounts() data creating (or updating) GdaxAccount objects and caching within Gdax._accounts dictionary :return: """ if self._accounts: for acc in self.get_accounts(): self._accounts[acc['currency']].update(acc) else: for acc in self.get_accounts(): a = GdaxAccount(acc, self) self._accounts[a.currency] = a @property def products(self) -> dict: """ Dynamic property compiles GdaxProducts when accessed for the first time and caches them within the Gdax._products dictionary, returning a reference to that dictionary. :return: """ if not self._products: for p in GdaxProducts.LIST: product = GdaxProduct(p, self) self._products[p] = product return self._products def get_product(self, name) -> GdaxProduct: """ Returns a GdaxProduct object for LTC-USD BTC-USD ETH-USD :param name: (str) LTC-USD, BTC-USD, ETH-USD :return: GdaxProduct """ return self.products[name] def get_database(self, **kwargs): """ Generates a GdaxDatabase object assigning it to the Gdax.db property as well as returning a reference to the object. :param kwargs: GdaxDatabase(**kwargs) :return: GdaxDatabase """ if kwargs or self._db is None: from .db import GdaxDatabase self._db = GdaxDatabase(self, **kwargs) return self._db @property def orders(self) -> dict: """ Dynamically generates a dict of GdaxOrder objects caching them in the Gdax._orders dictionary using the GdaxOrder.id as the keys. :return: """ if not self._orders: from stocklook.crypto.gdax.order import GdaxOrder for o in self.get_orders(): product = o.pop('product_id') o['order_type'] = o.pop('type') order = GdaxOrder(self, product, **o) self._orders[o['id']] = order return self._orders def authorize(self, api_key=None, api_secret=None, api_passphrase=None): """ Returns a new or pre-existing CoinbaseExchangeAuth object to be used in the auth keyword argument when calling requests.request(). Most users will not need to do this as it's called automatically by other methods that make requests. :param api_key: (str) :param api_secret: (str) :param api_passphrase: (str) :return: """ if api_key: self.api_key = api_key if api_secret: self.api_secret = api_secret if api_passphrase: self.api_passphrase = api_passphrase if hasattr(self._wallet_auth, 'api_key'): # Avoid rebuilding the CoinbaseExchangeAuth same_key = self._wallet_auth.api_key == self.api_key same_secret = self._wallet_auth.secret_key == self.api_secret same_passphrase = self._wallet_auth.passphrase == self.api_passphrase if all([same_key, same_secret, same_passphrase]): # Safe to return the existing wallet auth. return self._wallet_auth self._wallet_auth = CoinbaseExchangeAuth(self.api_key, self.api_secret, self.api_passphrase) return self._wallet_auth @property def wallet_auth(self): """ Returns a new or pre-existing CoinbaseExchangeAuth object. Used by Gdax.get, Gdax.delete, and Gdax.post methods. :return: """ if self._wallet_auth is None: self.authorize() return self._wallet_auth def get(self, url_extension, **kwargs) -> requests.Response: """ Makes a GET request to the GDAX api using the base Gdax.API_URL along with the given extension: Example: resp = Gdax.get('orders') contents = resp.json() :param url_extension: (str) :param kwargs: requests.get(**kwargs) :return: """ kwargs.update({ 'auth': kwargs.pop('auth', self.wallet_auth), 'method': 'get' }) return gdax_call_api(self.base_url + url_extension, **kwargs) def post(self, url_extension, **kwargs) -> requests.Response: """ Makes a POST request to the Gdax API using the base Gdax.API_URL along with the given extension. :param url_extension: :param kwargs: :return: """ kwargs.update({ 'auth': kwargs.pop('auth', self.wallet_auth), 'method': 'post' }) return gdax_call_api(self.base_url + url_extension, **kwargs) def delete(self, url_extension, **kwargs) -> requests.Response: """ Makes a DELETE request to the Gdax API using the base Gdax.API_URL along with the given extension. Example: res = Gdax.delete('orders/<order_id>') data = res.json() # The cancelled order ID should be inside of this list :param url_extension: (str) :param kwargs: :return: """ kwargs.update({ 'auth': kwargs.pop('auth', self.wallet_auth), 'method': 'delete' }) return gdax_call_api(self.base_url + url_extension, **kwargs) def get_current_user(self): """ Returns dictionary of user information. """ return self.get('user').json() def get_orders(self, order_id=None, paginate=True, status='all'): """ Returns a list containing data about orders. :param order_id: (int, default None) A specific order ID to get information about should be the long string UID generated by orders like Adwafeh-35rhhdfn-adwe3th-wadwagn :param paginate: (bool, default True) True will make a series of requests collecting ~100 chunks of data per request and return all in one list. False will only return the first ~100 orders :param status (str, default 'all') 'open', 'pending', 'active' :return: """ if order_id: ext = 'orders/{}'.format(order_id) return self.get(ext).json() else: ext = 'orders' p = (dict(status=status) if status else None) res = self.get(ext, params=p) if not paginate: return res.json() data = list(res.json()) while 'cb-after' in res.headers: p = {'after': res.headers['cb-after']} res = self.get(ext, params=p) data.extend(res.json()) return data def get_coinbase_accounts(self): """ Returns a list of dict objects each containing information about a coinbase account. :return: """ return self.get('coinbase-accounts').json() def get_fills(self, order_id=None, product_id=None, paginate=True, params=None): """ Get a list of recent fills. QUERY PARAMETERS Param Default Description order_id all Limit list of fills to this order_id product_id all Limit list of fills to this product_id SETTLEMENT AND FEES Fees are recorded in two stages. Immediately after the matching engine completes a match, the fill is inserted into our datastore. Once the fill is recorded, a settlement process will settle the fill and credit both trading counterparties. The fee field indicates the fees charged for this individual fill. LIQUIDITY The liquidity field indicates if the fill was the result of a liquidity provider or liquidity taker. M indicates Maker and T indicates Taker. PAGINATION Fills are returned sorted by descending trade_id from the largest trade_id to the smallest trade_id. The CB-BEFORE header will have this first trade id so that future requests using the cb-before parameter will fetch fills with a greater trade id (newer fills). :return: """ if params is None: params = dict() ext = 'fills' if order_id: params['order_id'] = order_id if product_id: params['product_id'] = product_id if not params: params = None res = self.get(ext, params=params) if not paginate: return res.json() data = list(res.json()) while 'cb-after' in res.headers: p = {'after': res.headers['cb-after']} res = self.get(ext, params=p) data.extend(res.json()) return data def get_book(self, product, level=2): """ Returns a dictionary from the Gdax Order Book like this: { "sequence": "3", "bids": [ [ price, size, num-orders ], ], "asks": [ [ price, size, num-orders ], ] } :param product: (str) BTC-USD, LTC-USD, or ETH-USD :param level: Level Description 1 Only the best bid and ask 2 Top 50 bids and asks (aggregated) 3 Full order book (non aggregated) :return: """ ext = 'products/{}/book'.format(product) params = dict(level=level) return self.get(ext, params=params).json() def get_ticker(self, product): """ Snapshot information about the last trade (tick), best bid/ask and 24h volume. { "trade_id": 4729088, "price": "333.99", "size": "0.193", "bid": "333.98", "ask": "333.99", "volume": "5957.11914015", "time": "2015-11-14T20:46:03.511254Z" } :param product: :return: """ ext = 'products/{}/ticker'.format(product) return self.get(ext).json() def get_trades(self, product): """ List the latest trades for a product. [{ "time": "2014-11-07T22:19:28.578544Z", "trade_id": 74, "price": "10.00000000", "size": "0.01000000", "side": "buy" }, { "time": "2014-11-07T01:08:43.642366Z", "trade_id": 73, "price": "100.00000000", "size": "0.01000000", "side": "sell" }] :param product: :return: """ ext = 'products/{}/trades'.format(product) return self.get(ext).json() def get_candles(self, product, start, end, granularity=60, convert_dates=False, to_frame=False): """ Historic rates for a product. Rates are returned in grouped buckets based on requested granularity. PARAMETERS Param Description start Start time in ISO 8601 end End time in ISO 8601 granularity Desired timeslice in seconds NOTES Historical rate data may be incomplete. No data is published for intervals where there are no ticks. Historical rates should not be polled frequently. If you need real-time information, use the trade and book endpoints along with the websocket feed. :return: Each bucket is an array of the following information: time bucket start time low lowest price during the bucket interval high highest price during the bucket interval open opening price (first trade) in the bucket interval close closing price (last trade) in the bucket interval volume volume of trading activity during the bucket interval """ self._validate_product(product) if not isinstance(start, str): start = timestamp_to_iso8601(start) if not isinstance(end, str): end = timestamp_to_iso8601(end) ext = 'products/{}/candles'.format(product) params = dict(start=start, end=end, granularity=granularity) res = self.get(ext, params=params).json() if convert_dates: for row in res: row[0] = timestamp_from_utc(row[0]) if to_frame: columns = ['time', 'low', 'high', 'open', 'close', 'volume'] res = pd.DataFrame(columns=columns, data=res, index=range(len(res))) return res def get_24hr_stats(self, product): """ { "open": "34.19000000", "high": "95.70000000", "low": "7.06000000", "volume": "2.41000000" } :param product: :return: """ self._validate_product(product) ext = 'products/{}/stats'.format(product) return self.get(ext).json() def get_position(self): """ Returns profile information. { "status": "active", "funding": { "max_funding_value": "10000", "funding_value": "622.48199522418175", "oldest_outstanding": { "id": "280c0a56-f2fa-4d3b-a199-92df76fff5cd", "order_id": "280c0a56-f2fa-4d3b-a199-92df76fff5cd", "created_at": "2017-03-18T00:34:34.270484Z", "currency": "USD", "account_id": "202af5e9-1ac0-4888-bdf5-15599ae207e2", "amount": "545.2400000000000000" } }, "accounts": { "USD": { "id": "202af5e9-1ac0-4888-bdf5-15599ae207e2", "balance": "0.0000000000000000", "hold": "0.0000000000000000", "funded_amount": "622.4819952241817500", "default_amount": "0" }, "BTC": { "id": "1f690a52-d557-41b5-b834-e39eb10d7df0", "balance": "4.7051564815292853", "hold": "0.6000000000000000", "funded_amount": "0.0000000000000000", "default_amount": "0" } }, "margin_call": { "active": true, "price": "175.96000000", "side": "sell", "size": "4.70515648", "funds": "624.04210048" }, "user_id": "521c20b3d4ab09621f000011", "profile_id": "d881e5a6-58eb-47cd-b8e2-8d9f2e3ec6f6", "position": { "type": "long", "size": "0.59968368", "complement": "-641.91999958602800000000000000", "max_size": "1.49000000" }, "product_id": "BTC-USD" } :return: """ return self.get('position').json() def get_accounts(self, account_id=None): """ ACCOUNT FIELDS Field Description id Account ID currency the currency of the account balance total funds in the account holds funds on hold (not available for use) available funds available to withdraw* or trade margin_enabled [margin] true if the account belongs to margin profile funded_amount [margin] amount of funding GDAX is currently providing this account default_amount [margin] amount defaulted on due to not being able to pay back funding * Only applicable to non margin accounts. Withdraws on margin accounts are subject to other restrictions. :return: """ if account_id is None: ext = 'accounts' else: ext = 'accounts/{}'.format(account_id) return self.get(ext).json() def get_total_value(self): """ Sums up the GdaxAccount.usd_value for each account in the user's profile returning a float of the total sum. :return: """ vals = [a.usd_value for a in self.accounts.values()] return round(sum(vals), 2) def post_order(self, order_json): """ Post's the output of GdaxOrder.json :param order_json: (dict, GdaxOrder.json) :return: { "id": "d0c5340b-6d6c-49d9-b567-48c4bfca13d2", "price": "0.10000000", "size": "0.01000000", "product_id": "BTC-USD", "side": "buy", "stp": "dc", "type": "limit", "time_in_force": "GTC", "post_only": false, "created_at": "2016-12-08T20:02:28.53864Z", "fill_fees": "0.0000000000000000", "filled_size": "0.00000000", "executed_value": "0.0000000000000000", "status": "pending", "settled": false } """ return self.post('orders', json=order_json).json() def get_account_ledger_history(self, paginate=True): """ Returns a pandas.DataFrame containing historical transactions for each GdaxAccount assigned to the user. NOTE: if paginate is set to true this method can take a long time to complete as each account may make multiple API calls to gather the data. [ { "id": "100", "created_at": "2014-11-07T08:19:27.028459Z", "amount": "0.001", "balance": "239.669", "type": "fee", "details": { "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", "trade_id": "74", "product_id": "BTC-USD" } } ] :return: """ data = [] for account in self.accounts.values(): if account.currency == account.USD: continue chunk = account.get_history(paginate=paginate) data.extend(chunk) for record in data: details = record.pop('details', None) if details: record.update(details) return pd.DataFrame.from_records(data, index=range(len(data))) def _validate_product(self, product): if product not in GdaxProducts.LIST: raise KeyError("Product not in GdaxProducts: " "{} ? {}".format(product, GdaxProducts.LIST)) def withdraw_to_coinbase(self, currency, amount, coinbase_account_id=None): """ Withdraw funds to a coinbase account. You can move funds between your Coinbase accounts and your GDAX trading accounts within your daily limits. Moving funds between Coinbase and GDAX is instant and free. See the Coinbase Accounts section for retrieving your Coinbase accounts. HTTP REQUEST POST /withdrawals/coinbase PARAMETERS Param Description amount The amount to withdraw currency The type of currency coinbase_account_id ID of the coinbase account :param currency: :param size: :return: """ if coinbase_account_id is None: try: coinbase_account_id = self.coinbase_accounts[currency]['id'] except KeyError as e: raise KeyError("Unable to find coinbase_account_id " "for currency '{}'. Error: " "{}".format(currency, e)) p = dict(currency=currency, amount=amount, coinbase_account_id=coinbase_account_id) return self.post("withdrawals/coinbase", json=json.dumps(p)) def deposit_from_coinbase(self, currency, amount, coinbase_account_id=None): if coinbase_account_id is None: try: coinbase_account_id = self.coinbase_accounts[currency]['id'] except KeyError as e: raise KeyError("Unable to find coinbase_account_id " "for currency '{}'. Error: " "{}".format(currency, e)) p = dict(currency=currency, amount=amount, coinbase_account_id=coinbase_account_id) return self.post("deposits/coinbase-account", json=json.dumps(p))
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from stocklook.utils.security import Credentials # You should remove USERNAME/PASSWORD variables # after running this script. SERVICE_NAME = Credentials.GDAX USER_NAME = '' SECRET_ITEMS = [] # List stores PASSWORD, PASSPHRASE if __name__ == '__main__': c = Credentials() c.reset_credentials(SERVICE_NAME, USER_NAME, SECRET_ITEMS)
class CoinbaseClient(CBClient): """ Subclass coinbase.wallet.client with support for credentials from config + a few convenience methods. """ Credentials.register_config_object_mapping(Credentials.COINBASE, { COINBASE_KEY: 'api_key', COINBASE_SECRET: 'api_secret', }) def __init__(self, api_key=None, api_secret=None, base_api_uri=None, api_version=None): self.api_key = None self.api_secret = None if not all((api_key, api_secret)): from stocklook.utils.security import Credentials creds = Credentials(allow_input=True) creds.configure_object_vars(self, creds.COINBASE, 'api_key', ['api_secret']) CBClient.__init__(self, api_key=api_key, api_secret=api_secret, base_api_uri=base_api_uri, api_version=api_version) self._pmt_methods = None self._accounts = None @property def pmt_methods(self): if self._pmt_methods is None: self._pmt_methods = self.get_payment_methods()\ .response.json()['data'] return self._pmt_methods @property def accounts(self): # dictionary of currency: account if self._accounts is None: res = self.get_accounts()\ .response.json()['data'] self._accounts = {a['currency']: a for a in res} return self._accounts def get_payment_method_by_last_4(self, last_four, name_key=None): # Convenient payment method via last 4 last_four = str(last_four) if name_key: name_key = name_key.upper() try: return [ m for m in self.pmt_methods if m['name'].endswith(last_four) and ( name_key is None or name_key in m['name'].upper()) ][0] except (IndexError, KeyError): return None
class Cryptopia: """ Represents a wrapper for cryptopia API """ Credentials.register_config_object_mapping( Credentials.CRYPTOPIA, {CRYPTOPIA_KEY: 'key', CRYPTOPIA_SECRET: 'secret'} ) def __init__(self, key=None, secret=None): self.key = key self.secret = secret self.public = ['GetCurrencies', 'GetTradePairs', 'GetMarkets', 'GetMarket', 'GetMarketHistory', 'GetMarketOrders', 'GetMarketOrderGroups'] self.private = ['GetBalance', 'GetDepositAddress', 'GetOpenOrders', 'GetTradeHistory', 'GetTransactions', 'SubmitTrade', 'CancelTrade', 'SubmitTip', 'SubmitWithdraw', 'SubmitTransfer'] if not all((key, secret)): c = Credentials() c.configure_object_vars( self, c.CRYPTOPIA, 'key', ['secret']) @property def secret(self): return self._secret @secret.getter def secret(self): return self._secret @secret.setter def secret(self, x): if not x: self._secret = x else: self._secret = base64.b64decode(x + '=' * (-len(x) % 4)) def api_query(self, feature_requested, get_parameters=None, post_parameters=None): """ Performs a generic api request """ time.sleep(1) if feature_requested in self.private: url = "https://www.cryptopia.co.nz/Api/" + feature_requested post_data = json.dumps(post_parameters) headers = self.secure_headers(url=url, post_data=post_data) req = requests.post(url, data=post_data, headers=headers) if req.status_code != 200: try: req.raise_for_status() except requests.exceptions.RequestException as ex: return None, "Status Code : " + str(ex) req = req.json() if req['Success'] is True: result = req['Data'] error = None else: result = None if req['Message'] is None: error = "Unknown response error" else: error = req['Message'] return (result, error) elif feature_requested in self.public: url = "https://www.cryptopia.co.nz/Api/" + feature_requested + "/" + \ ('/'.join(i for i in get_parameters.values() ) if get_parameters is not None else "") req = requests.get(url, params=get_parameters) if req.status_code != 200: try: req.raise_for_status() except requests.exceptions.RequestException as ex: return None, "Status Code : " + str(ex) req = req.json() if req['Success'] is True: result = req['Data'] error = None else: result = None if req['Message'] is None: error = "Unknown response error" else: error = req['Message'] return (result, error) else: return None, "Unknown feature" def get_currencies(self): """ Gets all the currencies """ return self.api_query(feature_requested='GetCurrencies') def get_tradepairs(self): """ GEts all the trade pairs """ return self.api_query(feature_requested='GetTradePairs') def get_markets(self): """ Gets data for all markets """ return self.api_query(feature_requested='GetMarkets') def get_market(self, market): """ Gets market data """ return self.api_query(feature_requested='GetMarket', get_parameters={'market': market}) def get_history(self, market): """ Gets the full order history for the market (all users) """ return self.api_query(feature_requested='GetMarketHistory', get_parameters={'market': market}) def get_orders(self, market): """ Gets the user history for the specified market """ return self.api_query(feature_requested='GetMarketOrders', get_parameters={'market': market}) def get_ordergroups(self, markets): """ Gets the order groups for the specified market """ return self.api_query(feature_requested='GetMarketOrderGroups', get_parameters={'markets': markets}) def get_balance(self, currency): """ Gets the balance of the user in the specified currency """ result, error = self.api_query(feature_requested='GetBalance', post_parameters={'Currency': currency}) if error is None: result = result[0] return (result, error) def get_openorders(self, market): """ Gets the open order for the user in the specified market """ return self.api_query(feature_requested='GetOpenOrders', post_parameters={'Market': market}) def get_deposit_address(self, currency): """ Gets the deposit address for the specified currency """ return self.api_query(feature_requested='GetDepositAddress', post_parameters={'Currency': currency}) def get_tradehistory(self, market): """ Gets the trade history for a market """ return self.api_query(feature_requested='GetTradeHistory', post_parameters={'Market': market}) def get_transactions(self, transaction_type): """ Gets all transactions for a user """ return self.api_query(feature_requested='GetTransactions', post_parameters={'Type': transaction_type}) def submit_trade(self, market, trade_type, rate, amount): """ Submits a trade """ return self.api_query(feature_requested='SubmitTrade', post_parameters={'Market': market, 'Type': trade_type, 'Rate': rate, 'Amount': amount}) def cancel_trade(self, trade_type, order_id, tradepair_id): """ Cancels an active trade """ return self.api_query(feature_requested='CancelTrade', post_parameters={'Type': trade_type, 'OrderID': order_id, 'TradePairID': tradepair_id}) def submit_tip(self, currency, active_users, amount): """ Submits a tip """ return self.api_query(feature_requested='SubmitTip', post_parameters={'Currency': currency, 'ActiveUsers': active_users, 'Amount': amount}) def submit_withdraw(self, currency, address, amount): """ Submits a withdraw request """ return self.api_query(feature_requested='SubmitWithdraw', post_parameters={'Currency': currency, 'Address': address, 'Amount': amount}) def submit_transfer(self, currency, username, amount): """ Submits a transfer """ return self.api_query(feature_requested='SubmitTransfer', post_parameters={'Currency': currency, 'Username': username, 'Amount': amount}) def secure_headers(self, url, post_data): """ Creates secure header for cryptopia private api. """ nonce = str(int(time.time())) post_data = post_data.encode('utf8') md5 = hashlib.md5() md5.update(post_data) rcb64 = base64.b64encode(md5.digest()).decode('utf8') signature = self.key + "POST" + \ urllib.parse.quote_plus(url).lower() + nonce + rcb64 signature = signature.encode('utf8') sign = base64.b64encode( hmac.new(self.secret, signature, hashlib.sha256).digest()) header_value = "amx " + self.key + ":" + sign.decode('utf8') + ":" + nonce return {'Authorization': header_value, 'Content-Type': 'application/json; charset=utf-8'}
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ # You should remove USERNAME/PASSWORD variables after running this script. SERVICE_NAME = 'coinbase' USER_NAME = '' PASSWORD = '' PASSPHRASE = None if __name__ == '__main__': from stocklook.utils.security import Credentials c = Credentials() c.reset_credentials(SERVICE_NAME, USER_NAME, PASSWORD, PASSPHRASE)
class BitMEX(object): """BitMEX API Connector.""" Credentials.register_config_object_mapping(Credentials.BITMEX, { BITMEX_KEY: 'apiKey', BITMEX_SECRET: 'apiSecret', }) def __init__(self, base_url=None, symbol=None, apiKey=None, apiSecret=None, orderIDPrefix='mm_bitmex_', shouldWSAuth=True, postOnly=False, timeout=7): """Init connector.""" self.logger = logging.getLogger('root') self.base_url = base_url self.symbol = symbol self.postOnly = postOnly self.apiKey = apiKey self.apiSecret = apiSecret if not all((apiKey, apiSecret)): c = Credentials() c.configure_object_vars(self, c.BITMEX, 'apiKey', ['apiSecret']) if len(orderIDPrefix) > 13: raise ValueError( "settings.ORDERID_PREFIX must be at most 13 characters long!") self.orderIDPrefix = orderIDPrefix self.retries = 0 # initialize counter # Prepare HTTPS session self.session = requests.Session() # These headers are always sent self.session.headers.update( {'user-agent': 'liquidbot-' + constants.VERSION}) self.session.headers.update({'content-type': 'application/json'}) self.session.headers.update({'accept': 'application/json'}) # Create websocket for streaming data self.ws = BitMEXWebsocket() self.ws.connect(base_url, symbol, shouldAuth=shouldWSAuth) self.timeout = timeout def __del__(self): self.exit() def exit(self): self.ws.exit() # # Public methods # def ticker_data(self, symbol=None): """Get ticker data.""" if symbol is None: symbol = self.symbol return self.ws.get_ticker(symbol) def instrument(self, symbol): """Get an instrument's details.""" return self.ws.get_instrument(symbol) def instruments(self, filter=None): query = {} if filter is not None: query['filter'] = json.dumps(filter) return self._curl_bitmex(path='instrument', query=query, verb='GET') def market_depth(self, symbol): """Get market depth / orderbook.""" return self.ws.market_depth(symbol) def recent_trades(self): """Get recent trades. Returns ------- A list of dicts: {u'amount': 60, u'date': 1306775375, u'price': 8.7401099999999996, u'tid': u'93842'}, """ return self.ws.recent_trades() # # Authentication required methods # def authentication_required(fn): """Annotation for methods that require auth.""" def wrapped(self, *args, **kwargs): if not (self.apiKey): msg = "You must be authenticated to use this method" raise errors.AuthenticationError(msg) else: return fn(self, *args, **kwargs) return wrapped @authentication_required def funds(self): """Get your current balance.""" return self.ws.funds() @authentication_required def position(self, symbol): """Get your open position.""" return self.ws.position(symbol) @authentication_required def isolate_margin(self, symbol, leverage, rethrow_errors=False): """Set the leverage on an isolated margin position""" path = "position/leverage" postdict = {'symbol': symbol, 'leverage': leverage} return self._curl_bitmex(path=path, postdict=postdict, verb="POST", rethrow_errors=rethrow_errors) @authentication_required def delta(self): return self.position(self.symbol)['homeNotional'] @authentication_required def buy(self, quantity, price): """Place a buy order. Returns order object. ID: orderID """ return self.place_order(quantity, price) @authentication_required def sell(self, quantity, price): """Place a sell order. Returns order object. ID: orderID """ return self.place_order(-quantity, price) @authentication_required def place_order(self, quantity, price): """Place an order.""" if price < 0: raise Exception("Price must be positive.") endpoint = "order" # Generate a unique clOrdID with our prefix so we can identify it. clOrdID = self.orderIDPrefix + base64.b64encode( uuid.uuid4().bytes).decode('utf8').rstrip('=\n') postdict = { 'symbol': self.symbol, 'orderQty': quantity, 'price': price, 'clOrdID': clOrdID } return self._curl_bitmex(path=endpoint, postdict=postdict, verb="POST") @authentication_required def amend_bulk_orders(self, orders): """Amend multiple orders.""" # Note rethrow; if this fails, we want to catch it and re-tick return self._curl_bitmex(path='order/bulk', postdict={'orders': orders}, verb='PUT', rethrow_errors=True) @authentication_required def create_bulk_orders(self, orders): """Create multiple orders.""" for order in orders: order['clOrdID'] = self.orderIDPrefix + base64.b64encode( uuid.uuid4().bytes).decode('utf8').rstrip('=\n') order['symbol'] = self.symbol if self.postOnly: order['execInst'] = 'ParticipateDoNotInitiate' return self._curl_bitmex(path='order/bulk', postdict={'orders': orders}, verb='POST') @authentication_required def open_orders(self): """Get open orders.""" return self.ws.open_orders(self.orderIDPrefix) @authentication_required def http_open_orders(self): """Get open orders via HTTP. Used on close to ensure we catch them all.""" path = "order" orders = self._curl_bitmex(path=path, query={ 'filter': json.dumps({ 'ordStatus.isTerminated': False, 'symbol': self.symbol }), 'count': 500 }, verb="GET") # Only return orders that start with our clOrdID prefix. return [ o for o in orders if str(o['clOrdID']).startswith(self.orderIDPrefix) ] @authentication_required def cancel(self, orderID): """Cancel an existing order.""" path = "order" postdict = { 'orderID': orderID, } return self._curl_bitmex(path=path, postdict=postdict, verb="DELETE") @authentication_required def withdraw(self, amount, fee, address): path = "user/requestWithdrawal" postdict = { 'amount': amount, 'fee': fee, 'currency': 'XBt', 'address': address } return self._curl_bitmex(path=path, postdict=postdict, verb="POST", max_retries=0) def _curl_bitmex(self, path, query=None, postdict=None, timeout=None, verb=None, rethrow_errors=False, max_retries=None): """Send a request to BitMEX Servers.""" # Handle URL url = self.base_url + path if timeout is None: timeout = self.timeout # Default to POST if data is attached, GET otherwise if not verb: verb = 'POST' if postdict else 'GET' # By default don't retry POST or PUT. Retrying GET/DELETE is okay because they are idempotent. # In the future we could allow retrying PUT, so long as 'leavesQty' is not used (not idempotent), # or you could change the clOrdID (set {"clOrdID": "new", "origClOrdID": "old"}) so that an amend # can't erroneously be applied twice. if max_retries is None: max_retries = 0 if verb in ['POST', 'PUT'] else 3 # Auth: API Key/Secret auth = APIKeyAuthWithExpires(self.apiKey, self.apiSecret) def exit_or_throw(e): if rethrow_errors: raise e else: exit(1) def retry(): self.retries += 1 if self.retries > max_retries: raise Exception("Max retries on %s (%s) hit, raising." % (path, json.dumps(postdict or ''))) return self._curl_bitmex(path, query, postdict, timeout, verb, rethrow_errors, max_retries) # Make the request response = None try: self.logger.info("sending req to %s: %s" % (url, json.dumps(postdict or query or ''))) req = requests.Request(verb, url, json=postdict, auth=auth, params=query) prepped = self.session.prepare_request(req) response = self.session.send(prepped, timeout=timeout) # Make non-200s throw response.raise_for_status() except requests.exceptions.HTTPError as e: if response is None: raise e # 401 - Auth error. This is fatal. if response.status_code == 401: self.logger.error( "API Key or Secret incorrect, please check and restart.") self.logger.error("Error: " + response.text) if postdict: self.logger.error(postdict) # Always exit, even if rethrow_errors, because this is fatal exit(1) # 404, can be thrown if order canceled or does not exist. elif response.status_code == 404: if verb == 'DELETE': self.logger.error("Order not found: %s" % postdict['orderID']) return self.logger.error("Unable to contact the BitMEX API (404). " + "Request: %s \n %s" % (url, json.dumps(postdict))) exit_or_throw(e) # 429, ratelimit; cancel orders & wait until X-Ratelimit-Reset elif response.status_code == 429: self.logger.error( "Ratelimited on current request. Sleeping, then trying again. Try fewer " + "order pairs or contact [email protected] to raise your limits. " + "Request: %s \n %s" % (url, json.dumps(postdict))) # Figure out how long we need to wait. ratelimit_reset = response.headers['X-Ratelimit-Reset'] to_sleep = int(ratelimit_reset) - int(time.time()) reset_str = datetime.datetime.fromtimestamp( int(ratelimit_reset)).strftime('%X') # We're ratelimited, and we may be waiting for a long time. Cancel orders. self.logger.warning( "Canceling all known orders in the meantime.") self.cancel([o['orderID'] for o in self.open_orders()]) self.logger.error( "Your ratelimit will reset at %s. Sleeping for %d seconds." % (reset_str, to_sleep)) time.sleep(to_sleep) # Retry the request. return retry() # 503 - BitMEX temporary downtime, likely due to a deploy. Try again elif response.status_code == 503: self.logger.warning( "Unable to contact the BitMEX API (503), retrying. " + "Request: %s \n %s" % (url, json.dumps(postdict))) time.sleep(3) return retry() elif response.status_code == 400: error = response.json()['error'] message = error['message'].lower() if error else '' # Duplicate clOrdID: that's fine, probably a deploy, go get the order(s) and return it if 'duplicate clordid' in message: orders = postdict[ 'orders'] if 'orders' in postdict else postdict IDs = json.dumps( {'clOrdID': [order['clOrdID'] for order in orders]}) orderResults = self._curl_bitmex('/order', query={'filter': IDs}, verb='GET') for i, order in enumerate(orderResults): if (order['orderQty'] != abs(postdict['orderQty']) or order['side'] != ('Buy' if postdict['orderQty'] > 0 else 'Sell') or order['price'] != postdict['price'] or order['symbol'] != postdict['symbol']): raise Exception( 'Attempted to recover from duplicate clOrdID, but order returned from API ' + 'did not match POST.\nPOST data: %s\nReturned order: %s' % (json.dumps(orders[i]), json.dumps(order))) # All good return orderResults elif 'insufficient available balance' in message: self.logger.error('Account out of funds. The message: %s' % error['message']) exit_or_throw(Exception('Insufficient Funds')) # If we haven't returned or re-raised yet, we get here. self.logger.error("Unhandled Error: %s: %s" % (e, response.text)) self.logger.error("Endpoint was: %s %s: %s" % (verb, path, json.dumps(postdict))) exit_or_throw(e) except requests.exceptions.Timeout as e: # Timeout, re-run this request self.logger.warning("Timed out on request: %s (%s), retrying..." % (path, json.dumps(postdict or ''))) return retry() except requests.exceptions.ConnectionError as e: self.logger.warning( "Unable to contact the BitMEX API (%s). Please check the URL. Retrying. " + "Request: %s %s \n %s" % (e, url, json.dumps(postdict))) time.sleep(1) return retry() # Reset retry counter on success self.retries = 0 return response.json()
def __init__(self, key=None, secret=None): self.api_key = key self.secret = secret if not all((key, secret)): c = Credentials() c.configure_object_vars(self, c.POLONIEX, 'api_key', ['secret'])
class Poloniex: Credentials.register_config_object_mapping(Credentials.POLONIEX, { POLONIEX_KEY: 'api_key', POLONIEX_SECRET: 'secret' }) def __init__(self, key=None, secret=None): self.api_key = key self.secret = secret if not all((key, secret)): c = Credentials() c.configure_object_vars(self, c.POLONIEX, 'api_key', ['secret']) def post_process(self, before): after = before # Add timestamps if there isnt one but is a datetime if ('return' in after): if (isinstance(after['return'], list)): for x in range(0, len(after['return'])): if (isinstance(after['return'][x], dict)): if ('datetime' in after['return'][x] and 'timestamp' not in after['return'][x]): after['return'][x]['timestamp'] = float( create_timestamp( after['return'][x]['datetime'])) return after def api_query(self, command, req={}): """ Queries poloniex Most Poloniex methods call this function. :param command: (str) returnTicker return24hVolume returnOrderBook returnMarketTradeHistory :param req: (dict) containing parameters that can be encoded into the URL. :return: """ if command == "returnTicker" or command == "return24hVolume": url = 'https://poloniex.com/public?command=' + command ret = urllib2.urlopen(urllib2.Request(url)) return json.loads(ret.read().decode('utf8')) elif command == "returnOrderBook": ret = urllib2.urlopen( urllib2.Request('https://poloniex.com/public?command=' + command + '¤cyPair=' + str(req['currencyPair']))) return json.loads(ret.read().decode('utf8')) elif command == "returnMarketTradeHistory": ret = urllib2.urlopen( urllib2.Request('https://poloniex.com/public?command=' + "returnTradeHistory" + '¤cyPair=' + str(req['currencyPair']))) return json.loads(ret.read().decode('utf8')) else: req['command'] = command req['nonce'] = int(time.time() * 1000) post_data = urllib.parse.urlencode(req) s = bytes(self.secret, encoding='utf8') post_data = bytes(post_data, encoding='utf8') sign = hmac.new(s, post_data, hashlib.sha512).hexdigest() headers = {'Sign': sign, 'Key': self.api_key} ret = urllib2.urlopen( urllib2.Request('https://poloniex.com/tradingApi', post_data, headers)) jsonRet = json.loads(ret.read().decode('utf8')) return self.post_process(jsonRet) @staticmethod def format_value(field, value, astype=None): if astype is not None: return astype(value) try: return POLONIEX_DATA_TYPE_MAP[field](value) except (KeyError, TypeError): return value def return_ticker(self): """ Returns a JSON object containing the following information for each currency pair: {currency_pair: {id: currency_id (int), highestBid: highest_bid (float), isFrozen: (0, 1) (int) quoteVolume: quote_volume (float) low24hr: low_24_hr (float), lowestAsk: lowest_ask (float), high24hr: high_24_hr (float), percentChange: pct_change (float), last: last (float), baseVolume: base_volume (float) } } :return: ^ """ res = self.api_query("returnTicker") for currency_pair, data in res.items(): for field, value in data.items(): data[field] = self.format_value(field, value) return res def return_24_volume(self): """ Returns a JSON object containing {currency_pair: {currency: volume, base_currency: volume}} :return: """ res = self.api_query("return24hVolume") for currency_pair, data in res.items(): if not hasattr(data, 'items'): continue for currency, value in data.items(): data[currency] = self.format_value(None, data[currency], float) return res def return_currency_pairs(self): """ Returns a list of available currency_pairs in Poloniex by taking the keys from Poloniex.return_24_volume. :return: """ return list( sorted( list(c for c in self.return_24_volume().keys() if not c.startswith('total')))) 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 return_balances(self, hide_zero=True): """ # Returns all of your balances. # Outputs: # {"BTC":"0.59098578","LTC":"3.31117268", ... } :param hide_zero: :return: """ res = self.api_query('returnBalances') if hide_zero: res = {k: float(v) for k, v in res.items() if float(v) > 0} else: res = {k: float(v) for k, v in res.items()} return res def return_open_orders(self, currency_pair): """ Returns your open orders for a given market, specified by the "currency_pair" POST parameter, e.g. "BTC_XCP" :param currency_pair: The currency pair e.g. "BTC_XCP" :return: # 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) """ return self.api_query('returnOpenOrders', {"currencyPair": currency_pair}) def return_trade_history(self, currency_pair): """ Returns your trade history for a given market, specified by the "currency_pair" POST parameter :param currency_pair: The currency pair e.g. "BTC_XCP" :return: # 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 """ return self.api_query('returnTradeHistory', {"currencyPair": currency_pair}) def buy(self, currency_pair, rate, amount): """ # Places a buy order in a given market. :param currency_pair: The curreny pair :param rate: price the order is buying at :param amount: Amount of coins to buy :return: """ return self.api_query('buy', { "CurrencyPair": currency_pair, "rate": rate, "amount": amount }) def sell(self, currency_pair, rate, amount): """ # Places a sell order in a given market. :param currency_pair: The curreny pair :param rate: price the order is selling at :param amount: Amount of coins to sell :return: The order number """ return self.api_query('sell', { "currencyPair": currency_pair, "rate": rate, "amount": amount }) def cancel(self, currency_pair, order_number): """ # Cancels an order you have placed in a given market. Required POST parameters are "currency_pair" and "order_number". :param currency_pair: The curreny pair :param order_number: The order number to cancel :return: succes 1 or 0 """ return self.api_query('cancelOrder', { "currencyPair": currency_pair, "orderNumber": order_number }) def withdraw(self, currency, amount, address): """ # 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". :param currency: The currency to withdraw :param amount: The amount of this coin to withdraw :param address: The withdrawal address :return: Text containing message about the withdrawal # Sample output: {"response":"Withdrew 2398 NXT."} """ return self.api_query('withdraw', { "currency": currency, "amount": amount, "address": address })