예제 #1
0
    def get_balances(self) -> dict:
        if self._config['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}'
            ) from e
        except ccxt.BaseError as e:
            raise OperationalException(e) from e
예제 #2
0
    def _load_config_file(self, path: str) -> Dict[str, Any]:
        """
        Loads a config file from the given path
        :param path: path as str
        :return: configuration as dictionary
        """
        try:
            with open(path) as file:
                conf = json.load(file)
        except FileNotFoundError:
            raise OperationalException(
                f'Config file "{path}" not found!'
                ' Please create a config file or check whether it exists.')

        if 'internals' not in conf:
            conf['internals'] = {}
        logger.info('Validating configuration ...')

        return self._validate_config(conf)
예제 #3
0
def load_data(
    datadir: str,
    ticker_interval: str,
    pairs: List[str],
    refresh_pairs: Optional[bool] = False,
    exchange: Optional[Exchange] = None,
    timerange: TimeRange = TimeRange(None, None, 0, 0)
) -> Dict[str, List]:
    """
    Loads ticker history data for the given parameters
    :return: dict
    """
    result = {}

    # If the user force the refresh of pairs
    if refresh_pairs:
        logger.info('Download data for all pairs and store them in %s',
                    datadir)
        if not exchange:
            raise OperationalException(
                "Exchange needs to be initialized when "
                "calling load_data with refresh_pairs=True")
        download_pairs(datadir,
                       exchange,
                       pairs,
                       ticker_interval,
                       timerange=timerange)

    for pair in pairs:
        pairdata = load_tickerdata_file(datadir,
                                        pair,
                                        ticker_interval,
                                        timerange=timerange)
        if pairdata:
            result[pair] = pairdata
        else:
            logger.warning(
                'No data for pair: "%s", Interval: %s. '
                'Use --refresh-pairs-cached to download the data', pair,
                ticker_interval)

    return result
예제 #4
0
파일: history.py 프로젝트: sprgn/freqtrade
def load_pair_history(pair: str,
                      ticker_interval: str,
                      datadir: Optional[Path],
                      timerange: TimeRange = TimeRange(None, None, 0, 0),
                      refresh_pairs: bool = False,
                      exchange: Optional[Exchange] = None,
                      fill_up_missing: bool = True
                      ) -> DataFrame:
    """
    Loads cached ticker history for the given pair.
    :return: DataFrame with ohlcv data
    """

    # If the user force the refresh of pairs
    if refresh_pairs:
        if not exchange:
            raise OperationalException("Exchange needs to be initialized when "
                                       "calling load_data with refresh_pairs=True")

        logger.info('Download data for pair and store them in %s', datadir)
        download_pair_history(datadir=datadir,
                              exchange=exchange,
                              pair=pair,
                              tick_interval=ticker_interval,
                              timerange=timerange)

    pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)

    if pairdata:
        if timerange.starttype == 'date' and pairdata[0][0] > timerange.startts * 1000:
            logger.warning('Missing data at start for pair %s, data starts at %s',
                           pair, arrow.get(pairdata[0][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
        if timerange.stoptype == 'date' and pairdata[-1][0] < timerange.stopts * 1000:
            logger.warning('Missing data at end for pair %s, data ends at %s',
                           pair,
                           arrow.get(pairdata[-1][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
        return parse_ticker_dataframe(pairdata, ticker_interval, fill_up_missing)
    else:
        logger.warning('No data for pair: "%s", Interval: %s. '
                       'Use --refresh-pairs-cached to download the data',
                       pair, ticker_interval)
        return None
예제 #5
0
    def get_real_amount(self, trade: Trade, order: Dict) -> float:
        """
        Get real amount for the trade
        Necessary for exchanges which charge fees in base currency (e.g. binance)
        """
        order_amount = order['amount']
        # Only run for closed orders
        if trade.fee_open == 0 or order['status'] == 'open':
            return order_amount

        # use fee from order-dict if possible
        if 'fee' in order and order['fee'] and (order['fee'].keys() >= {'currency', 'cost'}):
            if trade.pair.startswith(order['fee']['currency']):
                new_amount = order_amount - order['fee']['cost']
                logger.info("Applying fee on amount for %s (from %s to %s) from Order",
                            trade, order['amount'], new_amount)
                return new_amount

        # Fallback to Trades
        trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair,
                                                    trade.open_date)

        if len(trades) == 0:
            logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade)
            return order_amount
        amount = 0
        fee_abs = 0
        for exectrade in trades:
            amount += exectrade['amount']
            if "fee" in exectrade and (exectrade['fee'].keys() >= {'currency', 'cost'}):
                # only applies if fee is in quote currency!
                if trade.pair.startswith(exectrade['fee']['currency']):
                    fee_abs += exectrade['fee']['cost']

        if amount != order_amount:
            logger.warning(f"Amount {amount} does not match amount {trade.amount}")
            raise OperationalException("Half bought? Amounts don't match")
        real_amount = amount - fee_abs
        if fee_abs != 0:
            logger.info(f"Applying fee on amount for {trade} "
                        f"(from {order_amount} to {real_amount}) from Trades")
        return real_amount
예제 #6
0
    def __init__(self, config: Dict[str, Any]) -> None:
        self.config = config

        # Reset keys for backtesting
        remove_credentials(self.config)
        self.strategylist: List[IStrategy] = []
        self.exchange = ExchangeResolver(self.config['exchange']['name'],
                                         self.config).exchange

        if config.get('fee'):
            self.fee = config['fee']
        else:
            self.fee = self.exchange.get_fee()

        if self.config.get('runmode') != RunMode.HYPEROPT:
            self.dataprovider = DataProvider(self.config, self.exchange)
            IStrategy.dp = self.dataprovider

        if self.config.get('strategy_list', None):
            for strat in list(self.config['strategy_list']):
                stratconf = deepcopy(self.config)
                stratconf['strategy'] = strat
                self.strategylist.append(StrategyResolver(stratconf).strategy)
                validate_config_consistency(stratconf)

        else:
            # No strategy list specified, only one strategy
            self.strategylist.append(StrategyResolver(self.config).strategy)
            validate_config_consistency(self.config)

        if "ticker_interval" not in self.config:
            raise OperationalException(
                "Ticker-interval needs to be set in either configuration "
                "or as cli argument `--ticker-interval 5m`")
        self.timeframe = str(self.config.get('ticker_interval'))
        self.timeframe_mins = timeframe_to_minutes(self.timeframe)

        # Get maximum required startup period
        self.required_startup = max(
            [strat.startup_candle_count for strat in self.strategylist])
        # Load one (first) strategy
        self._set_strategy(self.strategylist[0])
예제 #7
0
def test_main_operational_exception(mocker, default_conf, caplog) -> None:
    patch_exchange(mocker)
    mocker.patch.multiple(
        'freqtrade.freqtradebot.FreqtradeBot',
        _init_modules=MagicMock(),
        worker=MagicMock(side_effect=OperationalException('Oh snap!')),
        cleanup=MagicMock(),
    )
    mocker.patch('freqtrade.configuration.Configuration._load_config_file',
                 lambda *args, **kwargs: default_conf)
    mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())

    args = ['-c', 'config.json.example']

    # Test Main + the KeyboardInterrupt exception
    with pytest.raises(SystemExit):
        main(args)
    assert log_has('Using config: config.json.example ...',
                   caplog.record_tuples)
    assert log_has('Oh snap!', caplog.record_tuples)
예제 #8
0
    def __init__(self, config: Optional[Dict] = None) -> None:
        """
        Load the custom class from config parameter
        :param config: configuration dictionary or None
        """
        config = config or {}

        # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt
        hyperopt_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS
        self.hyperoptloss = self._load_hyperoptloss(
            hyperopt_name, extra_dir=config.get('hyperopt_path'))

        # Assign ticker_interval to be used in hyperopt
        self.hyperoptloss.__class__.ticker_interval = str(
            config['ticker_interval'])

        if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'):
            raise OperationalException(
                f"Found hyperopt {hyperopt_name} does not implement `hyperopt_loss_function`."
            )
예제 #9
0
    def validate_pairs(self, pairs: List[str]) -> None:
        """
        Checks if all given pairs are tradable on the current exchange.
        Raises OperationalException if one pair is not available.
        :param pairs: list of pairs
        :return: None
        """

        if not self.markets:
            logger.warning(
                'Unable to validate pairs (assuming they are correct).')
        #     return

        for pair in pairs:
            # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
            # TODO: add a support for having coins in BTC/USDT format
            if self.markets and pair not in self.markets:
                raise OperationalException(
                    f'Pair {pair} is not available on {self.name}. '
                    f'Please remove {pair} from your whitelist.')
예제 #10
0
    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:
            # Allow 5s offset to catch slight time offsets (discovered in #1185)
            my_trades = self._api.fetch_my_trades(pair, since.timestamp() - 5)
            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)
예제 #11
0
    def __init__(self, config: Dict[str, Any]) -> None:
        self.config = config

        # Reset keys for backtesting
        self.config['exchange']['key'] = ''
        self.config['exchange']['secret'] = ''
        self.config['exchange']['password'] = ''
        self.config['exchange']['uid'] = ''
        self.config['dry_run'] = True
        self.strategylist: List[IStrategy] = []
        self.exchange = ExchangeResolver(self.config['exchange']['name'],
                                         self.config).exchange

        if config.get('fee'):
            self.fee = config['fee']
        else:
            self.fee = self.exchange.get_fee()

        if self.config.get('runmode') != RunMode.HYPEROPT:
            self.dataprovider = DataProvider(self.config, self.exchange)
            IStrategy.dp = self.dataprovider

        if self.config.get('strategy_list', None):
            for strat in list(self.config['strategy_list']):
                stratconf = deepcopy(self.config)
                stratconf['strategy'] = strat
                self.strategylist.append(StrategyResolver(stratconf).strategy)

        else:
            # No strategy list specified, only one strategy
            self.strategylist.append(StrategyResolver(self.config).strategy)

        if "ticker_interval" not in self.config:
            raise OperationalException(
                "Ticker-interval needs to be set in either configuration "
                "or as cli argument `--ticker-interval 5m`")
        self.ticker_interval = str(self.config.get('ticker_interval'))
        self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval)

        # Load one (first) strategy
        self._set_strategy(self.strategylist[0])
예제 #12
0
def init(db_url: str, clean_open_orders: bool = False) -> None:
    """
    Initializes this module with the given config,
    registers all known command handlers
    and starts polling for message updates
    :param db_url: Database to use
    :param clean_open_orders: Remove open orders from the database.
        Useful for dry-run or if all orders have been reset on the exchange.
    :return: None
    """
    kwargs = {}

    # Take care of thread ownership if in-memory db
    if db_url == 'sqlite://':
        kwargs.update({
            'connect_args': {
                'check_same_thread': False
            },
            'poolclass': StaticPool,
            'echo': False,
        })

    try:
        engine = create_engine(db_url, **kwargs)
    except NoSuchModuleError:
        raise OperationalException(
            f"Given value for db_url: '{db_url}' "
            f"is no valid database URL! (See {_SQL_DOCS_URL})")

    # https://docs.sqlalchemy.org/en/13/orm/contextual.html#thread-local-scope
    # Scoped sessions proxy requests to the appropriate thread-local session.
    # We should use the scoped_session object - not a seperately initialized version
    Trade.session = scoped_session(
        sessionmaker(bind=engine, autoflush=True, autocommit=True))
    Trade.query = Trade.session.query_property()
    _DECL_BASE.metadata.create_all(engine)
    check_migrate(engine)

    # Clean dry_run DB if the db is not in-memory
    if clean_open_orders and db_url != 'sqlite://':
        clean_dry_run_db()
예제 #13
0
 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]
예제 #14
0
 def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
     if refresh or pair not in self.cached_ticker.keys():
         data = _API.get_ticker(pair.replace('_', '-'))
         if not data['success']:
             Bittrex._validate_response(data)
             raise OperationalException('{message} params=({pair})'.format(
                 message=data['message'], pair=pair))
         keys = ['Bid', 'Ask', 'Last']
         if not data.get('result') or\
                 not all(key in data.get('result', {}) for key in keys) or\
                 not all(data.get('result', {})[key] is not None for key in keys):
             raise ContentDecodingError(
                 'Invalid response from Bittrex params=({pair})'.format(
                     pair=pair))
         # Update the pair
         self.cached_ticker[pair] = {
             'bid': float(data['result']['Bid']),
             'ask': float(data['result']['Ask']),
             'last': float(data['result']['Last']),
         }
     return self.cached_ticker[pair]
예제 #15
0
def init(config: Dict) -> None:
    """
    Initializes this module with the given config,
    registers all known command handlers
    and starts polling for message updates
    :param config: config to use
    :return: None
    """
    db_url = config.get('db_url', None)
    kwargs = {}

    # Take care of thread ownership if in-memory db
    if db_url == 'sqlite://':
        kwargs.update({
            'connect_args': {
                'check_same_thread': False
            },
            'poolclass': StaticPool,
            'echo': False,
        })

    try:
        engine = create_engine(db_url, **kwargs)
    except NoSuchModuleError:
        error = 'Given value for db_url: \'{}\' is no valid database URL! (See {}).'.format(
            db_url,
            'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
        )
        raise OperationalException(error)

    session = scoped_session(
        sessionmaker(bind=engine, autoflush=True, autocommit=True))
    Trade.session = session()
    Trade.query = session.query_property()
    _DECL_BASE.metadata.create_all(engine)
    check_migrate(engine)

    # Clean dry_run DB if the db is not in-memory
    if config.get('dry_run', False) and db_url != 'sqlite://':
        clean_dry_run_db()
예제 #16
0
 def get_order(self, order_id: str, pair: str) -> Dict:
     if self._config['dry_run']:
         try:
             order = self._dry_run_open_orders[order_id]
             return order
         except KeyError as e:
             # Gracefully handle errors with dry-run orders.
             raise InvalidOrderException(
                 f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}'
             ) from e
     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
예제 #17
0
def start_hyperopt(args: Dict[str, Any]) -> None:
    """
    Start hyperopt script
    :param args: Cli args from Arguments()
    :return: None
    """
    # Import here to avoid loading hyperopt module when it's not used
    try:
        from filelock import FileLock, Timeout
        from freqtrade.optimize.hyperopt import Hyperopt
    except ImportError as e:
        raise OperationalException(
            f"{e}. Please ensure that the hyperopt dependencies are installed."
        ) from e
    # Initialize configuration
    config = setup_configuration(args, RunMode.HYPEROPT)

    logger.info('Starting freqtrade in Hyperopt mode')

    lock = FileLock(Hyperopt.get_lock_filename(config))

    try:
        with lock.acquire(timeout=1):

            # Remove noisy log messages
            logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
            logging.getLogger('filelock').setLevel(logging.WARNING)

            # Initialize backtesting object
            hyperopt = Hyperopt(config)
            hyperopt.start()

    except Timeout:
        logger.info("Another running instance of freqtrade Hyperopt detected.")
        logger.info(
            "Simultaneous execution of multiple Hyperopt commands is not supported. "
            "Hyperopt module is resource hungry. Please run your Hyperopts sequentially "
            "or on separate machines.")
        logger.info("Quitting now.")
예제 #18
0
    def check_exchange(self, config: Dict[str, Any]) -> bool:
        """
        Check if the exchange name in the config file is supported by Freqtrade
        :return: True or raised an exception if the exchange if not supported
        """
        exchange = config.get('exchange', {}).get('name').lower()
        if exchange not in ccxt.exchanges:

            exception_msg = f'Exchange "{exchange}" not supported.\n' \
                            f'The following exchanges are supported: {", ".join(ccxt.exchanges)}'

            logger.critical(exception_msg)
            raise OperationalException(exception_msg)
        # Depreciation warning
        if 'ccxt_rate_limit' in config.get('exchange', {}):
            logger.warning(
                "`ccxt_rate_limit` has been deprecated in favor of "
                "`ccxt_config` and `ccxt_async_config` and will be removed "
                "in a future version.")

        logger.debug('Exchange "%s" supported', exchange)
        return True
예제 #19
0
def load_data(datadir: Optional[Path],
              ticker_interval: str,
              pairs: List[str],
              refresh_pairs: bool = False,
              exchange: Optional[Exchange] = None,
              timerange: TimeRange = TimeRange(None, None, 0, 0),
              fill_up_missing: bool = True,
              live: bool = False) -> Dict[str, DataFrame]:
    """
    Loads ticker history data for a list of pairs the given parameters
    :return: dict(<pair>:<tickerlist>)
    """
    result: Dict[str, DataFrame] = {}
    if live:
        if exchange:
            logger.info('Live: Downloading data for all defined pairs ...')
            exchange.refresh_latest_ohlcv([(pair, ticker_interval)
                                           for pair in pairs])
            result = {
                key[0]: value
                for key, value in exchange._klines.items() if value is not None
            }
        else:
            raise OperationalException(
                "Exchange needs to be initialized when using live data.")
    else:
        logger.info('Using local backtesting data ...')

        for pair in pairs:
            hist = load_pair_history(pair=pair,
                                     ticker_interval=ticker_interval,
                                     datadir=datadir,
                                     timerange=timerange,
                                     refresh_pairs=refresh_pairs,
                                     exchange=exchange,
                                     fill_up_missing=fill_up_missing)
            if hist is not None:
                result[pair] = hist
    return result
예제 #20
0
    def __init__(self, config: Dict[str, Any], exchange, strategy) -> None:

        self.config = config
        self.exchange = exchange
        self.strategy = strategy

        self.edge_config = self.config.get('edge', {})
        self._cached_pairs: Dict[str, Any] = {}  # Keeps a list of pairs
        self._final_pairs: list = []

        # checking max_open_trades. it should be -1 as with Edge
        # the number of trades is determined by position size
        if self.config['max_open_trades'] != float('inf'):
            logger.critical('max_open_trades should be -1 in config !')

        if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT:
            raise OperationalException('Edge works only with unlimited stake amount')

        self._capital_percentage: float = self.edge_config.get('capital_available_percentage')
        self._allowed_risk: float = self.edge_config.get('allowed_risk')
        self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
        self._last_updated: int = 0  # Timestamp of pairs last updated time
        self._refresh_pairs = True

        self._stoploss_range_min = float(self.edge_config.get('stoploss_range_min', -0.01))
        self._stoploss_range_max = float(self.edge_config.get('stoploss_range_max', -0.05))
        self._stoploss_range_step = float(self.edge_config.get('stoploss_range_step', -0.001))

        # calculating stoploss range
        self._stoploss_range = np.arange(
            self._stoploss_range_min,
            self._stoploss_range_max,
            self._stoploss_range_step
        )

        self._timerange: TimeRange = Arguments.parse_timerange("%s-" % arrow.now().shift(
            days=-1 * self._since_number_of_days).format('YYYYMMDD'))

        self.fee = self.exchange.get_fee()
예제 #21
0
    def buy(self, pair: str, rate: float, amount: float) -> 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': 'limit',
                '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)

            return self._api.create_limit_buy_order(pair, amount, rate)
        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)
예제 #22
0
    def __init__(self, exchange, config: dict) -> None:
        self._exchange = exchange
        self._config = config
        self._whitelist = self._config['exchange'].get('pair_whitelist')
        self._blacklist = self._config['exchange'].get('pair_blacklist', [])
        self._pairlists: List[IPairList] = []
        self._tickers_needed = False
        for pl in self._config.get('pairlists', None):
            if 'method' not in pl:
                logger.warning(f"No method in {pl}")
                continue
            pairl = PairListResolver(pl.get('method'),
                                     exchange=exchange,
                                     pairlistmanager=self,
                                     config=config,
                                     pairlistconfig=pl,
                                     pairlist_pos=len(
                                         self._pairlists)).pairlist
            self._tickers_needed = pairl.needstickers or self._tickers_needed
            self._pairlists.append(pairl)

        if not self._pairlists:
            raise OperationalException("No Pairlist defined!")
예제 #23
0
    def check_exchange(self, config: Dict[str, Any], check_for_bad: bool = True) -> bool:
        """
        Check if the exchange name in the config file is supported by Freqtrade
        :param check_for_bad: if True, check the exchange against the list of known 'bad'
                              exchanges
        :return: False if exchange is 'bad', i.e. is known to work with the bot with
                 critical issues or does not work at all, crashes, etc. True otherwise.
                 raises an exception if the exchange if not supported by ccxt
                 and thus is not known for the Freqtrade at all.
        """
        logger.info("Checking exchange...")

        exchange = config.get('exchange', {}).get('name').lower()
        if not is_exchange_available(exchange):
            raise OperationalException(
                    f'Exchange "{exchange}" is not supported by ccxt '
                    f'and therefore not available for the bot.\n'
                    f'The following exchanges are supported by ccxt: '
                    f'{", ".join(available_exchanges())}'
            )

        if check_for_bad and is_exchange_bad(exchange):
            logger.warning(f'Exchange "{exchange}" is known to not work with the bot yet. '
                           f'Use it only for development and testing purposes.')
            return False

        if is_exchange_officially_supported(exchange):
            logger.info(f'Exchange "{exchange}" is officially supported '
                        f'by the Freqtrade development team.')
        else:
            logger.warning(f'Exchange "{exchange}" is supported by ccxt '
                           f'and therefore available for the bot but not officially supported '
                           f'by the Freqtrade development team. '
                           f'It may work flawlessly (please report back) or have serious issues. '
                           f'Use it at your own discretion.')

        return True
예제 #24
0
def create_userdata_dir(directory: str, create_dir=False) -> Path:
    """
    Create userdata directory structure.
    if create_dir is True, then the parent-directory will be created if it does not exist.
    Sub-directories will always be created if the parent directory exists.
    Raises OperationalException if given a non-existing directory.
    :param directory: Directory to check
    :param create_dir: Create directory if it does not exist.
    :return: Path object containing the directory
    """
    sub_dirs = [
        "backtest_results",
        "data",
        "hyperopts",
        "hyperopt_results",
        "notebooks",
        "plot",
        "strategies",
    ]
    folder = Path(directory)
    if not folder.is_dir():
        if create_dir:
            folder.mkdir(parents=True)
            logger.info(f'Created user-data directory: {folder}')
        else:
            raise OperationalException(
                f"Directory `{folder}` does not exist. "
                "Please use `freqtrade create-userdir` to create a user directory"
            )

    # Create required subdirectories
    for f in sub_dirs:
        subfolder = folder / f
        if not subfolder.is_dir():
            subfolder.mkdir(parents=False)
    return folder
예제 #25
0
    def _load_pairlist(self, pairlist_name: str, kwargs: dict) -> IPairList:
        """
        Search and loads the specified pairlist.
        :param pairlist_name: name of the module to import
        :param extra_dir: additional directory to search for the given pairlist
        :return: PairList instance or None
        """
        current_path = Path(__file__).parent.parent.joinpath(
            'pairlist').resolve()

        abs_paths = [
            Path.cwd().joinpath('user_data/pairlist'),
            current_path,
        ]

        pairlist = self._load_object(paths=abs_paths,
                                     object_type=IPairList,
                                     object_name=pairlist_name,
                                     kwargs=kwargs)
        if pairlist:
            return pairlist
        raise OperationalException(
            f"Impossible to load Pairlist '{pairlist_name}'. This class does not exist "
            "or contains Python code errors.")
예제 #26
0
    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)
예제 #27
0
 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]
예제 #28
0
def main(sysargv: List[str] = None) -> None:
    """
    This function will initiate the bot and start the trading loop.
    :return: None
    """

    return_code: Any = 1
    try:
        arguments = Arguments(sysargv)
        args = arguments.get_parsed_arg()

        # Call subcommand.
        if 'func' in args:
            return_code = args['func'](args)
        else:
            # No subcommand was issued.
            raise OperationalException(
                "Usage of Freqtrade requires a subcommand to be specified.\n"
                "To have the previous behavior (bot executing trades in live/dry-run modes, "
                "depending on the value of the `dry_run` setting in the config), run freqtrade "
                "as `freqtrade trade [options...]`.\n"
                "To see the full list of options available, please use "
                "`freqtrade --help` or `freqtrade <command> --help`.")

    except SystemExit as e:
        return_code = e
    except KeyboardInterrupt:
        logger.info('SIGINT received, aborting ...')
        return_code = 0
    except OperationalException as e:
        logger.error(str(e))
        return_code = 2
    except Exception:
        logger.exception('Fatal exception!')
    finally:
        sys.exit(return_code)
예제 #29
0
def test_main_reload_conf(mocker, default_conf, caplog) -> None:
    patch_exchange(mocker)
    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())
    # Simulate Running, reload, running workflow
    worker_mock = MagicMock(side_effect=[State.RUNNING,
                                         State.RELOAD_CONF,
                                         State.RUNNING,
                                         OperationalException("Oh snap!")])
    mocker.patch('freqtrade.worker.Worker._worker', worker_mock)
    patched_configuration_load_config_file(mocker, default_conf)
    reconfigure_mock = mocker.patch('freqtrade.worker.Worker._reconfigure', MagicMock())

    mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
    mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())

    args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg()
    worker = Worker(args=args, config=default_conf)
    with pytest.raises(SystemExit):
        main(['trade', '-c', 'config.json.example'])

    assert log_has('Using config: config.json.example ...', caplog)
    assert worker_mock.call_count == 4
    assert reconfigure_mock.call_count == 1
    assert isinstance(worker.freqtrade, FreqtradeBot)
예제 #30
0
    def _resolve_pairs_list(self, config: Dict[str, Any]) -> None:
        """
        Helper for download script.
        Takes first found:
        * -p (pairs argument)
        * --pairs-file
        * whitelist from config
        """

        if "pairs" in config:
            return

        if "pairs_file" in self.args and self.args.pairs_file:
            pairs_file = Path(self.args.pairs_file)
            logger.info(f'Reading pairs file "{pairs_file}".')
            # Download pairs from the pairs file if no config is specified
            # or if pairs file is specified explicitely
            if not pairs_file.exists():
                raise OperationalException(
                    f'No pairs file found with path "{pairs_file}".')
            with pairs_file.open('r') as f:
                config['pairs'] = json_load(f)
                config['pairs'].sort()
            return

        if "config" in self.args and self.args.config:
            logger.info("Using pairlist from configuration.")
            config['pairs'] = config.get('exchange', {}).get('pair_whitelist')
        else:
            # Fall back to /dl_path/pairs.json
            pairs_file = Path(config['datadir']) / "pairs.json"
            if pairs_file.exists():
                with pairs_file.open('r') as f:
                    config['pairs'] = json_load(f)
                if 'pairs' in config:
                    config['pairs'].sort()