Exemple #1
0
def copy_sample_files(directory: Path, overwrite: bool = False) -> None:
    """
    Copy files from templates to User data directory.
    
    Parameters:
    -----------
    directory: 
        Directory to copy data to
        
    overwrite: 
        Overwrite existing sample files
    """
    if not directory.is_dir():
        raise OperationalException(f"Directory `{directory}` does not exist.")
    sourcedir = Path(__file__).parents[1] / "templates"
    for source, target in USER_DATA_FILES.items():
        targetdir = directory / target
        if not targetdir.is_dir():
            raise OperationalException(
                f"Directory `{targetdir}` does not exist.")
        targetfile = targetdir / source
        if targetfile.exists():
            if not overwrite:
                logger.warning(
                    f"File `{targetfile}` exists already, not deploying sample file."
                )
                continue
            else:
                logger.warning(
                    f"File `{targetfile}` exists already, overwriting.")
        shutil.copy(str(sourcedir / source), str(targetfile))
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:

    if conf.get('stoploss') == 0.0:
        raise OperationalException(
            'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
        )
    # Skip if trailing stoploss is not activated
    if not conf.get('trailing_stop', False):
        return

    tsl_positive = float(conf.get('trailing_stop_positive', 0))
    tsl_offset = float(conf.get('trailing_stop_positive_offset', 0))
    tsl_only_offset = conf.get('trailing_only_offset_is_reached', False)

    if tsl_only_offset:
        if tsl_positive == 0.0:
            raise OperationalException(
                'The config trailing_only_offset_is_reached needs '
                'trailing_stop_positive_offset to be more than 0 in your config.'
            )
    if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
        raise OperationalException(
            'The config trailing_stop_positive_offset needs '
            'to be greater than trailing_stop_positive in your config.')

    # Fetch again without default
    if 'trailing_stop_positive' in conf and float(
            conf['trailing_stop_positive']) == 0.0:
        raise OperationalException(
            'The config trailing_stop_positive needs to be different from 0 '
            'to avoid problems with sell orders.')
Exemple #3
0
def load_config_file(path: str) -> Dict[str, Any]:
    """
    Loads a config file from the given path
    
    Parameters:
    -----------
    path: 
        path as str
        
    Return: 
    -------
        configuration as dictionary
    """
    try:
        # Read config from stdin if requested in the options
        with open(path) if path != '-' else sys.stdin as file:
            config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
    except FileNotFoundError:
        raise OperationalException(
            f'Config file "{path}" not found!'
            ' Please create a config file or check whether it exists.')
    except rapidjson.JSONDecodeError as e:
        err_range = log_config_error_range(path, str(e))
        raise OperationalException(
            f'{e}\n'
            f'Please verify the following segment of your configuration:\n{err_range}'
            if err_range else 'Please verify your configuration file for syntax errors.'
        )

    return config
Exemple #4
0
def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
    """
    Check if the exchange name in the config file is supported by ccxt
    
    Parameters:
    -----------
    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.
    """

    if (config['runmode']
            in [RunMode.PLOT, RunMode.UTIL_NO_EXCHANGE, RunMode.OTHER]
            and not config.get('exchange', {}).get('name')):
        # Skip checking exchange in plot mode, since it requires no exchange
        return True
    logger.info("Checking exchange...")

    exchange = config.get('exchange', {}).get('name').lower()
    if not exchange:
        raise OperationalException(
            f'This command requires a configured exchange. You should either use '
            f'`--exchange <exchange_name>` or specify a configuration file via `--config`.\n'
            f'The following exchanges are available for: '
            f'{", ".join(available_exchanges())}')

    if not is_exchange_known_ccxt(exchange):
        raise OperationalException(
            f'Exchange "{exchange}" is not known to the ccxt library '
            f'and therefore not available for the bot.\n'
            f'The following exchanges are available: '
            f'{", ".join(available_exchanges())}')

    if check_for_bad and is_exchange_bad(exchange):
        raise OperationalException(
            f'Exchange "{exchange}" is known to not work with the bot yet. '
            f'Reason: {get_exchange_bad_reason(exchange)}')

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

    return True
Exemple #5
0
    def fetch_stoploss_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:
            orders = self._api.fetch_orders(pair, None, params={'type': 'stop'})

            order = [order for order in orders if order['id'] == order_id]
            if len(order) == 1:
                return order[0]
            else:
                raise InvalidOrderException(f"Could not get stoploss order for id {order_id}")

        except ccxt.InvalidOrder as e:
            raise InvalidOrderException(
                f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
        except ccxt.DDoSProtection as e:
            raise DDosProtection(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 _validate_edge(conf: Dict[str, Any]) -> None:
    """
    Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists.
    """

    if not conf.get('edge', {}).get('enabled'):
        return

    if conf.get('pairlist', {}).get('method') == 'VolumePairList':
        raise OperationalException(
            "Edge and VolumePairList are incompatible, "
            "Edge will override whatever pairs VolumePairlist selects.")
    if not conf.get('ask_strategy', {}).get('use_sell_signal', True):
        raise OperationalException(
            "Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
        )
Exemple #7
0
def start_show_trades(args: Dict[str, Any]) -> None:
    """
    Show trades
    """
    import json

    from freqtrade.persistence import Trade, init_db
    config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)

    if 'db_url' not in config:
        raise OperationalException("--db-url is required for this command.")

    logger.info(f'Using DB: "{config["db_url"]}"')
    init_db(config['db_url'], clean_open_orders=False)
    tfilter = []

    if config.get('trade_ids'):
        tfilter.append(Trade.id.in_(config['trade_ids']))

    trades = Trade.get_trades(tfilter).all()
    logger.info(f"Printing {len(trades)} Trades: ")
    if config.get('print_json', False):
        print(json.dumps([trade.to_json() for trade in trades], indent=4))
    else:
        for trade in trades:
            print(trade)
Exemple #8
0
    def load_object(cls,
                    object_name: str,
                    config: dict,
                    *,
                    kwargs: dict,
                    extra_dir: Optional[str] = None) -> Any:
        """
        Search and loads the specified object as configured in hte child class.
        :param objectname: name of the module to import
        :param config: configuration dictionary
        :param extra_dir: additional directory to search for the given pairlist
        :raises: OperationalException if the class is invalid or does not exist.
        :return: Object instance or None
        """

        abs_paths = cls.build_search_paths(config,
                                           user_subdir=cls.user_subdir,
                                           extra_dir=extra_dir)

        found_object = cls._load_object(paths=abs_paths,
                                        object_name=object_name,
                                        kwargs=kwargs)
        if found_object:
            return found_object
        raise OperationalException(
            f"Impossible to load {cls.object_type_str} '{object_name}'. This class does not exist "
            "or contains Python code errors.")
Exemple #9
0
def create_userdata_dir(directory: str, create_dir: bool = 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", "logs",
                "notebooks", "plot", "agents_trained", ]
    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 `finrl 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
Exemple #10
0
def setup_logging(config: Dict[str, Any]) -> None:
    """
    Process -v/--verbose, --logfile options
    """
    # Log level
    verbosity = config['verbosity']
    logging.root.addHandler(bufferHandler)

    logfile = config.get('logfile')

    if logfile:
        s = logfile.split(':')
        if s[0] == 'syslog':
            # Address can be either a string (socket filename) for Unix domain socket or
            # a tuple (hostname, port) for UDP socket.
            # Address can be omitted (i.e. simple 'syslog' used as the value of
            # config['logfilename']), which defaults to '/dev/log', applicable for most
            # of the systems.
            address = (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else '/dev/log'
            handler_sl = get_existing_handlers(SysLogHandler)
            if handler_sl:
                logging.root.removeHandler(handler_sl)
            handler_sl = SysLogHandler(address=address)
            # No datetime field for logging into syslog, to allow syslog
            # to perform reduction of repeating messages if this is set in the
            # syslog config. The messages should be equal for this.
            handler_sl.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
            logging.root.addHandler(handler_sl)
        elif s[0] == 'journald':
            try:
                from systemd.journal import JournaldLogHandler
            except ImportError:
                raise OperationalException("You need the systemd python package be installed in "
                                           "order to use logging to journald.")
            handler_jd = get_existing_handlers(JournaldLogHandler)
            if handler_jd:
                logging.root.removeHandler(handler_jd)
            handler_jd = JournaldLogHandler()
            # No datetime field for logging into journald, to allow syslog
            # to perform reduction of repeating messages if this is set in the
            # syslog config. The messages should be equal for this.
            handler_jd.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
            logging.root.addHandler(handler_jd)
        else:
            handler_rf = get_existing_handlers(RotatingFileHandler)
            if handler_rf:
                logging.root.removeHandler(handler_rf)
            handler_rf = RotatingFileHandler(logfile,
                                             maxBytes=1024 * 1024 * 10,  # 10Mb
                                             backupCount=10)
            handler_rf.setFormatter(Formatter(LOGFORMAT))
            logging.root.addHandler(handler_rf)

    logging.root.setLevel(logging.INFO if verbosity < 1 else logging.DEBUG)
    _set_loggers(verbosity, config.get('api_server', {}).get('verbosity', 'info'))

    logger.info('Verbosity set to %s', verbosity)
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
    """
    If edge is disabled, either max_open_trades or stake_amount need to be set.
    :raise: OperationalException if config validation failed
    """
    if (not conf.get('edge', {}).get('enabled')
            and conf.get('max_open_trades') == float('inf')
            and conf.get('stake_amount') == constants.UNLIMITED_STAKE_AMOUNT):
        raise OperationalException(
            "`max_open_trades` and `stake_amount` cannot both be unlimited.")
Exemple #12
0
    def stoploss(self, pair: str, amount: float, stop_price: float,
                 order_types: Dict) -> Dict:
        """
        Creates a stoploss market order.
        Stoploss market orders is the only stoploss type supported by kraken.
        """
        params = self._params.copy()

        if order_types.get('stoploss', 'market') == 'limit':
            ordertype = "stop-loss-limit"
            limit_price_pct = order_types.get(
                'stoploss_on_exchange_limit_ratio', 0.99)
            limit_rate = stop_price * limit_price_pct
            params['price2'] = self.price_to_precision(pair, limit_rate)
        else:
            ordertype = "stop-loss"

        stop_price = self.price_to_precision(pair, stop_price)

        if self._config['dry_run']:
            dry_order = self.dry_run_order(pair, ordertype, "sell", amount,
                                           stop_price)
            return dry_order

        try:
            amount = self.amount_to_precision(pair, amount)

            order = self._api.create_order(symbol=pair,
                                           type=ordertype,
                                           side='sell',
                                           amount=amount,
                                           price=stop_price,
                                           params=params)
            logger.info('stoploss order added for %s. '
                        'stop price: %s.', pair, stop_price)
            return order
        except ccxt.InsufficientFunds as e:
            raise InsufficientFundsError(
                f'Insufficient funds to create {ordertype} sell order on market {pair}. '
                f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
                f'Message: {e}') from e
        except ccxt.InvalidOrder as e:
            raise InvalidOrderException(
                f'Could not create {ordertype} sell order on market {pair}. '
                f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
                f'Message: {e}') from e
        except ccxt.DDoSProtection as e:
            raise DDosProtection(e) from e
        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
            raise TemporaryError(
                f'Could not place sell order due to {e.__class__.__name__}. Message: {e}'
            ) from e
        except ccxt.BaseError as e:
            raise OperationalException(e) from e
Exemple #13
0
    def current_whitelist(self) -> List[str]:
        """
        fetch latest available whitelist.

        Useful when you have a large whitelist and need to call each pair as an informative pair.
        As available pairs does not show whitelist until after informative pairs have been cached.
        :return: list of pairs in whitelist
        """

        if self._pairlists:
            return self._pairlists.whitelist
        else:
            raise OperationalException(
                "Dataprovider was not initialized with a pairlist provider.")
Exemple #14
0
def start_download_stockdata(args: Dict[str, Any]) -> None:
    """Fetches data from Yahoo API
    
    Parameters
    ----------
      ticker_list, timerange, 
      
    Returns
    -------
      Json of data
    """
    args["exchange"] = "yahoo"
    config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)

    if 'days' in config and 'timerange' in config:
        raise OperationalException(
            "--days and --timerange are mutually exclusive. "
            "You can only specify one or the other.")

    config["datadir"] = "user_data/data/yahoo"

    timerange = TimeRange()
    if 'days' in config:
        time_since = (datetime.now() -
                      timedelta(days=config['days'])).strftime("%Y%m%d")
        timerange = TimeRange.parse_timerange(f'{time_since}-')
        start = datetime.fromtimestamp(timerange.startts).strftime("%Y-%m-%d")
        end = datetime.now().strftime("%Y-%m-%d")

    if 'timerange' in config:
        timerange = timerange.parse_timerange(config['timerange'])
        start = datetime.fromtimestamp(timerange.startts).strftime("%Y-%m-%d")
        end = datetime.fromtimestamp(timerange.stopts).strftime("%Y-%m-%d")
    try:
        data_df = pd.DataFrame()
        for tic in config['ticker_list']:
            temp_df = yf.download(tic, start=start, end=end)
            temp_df.columns = [
                "open",
                "high",
                "low",
                "close",
                "adjcp",
                "volume",
            ]
            temp_df["close"] = temp_df["adjcp"]
            temp_df = temp_df.drop(["adjcp"], axis=1)
            temp_df.to_json(f'{os.getcwd()}/{config["datadir"]}/{tic}.json')
    except KeyboardInterrupt:
        sys.exit("Interrupt received, aborting ...")
Exemple #15
0
    def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
        """
        Creates a stoploss order.
        depending on order_types.stoploss configuration, uses 'market' or limit order.

        Limit orders are defined by having orderPrice set, otherwise a market order is used.
        """
        limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
        limit_rate = stop_price * limit_price_pct

        ordertype = "stop"

        stop_price = self.price_to_precision(pair, stop_price)

        if self._config['dry_run']:
            dry_order = self.dry_run_order(
                pair, ordertype, "sell", amount, stop_price)
            return dry_order

        try:
            params = self._params.copy()
            if order_types.get('stoploss', 'market') == 'limit':
                # set orderPrice to place limit order, otherwise it's a market order
                params['orderPrice'] = limit_rate

            amount = self.amount_to_precision(pair, amount)

            order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
                                           amount=amount, price=stop_price, params=params)
            logger.info('stoploss order added for %s. '
                        'stop price: %s.', pair, stop_price)
            return order
        except ccxt.InsufficientFunds as e:
            raise InsufficientFundsError(
                f'Insufficient funds to create {ordertype} sell order on market {pair}. '
                f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
                f'Message: {e}') from e
        except ccxt.InvalidOrder as e:
            raise InvalidOrderException(
                f'Could not create {ordertype} sell order on market {pair}. '
                f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
                f'Message: {e}') from e
        except ccxt.DDoSProtection as e:
            raise DDosProtection(e) from e
        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
            raise TemporaryError(
                f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
        except ccxt.BaseError as e:
            raise OperationalException(e) from e
def _validate_whitelist(conf: Dict[str, Any]) -> None:
    """
    Dynamic whitelist does not require pair_whitelist to be set - however StaticWhitelist does.
    """
    if conf.get('runmode', RunMode.OTHER) in [
            RunMode.OTHER, RunMode.PLOT, RunMode.UTIL_NO_EXCHANGE,
            RunMode.UTIL_EXCHANGE
    ]:
        return

    for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]):
        if (pl.get('method') == 'StaticPairList'
                and not conf.get('exchange', {}).get('pair_whitelist')):
            raise OperationalException(
                "StaticPairList requires pair_whitelist to be set.")
Exemple #17
0
 def cancel_stoploss_order(self, order_id: str, pair: str) -> Dict:
     if self._config['dry_run']:
         return {}
     try:
         return self._api.cancel_order(order_id, pair, params={'type': 'stop'})
     except ccxt.InvalidOrder as e:
         raise InvalidOrderException(
             f'Could not cancel order. Message: {e}') from e
     except ccxt.DDoSProtection as e:
         raise DDosProtection(e) from e
     except (ccxt.NetworkError, ccxt.ExchangeError) as e:
         raise TemporaryError(
             f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
     except ccxt.BaseError as e:
         raise OperationalException(e) from e
Exemple #18
0
    def gen_pairlist(self, cached_pairlist: List[str], tickers: Dict) -> List[str]:
        """
        Generate the pairlist.

        This method is called once by the pairlistmanager in the refresh_pairlist()
        method to supply the starting pairlist for the chain of the Pairlist Handlers.
        Pairlist Filters (those Pairlist Handlers that cannot be used at the first
        position in the chain) shall not override this base implementation --
        it will raise the exception if a Pairlist Handler is used at the first
        position in the chain.

        :param cached_pairlist: Previously generated pairlist (cached)
        :param tickers: Tickers (from exchange.get_tickers()).
        :return: List of pairs
        """
        raise OperationalException("This Pairlist Handler should not be used "
                                   "at the first position in the list of Pairlist Handlers.")
Exemple #19
0
    def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
        """
        Check available markets and remove pair from whitelist if necessary
        :param whitelist: the sorted list of pairs the user might want to trade
        :return: the list of pairs the user wants to trade without those unavailable or
        black_listed
        """
        markets = self._exchange.markets
        if not markets:
            raise OperationalException(
                    'Markets not loaded. Make sure that exchange is initialized correctly.')

        sanitized_whitelist: List[str] = []
        for pair in pairlist:
            # pair is not in the generated dynamic market or has the wrong stake currency
            if pair not in markets:
                logger.warning(f"Pair {pair} is not compatible with exchange "
                               f"{self._exchange.name}. Removing it from whitelist..")
                continue

            if not self._exchange.market_is_tradable(markets[pair]):
                logger.warning(f"Pair {pair} is not tradable with Freqtrade."
                               "Removing it from whitelist..")
                continue

            if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']:
                logger.warning(f"Pair {pair} is not compatible with your stake currency "
                               f"{self._config['stake_currency']}. Removing it from whitelist..")
                continue

            # Check if market is active
            market = markets[pair]
            if not market_is_active(market):
                logger.info(f"Ignoring {pair} from whitelist. Market is not active.")
                continue
            if pair not in sanitized_whitelist:
                sanitized_whitelist.append(pair)

        # We need to remove pairs that are unknown
        return sanitized_whitelist
Exemple #20
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._pairlist_handlers: List[IPairList] = []
        self._tickers_needed = False
        for pairlist_handler_config in self._config.get('pairlists', None):
            if 'method' not in pairlist_handler_config:
                logger.warning(f"No method found in {pairlist_handler_config}, ignoring.")
                continue
            pairlist_handler = PairListResolver.load_pairlist(
                    pairlist_handler_config['method'],
                    exchange=exchange,
                    pairlistmanager=self,
                    config=config,
                    pairlistconfig=pairlist_handler_config,
                    pairlist_pos=len(self._pairlist_handlers)
                    )
            self._tickers_needed |= pairlist_handler.needstickers
            self._pairlist_handlers.append(pairlist_handler)

        if not self._pairlist_handlers:
            raise OperationalException("No Pairlist Handlers defined")
Exemple #21
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 = 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()
Exemple #22
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)

            orders = self._api.fetch_open_orders()
            order_list = [
                (
                    x["symbol"].split("/")[0 if x["side"] == "sell" else 1],
                    x["remaining"],
                    # Don't remove the below comment, this can be important for debuggung
                    # x["side"], x["amount"],
                ) for x in orders
            ]
            for bal in balances:
                balances[bal]['used'] = sum(order[1] for order in order_list
                                            if order[0] == bal)
                balances[bal][
                    'free'] = balances[bal]['total'] - balances[bal]['used']

            return balances
        except ccxt.DDoSProtection as e:
            raise DDosProtection(e) from e
        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
Exemple #23
0
    def stoploss(self, pair: str, amount: float, stop_price: float,
                 order_types: Dict) -> Dict:
        """
        creates a stoploss limit order.
        this stoploss-limit is binance-specific.
        It may work with a limited number of other exchanges, but this has not been tested yet.
        """
        # Limit price threshold: As limit price should always be below stop-price
        limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio',
                                          0.99)
        rate = stop_price * limit_price_pct

        ordertype = "stop_loss_limit"

        stop_price = self.price_to_precision(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._config['dry_run']:
            dry_order = self.dry_run_order(pair, ordertype, "sell", amount,
                                           stop_price)
            return dry_order

        try:
            params = self._params.copy()
            params.update({'stopPrice': stop_price})

            amount = self.amount_to_precision(pair, amount)

            rate = self.price_to_precision(pair, rate)

            order = self._api.create_order(symbol=pair,
                                           type=ordertype,
                                           side='sell',
                                           amount=amount,
                                           price=rate,
                                           params=params)
            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 InsufficientFundsError(
                f'Insufficient funds to create {ordertype} sell order on market {pair}. '
                f'Tried to sell amount {amount} at rate {rate}. '
                f'Message: {e}') from e
        except ccxt.InvalidOrder as e:
            # Errors:
            # `binance Order would trigger immediately.`
            raise InvalidOrderException(
                f'Could not create {ordertype} sell order on market {pair}. '
                f'Tried to sell amount {amount} at rate {rate}. '
                f'Message: {e}') from e
        except ccxt.DDoSProtection as e:
            raise DDosProtection(e) from e
        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
            raise TemporaryError(
                f'Could not place sell order due to {e.__class__.__name__}. Message: {e}'
            ) from e
        except ccxt.BaseError as e:
            raise OperationalException(e) from e
Exemple #24
0
def start_download_cryptodata(args: Dict[str, Any]) -> None:
    """
    Parameters:
      ARGS_DOWNLOAD_DATA = {'config': ['config.json'], 'datadir': None, 
                        'user_data_dir': None, 'pairs': None, 'pairs_file': None, 
                        'days': 160, 'timerange': None, 
                        'download_trades': False, 'exchange': 'binance', 
                        'timeframes': ['1d'], 'erase': False, 
                        'dataformat_ohlcv': None, 'dataformat_trades': None}
    
    Returns:
      Json files in user_data/data/exchange/*.json
    """
    config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
    if 'days' in config and 'timerange' in config:
        raise OperationalException(
            "--days and --timerange are mutually exclusive. "
            "You can only specify one or the other.")
    timerange = TimeRange()
    if 'days' in config:
        time_since = (datetime.now() -
                      timedelta(days=config['days'])).strftime("%Y%m%d")
        timerange = TimeRange.parse_timerange(f'{time_since}-')

    if 'timerange' in config:
        timerange = timerange.parse_timerange(config['timerange'])

    # Remove stake-currency to skip checks which are not relevant for datadownload
    config['stake_currency'] = ''

    if 'pairs' not in config:
        raise OperationalException(
            "Downloading data requires a list of pairs. "
            "Please check the documentation on how to configure this.")

    logger.info(f"About to download pairs: {config['pairs']}, "
                f"intervals: {config['timeframes']} to {config['datadir']}")

    pairs_not_available: List[str] = []

    # Init exchange
    exchange = ExchangeResolver.load_exchange(config['exchange']['name'],
                                              config,
                                              validate=False)
    # Manual validations of relevant settings
    exchange.validate_pairs(config['pairs'])
    for timeframe in config['timeframes']:
        exchange.validate_timeframes(timeframe)

    try:
        if config.get('download_trades'):
            pairs_not_available = refresh_backtest_trades_data(
                exchange,
                pairs=config['pairs'],
                datadir=config['datadir'],
                timerange=timerange,
                erase=bool(config.get('erase')),
                data_format=config['dataformat_trades'])

            # Convert downloaded trade data to different timeframes
            convert_trades_to_ohlcv(
                pairs=config['pairs'],
                timeframes=config['timeframes'],
                datadir=config['datadir'],
                timerange=timerange,
                erase=bool(config.get('erase')),
                data_format_ohlcv=config['dataformat_ohlcv'],
                data_format_trades=config['dataformat_trades'],
            )
        else:
            pairs_not_available = refresh_backtest_ohlcv_data(
                exchange,
                pairs=config['pairs'],
                timeframes=config['timeframes'],
                datadir=config['datadir'],
                timerange=timerange,
                erase=bool(config.get('erase')),
                data_format=config['dataformat_ohlcv'])

    except KeyboardInterrupt:
        sys.exit("Interrupt received, aborting ...")

    finally:
        if pairs_not_available:
            logger.info(
                f"Pairs [{','.join(pairs_not_available)}] not available "
                f"on exchange {exchange.name}.")
Exemple #25
0
def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
    """
    Print pairs/markets on the exchange
    :param args: Cli args from Arguments()
    :param pairs_only: if True print only pairs, otherwise print all instruments (markets)
    :return: None
    """
    config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)

    # Init exchange
    exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)

    # By default only active pairs/markets are to be shown
    active_only = not args.get('list_pairs_all', False)

    base_currencies = args.get('base_currencies', [])
    quote_currencies = args.get('quote_currencies', [])

    try:
        pairs = exchange.get_markets(base_currencies=base_currencies,
                                     quote_currencies=quote_currencies,
                                     pairs_only=pairs_only,
                                     active_only=active_only)
        # Sort the pairs/markets by symbol
        pairs = OrderedDict(sorted(pairs.items()))
    except Exception as e:
        raise OperationalException(f"Cannot get markets. Reason: {e}") from e

    else:
        summary_str = ((f"Exchange {exchange.name} has {len(pairs)} ") +
                       ("active " if active_only else "") +
                       (plural(len(pairs), "pair" if pairs_only else "market")) +
                       (f" with {', '.join(base_currencies)} as base "
                        f"{plural(len(base_currencies), 'currency', 'currencies')}"
                        if base_currencies else "") +
                       (" and" if base_currencies and quote_currencies else "") +
                       (f" with {', '.join(quote_currencies)} as quote "
                        f"{plural(len(quote_currencies), 'currency', 'currencies')}"
                        if quote_currencies else ""))

        headers = ["Id", "Symbol", "Base", "Quote", "Active",
                   *(['Is pair'] if not pairs_only else [])]

        tabular_data = []
        for _, v in pairs.items():
            tabular_data.append({'Id': v['id'], 'Symbol': v['symbol'],
                                 'Base': v['base'], 'Quote': v['quote'],
                                 'Active': market_is_active(v),
                                 **({'Is pair': exchange.market_is_tradable(v)}
                                    if not pairs_only else {})})

        if (args.get('print_one_column', False) or
                args.get('list_pairs_print_json', False) or
                args.get('print_csv', False)):
            # Print summary string in the log in case of machine-readable
            # regular formats.
            logger.info(f"{summary_str}.")
        else:
            # Print empty string separating leading logs and output in case of
            # human-readable formats.
            print()

        if len(pairs):
            if args.get('print_list', False):
                # print data as a list, with human-readable summary
                print(f"{summary_str}: {', '.join(pairs.keys())}.")
            elif args.get('print_one_column', False):
                print('\n'.join(pairs.keys()))
            elif args.get('list_pairs_print_json', False):
                print(rapidjson.dumps(list(pairs.keys()), default=str))
            elif args.get('print_csv', False):
                writer = csv.DictWriter(sys.stdout, fieldnames=headers)
                writer.writeheader()
                writer.writerows(tabular_data)
            else:
                # print data as a table, with the human-readable summary
                print(f"{summary_str}:")
                print(tabulate(tabular_data, headers='keys', tablefmt='psql', stralign='right'))
        elif not (args.get('print_one_column', False) or
                  args.get('list_pairs_print_json', False) or
                  args.get('print_csv', False)):
            print(f"{summary_str}.")