def get_order(self, order_id: str, pair: str) -> Dict: if self._conf['dry_run']: order = self._dry_run_open_orders[order_id] order.update({'id': order_id}) return order try: return self._api.fetch_order(order_id, pair) except ccxt.InvalidOrder as e: raise DependencyException(f'Could not get order. Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
def cancel_order(self, order_id: str, pair: str) -> None: if self._config['dry_run']: return try: return self._api.cancel_order(order_id, pair) except ccxt.InvalidOrder as e: raise InvalidOrderException( f'Could not cancel order. Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not cancel order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
def get_order(self, order_id: str, pair: str) -> Dict: if self._config['dry_run']: order = self._dry_run_open_orders[order_id] return order try: return self._api.fetch_order(order_id, pair) except ccxt.InvalidOrder as e: raise InvalidOrderException( f'Tried to get an invalid order (id: {order_id}). Message: {e}' ) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get order due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e
def buy(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force) -> Dict: if self._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { 'pair': pair, 'price': rate, 'amount': amount, 'type': ordertype, 'side': 'buy', 'remaining': 0.0, 'datetime': arrow.utcnow().isoformat(), 'status': 'closed', 'fee': None } return {'id': order_id} try: # Set the precision for amount and price(rate) as accepted by the exchange amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec( pair, rate) if ordertype != 'market' else None if time_in_force == 'gtc': return self._api.create_order(pair, ordertype, 'buy', amount, rate) else: return self._api.create_order(pair, ordertype, 'buy', amount, rate, {'timeInForce': time_in_force}) except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create limit buy order on market {pair}.' f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' f'Message: {e}') except ccxt.InvalidOrder as e: raise DependencyException( f'Could not create limit buy order on market {pair}.' f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' f'Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place buy order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
async def _async_get_candle_history( self, pair: str, ticker_interval: str, since_ms: Optional[int] = None) -> Tuple[str, str, List]: """ Asyncronously gets candle histories using fetch_ohlcv returns tuple: (pair, ticker_interval, ohlcv_list) """ try: # fetch ohlcv asynchronously s = '(' + arrow.get( since_ms // 1000).isoformat() + ') ' if since_ms is not None else '' logger.debug("Fetching pair %s, interval %s, since %s %s...", pair, ticker_interval, since_ms, s) data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval, since=since_ms) # Because some exchange sort Tickers ASC and other DESC. # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) # when GDAX returns a list of tickers DESC (newest first, oldest last) # Only sort if necessary to save computing time try: if data and data[0][0] > data[-1][0]: data = sorted(data, key=lambda x: x[0]) except IndexError: logger.exception("Error loading %s. Result was %s.", pair, data) return pair, ticker_interval, [] logger.debug("Done fetching pair %s, interval %s ...", pair, ticker_interval) return pair, ticker_interval, data except ccxt.NotSupported as e: raise OperationalException( f'Exchange {self._api.name} does not support fetching historical candlestick data.' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load ticker history due to {e.__class__.__name__}. ' f'Message: {e}') from e except ccxt.BaseError as e: raise OperationalException( f'Could not fetch ticker data. Msg: {e}') from e
def test_rpc_balance_handle(default_conf, mocker): mock_balance = { 'BTC': { 'free': 10.0, 'total': 12.0, 'used': 2.0, }, 'ETH': { 'free': 1.0, 'total': 5.0, 'used': 4.0, } } # ETH will be skipped due to mocked Error below mocker.patch.multiple( 'freqtrade.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=mock_balance), get_ticker=MagicMock( side_effect=TemporaryError('Could not load ticker due to xxx'))) freqtradebot = FreqtradeBot(default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['fiat_display_currency']) assert prec_satoshi(result['total'], 12) assert prec_satoshi(result['value'], 180000) assert 'USD' == result['symbol'] assert result['currencies'] == [{ 'currency': 'BTC', 'available': 10.0, 'balance': 12.0, 'pending': 2.0, 'est_btc': 12.0, }] assert result['total'] == 12.0
def get_balances(self) -> dict: if self._conf['dry_run']: return {} try: balances = self._api.fetch_balance() # Remove additional info from ccxt results balances.pop("info", None) balances.pop("free", None) balances.pop("total", None) balances.pop("used", None) return balances except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get balance due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: if self._conf['dry_run']: return [] if not self.exchange_has('fetchMyTrades'): return [] try: my_trades = self._api.fetch_my_trades(pair, since.timestamp()) matched_trades = [ trade for trade in my_trades if trade['order'] == order_id ] return matched_trades except ccxt.NetworkError as e: raise TemporaryError( f'Could not get trades due to networking error. Message: {e}') except ccxt.BaseError as e: raise OperationalException(e)
def get_order_book(self, pair: str, limit: int = 100) -> dict: """ get order book level 2 from exchange Notes: 20180619: bittrex doesnt support limits -.- """ try: return self._api.fetch_l2_order_book(pair, limit) except ccxt.NotSupported as e: raise OperationalException( f'Exchange {self._api.name} does not support fetching order book.' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get order book due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: if refresh or pair not in self._cached_ticker.keys(): try: data = self._api.fetch_ticker(pair) try: self._cached_ticker[pair] = { 'bid': float(data['bid']), 'ask': float(data['ask']), } except KeyError: logger.debug("Could not cache ticker data for %s", pair) return data except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e) else: logger.info("returning cached ticker-data for %s", pair) return self._cached_ticker[pair]
async def _async_fetch_trades(self, pair: str, since: Optional[int] = None, params: Optional[dict] = None) -> List[Dict]: """ Asyncronously gets trade history using fetch_trades. Handles exchange errors, does one call to the exchange. :param pair: Pair to fetch trade data for :param since: Since as integer timestamp in milliseconds returns: List of dicts containing trades """ try: # fetch trades asynchronously if params: logger.debug("Fetching trades for pair %s, params: %s ", pair, params) trades = await self._api_async.fetch_trades(pair, params=params, limit=1000) else: logger.debug( "Fetching trades for pair %s, since %s %s...", pair, since, '(' + arrow.get(since // 1000).isoformat() + ') ' if since is not None else '') trades = await self._api_async.fetch_trades(pair, since=since, limit=1000) return trades except ccxt.NotSupported as e: raise OperationalException( f'Exchange {self._api.name} does not support fetching historical trade data.' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load trade history due to {e.__class__.__name__}. ' f'Message: {e}') from e except ccxt.BaseError as e: raise OperationalException( f'Could not fetch trade data. Msg: {e}') from e
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: if self._config['dry_run']: return [] if not self.exchange_has('fetchMyTrades'): return [] try: # Allow 5s offset to catch slight time offsets (discovered in #1185) # since needs to be int in milliseconds my_trades = self._api.fetch_my_trades( pair, int((since.timestamp() - 5) * 1000)) matched_trades = [ trade for trade in my_trades if trade['order'] == order_id ] return matched_trades except ccxt.NetworkError as e: raise TemporaryError( f'Could not get trades due to networking error. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e
def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1, price=1, taker_or_maker='maker') -> float: try: # validate that markets are loaded before trying to get fee if self._api.markets is None or len(self._api.markets) == 0: self._api.load_markets() return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount, price=price, takerOrMaker=taker_or_maker)['rate'] except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get fee info due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: if refresh or pair not in self._cached_ticker.keys(): try: if pair not in self._api.markets or not self._api.markets[ pair].get('active'): raise DependencyException(f"Pair {pair} not available") data = self._api.fetch_ticker(pair) try: self._cached_ticker[pair] = { 'bid': float(data['bid']), 'ask': float(data['ask']), } except KeyError: logger.debug("Could not cache ticker data for %s", pair) return data except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load ticker due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e else: logger.info("returning cached ticker-data for %s", pair) return self._cached_ticker[pair]
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: """ creates a stoploss limit order. NOTICE: it is not supported by all exchanges. only binance is tested for now. """ # Set the precision for amount and price(rate) as accepted by the exchange amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) stop_price = self.symbol_price_prec(pair, stop_price) # Ensure rate is less than stop price if stop_price <= rate: raise OperationalException( 'In stoploss limit order, stop price should be more than limit price' ) if self._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { 'info': {}, 'id': order_id, 'pair': pair, 'price': stop_price, 'amount': amount, 'type': 'stop_loss_limit', 'side': 'sell', 'remaining': amount, 'datetime': arrow.utcnow().isoformat(), 'status': 'open', 'fee': None } return self._dry_run_open_orders[order_id] try: order = self._api.create_order(pair, 'stop_loss_limit', 'sell', amount, rate, {'stopPrice': stop_price}) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s' % (pair, stop_price, rate)) return order except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to place stoploss limit order on market {pair}. ' f'Tried to put a stoploss amount {amount} with ' f'stop {stop_price} and limit {rate} (total {rate*amount}).' f'Message: {e}') except ccxt.InvalidOrder as e: raise DependencyException( f'Could not place stoploss limit order on market {pair}.' f'Tried to place stoploss amount {amount} with ' f'stop {stop_price} and limit {rate} (total {rate*amount}).' f'Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place stoploss limit order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)