def __init__(self, low: Union[float_or_int, Sequence[float_or_int]], high: Optional[float_or_int] = None, *, default: float_or_int, space: Optional[str] = None, optimize: bool = True, load: bool = True, **kwargs): """ Initialize hyperopt-optimizable numeric parameter. Cannot be instantiated, but provides the validation for other numeric parameters :param low: Lower end (inclusive) of optimization space or [low, high]. :param high: Upper end (inclusive) of optimization space. Must be none of entire range is passed first parameter. :param default: A default value. :param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if parameter fieldname is prefixed with 'buy_' or 'sell_'. :param optimize: Include parameter in hyperopt optimizations. :param load: Load parameter value from {space}_params. :param kwargs: Extra parameters to skopt.space.*. """ if high is not None and isinstance(low, Sequence): raise OperationalException( f'{self.__class__.__name__} space invalid.') if high is None or isinstance(low, Sequence): if not isinstance(low, Sequence) or len(low) != 2: raise OperationalException( f'{self.__class__.__name__} space must be [low, high]') self.low, self.high = low else: self.low = low self.high = high super().__init__(default=default, space=space, optimize=optimize, load=load, **kwargs)
def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: dict, pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) if 'number_assets' not in self._pairlistconfig: raise OperationalException( f'`number_assets` not specified. Please check your configuration ' 'for "pairlist.config.number_assets"') self._number_pairs = self._pairlistconfig['number_assets'] self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume') self._min_value = self._pairlistconfig.get('min_value', 0) self.refresh_period = self._pairlistconfig.get('refresh_period', 1800) if not self._exchange.exchange_has('fetchTickers'): raise OperationalException( 'Exchange does not support dynamic whitelist.' 'Please edit your config and restart the bot') if not self._validate_keys(self._sort_key): raise OperationalException( f'key {self._sort_key} not in {SORT_VALUES}') self._last_refresh = 0
def detect_parameters(cls, category: str) -> Iterator[Tuple[str, BaseParameter]]: """ Detect all parameters for 'category' """ for attr_name in dir(cls): if not attr_name.startswith('__'): # Ignore internals, not strictly necessary. attr = getattr(cls, attr_name) if issubclass(attr.__class__, BaseParameter): if (attr_name.startswith(category + '_') and attr.category is not None and attr.category != category): raise OperationalException( f'Inconclusive parameter name {attr_name}, category: {attr.category}.') if (category == attr.category or (attr_name.startswith(category + '_') and attr.category is None)): yield attr_name, attr
def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: Dict[str, Any], pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) self._days = pairlistconfig.get('lookback_days', 10) self._min_volatility = pairlistconfig.get('min_volatility', 0) self._max_volatility = pairlistconfig.get('max_volatility', sys.maxsize) self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) if self._days < 1: raise OperationalException( "VolatilityFilter requires lookback_days to be >= 1") if self._days > exchange.ohlcv_candle_limit('1d'): raise OperationalException( "VolatilityFilter requires lookback_days to not " "exceed exchange max request size " f"({exchange.ohlcv_candle_limit('1d')})")
def load_previous_results(trials_file: Path) -> List: """ Load data for epochs from the file if we have one """ trials: List = [] if trials_file.is_file() and trials_file.stat().st_size > 0: trials = Hyperopt._read_trials(trials_file) if trials[0].get('is_best') is None: raise OperationalException( "The file with Hyperopt results is incompatible with this version " "of Freqtrade and cannot be loaded.") logger.info(f"Loaded {len(trials)} previous evaluations from disk.") return trials
def check_conflicting_settings(config: Dict[str, Any], section_old: str, name_old: str, section_new: Optional[str], name_new: str) -> None: section_new_config = config.get(section_new, {}) if section_new else config section_old_config = config.get(section_old, {}) if name_new in section_new_config and name_old in section_old_config: new_name = f"{section_new}.{name_new}" if section_new else f"{name_new}" raise OperationalException( f"Conflicting settings `{new_name}` and `{section_old}.{name_old}` " "(DEPRECATED) detected in the configuration file. " "This deprecated setting will be removed in the next versions of Freqtrade. " f"Please delete it from your configuration and use the `{new_name}` " "setting instead.")
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: """ creates a stoploss limit order. Since ccxt does not unify stoploss-limit orders yet, this needs to be implemented in each exchange's subclass. The exception below should never raise, since we disallow starting the bot in validate_ordertypes() Note: Changes to this interface need to be applied to all sub-classes too. """ raise OperationalException( f"stoploss_limit is not implemented for {self.name}.")
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 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.")
def fetch_ticker(self, pair: str) -> dict: 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) 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
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.create_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 params['stopPrice'] = stop_price amount = self.amount_to_precision(pair, amount) order = self._api.create_order(symbol=pair, type=ordertype, side='sell', amount=amount, params=params) self._log_exchange_response('create_stoploss_order', order) 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 load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = None) -> pd.DataFrame: """ Load backtest data file. :param filename: pathlib.Path object, or string pointing to a file or directory :param strategy: Strategy to load - mainly relevant for multi-strategy backtests Can also serve as protection to load the correct result. :return: a dataframe with the analysis results :raise: ValueError if loading goes wrong. """ data = load_backtest_stats(filename) if not isinstance(data, list): # new, nested format if 'strategy' not in data: raise ValueError("Unknown dataformat.") if not strategy: if len(data['strategy']) == 1: strategy = list(data['strategy'].keys())[0] else: raise ValueError( "Detected backtest result with more than one strategy. " "Please specify a strategy.") if strategy not in data['strategy']: raise ValueError( f"Strategy {strategy} not available in the backtest result.") data = data['strategy'][strategy]['trades'] df = pd.DataFrame(data) if not df.empty: df['open_date'] = pd.to_datetime(df['open_date'], utc=True, infer_datetime_format=True) df['close_date'] = pd.to_datetime(df['close_date'], utc=True, infer_datetime_format=True) # Compatibility support for pre short Columns if 'is_short' not in df.columns: df['is_short'] = 0 if 'enter_tag' not in df.columns: df['enter_tag'] = df['buy_tag'] df = df.drop(['buy_tag'], axis=1) else: # old format - only with lists. raise OperationalException( "Backtest-results with only trades data are no longer supported.") if not df.empty: df = df.sort_values("open_date").reset_index(drop=True) return df
def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], trades: pd.DataFrame, timeframe: str, stake_currency: str) -> go.Figure: # Combine close-values for all pairs, rename columns to "pair" df_comb = combine_dataframes_with_mean(data, "close") # Trim trades to available OHLCV data trades = extract_trades_of_period(df_comb, trades, date_index=True) if len(trades) == 0: raise OperationalException('No trades found in selected timerange.') # Add combined cumulative profit df_comb = create_cum_profit(df_comb, trades, 'cum_profit', timeframe) # Plot the pairs average close prices, and total profit growth avgclose = go.Scatter( x=df_comb.index, y=df_comb['mean'], name='Avg close price', ) fig = make_subplots(rows=3, cols=1, shared_xaxes=True, row_width=[1, 1, 1], vertical_spacing=0.05, subplot_titles=[ "AVG Close Price", "Combined Profit", "Profit per pair" ]) fig['layout'].update(title="Freqtrade Profit plot") fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}') fig['layout']['yaxis3'].update(title=f'Profit {stake_currency}') fig['layout']['xaxis']['rangeslider'].update(visible=False) fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"]) fig.add_trace(avgclose, 1, 1) fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit') fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe) for pair in pairs: profit_col = f'cum_profit_{pair}' try: df_comb = create_cum_profit(df_comb, trades[trades['pair'] == pair], profit_col, timeframe) fig = add_profit(fig, 3, df_comb, profit_col, f"Profit {pair}") except ValueError: pass return fig
def init_db(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() # Copy session attributes to order object too Order.session = Trade.session Order.query = Order.session.query_property() PairLock.session = Trade.session PairLock.query = PairLock.session.query_property() previous_tables = inspect(engine).get_table_names() _DECL_BASE.metadata.create_all(engine) check_migrate(engine, decl_base=_DECL_BASE, previous_tables=previous_tables) # Clean dry_run DB if the db is not in-memory if clean_open_orders and db_url != 'sqlite://': clean_dry_run_db()
def validate_timeframes(self, timeframe: Optional[str]) -> None: """ Checks if ticker interval from config is a supported timeframe on the exchange """ if not hasattr(self._api, "timeframes") or self._api.timeframes is None: # If timeframes attribute is missing (or is None), the exchange probably # has no fetchOHLCV method. # Therefore we also show that. raise OperationalException( f"The ccxt library does not provide the list of timeframes " f"for the exchange \"{self.name}\" and this exchange " f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}" ) if timeframe and (timeframe not in self.timeframes): raise OperationalException( f"Invalid ticker interval '{timeframe}'. This exchange supports: {self.timeframes}" ) if timeframe and timeframe_to_minutes(timeframe) < 1: raise OperationalException( f"Timeframes < 1m are currently not supported by Freqtrade.")
def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: Dict[str, Any], pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) if 'stoploss' not in self._config: raise OperationalException( 'PrecisionFilter can only work with stoploss defined. Please add the ' 'stoploss key to your configuration (overwrites eventual strategy settings).') self._stoploss = self._config['stoploss'] self._enabled = self._stoploss != 0 # Precalculate sanitized stoploss value to avoid recalculation for every pair self._stoploss = 1 - abs(self._stoploss)
def load_previous_results(results_file: Path) -> List: """ Load data for epochs from the file if we have one """ epochs: List = [] if results_file.is_file() and results_file.stat().st_size > 0: epochs = Hyperopt._read_results(results_file) # Detection of some old format, without 'is_best' field saved if epochs[0].get('is_best') is None: raise OperationalException( "The file with Hyperopt results is incompatible with this version " "of Freqtrade and cannot be loaded.") logger.info(f"Loaded {len(epochs)} previous evaluations from disk.") return epochs
def process_removed_setting(config: Dict[str, Any], section1: str, name1: str, section2: str, name2: str) -> None: """ :param section1: Removed section :param name1: Removed setting name :param section2: new section for this key :param name2: new setting name """ section1_config = config.get(section1, {}) if name1 in section1_config: raise OperationalException( f"Setting `{section1}.{name1}` has been moved to `{section2}.{name2}. " f"Please delete it from your configuration and use the `{section2}.{name2}` " "setting instead.")
def _load_strategy(strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy: """ Search and loads the specified strategy. :param strategy_name: name of the module to import :param config: configuration for the strategy :param extra_dir: additional directory to search for the given strategy :return: Strategy instance or None """ abs_paths = StrategyResolver.build_search_paths(config, user_subdir=USERPATH_STRATEGIES, extra_dir=extra_dir) if ":" in strategy_name: logger.info("loading base64 encoded strategy") strat = strategy_name.split(":") if len(strat) == 2: temp = Path(tempfile.mkdtemp("freq", "strategy")) name = strat[0] + ".py" temp.joinpath(name).write_text(urlsafe_b64decode(strat[1]).decode('utf-8')) temp.joinpath("__init__.py").touch() strategy_name = strat[0] # register temp path with the bot abs_paths.insert(0, temp.resolve()) strategy = StrategyResolver._load_object(paths=abs_paths, object_name=strategy_name, add_source=True, kwargs={'config': config}, ) if strategy: strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) if any([x == 2 for x in [strategy._populate_fun_len, strategy._buy_fun_len, strategy._sell_fun_len]]): strategy.INTERFACE_VERSION = 1 return strategy raise OperationalException( f"Impossible to load Strategy '{strategy_name}'. This class does not exist " "or contains Python code errors." )
def copy_sample_files(directory: Path, overwrite: bool = False) -> None: """ Copy files from templates to User data directory. :param directory: Directory to copy data to :param 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 logger.warning(f"File `{targetfile}` exists already, overwriting.") shutil.copy(str(sourcedir / source), str(targetfile))
def setup_logging(config: Dict[str, Any]) -> None: """ Process -v/--verbose, --logfile options """ # Log level verbosity = config['verbosity'] # Log to stderr log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stderr)] 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 = 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.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) log_handlers.append(handler) 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 = 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.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) log_handlers.append(handler) else: log_handlers.append(RotatingFileHandler(logfile, maxBytes=1024 * 1024, # 1Mb backupCount=10)) logging.basicConfig( level=logging.INFO if verbosity < 1 else logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=log_handlers ) _set_loggers(verbosity) logger.info('Verbosity set to %s', verbosity)
def get_fee(self, symbol: str, type: str = '', side: str = '', amount: float = 1, price: float = 1, taker_or_maker: str = '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}') from e except ccxt.BaseError as e: raise OperationalException(e) from 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}') 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
def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: Dict[str, Any], pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0) if self._low_price_ratio < 0: raise OperationalException( "PriceFilter requires low_price_ratio to be >= 0") self._min_price = pairlistconfig.get('min_price', 0) if self._min_price < 0: raise OperationalException( "PriceFilter requires min_price to be >= 0") self._max_price = pairlistconfig.get('max_price', 0) if self._max_price < 0: raise OperationalException( "PriceFilter requires max_price to be >= 0") self._max_value = pairlistconfig.get('max_value', 0) if self._max_value < 0: raise OperationalException( "PriceFilter requires max_value to be >= 0") self._enabled = ((self._low_price_ratio > 0) or (self._min_price > 0) or (self._max_price > 0) or (self._max_value > 0))
def load_config_file(path: str) -> Dict[str, Any]: """ Loads a config file from the given path :param 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
def __init__(self, config: Dict[str, Any], exchange, strategy) -> None: self.config = config self.exchange = exchange self.strategy: IStrategy = 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'] != UNLIMITED_STAKE_AMOUNT: raise OperationalException( 'Edge works only with unlimited stake amount') self._capital_ratio: float = self.config['tradable_balance_ratio'] 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 = TimeRange.parse_timerange( "%s-" % arrow.now().shift( days=-1 * self._since_number_of_days).format('YYYYMMDD')) if config.get('fee'): self.fee = config['fee'] else: try: self.fee = self.exchange.get_fee(symbol=expand_pairlist( self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None
def load_filtered_results(results_file: Path, config: Dict[str, Any]) -> Tuple[List, int]: filteroptions = { 'only_best': config.get('hyperopt_list_best', False), 'only_profitable': config.get('hyperopt_list_profitable', False), 'filter_min_trades': config.get('hyperopt_list_min_trades', 0), 'filter_max_trades': config.get('hyperopt_list_max_trades', 0), 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), 'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None), 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None), 'filter_min_objective': config.get('hyperopt_list_min_objective', None), 'filter_max_objective': config.get('hyperopt_list_max_objective', None), } if not HyperoptTools._test_hyperopt_results_exist(results_file): # No file found. return [], 0 epochs = [] total_epochs = 0 for epochs_tmp in HyperoptTools._read_results(results_file): if total_epochs == 0 and epochs_tmp[0].get('is_best') is None: raise OperationalException( "The file with HyperoptTools results is incompatible with this version " "of Freqtrade and cannot be loaded.") total_epochs += len(epochs_tmp) epochs += hyperopt_filter_epochs(epochs_tmp, filteroptions, log=False) logger.info(f"Loaded {total_epochs} previous evaluations from disk.") # Final filter run ... epochs = hyperopt_filter_epochs(epochs, filteroptions, log=True) return epochs, total_epochs
def init_backtest_detail(self): # Load detail timeframe if specified self.timeframe_detail = str(self.config.get('timeframe_detail', '')) if self.timeframe_detail: self.timeframe_detail_min = timeframe_to_minutes( self.timeframe_detail) if self.timeframe_min <= self.timeframe_detail_min: raise OperationalException( "Detail timeframe must be smaller than strategy timeframe." ) else: self.timeframe_detail_min = 0 self.detail_data: Dict[str, DataFrame] = {}
def __init__(self, low: Union[float, Sequence[float]], high: Optional[float] = None, *, default: float, space: Optional[str] = None, optimize: bool = True, load: bool = True, **kwargs): """ Initialize hyperopt-optimizable floating point parameter with unlimited precision. :param low: Lower end (inclusive) of optimization space or [low, high]. :param high: Upper end (inclusive) of optimization space. Must be none if entire range is passed first parameter. :param default: A default value. :param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if parameter fieldname is prefixed with 'buy_' or 'sell_'. :param optimize: Include parameter in hyperopt optimizations. :param load: Load parameter value from {space}_params. :param kwargs: Extra parameters to skopt.space.Real. """ if high is not None and isinstance(low, Sequence): raise OperationalException( f'{self.__class__.__name__} space invalid.') if high is None or isinstance(low, Sequence): if not isinstance(low, Sequence) or len(low) != 2: raise OperationalException( f'{self.__class__.__name__} space must be [low, high]') opt_range = low else: opt_range = [low, high] super().__init__(opt_range=opt_range, default=default, space=space, optimize=optimize, load=load, **kwargs)
def _init_ccxt(self, exchange_config: dict, ccxt_module=ccxt, ccxt_kwargs: dict = None) -> ccxt.Exchange: """ Initialize ccxt with given config and return valid ccxt instance. """ # Find matching class for the given exchange name name = exchange_config['name'] if not is_exchange_known_ccxt(name, ccxt_module): raise OperationalException( f'Exchange {name} is not supported by ccxt') ex_config = { 'apiKey': exchange_config.get('key'), 'secret': exchange_config.get('secret'), 'password': exchange_config.get('password'), 'uid': exchange_config.get('uid', ''), } if ccxt_kwargs: logger.info('Applying additional ccxt config: %s', ccxt_kwargs) ex_config.update(ccxt_kwargs) try: api = getattr(ccxt_module, name.lower())(ex_config) except (KeyError, AttributeError) as e: raise OperationalException( f'Exchange {name} is not supported') from e except ccxt.BaseError as e: raise OperationalException( f"Initialization of ccxt failed. Reason: {e}") from e self.set_sandbox(api, exchange_config, name) return api