Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
 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'])
Exemplo n.º 4
0
 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
Exemplo n.º 5
0
    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'])
Exemplo n.º 6
0
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
Exemplo n.º 7
0
    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]
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
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]
Exemplo n.º 11
0
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))
Exemplo n.º 12
0
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)
Exemplo n.º 13
0
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
Exemplo n.º 14
0
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'}
Exemplo n.º 15
0
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)
Exemplo n.º 16
0
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()
Exemplo n.º 17
0
 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'])
Exemplo n.º 18
0
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 + '&currencyPair=' +
                                str(req['currencyPair'])))
            return json.loads(ret.read().decode('utf8'))

        elif command == "returnMarketTradeHistory":
            ret = urllib2.urlopen(
                urllib2.Request('https://poloniex.com/public?command=' +
                                "returnTradeHistory" + '&currencyPair=' +
                                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
        })