예제 #1
0
 def ticker(self, symbol: str, retry=None, retry_wait=0):
     sym = symbol_std_to_exchange(symbol, self.ID)
     data = self._get(f"/v1/pubticker/{sym}", retry, retry_wait)
     return {'symbol': symbol,
             'feed': self.ID,
             'bid': Decimal(data['bid']),
             'ask': Decimal(data['ask'])
             }
예제 #2
0
 def trades(self,
            symbol: str,
            start=None,
            end=None,
            retry=None,
            retry_wait=10):
     symbol = symbol_std_to_exchange(symbol, self.ID)
     for data in self._get_trades(symbol, start, end, retry, retry_wait):
         yield data
예제 #3
0
 def ticker(self, symbol: str, retry=None, retry_wait=10):
     sym = symbol_std_to_exchange(symbol, self.ID)
     data = self._get("returnTicker", retry=retry, retry_wait=retry_wait)
     return {
         'symbol': symbol,
         'feed': self.ID,
         'bid': Decimal(data[sym]['lowestAsk']),
         'ask': Decimal(data[sym]['highestBid'])
     }
예제 #4
0
 def l2_book(self, symbol: str, retry=None, retry_wait=10):
     ret = {symbol: {BID: sd(), ASK: sd()}}
     data = next(
         self._get('orderBook/L2', symbol_std_to_exchange(symbol, self.ID),
                   None, None, retry, retry_wait))
     for update in data:
         side = ASK if update['side'] == 'Sell' else BID
         ret[symbol][side][update['price']] = update['size']
     return ret
예제 #5
0
    def ticker(self, symbol: str, retry=None, retry_wait=0):
        sym = symbol_std_to_exchange(symbol, self.ID)
        data = self._get(f"/markets/{sym}", retry=retry, retry_wait=retry_wait)

        return {
            'symbol': symbol,
            'feed': self.ID,
            'bid': data['result']['bid'],
            'ask': data['result']['ask']
        }
예제 #6
0
 def l2_book(self, symbol: str, retry=None, retry_wait=0):
     sym = symbol_std_to_exchange(symbol, self.ID)
     data = self._get("returnOrderBook", {'currencyPair': sym},
                      retry=retry,
                      retry_wait=retry_wait)
     return {
         BID: sd({Decimal(u[0]): Decimal(u[1])
                  for u in data['bids']}),
         ASK: sd({Decimal(u[0]): Decimal(u[1])
                  for u in data['asks']})
     }
예제 #7
0
 def l2_book(self, symbol: str, retry=None, retry_wait=0):
     sym = symbol_std_to_exchange(symbol, self.ID)
     data = self._get(f"/markets/{sym}/orderbook", {'depth': 100},
                      retry=retry,
                      retry_wait=retry_wait)
     return {
         BID: sd({u[0]: u[1]
                  for u in data['result']['bids']}),
         ASK: sd({u[0]: u[1]
                  for u in data['result']['asks']})
     }
예제 #8
0
 def l2_book(self, symbol: str, retry=None, retry_wait=0):
     sym = symbol_std_to_exchange(symbol, self.ID)
     data = self._get(f"/v1/book/{sym}", retry, retry_wait)
     return {
         BID: sd({
             Decimal(u['price']): Decimal(u['amount'])
             for u in data['bids']
         }),
         ASK: sd({
             Decimal(u['price']): Decimal(u['amount'])
             for u in data['asks']
         })
     }
예제 #9
0
    async def _book(self, msg: dict, timestamp: float):
        """
        {
            'ch':'market.BTC_CW.depth.step0',
            'ts':1565857755564,
            'tick':{
                'mrid':14848858327,
                'id':1565857755,
                'bids':[
                    [  Decimal('9829.99'), 1], ...
                ]
                'asks':[
                    [ 9830, 625], ...
                ]
            },
            'ts':1565857755552,
            'version':1565857755,
            'ch':'market.BTC_CW.depth.step0'
        }
        """
        pair = symbol_std_to_exchange(msg['ch'].split('.')[1], self.id)
        data = msg['tick']
        forced = pair not in self.l2_book

        # When Huobi Delists pairs, empty updates still sent:
        # {'ch': 'market.AKRO-USD.depth.step0', 'ts': 1606951241196, 'tick': {'mrid': 50651100044, 'id': 1606951241, 'ts': 1606951241195, 'version': 1606951241, 'ch': 'market.AKRO-USD.depth.step0'}}
        # {'ch': 'market.AKRO-USD.depth.step0', 'ts': 1606951242297, 'tick': {'mrid': 50651100044, 'id': 1606951242, 'ts': 1606951242295, 'version': 1606951242, 'ch': 'market.AKRO-USD.depth.step0'}}
        if 'bids' in data and 'asks' in data:
            update = {
                BID:
                sd({
                    Decimal(price): Decimal(amount)
                    for price, amount in data['bids']
                }),
                ASK:
                sd({
                    Decimal(price): Decimal(amount)
                    for price, amount in data['asks']
                })
            }

            if not forced:
                self.previous_book[pair] = self.l2_book[pair]
            self.l2_book[pair] = update

            await self.book_callback(self.l2_book[pair], L2_BOOK, pair, forced,
                                     False,
                                     timestamp_normalize(self.id,
                                                         msg['ts']), timestamp)
예제 #10
0
    def ticker(self, symbol: str, retry=None, retry_wait=0):
        sym = symbol_std_to_exchange(symbol, self.ID + 'REST')
        data = self._post_public("/public/Ticker",
                                 payload={'pair': sym},
                                 retry=retry,
                                 retry_wait=retry_wait)

        data = data['result']
        for _, val in data.items():
            return {
                'symbol': symbol,
                'feed': self.ID,
                'bid': Decimal(val['b'][0]),
                'ask': Decimal(val['a'][0])
            }
예제 #11
0
 def l2_book(self, symbol: str, retry=None, retry_wait=0):
     sym = symbol_std_to_exchange(symbol, self.ID + 'REST')
     data = self._post_public("/public/Depth", {
         'pair': sym,
         'count': 200
     },
                              retry=retry,
                              retry_wait=retry_wait)
     for _, val in data['result'].items():
         return {
             BID: sd({Decimal(u[0]): Decimal(u[1])
                      for u in val['bids']}),
             ASK: sd({Decimal(u[0]): Decimal(u[1])
                      for u in val['asks']})
         }
예제 #12
0
    def trades(self,
               symbol: str,
               start=None,
               end=None,
               retry=None,
               retry_wait=10):
        sym = symbol_std_to_exchange(symbol, self.ID)
        params = {'limit_trades': 500}
        if start:
            params['since'] = int(pd.Timestamp(start).timestamp() * 1000)
        if end:
            end_ts = int(pd.Timestamp(end).timestamp() * 1000)

        def _trade_normalize(trade):
            return {
                'feed': self.ID,
                'order_id': trade['tid'],
                'symbol': sym,
                'side': trade['type'],
                'amount': Decimal(trade['amount']),
                'price': Decimal(trade['price']),
                'timestamp': trade['timestampms'] / 1000.0
            }

        while True:
            data = reversed(
                self._get(f"/v1/trades/{sym}?",
                          retry,
                          retry_wait,
                          params=params))
            if end:
                data = [
                    _trade_normalize(d) for d in data
                    if d['timestampms'] <= end_ts
                ]
            else:
                data = [_trade_normalize(d) for d in data]
            yield data

            if start:
                params['since'] = int(data[-1]['timestamp'] * 1000) + 1
            if len(data) < 500:
                break
            if not start and not end:
                break
            # GEMINI rate limits to 120 requests a minute
            sleep(RATE_LIMIT_SLEEP)
예제 #13
0
    def trade_history(self, symbol: str, start=None, end=None):
        sym = symbol_std_to_exchange(symbol, self.ID)

        params = {'symbol': sym, 'limit_trades': 500}
        if start:
            params['timestamp'] = API._timestamp(start).timestamp()

        data = self._post("/v1/mytrades", params)
        return [{
            'price': Decimal(trade['price']),
            'amount': Decimal(trade['amount']),
            'timestamp': trade['timestampms'] / 1000,
            'side': BUY if trade['type'].lower() == 'buy' else SELL,
            'fee_currency': trade['fee_currency'],
            'fee_amount': trade['fee_amount'],
            'trade_id': trade['tid'],
            'order_id': trade['order_id']
        } for trade in data]
예제 #14
0
    def trades(self, symbol, start=None, end=None, retry=None, retry_wait=10):
        """
        data format

        {
            'timestamp': '2018-01-01T23:59:59.907Z',
            'symbol': 'XBTUSD',
            'side': 'Buy',
            'size': 1900,
            'price': 13477,
            'tickDirection': 'ZeroPlusTick',
            'trdMatchID': '14fcc8d7-d056-768d-3c46-1fdf98728343',
            'grossValue': 14098000,
            'homeNotional': 0.14098,
            'foreignNotional': 1900
        }
        """
        symbol = symbol_std_to_exchange(symbol, self.ID)

        d = dt.utcnow().date()
        d -= timedelta(days=1)
        rest_end_date = pd.Timestamp(dt(d.year, d.month, d.day))
        start = API._timestamp(start) if start else start
        end = API._timestamp(end) if end else end
        rest_start = start
        s3_scrape = False

        if start:
            if rest_end_date - pd.Timedelta(microseconds=1) > start:
                rest_start = rest_end_date
                s3_scrape = True

        if s3_scrape:
            rest_end_date -= pd.Timedelta(microseconds=1)
            if API._timestamp(end) < rest_end_date:
                rest_end_date = end
            for data in self._scrape_s3(symbol, 'trade', start, rest_end_date):
                yield list(map(self._s3_data_normalization, data))

        if end is None or end > rest_end_date:
            for data in self._get('trade', symbol, rest_start, end, retry,
                                  retry_wait):
                yield list(map(self._trade_normalization, data))
예제 #15
0
    def place_order(self, symbol: str, side: str, order_type: str, amount: Decimal, price=None, client_order_id=None, options=None):
        if not price:
            raise ValueError('Gemini only supports limit orders, must specify price')
        ot = normalize_trading_options(self.ID, order_type)
        sym = symbol_std_to_exchange(symbol, self.ID)

        parameters = {
            'type': ot,
            'symbol': sym,
            'side': side,
            'amount': str(amount),
            'price': str(price),
            'options': [normalize_trading_options(self.ID, o) for o in options] if options else []
        }

        if client_order_id:
            parameters['client_order_id'] = client_order_id

        data = self._post("/v1/order/new", parameters)
        return Gemini._order_status(data)
예제 #16
0
    def _historical_trades(self,
                           symbol,
                           start_date,
                           end_date,
                           retry,
                           retry_wait,
                           freq='6H'):
        symbol = symbol_std_to_exchange(symbol, self.ID + 'REST')

        @request_retry(self.ID, retry, retry_wait)
        def helper(start_date):
            endpoint = f"{self.api}/public/Trades?symbol={symbol}&since={start_date}"
            return requests.get(endpoint)

        start_date = API._timestamp(start_date).timestamp() * 1000000000
        end_date = API._timestamp(end_date).timestamp() * 1000000000

        while start_date < end_date:
            r = helper(start_date)

            if r.status_code == 504 or r.status_code == 520:
                # cloudflare gateway timeout or other error
                time.sleep(60)
                continue
            elif r.status_code != 200:
                self._handle_error(r, LOG)
            else:
                time.sleep(RATE_LIMIT_SLEEP)

            data = r.json()
            if 'error' in data and data['error']:
                if data['error'] == ['EAPI:Rate limit exceeded']:
                    time.sleep(5)
                    continue
                else:
                    raise Exception(
                        f"Error processing URL {r.url}: {data['error']}")

            yield data

            start_date = int(data['result']['last'])
예제 #17
0
 def trades(self,
            symbol: str,
            start=None,
            end=None,
            retry=None,
            retry_wait=10):
     if start:
         if not end:
             end = pd.Timestamp.utcnow()
         for data in self._historical_trades(symbol, start, end, retry,
                                             retry_wait):
             yield list(
                 map(lambda x: self._trade_normalization(x, symbol),
                     data['result'][next(iter(data['result']))]))
     else:
         sym = symbol_std_to_exchange(symbol, self.ID + 'REST')
         data = self._post_public("/public/Trades", {'pair': sym},
                                  retry=retry,
                                  retry_wait=retry_wait)
         data = data['result']
         data = data[list(data.keys())[0]]
         yield [self._trade_normalization(d, symbol) for d in data]
예제 #18
0
    def trades(self, symbol, start=None, end=None, retry=None, retry_wait=10):
        symbol = symbol_std_to_exchange(symbol, self.ID)

        @request_retry(self.ID, retry, retry_wait)
        def helper(s=None, e=None):
            data = self._get("returnTradeHistory", {
                'currencyPair': symbol,
                'start': s,
                'end': e
            })
            data.reverse()
            return data

        if not start:
            yield map(lambda x: self._trade_normalize(x, symbol), helper())

        else:
            if not end:
                end = pd.Timestamp.utcnow()
            start = API._timestamp(start)
            end = API._timestamp(end) - pd.Timedelta(nanoseconds=1)

            start = int(start.timestamp())
            end = int(end.timestamp())

            s = start
            e = start + 21600
            while True:
                if e > end:
                    e = end

                yield map(lambda x: self._trade_normalize(x, symbol),
                          helper(s=s, e=e))

                s = e
                e += 21600
                if s >= end:
                    break
예제 #19
0
    def place_order(self,
                    symbol: str,
                    side: str,
                    order_type: str,
                    amount: Decimal,
                    price=None,
                    options=None):
        if not price:
            raise ValueError(
                'Poloniex only supports limit orders, must specify price')
        # Poloniex only supports limit orders, so check the order type
        _ = normalize_trading_options(self.ID, order_type)
        parameters = {}
        if options:
            parameters = {
                normalize_trading_options(self.ID, o): 1
                for o in options
            }
        parameters['currencyPair'] = symbol_std_to_exchange(symbol, self.ID)
        parameters['amount'] = str(amount)
        parameters['rate'] = str(price)

        endpoint = None
        if side == BUY:
            endpoint = 'buy'
        elif side == SELL:
            endpoint = 'sell'

        data = self._post(endpoint, parameters)
        order = self.order_status(data['orderNumber'])

        if 'error' not in order:
            if len(data['resultingTrades']) == 0:
                return order
            else:
                return Poloniex._trade_status(data['resultingTrades'], symbol,
                                              data['orderNumber'], amount)
        return data
예제 #20
0
 async def _trade(self, msg: dict, timestamp: float):
     """
     {
         'ch': 'market.btcusd.trade.detail',
         'ts': 1549773923965,
         'tick': {
             'id': 100065340982,
             'ts': 1549757127140,
             'data': [{'id': '10006534098224147003732', 'amount': Decimal('0.0777'), 'price': Decimal('3669.69'), 'direction': 'buy', 'ts': 1549757127140}]}
     }
     """
     for trade in msg['tick']['data']:
         await self.callback(
             TRADES,
             feed=self.id,
             symbol=symbol_std_to_exchange(msg['ch'].split('.')[1],
                                           self.id),
             order_id=trade['id'],
             side=BUY if trade['direction'] == 'buy' else SELL,
             amount=Decimal(trade['amount']),
             price=Decimal(trade['price']),
             timestamp=timestamp_normalize(self.id, trade['ts']),
             receipt_timestamp=timestamp)
예제 #21
0
    def trade_history(self, symbol: str, start=None, end=None):
        payload = {'currencyPair': symbol_std_to_exchange(symbol, self.ID)}

        if start:
            payload['start'] = API._timestamp(start).timestamp()
        if end:
            payload['end'] = API._timestamp(end).timestamp()

        payload['limit'] = 10000
        data = self._post("returnTradeHistory", payload)
        ret = []
        for trade in data:
            ret.append({
                'price': Decimal(trade['rate']),
                'amount': Decimal(trade['amount']),
                'timestamp': pd.Timestamp(trade['date']).timestamp(),
                'side': BUY if trade['type'] == 'buy' else SELL,
                'fee_currency': symbol.split('-')[1],
                'fee_amount': Decimal(trade['fee']),
                'trade_id': trade['tradeID'],
                'order_id': trade['orderNumber']
            })
        return ret
예제 #22
0
def test_bitcoincom_symbol_conversions():
    load_exchange_symbol_mapping(BITCOINCOM)
    for _, symbol in bitcoincom_symbols().items():
        std = symbol_exchange_to_std(symbol)
        assert symbol == symbol_std_to_exchange(std, BITCOINCOM)
예제 #23
0
    def __init__(self,
                 address: Union[dict, str],
                 sandbox=False,
                 symbols=None,
                 channels=None,
                 subscription=None,
                 config: Union[Config, dict, str] = None,
                 callbacks=None,
                 max_depth=None,
                 book_interval=1000,
                 snapshot_interval=False,
                 checksum_validation=False,
                 cross_check=False,
                 origin=None):
        """
        address: str, or dict
            address to be used to create the connection.
            The address protocol (wss or https) will be used to determine the connection type.
            Use a "str" to pass one single address, or a dict of option/address
        sandbox: bool
            For authenticated channels, run against the sandbox websocket (when True)
        max_depth: int
            Maximum number of levels per side to return in book updates
        book_interval: int
            Number of updates between snapshots. Only applicable when book deltas are enabled.
            Book deltas are enabled by subscribing to the book delta callback.
        snapshot_interval: bool/int
            Number of updates between snapshots. Only applicable when book delta is not enabled.
            Updates between snapshots are not delivered to the client
        checksum_validation: bool
            Toggle checksum validation, when supported by an exchange.
        cross_check: bool
            Toggle a check for a crossed book. Should not be needed on exchanges that support
            checksums or provide message sequence numbers.
        origin: str
            Passed into websocket connect. Sets the origin header.
        """
        if isinstance(config, Config):
            LOG.info(
                '%s: reuse object Config containing the following main keys: %s',
                self.id, ", ".join(config.config.keys()))
            self.config = config
        else:
            LOG.info('%s: create Config from type: %r', self.id, type(config))
            self.config = Config(config)

        self.subscription = defaultdict(set)
        self.address = address
        self.book_update_interval = book_interval
        self.snapshot_interval = snapshot_interval
        self.cross_check = cross_check
        self.updates = defaultdict(int)
        self.do_deltas = False
        self.symbols = []
        self.normalized_symbols = []
        self.channels = []
        self.max_depth = max_depth
        self.previous_book = defaultdict(dict)
        self.origin = origin
        self.checksum_validation = checksum_validation
        self.ws_defaults = {
            'ping_interval': 10,
            'ping_timeout': None,
            'max_size': 2**23,
            'max_queue': None,
            'origin': self.origin
        }
        self.key_id = os.environ.get(f'CF_{self.id}_KEY_ID') or self.config[
            self.id.lower()].key_id
        self.key_secret = os.environ.get(
            f'CF_{self.id}_KEY_SECRET') or self.config[
                self.id.lower()].key_secret
        self._feed_config = defaultdict(list)

        load_exchange_symbol_mapping(self.id, key_id=self.key_id)

        if subscription is not None and (symbols is not None
                                         or channels is not None):
            raise ValueError(
                "Use subscription, or channels and symbols, not both")

        if subscription is not None:
            for channel in subscription:
                chan = feed_to_exchange(self.id, channel)
                if is_authenticated_channel(channel):
                    if not self.key_id or not self.key_secret:
                        raise ValueError(
                            "Authenticated channel subscribed to, but no auth keys provided"
                        )
                self.normalized_symbols.extend(subscription[channel])
                self.subscription[chan].update([
                    symbol_std_to_exchange(symbol, self.id)
                    for symbol in subscription[channel]
                ])
                self._feed_config[channel].extend(self.normalized_symbols)

        if symbols:
            self.normalized_symbols = symbols
            self.symbols = [
                symbol_std_to_exchange(symbol, self.id) for symbol in symbols
            ]
        if channels:
            self.channels = list(
                set([feed_to_exchange(self.id, chan) for chan in channels]))
            [
                self._feed_config[channel].extend(self.normalized_symbols)
                for channel in channels
            ]
            if any(is_authenticated_channel(chan) for chan in channels):
                if not self.key_id or not self.key_secret:
                    raise ValueError(
                        "Authenticated channel subscribed to, but no auth keys provided"
                    )
        self._feed_config = dict(self._feed_config)

        self.l3_book = {}
        self.l2_book = {}
        self.callbacks = {
            FUNDING: Callback(None),
            FUTURES_INDEX: Callback(None),
            L2_BOOK: Callback(None),
            L3_BOOK: Callback(None),
            LIQUIDATIONS: Callback(None),
            OPEN_INTEREST: Callback(None),
            MARKET_INFO: Callback(None),
            TICKER: Callback(None),
            TRADES: Callback(None),
            TRANSACTIONS: Callback(None),
            VOLUME: Callback(None),
            CANDLES: Callback(None),
            ORDER_INFO: Callback(None)
        }

        if callbacks:
            for cb_type, cb_func in callbacks.items():
                self.callbacks[cb_type] = cb_func
                if cb_type == BOOK_DELTA:
                    self.do_deltas = True

        for key, callback in self.callbacks.items():
            if not isinstance(callback, list):
                self.callbacks[key] = [callback]
예제 #24
0
def test_coinbase_symbol_conversions():
    load_exchange_symbol_mapping(COINBASE)
    for _, symbol in coinbase_symbols().items():
        assert symbol_exchange_to_std(symbol) == symbol_std_to_exchange(
            symbol, COINBASE)
예제 #25
0
def test_poloniex_symbol_conversions():
    load_exchange_symbol_mapping(POLONIEX)
    for _, symbol in poloniex_symbols().items():
        std = symbol_exchange_to_std(symbol)
        assert symbol == symbol_std_to_exchange(std, POLONIEX)
예제 #26
0
def test_bitfinex_symbol_conversions():
    load_exchange_symbol_mapping(BITFINEX)
    for _, symbol in bitfinex_symbols().items():
        std = symbol_exchange_to_std(symbol)
        assert symbol == symbol_std_to_exchange(std, BITFINEX)
예제 #27
0
def test_hitbtc_symbol_conversions():
    load_exchange_symbol_mapping(HITBTC)
    for _, symbol in hitbtc_symbols().items():
        std = symbol_exchange_to_std(symbol)
        assert symbol == symbol_std_to_exchange(std, HITBTC)
예제 #28
0
def test_blockchain_symbol_conversions():
    load_exchange_symbol_mapping(BLOCKCHAIN)
    for _, symbol in blockchain_symbols().items():
        assert symbol_exchange_to_std(symbol) == symbol_std_to_exchange(
            symbol, BLOCKCHAIN)
예제 #29
0
def test_gemini_symbol_conversions():
    load_exchange_symbol_mapping(GEMINI)
    for normalized, symbol in gemini_symbols().items():
        std = symbol_exchange_to_std(symbol)
        assert symbol == symbol_std_to_exchange(std, GEMINI)
        assert normalized == std
예제 #30
0
def test_bitstamp_symbol_conversions():
    load_exchange_symbol_mapping(BITSTAMP)
    for _, symbol in bitstamp_symbols().items():
        std = symbol_exchange_to_std(symbol)
        assert symbol == symbol_std_to_exchange(std, BITSTAMP)