diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8fa4bf9ee..5d8903496 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.5.0' + rev: 'v0.5.1' hooks: - id: ruff diff --git a/docs/developer.md b/docs/developer.md index 705e8d116..8cf20d966 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -22,7 +22,7 @@ This will spin up a local server (usually on port 8000) so you can see if everyt ## Developer setup To configure a development environment, you can either use the provided [DevContainer](#devcontainer-setup), or use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ". -Alternatively (e.g. if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -e .[all]`. +Alternatively (e.g. if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -r requirements-dev.txt` - followed by `pip3 install -e .[all]`. This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`. diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 845aba7bb..27c1a125e 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.6 mkdocs==1.6.0 -mkdocs-material==9.5.27 +mkdocs-material==9.5.28 mdx_truly_sane_lists==1.3 pymdown-extensions==10.8.1 jinja2==3.1.4 diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index d9c41c38b..0cb8dd961 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -6,7 +6,7 @@ if "dev" in __version__: from pathlib import Path try: - import subprocess + import subprocess # noqa: S404 freqtrade_basedir = Path(__file__).parent diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 92e60daa4..f3f56c7b2 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -16,6 +16,7 @@ from freqtrade.exceptions import ConfigurationError from freqtrade.exchange import timeframe_to_minutes from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist from freqtrade.resolvers import ExchangeResolver +from freqtrade.util import print_rich_table from freqtrade.util.migrations import migrate_data @@ -119,8 +120,6 @@ def start_list_data(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) - from tabulate import tabulate - from freqtrade.data.history import get_datahandler dhc = get_datahandler(config["datadir"], config["dataformat_ohlcv"]) @@ -131,8 +130,7 @@ def start_list_data(args: Dict[str, Any]) -> None: if args["pairs"]: paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]] - - print(f"Found {len(paircombs)} pair / timeframe combinations.") + title = f"Found {len(paircombs)} pair / timeframe combinations." if not config.get("show_timerange"): groupedpair = defaultdict(list) for pair, timeframe, candle_type in sorted( @@ -141,40 +139,35 @@ def start_list_data(args: Dict[str, Any]) -> None: groupedpair[(pair, candle_type)].append(timeframe) if groupedpair: - print( - tabulate( - [ - (pair, ", ".join(timeframes), candle_type) - for (pair, candle_type), timeframes in groupedpair.items() - ], - headers=("Pair", "Timeframe", "Type"), - tablefmt="psql", - stralign="right", - ) + print_rich_table( + [ + (pair, ", ".join(timeframes), candle_type) + for (pair, candle_type), timeframes in groupedpair.items() + ], + ("Pair", "Timeframe", "Type"), + title, + table_kwargs={"min_width": 50}, ) else: paircombs1 = [ (pair, timeframe, candle_type, *dhc.ohlcv_data_min_max(pair, timeframe, candle_type)) for pair, timeframe, candle_type in paircombs ] - - print( - tabulate( - [ - ( - pair, - timeframe, - candle_type, - start.strftime(DATETIME_PRINT_FORMAT), - end.strftime(DATETIME_PRINT_FORMAT), - length, - ) - for pair, timeframe, candle_type, start, end, length in sorted( - paircombs1, key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2]) - ) - ], - headers=("Pair", "Timeframe", "Type", "From", "To", "Candles"), - tablefmt="psql", - stralign="right", - ) + print_rich_table( + [ + ( + pair, + timeframe, + candle_type, + start.strftime(DATETIME_PRINT_FORMAT), + end.strftime(DATETIME_PRINT_FORMAT), + str(length), + ) + for pair, timeframe, candle_type, start, end, length in sorted( + paircombs1, key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2]) + ) + ], + ("Pair", "Timeframe", "Type", "From", "To", "Candles"), + summary=title, + table_kwargs={"min_width": 50}, ) diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index ac0b8453f..d89d25796 100644 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -2,8 +2,6 @@ import logging from operator import itemgetter from typing import Any, Dict -from colorama import init as colorama_init - from freqtrade.configuration import setup_utils_configuration from freqtrade.data.btanalysis import get_latest_hyperopt_file from freqtrade.enums import RunMode @@ -18,6 +16,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: """ List hyperopt epochs previously evaluated """ + from freqtrade.optimize.hyperopt_output import HyperoptOutput from freqtrade.optimize.hyperopt_tools import HyperoptTools config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) @@ -35,21 +34,17 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: # Previous evaluations epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config) - if print_colorized: - colorama_init(autoreset=True) - if not export_csv: try: - print( - HyperoptTools.get_result_table( - config, - epochs, - total_epochs, - not config.get("hyperopt_list_best", False), - print_colorized, - 0, - ) + h_out = HyperoptOutput() + h_out.add_data( + config, + epochs, + total_epochs, + not config.get("hyperopt_list_best", False), ) + h_out.print(print_colorized=print_colorized) + except KeyboardInterrupt: print("User interrupted..") diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 3a542226c..1696fc8f0 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -4,9 +4,9 @@ import sys from typing import Any, Dict, List, Union import rapidjson -from colorama import Fore, Style -from colorama import init as colorama_init -from tabulate import tabulate +from rich.console import Console +from rich.table import Table +from rich.text import Text from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode @@ -14,7 +14,8 @@ from freqtrade.exceptions import ConfigurationError, OperationalException from freqtrade.exchange import list_available_exchanges, market_is_active from freqtrade.misc import parse_db_uri_for_logging, plural from freqtrade.resolvers import ExchangeResolver, StrategyResolver -from freqtrade.types import ValidExchangesType +from freqtrade.types.valid_exchanges_type import ValidExchangesType +from freqtrade.util import print_rich_table logger = logging.getLogger(__name__) @@ -26,72 +27,69 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: :param args: Cli args from Arguments() :return: None """ - exchanges = list_available_exchanges(args["list_exchanges_all"]) + available_exchanges: List[ValidExchangesType] = list_available_exchanges( + args["list_exchanges_all"] + ) if args["print_one_column"]: - print("\n".join([e["name"] for e in exchanges])) + print("\n".join([e["name"] for e in available_exchanges])) else: - headers = { - "name": "Exchange name", - "supported": "Supported", - "trade_modes": "Markets", - "comment": "Reason", - } - headers.update({"valid": "Valid"} if args["list_exchanges_all"] else {}) + if args["list_exchanges_all"]: + title = ( + f"All exchanges supported by the ccxt library " + f"({len(available_exchanges)} exchanges):" + ) + else: + available_exchanges = [e for e in available_exchanges if e["valid"] is not False] + title = f"Exchanges available for Freqtrade ({len(available_exchanges)} exchanges):" - def build_entry(exchange: ValidExchangesType, valid: bool): - valid_entry = {"valid": exchange["valid"]} if valid else {} - result: Dict[str, Union[str, bool]] = { - "name": exchange["name"], - **valid_entry, - "supported": "Official" if exchange["supported"] else "", - "trade_modes": ("DEX: " if exchange["dex"] else "") - + ", ".join( - (f"{a['margin_mode']} " if a["margin_mode"] else "") + a["trading_mode"] + table = Table(title=title) + + table.add_column("Exchange Name") + table.add_column("Markets") + table.add_column("Reason") + + for exchange in available_exchanges: + name = Text(exchange["name"]) + if exchange["supported"]: + name.append(" (Official)", style="italic") + name.stylize("green bold") + + trade_modes = Text( + ", ".join( + (f"{a.get('margin_mode', '')} {a['trading_mode']}").lstrip() for a in exchange["trade_modes"] ), - "comment": exchange["comment"], - } - - return result - - if args["list_exchanges_all"]: - print("All exchanges supported by the ccxt library:") - exchanges = [build_entry(e, True) for e in exchanges] - else: - print("Exchanges available for Freqtrade:") - exchanges = [build_entry(e, False) for e in exchanges if e["valid"] is not False] - - print( - tabulate( - exchanges, - headers=headers, + style="", ) - ) + if exchange["dex"]: + trade_modes = Text("DEX: ") + trade_modes + trade_modes.stylize("bold", 0, 3) + + table.add_row( + name, + trade_modes, + exchange["comment"], + style=None if exchange["valid"] else "red", + ) + # table.add_row(*[exchange[header] for header in headers]) + + console = Console() + console.print(table) def _print_objs_tabular(objs: List, print_colorized: bool) -> None: - if print_colorized: - colorama_init(autoreset=True) - red = Fore.RED - yellow = Fore.YELLOW - reset = Style.RESET_ALL - else: - red = "" - yellow = "" - reset = "" - names = [s["name"] for s in objs] - objs_to_print = [ + objs_to_print: List[Dict[str, Union[Text, str]]] = [ { - "name": s["name"] if s["name"] else "--", + "name": Text(s["name"] if s["name"] else "--"), "location": s["location_rel"], "status": ( - red + "LOAD FAILED" + reset + Text("LOAD FAILED", style="bold red") if s["class"] is None - else "OK" + else Text("OK", style="bold green") if names.count(s["name"]) == 1 - else yellow + "DUPLICATE NAME" + reset + else Text("DUPLICATE NAME", style="bold yellow") ), } for s in objs @@ -101,11 +99,23 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: objs_to_print[idx].update( { "hyperoptable": "Yes" if s["hyperoptable"]["count"] > 0 else "No", - "buy-Params": len(s["hyperoptable"].get("buy", [])), - "sell-Params": len(s["hyperoptable"].get("sell", [])), + "buy-Params": str(len(s["hyperoptable"].get("buy", []))), + "sell-Params": str(len(s["hyperoptable"].get("sell", []))), } ) - print(tabulate(objs_to_print, headers="keys", tablefmt="psql", stralign="right")) + table = Table() + + for header in objs_to_print[0].keys(): + table.add_column(header.capitalize(), justify="right") + + for row in objs_to_print: + table.add_row(*[row[header] for header in objs_to_print[0].keys()]) + + console = Console( + color_system="auto" if print_colorized else None, + width=200 if "pytest" in sys.modules else None, + ) + console.print(table) def start_list_strategies(args: Dict[str, Any]) -> None: @@ -270,9 +280,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: 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")) + print_rich_table(tabular_data, headers, summary_str) elif not ( args.get("print_one_column", False) or args.get("list_pairs_print_json", False) diff --git a/freqtrade/configuration/directory_operations.py b/freqtrade/configuration/directory_operations.py index 99d72dabe..3e6ed92ed 100644 --- a/freqtrade/configuration/directory_operations.py +++ b/freqtrade/configuration/directory_operations.py @@ -38,7 +38,7 @@ def chown_user_directory(directory: Path) -> None: """ if running_in_docker(): try: - import subprocess + import subprocess # noqa: S404 subprocess.check_output(["sudo", "chown", "-R", "ftuser:", str(directory.resolve())]) except Exception: diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py index 9d936d295..e76f2dff7 100644 --- a/freqtrade/data/entryexitanalysis.py +++ b/freqtrade/data/entryexitanalysis.py @@ -4,7 +4,6 @@ from typing import List import joblib import pandas as pd -from tabulate import tabulate from freqtrade.configuration import TimeRange from freqtrade.constants import Config @@ -14,6 +13,7 @@ from freqtrade.data.btanalysis import ( load_backtest_stats, ) from freqtrade.exceptions import OperationalException +from freqtrade.util import print_df_rich_table logger = logging.getLogger(__name__) @@ -307,7 +307,7 @@ def _print_table( if name is not None: print(name) - print(tabulate(data, headers="keys", tablefmt="psql", showindex=show_index)) + print_df_rich_table(data, data.keys(), show_index=show_index) def process_entry_exit_reasons(config: Config): diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 251325a0c..21c67d291 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -65,6 +65,7 @@ SUPPORTED_EXCHANGES = [ EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = { # Required / private "fetchOrder": ["fetchOpenOrder", "fetchClosedOrder"], + "fetchL2OrderBook": ["fetchTicker"], "cancelOrder": [], "createOrder": [], "fetchBalance": [], diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e4e4e0e23..983a0914d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -624,9 +624,12 @@ class Exchange: # Reload async markets, then assign them to sync api self._markets = self._load_async_markets(reload=True) self._api.set_markets(self._api_async.markets, self._api_async.currencies) + # Assign options array, as it contains some temporary information from the exchange. + self._api.options = self._api_async.options if self._exchange_ws: # Set markets to avoid reloading on websocket api self._ws_async.set_markets(self._api.markets, self._api.currencies) + self._ws_async.options = self._api.options self._last_markets_refresh = dt_ts() if is_initial and self._ft_has["needs_trading_fees"]: @@ -2084,7 +2087,7 @@ class Exchange: def get_fee( self, symbol: str, - type: str = "", + order_type: str = "", side: str = "", amount: float = 1, price: float = 1, @@ -2093,13 +2096,13 @@ class Exchange: """ Retrieve fee from exchange :param symbol: Pair - :param type: Type of order (market, limit, ...) + :param order_type: Type of order (market, limit, ...) :param side: Side of order (buy, sell) :param amount: Amount of order :param price: Price of order :param taker_or_maker: 'maker' or 'taker' (ignored if "type" is provided) """ - if type and type == "market": + if order_type and order_type == "market": taker_or_maker = "taker" try: if self._config["dry_run"] and self._config.get("fee", None) is not None: @@ -2110,7 +2113,7 @@ class Exchange: return self._api.calculate_fee( symbol=symbol, - type=type, + type=order_type, side=side, amount=amount, price=price, diff --git a/freqtrade/freqai/RL/BaseEnvironment.py b/freqtrade/freqai/RL/BaseEnvironment.py index ba72c90ed..5ddfdeb68 100644 --- a/freqtrade/freqai/RL/BaseEnvironment.py +++ b/freqtrade/freqai/RL/BaseEnvironment.py @@ -52,7 +52,7 @@ class BaseEnvironment(gym.Env): reward_kwargs: dict = {}, window_size=10, starting_point=True, - id: str = "baseenv-1", + id: str = "baseenv-1", # noqa: A002 seed: int = 1, config: dict = {}, live: bool = False, diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 37780a945..124ed9e26 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -238,9 +238,9 @@ class FreqaiDataDrawer: metadata, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE ) - def np_encoder(self, object): - if isinstance(object, np.generic): - return object.item() + def np_encoder(self, obj): + if isinstance(obj, np.generic): + return obj.item() def get_pair_dict_info(self, pair: str) -> Tuple[str, int]: """ @@ -448,8 +448,8 @@ class FreqaiDataDrawer: delete_dict: Dict[str, Any] = {} - for dir in model_folders: - result = pattern.match(str(dir.name)) + for directory in model_folders: + result = pattern.match(str(directory.name)) if result is None: continue coin = result.group(1) @@ -458,10 +458,10 @@ class FreqaiDataDrawer: if coin not in delete_dict: delete_dict[coin] = {} delete_dict[coin]["num_folders"] = 1 - delete_dict[coin]["timestamps"] = {int(timestamp): dir} + delete_dict[coin]["timestamps"] = {int(timestamp): directory} else: delete_dict[coin]["num_folders"] += 1 - delete_dict[coin]["timestamps"][int(timestamp)] = dir + delete_dict[coin]["timestamps"][int(timestamp)] = directory for coin in delete_dict: if delete_dict[coin]["num_folders"] > num_keep: @@ -612,9 +612,9 @@ class FreqaiDataDrawer: elif self.model_type == "pytorch": import torch - zip = torch.load(dk.data_path / f"{dk.model_filename}_model.zip") - model = zip["pytrainer"] - model = model.load_from_checkpoint(zip) + zipfile = torch.load(dk.data_path / f"{dk.model_filename}_model.zip") + model = zipfile["pytrainer"] + model = model.load_from_checkpoint(zipfile) if not model: raise OperationalException( diff --git a/freqtrade/freqai/tensorboard/tensorboard.py b/freqtrade/freqai/tensorboard/tensorboard.py index 3a306f377..81f48047e 100644 --- a/freqtrade/freqai/tensorboard/tensorboard.py +++ b/freqtrade/freqai/tensorboard/tensorboard.py @@ -45,10 +45,10 @@ class TensorBoardCallback(BaseTensorBoardCallback): return False evals = ["validation", "train"] - for metric, eval in zip(evals_log.items(), evals): + for metric, eval_ in zip(evals_log.items(), evals): for metric_name, log in metric[1].items(): score = log[-1][0] if isinstance(log[-1], tuple) else log[-1] - self.writer.add_scalar(f"{eval}-{metric_name}", score, epoch) + self.writer.add_scalar(f"{eval_}-{metric_name}", score, epoch) return False diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index b3ff4923d..5a33e9fa6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -2370,6 +2370,18 @@ class FreqtradeBot(LoggingMixin): trade, order, order_obj, order_amount, order.get("trades", []) ) + def _trades_valid_for_fee(self, trades: List[Dict[str, Any]]) -> bool: + """ + Check if trades are valid for fee detection. + :return: True if trades are valid for fee detection, False otherwise + """ + if not trades: + return False + # We expect amount and cost to be present in all trade objects. + if any(trade.get("amount") is None or trade.get("cost") is None for trade in trades): + return False + return True + def fee_detection_from_trades( self, trade: Trade, order: Dict, order_obj: Order, order_amount: float, trades: List ) -> Optional[float]: @@ -2377,7 +2389,7 @@ class FreqtradeBot(LoggingMixin): fee-detection fallback to Trades. Either uses provided trades list or the result of fetch_my_trades to get correct fee. """ - if not trades: + if not self._trades_valid_for_fee(trades): trades = self.exchange.get_trades_for_order( self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date ) diff --git a/freqtrade/optimize/analysis/lookahead_helpers.py b/freqtrade/optimize/analysis/lookahead_helpers.py index c0e6fa1ba..a8fb1cd35 100644 --- a/freqtrade/optimize/analysis/lookahead_helpers.py +++ b/freqtrade/optimize/analysis/lookahead_helpers.py @@ -4,11 +4,13 @@ from pathlib import Path from typing import Any, Dict, List import pandas as pd +from rich.text import Text from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.optimize.analysis.lookahead import LookaheadAnalysis from freqtrade.resolvers import StrategyResolver +from freqtrade.util import print_rich_table logger = logging.getLogger(__name__) @@ -53,18 +55,18 @@ class LookaheadAnalysisSubFunctions: [ inst.strategy_obj["location"].parts[-1], inst.strategy_obj["name"], - inst.current_analysis.has_bias, + Text("Yes", style="bold red") + if inst.current_analysis.has_bias + else Text("No", style="bold green"), inst.current_analysis.total_signals, inst.current_analysis.false_entry_signals, inst.current_analysis.false_exit_signals, ", ".join(inst.current_analysis.false_indicators), ] ) - from tabulate import tabulate - table = tabulate(data, headers=headers, tablefmt="orgtbl") - print(table) - return table, headers, data + print_rich_table(data, headers, summary="Lookahead Analysis") + return data @staticmethod def export_to_csv(config: Dict[str, Any], lookahead_analysis: List[LookaheadAnalysis]): diff --git a/freqtrade/optimize/analysis/recursive_helpers.py b/freqtrade/optimize/analysis/recursive_helpers.py index cde1a214e..be596fa68 100644 --- a/freqtrade/optimize/analysis/recursive_helpers.py +++ b/freqtrade/optimize/analysis/recursive_helpers.py @@ -7,6 +7,7 @@ from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.optimize.analysis.recursive import RecursiveAnalysis from freqtrade.resolvers import StrategyResolver +from freqtrade.util import print_rich_table logger = logging.getLogger(__name__) @@ -16,9 +17,9 @@ class RecursiveAnalysisSubFunctions: @staticmethod def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]): startups = recursive_instances[0]._startup_candle - headers = ["indicators"] + headers = ["Indicators"] for candle in startups: - headers.append(candle) + headers.append(str(candle)) data = [] for inst in recursive_instances: @@ -30,13 +31,11 @@ class RecursiveAnalysisSubFunctions: data.append(temp_data) if len(data) > 0: - from tabulate import tabulate + print_rich_table(data, headers, summary="Recursive Analysis") - table = tabulate(data, headers=headers, tablefmt="orgtbl") - print(table) - return table, headers, data + return data - return None, None, data + return data @staticmethod def calculate_config_overrides(config: Config): diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a71ec170d..c28c080f5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -465,25 +465,25 @@ class Backtesting: return data def _get_close_rate( - self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int + self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int ) -> float: """ Get close rate for backtesting result """ # Special handling if high or low hit STOP_LOSS or ROI - if exit.exit_type in ( + if exit_.exit_type in ( ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS, ExitType.LIQUIDATION, ): - return self._get_close_rate_for_stoploss(row, trade, exit, trade_dur) - elif exit.exit_type == (ExitType.ROI): - return self._get_close_rate_for_roi(row, trade, exit, trade_dur) + return self._get_close_rate_for_stoploss(row, trade, exit_, trade_dur) + elif exit_.exit_type == (ExitType.ROI): + return self._get_close_rate_for_roi(row, trade, exit_, trade_dur) else: return row[OPEN_IDX] def _get_close_rate_for_stoploss( - self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int + self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int ) -> float: # our stoploss was already lower than candle high, # possibly due to a cancelled trade exit. @@ -491,7 +491,7 @@ class Backtesting: is_short = trade.is_short or False leverage = trade.leverage or 1.0 side_1 = -1 if is_short else 1 - if exit.exit_type == ExitType.LIQUIDATION and trade.liquidation_price: + if exit_.exit_type == ExitType.LIQUIDATION and trade.liquidation_price: stoploss_value = trade.liquidation_price else: stoploss_value = trade.stop_loss @@ -506,7 +506,7 @@ class Backtesting: # Special case: trailing triggers within same candle as trade opened. Assume most # pessimistic price movement, which is moving just enough to arm stoploss and # immediately going down to stop price. - if exit.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: + if exit_.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: if ( not self.strategy.use_custom_stoploss and self.strategy.trailing_stop @@ -537,7 +537,7 @@ class Backtesting: return stoploss_value def _get_close_rate_for_roi( - self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int + self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int ) -> float: is_short = trade.is_short or False leverage = trade.leverage or 1.0 diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 9bd8ff1c9..65dd30efc 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -52,4 +52,4 @@ class EdgeCli: result = self.edge.calculate(self.config["exchange"]["pair_whitelist"]) if result: print("") # blank line for readability - print(generate_edge_table(self.edge._cached_pairs)) + generate_edge_table(self.edge._cached_pairs) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index b19fca9dc..b411e7752 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -14,14 +14,14 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Tuple import rapidjson -from colorama import init as colorama_init from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects from joblib.externals import cloudpickle from pandas import DataFrame +from rich.align import Align +from rich.console import Console from rich.progress import ( BarColumn, MofNCompleteColumn, - Progress, TaskProgressColumn, TextColumn, TimeElapsedColumn, @@ -40,6 +40,7 @@ from freqtrade.optimize.backtesting import Backtesting # Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules from freqtrade.optimize.hyperopt_auto import HyperOptAuto from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss +from freqtrade.optimize.hyperopt_output import HyperoptOutput from freqtrade.optimize.hyperopt_tools import ( HyperoptStateContainer, HyperoptTools, @@ -47,6 +48,7 @@ from freqtrade.optimize.hyperopt_tools import ( ) from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver +from freqtrade.util import CustomProgress # Suppress scikit-learn FutureWarnings from skopt @@ -86,6 +88,8 @@ class Hyperopt: self.max_open_trades_space: List[Dimension] = [] self.dimensions: List[Dimension] = [] + self._hyper_out: HyperoptOutput = HyperoptOutput() + self.config = config self.min_date: datetime self.max_date: datetime @@ -260,7 +264,7 @@ class Hyperopt: result["max_open_trades"] = {"max_open_trades": strategy.max_open_trades} return result - def print_results(self, results) -> None: + def print_results(self, results: Dict[str, Any]) -> None: """ Log results if it is better than any previous evaluation TODO: this should be moved to HyperoptTools too @@ -268,17 +272,12 @@ class Hyperopt: is_best = results["is_best"] if self.print_all or is_best: - print( - HyperoptTools.get_result_table( - self.config, - results, - self.total_epochs, - self.print_all, - self.print_colorized, - self.hyperopt_table_header, - ) + self._hyper_out.add_data( + self.config, + [results], + self.total_epochs, + self.print_all, ) - self.hyperopt_table_header = 2 def init_spaces(self): """ @@ -626,16 +625,16 @@ class Hyperopt: self.opt = self.get_optimizer(self.dimensions, config_jobs) - if self.print_colorized: - colorama_init(autoreset=True) - try: with Parallel(n_jobs=config_jobs) as parallel: jobs = parallel._effective_n_jobs() logger.info(f"Effective number of parallel workers used: {jobs}") + console = Console( + color_system="auto" if self.print_colorized else None, + ) # Define progressbar - with Progress( + with CustomProgress( TextColumn("[progress.description]{task.description}"), BarColumn(bar_width=None), MofNCompleteColumn(), @@ -645,6 +644,8 @@ class Hyperopt: "•", TimeRemainingColumn(), expand=True, + console=console, + cust_objs=[Align.center(self._hyper_out.table)], ) as pbar: task = pbar.add_task("Epochs", total=self.total_epochs) diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py new file mode 100644 index 000000000..72e049745 --- /dev/null +++ b/freqtrade/optimize/hyperopt_output.py @@ -0,0 +1,123 @@ +import sys +from typing import List, Optional, Union + +from rich.console import Console +from rich.table import Table +from rich.text import Text + +from freqtrade.constants import Config +from freqtrade.optimize.optimize_reports import generate_wins_draws_losses +from freqtrade.util import fmt_coin + + +class HyperoptOutput: + def __init__(self): + self.table = Table( + title="Hyperopt results", + ) + # Headers + self.table.add_column("Best", justify="left") + self.table.add_column("Epoch", justify="right") + self.table.add_column("Trades", justify="right") + self.table.add_column("Win Draw Loss Win%", justify="right") + self.table.add_column("Avg profit", justify="right") + self.table.add_column("Profit", justify="right") + self.table.add_column("Avg duration", justify="right") + self.table.add_column("Objective", justify="right") + self.table.add_column("Max Drawdown (Acct)", justify="right") + + def _add_row(self, data: List[Union[str, Text]]): + """Add single row""" + row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in data] + + self.table.add_row(*row_to_add) + + def _add_rows(self, data: List[List[Union[str, Text]]]): + """add multiple rows""" + for row in data: + self._add_row(row) + + def print(self, console: Optional[Console] = None, *, print_colorized=True): + if not console: + console = Console( + color_system="auto" if print_colorized else None, + width=200 if "pytest" in sys.modules else None, + ) + + console.print(self.table) + + def add_data( + self, + config: Config, + results: list, + total_epochs: int, + highlight_best: bool, + ) -> None: + """Format one or multiple rows and add them""" + stake_currency = config["stake_currency"] + + for r in results: + self.table.add_row( + *[ + # "Best": + ( + ("*" if r["is_initial_point"] or r["is_random"] else "") + + (" Best" if r["is_best"] else "") + ).lstrip(), + # "Epoch": + f"{r['current_epoch']}/{total_epochs}", + # "Trades": + str(r["results_metrics"]["total_trades"]), + # "Win Draw Loss Win%": + generate_wins_draws_losses( + r["results_metrics"]["wins"], + r["results_metrics"]["draws"], + r["results_metrics"]["losses"], + ), + # "Avg profit": + f"{r['results_metrics']['profit_mean']:.2%}" + if r["results_metrics"]["profit_mean"] is not None + else "--", + # "Profit": + Text( + "{} {}".format( + fmt_coin( + r["results_metrics"]["profit_total_abs"], + stake_currency, + keep_trailing_zeros=True, + ), + f"({r['results_metrics']['profit_total']:,.2%})".rjust(10, " "), + ) + if r["results_metrics"].get("profit_total_abs", 0) != 0.0 + else "--", + style=( + "green" + if r["results_metrics"].get("profit_total_abs", 0) > 0 + else "red" + ) + if not r["is_best"] + else "", + ), + # "Avg duration": + str(r["results_metrics"]["holding_avg"]), + # "Objective": + f"{r['loss']:,.5f}" if r["loss"] != 100000 else "N/A", + # "Max Drawdown (Acct)": + "{} {}".format( + fmt_coin( + r["results_metrics"]["max_drawdown_abs"], + stake_currency, + keep_trailing_zeros=True, + ), + (f"({r['results_metrics']['max_drawdown_account']:,.2%})").rjust(10, " "), + ) + if r["results_metrics"]["max_drawdown_account"] != 0.0 + else "--", + ], + style=" ".join( + [ + "bold gold1" if r["is_best"] and highlight_best else "", + "italic " if r["is_initial_point"] else "", + ] + ), + ) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 50c55c43d..975338cd5 100644 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -5,10 +5,7 @@ from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple import numpy as np -import pandas as pd import rapidjson -import tabulate -from colorama import Fore, Style from pandas import isna, json_normalize from freqtrade.constants import FTHYPT_FILEVERSION, Config @@ -16,8 +13,6 @@ from freqtrade.enums import HyperoptState from freqtrade.exceptions import OperationalException from freqtrade.misc import deep_merge_dicts, round_dict, safe_value_fallback2 from freqtrade.optimize.hyperopt_epoch_filters import hyperopt_filter_epochs -from freqtrade.optimize.optimize_reports import generate_wins_draws_losses -from freqtrade.util import fmt_coin logger = logging.getLogger(__name__) @@ -357,175 +352,6 @@ class HyperoptTools: + f"Objective: {results['loss']:.5f}" ) - @staticmethod - def prepare_trials_columns(trials: pd.DataFrame) -> pd.DataFrame: - trials["Best"] = "" - - if "results_metrics.winsdrawslosses" not in trials.columns: - # Ensure compatibility with older versions of hyperopt results - trials["results_metrics.winsdrawslosses"] = "N/A" - - has_account_drawdown = "results_metrics.max_drawdown_account" in trials.columns - if not has_account_drawdown: - # Ensure compatibility with older versions of hyperopt results - trials["results_metrics.max_drawdown_account"] = None - if "is_random" not in trials.columns: - trials["is_random"] = False - - # New mode, using backtest result for metrics - trials["results_metrics.winsdrawslosses"] = trials.apply( - lambda x: generate_wins_draws_losses( - x["results_metrics.wins"], x["results_metrics.draws"], x["results_metrics.losses"] - ), - axis=1, - ) - - trials = trials[ - [ - "Best", - "current_epoch", - "results_metrics.total_trades", - "results_metrics.winsdrawslosses", - "results_metrics.profit_mean", - "results_metrics.profit_total_abs", - "results_metrics.profit_total", - "results_metrics.holding_avg", - "results_metrics.max_drawdown_account", - "results_metrics.max_drawdown_abs", - "loss", - "is_initial_point", - "is_random", - "is_best", - ] - ] - - trials.columns = [ - "Best", - "Epoch", - "Trades", - " Win Draw Loss Win%", - "Avg profit", - "Total profit", - "Profit", - "Avg duration", - "max_drawdown_account", - "max_drawdown_abs", - "Objective", - "is_initial_point", - "is_random", - "is_best", - ] - - return trials - - @staticmethod - def get_result_table( - config: Config, - results: list, - total_epochs: int, - highlight_best: bool, - print_colorized: bool, - remove_header: int, - ) -> str: - """ - Log result table - """ - if not results: - return "" - - tabulate.PRESERVE_WHITESPACE = True - trials = json_normalize(results, max_level=1) - - trials = HyperoptTools.prepare_trials_columns(trials) - - trials["is_profit"] = False - trials.loc[trials["is_initial_point"] | trials["is_random"], "Best"] = "* " - trials.loc[trials["is_best"], "Best"] = "Best" - trials.loc[ - (trials["is_initial_point"] | trials["is_random"]) & trials["is_best"], "Best" - ] = "* Best" - trials.loc[trials["Total profit"] > 0, "is_profit"] = True - trials["Trades"] = trials["Trades"].astype(str) - # perc_multi = 1 if legacy_mode else 100 - trials["Epoch"] = trials["Epoch"].apply( - lambda x: "{}/{}".format(str(x).rjust(len(str(total_epochs)), " "), total_epochs) - ) - trials["Avg profit"] = trials["Avg profit"].apply( - lambda x: f"{x:,.2%}".rjust(7, " ") if not isna(x) else "--".rjust(7, " ") - ) - trials["Avg duration"] = trials["Avg duration"].apply( - lambda x: ( - f"{x:,.1f} m".rjust(7, " ") - if isinstance(x, float) - else f"{x}" - if not isna(x) - else "--".rjust(7, " ") - ) - ) - trials["Objective"] = trials["Objective"].apply( - lambda x: f"{x:,.5f}".rjust(8, " ") if x != 100000 else "N/A".rjust(8, " ") - ) - - stake_currency = config["stake_currency"] - - trials["Max Drawdown (Acct)"] = trials.apply( - lambda x: ( - "{} {}".format( - fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True), - (f"({x['max_drawdown_account']:,.2%})").rjust(10, " "), - ).rjust(25 + len(stake_currency)) - if x["max_drawdown_account"] != 0.0 - else "--".rjust(25 + len(stake_currency)) - ), - axis=1, - ) - - trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown_account"]) - - trials["Profit"] = trials.apply( - lambda x: ( - "{} {}".format( - fmt_coin(x["Total profit"], stake_currency, keep_trailing_zeros=True), - f"({x['Profit']:,.2%})".rjust(10, " "), - ).rjust(25 + len(stake_currency)) - if x["Total profit"] != 0.0 - else "--".rjust(25 + len(stake_currency)) - ), - axis=1, - ) - trials = trials.drop(columns=["Total profit"]) - - if print_colorized: - trials2 = trials.astype(str) - for i in range(len(trials)): - if trials.loc[i]["is_profit"]: - for j in range(len(trials.loc[i]) - 3): - trials2.iat[i, j] = f"{Fore.GREEN}{str(trials.iloc[i, j])}{Fore.RESET}" - if trials.loc[i]["is_best"] and highlight_best: - for j in range(len(trials.loc[i]) - 3): - trials2.iat[i, j] = ( - f"{Style.BRIGHT}{str(trials.iloc[i, j])}{Style.RESET_ALL}" - ) - trials = trials2 - del trials2 - trials = trials.drop(columns=["is_initial_point", "is_best", "is_profit", "is_random"]) - if remove_header > 0: - table = tabulate.tabulate( - trials.to_dict(orient="list"), tablefmt="orgtbl", headers="keys", stralign="right" - ) - - table = table.split("\n", remove_header)[remove_header] - elif remove_header < 0: - table = tabulate.tabulate( - trials.to_dict(orient="list"), tablefmt="psql", headers="keys", stralign="right" - ) - table = "\n".join(table.split("\n")[0:remove_header]) - else: - table = tabulate.tabulate( - trials.to_dict(orient="list"), tablefmt="psql", headers="keys", stralign="right" - ) - return table - @staticmethod def export_csv_file(config: Config, results: list, csv_file: str) -> None: """ diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index f20d7f190..620b6da7e 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -1,12 +1,10 @@ import logging -from typing import Any, Dict, List, Union - -from tabulate import tabulate +from typing import Any, Dict, List, Literal, Union from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config from freqtrade.optimize.optimize_reports.optimize_reports import generate_periodic_breakdown_stats from freqtrade.types import BacktestResultType -from freqtrade.util import decimals_per_coin, fmt_coin +from freqtrade.util import decimals_per_coin, fmt_coin, print_rich_table logger = logging.getLogger(__name__) @@ -46,22 +44,23 @@ def generate_wins_draws_losses(wins, draws, losses): return f"{wins:>4} {draws:>4} {losses:>4} {wl_ratio:>4}" -def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: str) -> str: +def text_table_bt_results( + pair_results: List[Dict[str, Any]], stake_currency: str, title: str +) -> None: """ Generates and returns a text table for the given backtest data and the results dataframe :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row :param stake_currency: stake-currency - used to correctly name headers - :return: pretty printed table with tabulate as string + :param title: Title of the table """ headers = _get_line_header("Pair", stake_currency, "Trades") - floatfmt = _get_line_floatfmt(stake_currency) output = [ [ t["key"], t["trades"], t["profit_mean_pct"], - t["profit_total_abs"], + f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}", t["profit_total_pct"], t["duration_avg"], generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]), @@ -69,26 +68,32 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st for t in pair_results ] # Ignore type as floatfmt does allow tuples but mypy does not know that - return tabulate(output, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") + print_rich_table(output, headers, summary=title) -def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str: +def text_table_tags( + tag_type: Literal["enter_tag", "exit_tag", "mix_tag"], + tag_results: List[Dict[str, Any]], + stake_currency: str, +) -> None: """ Generates and returns a text table for the given backtest data and the results dataframe :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row :param stake_currency: stake-currency - used to correctly name headers - :return: pretty printed table with tabulate as string """ floatfmt = _get_line_floatfmt(stake_currency) fallback: str = "" is_list = False if tag_type == "enter_tag": - headers = _get_line_header("Enter Tag", stake_currency, "Entries") + title = "Enter Tag" + headers = _get_line_header(title, stake_currency, "Entries") elif tag_type == "exit_tag": - headers = _get_line_header("Exit Reason", stake_currency, "Exits") + title = "Exit Reason" + headers = _get_line_header(title, stake_currency, "Exits") fallback = "exit_reason" else: # Mix tag + title = "Mixed Tag" headers = _get_line_header(["Enter Tag", "Exit Reason"], stake_currency, "Trades") floatfmt.insert(0, "s") is_list = True @@ -106,7 +111,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr ), t["trades"], t["profit_mean_pct"], - t["profit_total_abs"], + f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}", t["profit_total_pct"], t.get("duration_avg"), generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]), @@ -114,17 +119,16 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr for t in tag_results ] # Ignore type as floatfmt does allow tuples but mypy does not know that - return tabulate(output, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") + print_rich_table(output, headers, summary=f"{title.upper()} STATS") def text_table_periodic_breakdown( days_breakdown_stats: List[Dict[str, Any]], stake_currency: str, period: str -) -> str: +) -> None: """ Generate small table with Backtest results by days :param days_breakdown_stats: Days breakdown metrics :param stake_currency: Stakecurrency used - :return: pretty printed table with tabulate as string """ headers = [ period.capitalize(), @@ -143,17 +147,15 @@ def text_table_periodic_breakdown( ] for d in days_breakdown_stats ] - return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") + print_rich_table(output, headers, summary=f"{period.upper()} BREAKDOWN") -def text_table_strategy(strategy_results, stake_currency: str) -> str: +def text_table_strategy(strategy_results, stake_currency: str, title: str): """ Generate summary table per strategy :param strategy_results: Dict of containing results for all strategies :param stake_currency: stake-currency - used to correctly name headers - :return: pretty printed table with tabulate as string """ - floatfmt = _get_line_floatfmt(stake_currency) headers = _get_line_header("Strategy", stake_currency, "Trades") # _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless # therefore we slip this column in only for strategy summary here. @@ -177,8 +179,8 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: [ t["key"], t["trades"], - t["profit_mean_pct"], - t["profit_total_abs"], + f"{t['profit_mean_pct']:.2f}", + f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}", t["profit_total_pct"], t["duration_avg"], generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]), @@ -186,11 +188,10 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: ] for t, drawdown in zip(strategy_results, drawdown) ] - # Ignore type as floatfmt does allow tuples but mypy does not know that - return tabulate(output, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") + print_rich_table(output, headers, summary=title) -def text_table_add_metrics(strat_results: Dict) -> str: +def text_table_add_metrics(strat_results: Dict) -> None: if len(strat_results["trades"]) > 0: best_trade = max(strat_results["trades"], key=lambda x: x["profit_ratio"]) worst_trade = min(strat_results["trades"], key=lambda x: x["profit_ratio"]) @@ -372,8 +373,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: *drawdown_metrics, ("Market change", f"{strat_results['market_change']:.2%}"), ] + print_rich_table(metrics, ["Metric", "Value"], summary="SUMMARY METRICS", justify="left") - return tabulate(metrics, headers=["Metric", "Value"], tablefmt="orgtbl") else: start_balance = fmt_coin(strat_results["starting_balance"], strat_results["stake_currency"]) stake_amount = ( @@ -387,7 +388,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: f"Your starting balance was {start_balance}, " f"and your stake was {stake_amount}." ) - return message + print(message) def _show_tag_subresults(results: Dict[str, Any], stake_currency: str): @@ -395,25 +396,13 @@ def _show_tag_subresults(results: Dict[str, Any], stake_currency: str): Print tag subresults (enter_tag, exit_reason_summary, mix_tag_stats) """ if (enter_tags := results.get("results_per_enter_tag")) is not None: - table = text_table_tags("enter_tag", enter_tags, stake_currency) - - if isinstance(table, str) and len(table) > 0: - print(" ENTER TAG STATS ".center(len(table.splitlines()[0]), "=")) - print(table) + text_table_tags("enter_tag", enter_tags, stake_currency) if (exit_reasons := results.get("exit_reason_summary")) is not None: - table = text_table_tags("exit_tag", exit_reasons, stake_currency) - - if isinstance(table, str) and len(table) > 0: - print(" EXIT REASON STATS ".center(len(table.splitlines()[0]), "=")) - print(table) + text_table_tags("exit_tag", exit_reasons, stake_currency) if (mix_tag := results.get("mix_tag_stats")) is not None: - table = text_table_tags("mix_tag", mix_tag, stake_currency) - - if isinstance(table, str) and len(table) > 0: - print(" MIXED TAG STATS ".center(len(table.splitlines()[0]), "=")) - print(table) + text_table_tags("mix_tag", mix_tag, stake_currency) def show_backtest_result( @@ -424,15 +413,12 @@ def show_backtest_result( """ # Print results print(f"Result for strategy {strategy}") - table = text_table_bt_results(results["results_per_pair"], stake_currency=stake_currency) - if isinstance(table, str): - print(" BACKTESTING REPORT ".center(len(table.splitlines()[0]), "=")) - print(table) - - table = text_table_bt_results(results["left_open_trades"], stake_currency=stake_currency) - if isinstance(table, str) and len(table) > 0: - print(" LEFT OPEN TRADES REPORT ".center(len(table.splitlines()[0]), "=")) - print(table) + text_table_bt_results( + results["results_per_pair"], stake_currency=stake_currency, title="BACKTESTING REPORT" + ) + text_table_bt_results( + results["left_open_trades"], stake_currency=stake_currency, title="LEFT OPEN TRADES REPORT" + ) _show_tag_subresults(results, stake_currency) @@ -443,20 +429,11 @@ def show_backtest_result( days_breakdown_stats = generate_periodic_breakdown_stats( trade_list=results["trades"], period=period ) - table = text_table_periodic_breakdown( + text_table_periodic_breakdown( days_breakdown_stats=days_breakdown_stats, stake_currency=stake_currency, period=period ) - if isinstance(table, str) and len(table) > 0: - print(f" {period.upper()} BREAKDOWN ".center(len(table.splitlines()[0]), "=")) - print(table) - table = text_table_add_metrics(results) - if isinstance(table, str) and len(table) > 0: - print(" SUMMARY METRICS ".center(len(table.splitlines()[0]), "=")) - print(table) - - if isinstance(table, str) and len(table) > 0: - print("=" * len(table.splitlines()[0])) + text_table_add_metrics(results) print() @@ -472,15 +449,13 @@ def show_backtest_results(config: Config, backtest_stats: BacktestResultType): if len(backtest_stats["strategy"]) > 0: # Print Strategy summary table - table = text_table_strategy(backtest_stats["strategy_comparison"], stake_currency) print( f"Backtested {results['backtest_start']} -> {results['backtest_end']} |" f" Max open trades : {results['max_open_trades']}" ) - print(" STRATEGY SUMMARY ".center(len(table.splitlines()[0]), "=")) - print(table) - print("=" * len(table.splitlines()[0])) - print("\nFor more details, please look at the detail tables above") + text_table_strategy( + backtest_stats["strategy_comparison"], stake_currency, "STRATEGY SUMMARY" + ) def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType): @@ -493,8 +468,7 @@ def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType): print("]") -def generate_edge_table(results: dict) -> str: - floatfmt = ("s", ".10g", ".2f", ".2f", ".2f", ".2f", "d", "d", "d") +def generate_edge_table(results: dict) -> None: tabular_data = [] headers = [ "Pair", @@ -512,17 +486,13 @@ def generate_edge_table(results: dict) -> str: tabular_data.append( [ result[0], - result[1].stoploss, - result[1].winrate, - result[1].risk_reward_ratio, - result[1].required_risk_reward, - result[1].expectancy, + f"{result[1].stoploss:.10g}", + f"{result[1].winrate:.2f}", + f"{result[1].risk_reward_ratio:.2f}", + f"{result[1].required_risk_reward:.2f}", + f"{result[1].expectancy:.2f}", result[1].nb_trades, round(result[1].avg_trade_duration), ] ) - - # Ignore type as floatfmt does allow tuples but mypy does not know that - return tabulate( - tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right" - ) + print_rich_table(tabular_data, headers, summary="EDGE TABLE") diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 261148baa..d5d5fd144 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -32,12 +32,12 @@ def get_request_or_thread_id() -> Optional[str]: """ Helper method to get either async context (for fastapi requests), or thread id """ - id = _request_id_ctx_var.get() - if id is None: + request_id = _request_id_ctx_var.get() + if request_id is None: # when not in request context - use thread id - id = str(threading.current_thread().ident) + request_id = str(threading.current_thread().ident) - return id + return request_id _SQL_DOCS_URL = "http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls" diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 7d803ea0f..e731f7552 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -2012,7 +2012,7 @@ class Trade(ModelBase, LocalTrade): ).all() resp: List[Dict] = [] - for id, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf: + for _, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf: enter_tag = enter_tag if enter_tag is not None else "Other" exit_reason = exit_reason if exit_reason is not None else "Other" diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index fc6ac5ec3..bac5c08a7 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -63,8 +63,8 @@ class IResolver: # Add extra directory to the top of the search paths if extra_dirs: - for dir in extra_dirs: - abs_paths.insert(0, Path(dir).resolve()) + for directory in extra_dirs: + abs_paths.insert(0, Path(directory).resolve()) if cls.extra_path and (extra := config.get(cls.extra_path)): abs_paths.insert(0, Path(extra).resolve()) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 72b1db034..d234a680f 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -311,9 +311,9 @@ def warn_deprecated_setting(strategy: IStrategy, old: str, new: str, error=False setattr(strategy, new, getattr(strategy, f"{old}")) -def check_override(object, parentclass, attribute): +def check_override(obj, parentclass, attribute: str): """ Checks if a object overrides the parent class attribute. :returns: True if the object is overridden. """ - return getattr(type(object), attribute) != getattr(parentclass, attribute) + return getattr(type(obj), attribute) != getattr(parentclass, attribute) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index b2406594a..a54b28e3e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -11,7 +11,7 @@ from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union import psutil from dateutil.relativedelta import relativedelta from dateutil.tz import tzlocal -from numpy import NAN, inf, int64, mean +from numpy import inf, int64, mean, nan from pandas import DataFrame, NaT from sqlalchemy import func, select @@ -204,9 +204,9 @@ class RPC: trade.pair, side="exit", is_short=trade.is_short, refresh=False ) except (ExchangeError, PricingError): - current_rate = NAN + current_rate = nan if len(trade.select_filled_orders(trade.entry_side)) > 0: - current_profit = current_profit_abs = current_profit_fiat = NAN + current_profit = current_profit_abs = current_profit_fiat = nan if not isnan(current_rate): prof = trade.calculate_profit(current_rate) current_profit = prof.profit_ratio @@ -277,7 +277,7 @@ class RPC: raise RPCException("no active trade") else: trades_list = [] - fiat_profit_sum = NAN + fiat_profit_sum = nan for trade in trades: # calculate profit and send message to user try: @@ -285,9 +285,9 @@ class RPC: trade.pair, side="exit", is_short=trade.is_short, refresh=False ) except (PricingError, ExchangeError): - current_rate = NAN - trade_profit = NAN - profit_str = f"{NAN:.2%}" + current_rate = nan + trade_profit = nan + profit_str = f"{nan:.2%}" else: if trade.nr_of_successful_entries > 0: profit = trade.calculate_profit(current_rate) @@ -533,9 +533,9 @@ class RPC: trade.pair, side="exit", is_short=trade.is_short, refresh=False ) except (PricingError, ExchangeError): - current_rate = NAN - profit_ratio = NAN - profit_abs = NAN + current_rate = nan + profit_ratio = nan + profit_abs = nan else: _profit = trade.calculate_profit(trade.close_rate or current_rate) @@ -1321,7 +1321,7 @@ class RPC: dataframe = dataframe.drop( ["orderflow", "trades", "imbalances"], axis=1, errors="ignore" ) - dataframe = dataframe.replace({inf: None, -inf: None, NAN: None}) + dataframe = dataframe.replace({inf: None, -inf: None, nan: None}) res = { "pair": pair, diff --git a/freqtrade/util/__init__.py b/freqtrade/util/__init__.py index 503f5861a..76902b176 100644 --- a/freqtrade/util/__init__.py +++ b/freqtrade/util/__init__.py @@ -15,6 +15,8 @@ from freqtrade.util.formatters import decimals_per_coin, fmt_coin, round_value from freqtrade.util.ft_precise import FtPrecise from freqtrade.util.measure_time import MeasureTime from freqtrade.util.periodic_cache import PeriodicCache +from freqtrade.util.rich_progress import CustomProgress +from freqtrade.util.rich_tables import print_df_rich_table, print_rich_table from freqtrade.util.template_renderer import render_template, render_template_with_fallback # noqa @@ -36,4 +38,7 @@ __all__ = [ "round_value", "fmt_coin", "MeasureTime", + "print_rich_table", + "print_df_rich_table", + "CustomProgress", ] diff --git a/freqtrade/util/rich_progress.py b/freqtrade/util/rich_progress.py new file mode 100644 index 000000000..d295dafd5 --- /dev/null +++ b/freqtrade/util/rich_progress.py @@ -0,0 +1,14 @@ +from typing import Union + +from rich.console import ConsoleRenderable, Group, RichCast +from rich.progress import Progress + + +class CustomProgress(Progress): + def __init__(self, *args, cust_objs, **kwargs) -> None: + self._cust_objs = cust_objs + super().__init__(*args, **kwargs) + + def get_renderable(self) -> Union[ConsoleRenderable, RichCast, str]: + renderable = Group(*self._cust_objs, *self.get_renderables()) + return renderable diff --git a/freqtrade/util/rich_tables.py b/freqtrade/util/rich_tables.py new file mode 100644 index 000000000..d36bf9004 --- /dev/null +++ b/freqtrade/util/rich_tables.py @@ -0,0 +1,77 @@ +import sys +from typing import Any, Dict, List, Optional, Sequence, Union + +from pandas import DataFrame +from rich.console import Console +from rich.table import Column, Table +from rich.text import Text + + +TextOrString = Union[str, Text] + + +def print_rich_table( + tabular_data: Sequence[Union[Dict[str, Any], Sequence[TextOrString]]], + headers: Sequence[str], + summary: Optional[str] = None, + *, + justify="right", + table_kwargs: Optional[Dict[str, Any]] = None, +) -> None: + table = Table( + *[c if isinstance(c, Column) else Column(c, justify=justify) for c in headers], + title=summary, + **(table_kwargs or {}), + ) + + for row in tabular_data: + if isinstance(row, dict): + table.add_row( + *[ + row[header] if isinstance(row[header], Text) else str(row[header]) + for header in headers + ] + ) + + else: + row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in row] + table.add_row(*row_to_add) + + console = Console( + width=200 if "pytest" in sys.modules else None, + ) + console.print(table) + + +def _format_value(value: Any, *, floatfmt: str) -> str: + if isinstance(value, float): + return f"{value:{floatfmt}}" + return str(value) + + +def print_df_rich_table( + tabular_data: DataFrame, + headers: Sequence[str], + summary: Optional[str] = None, + *, + show_index=False, + index_name: Optional[str] = None, + table_kwargs: Optional[Dict[str, Any]] = None, +) -> None: + table = Table(title=summary, **(table_kwargs or {})) + + if show_index: + index_name = str(index_name) if index_name else tabular_data.index.name + table.add_column(index_name) + + for header in headers: + table.add_column(header, justify="right") + + for value_list in tabular_data.itertuples(index=show_index): + row = [_format_value(x, floatfmt=".3f") for x in value_list] + table.add_row(*row) + + console = Console( + width=200 if "pytest" in sys.modules else None, + ) + console.print(table) diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index 6c0da7eae..9311fc85d 100644 --- a/ft_client/freqtrade_client/__init__.py +++ b/ft_client/freqtrade_client/__init__.py @@ -7,7 +7,7 @@ if "dev" in __version__: from pathlib import Path try: - import subprocess + import subprocess # noqa: S404 freqtrade_basedir = Path(__file__).parent diff --git a/ft_client/test_client/test_rest_client.py b/ft_client/test_client/test_rest_client.py index 541577e59..1a709b9c5 100644 --- a/ft_client/test_client/test_rest_client.py +++ b/ft_client/test_client/test_rest_client.py @@ -133,8 +133,8 @@ def test_FtRestClient_call_invalid(caplog): ) def test_FtRestClient_call_explicit_methods(method, args, kwargs): client, mock = get_rest_client() - exec = getattr(client, method) - exec(*args, **kwargs) + executor = getattr(client, method) + executor(*args, **kwargs) assert mock.call_count == 1 diff --git a/pyproject.toml b/pyproject.toml index a0dc7aa60..5918e92e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,17 +134,19 @@ extend-select = [ "W", # pycodestyle "UP", # pyupgrade "I", # isort + "A", # flake8-builtins "TID", # flake8-tidy-imports # "EXE", # flake8-executable # "C4", # flake8-comprehensions "YTT", # flake8-2020 - "S", # flake8-bandit + "S", # flake8-bandit # "DTZ", # flake8-datetimez # "RSE", # flake8-raise # "TCH", # flake8-type-checking "PTH", # flake8-use-pathlib # "RUF", # ruff "ASYNC", # flake8-async + "NPY", # numpy ] extend-ignore = [ @@ -155,6 +157,7 @@ extend-ignore = [ "S603", # `subprocess` call: check for execution of untrusted input "S607", # Starting a process with a partial executable path "S608", # Possible SQL injection vector through string-based query construction + "NPY002", # Numpy legacy random generator ] [tool.ruff.lint.mccabe] diff --git a/requirements-dev.txt b/requirements-dev.txt index f8d811257..8f529475b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.5.0 +ruff==0.5.1 mypy==1.10.1 pre-commit==3.7.1 pytest==8.2.2 diff --git a/requirements-freqai.txt b/requirements-freqai.txt index d864b45e8..c57e66d2d 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -3,7 +3,7 @@ -r requirements-plot.txt # Required for freqai -scikit-learn==1.5.0 +scikit-learn==1.5.1 joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine lightgbm==4.4.0 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 30ae0fac1..6475525de 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -4,6 +4,6 @@ # Required for hyperopt scipy==1.14.0; python_version >= "3.10" scipy==1.13.1; python_version < "3.10" -scikit-learn==1.5.0 +scikit-learn==1.5.1 ft-scikit-optimize==0.9.2 filelock==3.15.4 diff --git a/requirements.txt b/requirements.txt index 170e5d805..782cbcc2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.0 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.3.54 +ccxt==4.3.58 cryptography==42.0.8 aiohttp==3.9.5 SQLAlchemy==2.0.31 @@ -32,21 +32,19 @@ py_find_1st==1.1.6 # Load ticker files 30% faster python-rapidjson==1.18 # Properly format api responses -orjson==3.10.5 +orjson==3.10.6 # Notify systemd sdnotify==0.3.2 # API Server fastapi==0.111.0 -pydantic==2.7.4 +pydantic==2.8.2 uvicorn==0.30.1 pyjwt==2.8.0 aiofiles==24.1.0 psutil==6.0.0 -# Support for colorized terminal output -colorama==0.4.6 # Building config files interactively questionary==2.0.1 prompt-toolkit==3.0.36 diff --git a/scripts/ws_client.py b/scripts/ws_client.py index ec6df5742..81e539e26 100755 --- a/scripts/ws_client.py +++ b/scripts/ws_client.py @@ -172,10 +172,10 @@ class ClientProtocol: return readable_timedelta(time_delta) - async def _handle_whitelist(self, name, type, data): + async def _handle_whitelist(self, name, msgtype, data): self.logger.info(data) - async def _handle_analyzed_df(self, name, type, data): + async def _handle_analyzed_df(self, name, msgtype, data): key, la, df = data["key"], data["la"], data["df"] if not df.empty: @@ -189,8 +189,8 @@ class ClientProtocol: else: self.logger.info("Empty DataFrame") - async def _handle_default(self, name, type, data): - self.logger.info("Unknown message of type {type} received...") + async def _handle_default(self, name, msgtype, data): + self.logger.info("Unknown message of type {msgtype} received...") self.logger.info(data) diff --git a/setup.py b/setup.py index 8865f46be..6963862e0 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ setup( "httpx>=0.24.1", "urllib3", "jsonschema", - "numpy", + "numpy<2.0", "pandas>=2.2.0,<3.0", "TA-Lib", "pandas-ta", @@ -88,7 +88,6 @@ setup( "py_find_1st", "python-rapidjson", "orjson", - "colorama", "jinja2", "questionary", "prompt-toolkit", diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index c98c6302e..687bff69f 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -116,7 +116,7 @@ def test_list_exchanges(capsys): start_list_exchanges(get_args(args)) captured = capsys.readouterr() - assert re.match(r"Exchanges available for Freqtrade.*", captured.out) + assert re.search(r".*Exchanges available for Freqtrade.*", captured.out) assert re.search(r".*binance.*", captured.out) assert re.search(r".*bybit.*", captured.out) @@ -139,7 +139,7 @@ def test_list_exchanges(capsys): start_list_exchanges(get_args(args)) captured = capsys.readouterr() - assert re.match(r"All exchanges supported by the ccxt library.*", captured.out) + assert re.search(r"All exchanges supported by the ccxt library.*", captured.out) assert re.search(r".*binance.*", captured.out) assert re.search(r".*bingx.*", captured.out) assert re.search(r".*bitmex.*", captured.out) @@ -167,7 +167,7 @@ def test_list_timeframes(mocker, capsys): "1h": "hour", "1d": "day", } - patch_exchange(mocker, api_mock=api_mock, id="bybit") + patch_exchange(mocker, api_mock=api_mock, exchange="bybit") args = [ "list-timeframes", ] @@ -213,7 +213,7 @@ def test_list_timeframes(mocker, capsys): "1d": "1d", "3d": "3d", } - patch_exchange(mocker, api_mock=api_mock, id="binance") + patch_exchange(mocker, api_mock=api_mock, exchange="binance") # Test with --exchange binance args = [ "list-timeframes", @@ -258,7 +258,7 @@ def test_list_timeframes(mocker, capsys): def test_list_markets(mocker, markets_static, capsys): api_mock = MagicMock() - patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) + patch_exchange(mocker, api_mock=api_mock, exchange="binance", mock_markets=markets_static) # Test with no --config args = [ @@ -286,16 +286,16 @@ def test_list_markets(mocker, markets_static, capsys): "LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out ) - patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) + patch_exchange(mocker, api_mock=api_mock, exchange="binance", mock_markets=markets_static) # Test with --exchange args = ["list-markets", "--exchange", "binance"] pargs = get_args(args) pargs["config"] = None start_list_markets(pargs, False) captured = capsys.readouterr() - assert re.match("\nExchange Binance has 12 active markets:\n", captured.out) + assert re.search(r".*Exchange Binance has 12 active markets.*", captured.out) - patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) + patch_exchange(mocker, api_mock=api_mock, exchange="binance", mock_markets=markets_static) # Test with --all: all markets args = [ "list-markets", @@ -491,7 +491,7 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert "Exchange Binance has 12 active markets:\n" in captured.out + assert "Exchange Binance has 12 active markets" in captured.out # Test tabular output, no markets found args = [ @@ -823,7 +823,7 @@ def test_download_data_no_markets(mocker, caplog): "freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data", MagicMock(return_value=["ETH/BTC", "XRP/BTC"]), ) - patch_exchange(mocker, id="binance") + patch_exchange(mocker, exchange="binance") mocker.patch(f"{EXMS}.get_markets", return_value={}) args = [ "download-data", @@ -952,7 +952,7 @@ def test_download_data_trades(mocker): def test_download_data_data_invalid(mocker): - patch_exchange(mocker, id="kraken") + patch_exchange(mocker, exchange="kraken") mocker.patch(f"{EXMS}.get_markets", return_value={}) args = [ "download-data", @@ -1633,8 +1633,8 @@ def test_start_list_data(testdatadir, capsys): start_list_data(pargs) captured = capsys.readouterr() assert "Found 16 pair / timeframe combinations." in captured.out - assert "\n| Pair | Timeframe | Type |\n" in captured.out - assert "\n| UNITTEST/BTC | 1m, 5m, 8m, 30m | spot |\n" in captured.out + assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out) + assert re.search(r"\n.* UNITTEST/BTC .* 1m, 5m, 8m, 30m .* spot |\n", captured.out) args = [ "list-data", @@ -1650,9 +1650,9 @@ def test_start_list_data(testdatadir, capsys): start_list_data(pargs) captured = capsys.readouterr() assert "Found 2 pair / timeframe combinations." in captured.out - assert "\n| Pair | Timeframe | Type |\n" in captured.out + assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out) assert "UNITTEST/BTC" not in captured.out - assert "\n| XRP/ETH | 1m, 5m | spot |\n" in captured.out + assert re.search(r"\n.* XRP/ETH .* 1m, 5m .* spot |\n", captured.out) args = [ "list-data", @@ -1667,9 +1667,9 @@ def test_start_list_data(testdatadir, capsys): captured = capsys.readouterr() assert "Found 6 pair / timeframe combinations." in captured.out - assert "\n| Pair | Timeframe | Type |\n" in captured.out - assert "\n| XRP/USDT:USDT | 5m, 1h | futures |\n" in captured.out - assert "\n| XRP/USDT:USDT | 1h, 8h | mark |\n" in captured.out + assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out) + assert re.search(r"\n.* XRP/USDT:USDT .* 5m, 1h .* futures |\n", captured.out) + assert re.search(r"\n.* XRP/USDT:USDT .* 1h, 8h .* mark |\n", captured.out) args = [ "list-data", @@ -1684,15 +1684,12 @@ def test_start_list_data(testdatadir, capsys): start_list_data(pargs) captured = capsys.readouterr() assert "Found 2 pair / timeframe combinations." in captured.out - assert ( - "\n| Pair | Timeframe | Type " - "| From | To | Candles |\n" - ) in captured.out + assert re.search(r".*Pair.*Timeframe.*Type.*From .* To .* Candles .*\n", captured.out) assert "UNITTEST/BTC" not in captured.out - assert ( - "\n| XRP/ETH | 1m | spot | " - "2019-10-11 00:00:00 | 2019-10-13 11:19:00 | 2469 |\n" - ) in captured.out + assert re.search( + r"\n.* XRP/USDT .* 1m .* spot .* 2019-10-11 00:00:00 .* 2019-10-13 11:19:00 .* 2469 |\n", + captured.out, + ) @pytest.mark.usefixtures("init_persistence") diff --git a/tests/conftest.py b/tests/conftest.py index ac55532dc..2d56d9d9e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -137,7 +137,7 @@ def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days= random_timestamps_in_seconds = np.random.uniform(_start_timestamp, _end_timestamp, n_rows) timestamp = pd.to_datetime(random_timestamps_in_seconds, unit="s") - id = [ + trade_id = [ f"a{np.random.randint(1e6, 1e7 - 1)}cd{np.random.randint(100, 999)}" for _ in range(n_rows) ] @@ -155,7 +155,7 @@ def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days= df = pd.DataFrame( { "timestamp": timestamp, - "id": id, + "id": trade_id, "type": None, "side": side, "price": price, @@ -236,12 +236,12 @@ def patched_configuration_load_config_file(mocker, config) -> None: def patch_exchange( - mocker, api_mock=None, id="binance", mock_markets=True, mock_supported_modes=True + mocker, api_mock=None, exchange="binance", mock_markets=True, mock_supported_modes=True ) -> None: mocker.patch(f"{EXMS}.validate_config", MagicMock()) mocker.patch(f"{EXMS}.validate_timeframes", MagicMock()) - mocker.patch(f"{EXMS}.id", PropertyMock(return_value=id)) - mocker.patch(f"{EXMS}.name", PropertyMock(return_value=id.title())) + mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange)) + mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title())) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2)) # Temporary patch ... mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers") @@ -254,7 +254,8 @@ def patch_exchange( if mock_supported_modes: mocker.patch( - f"freqtrade.exchange.{id}.{id.capitalize()}._supported_trading_mode_margin_pairs", + f"freqtrade.exchange.{exchange}.{exchange.capitalize()}" + "._supported_trading_mode_margin_pairs", PropertyMock( return_value=[ (TradingMode.MARGIN, MarginMode.CROSS), @@ -274,10 +275,10 @@ def patch_exchange( def get_patched_exchange( - mocker, config, api_mock=None, id="binance", mock_markets=True, mock_supported_modes=True + mocker, config, api_mock=None, exchange="binance", mock_markets=True, mock_supported_modes=True ) -> Exchange: - patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes) - config["exchange"]["name"] = id + patch_exchange(mocker, api_mock, exchange, mock_markets, mock_supported_modes) + config["exchange"]["name"] = exchange try: exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True) except ImportError: diff --git a/tests/conftest_hyperopt.py b/tests/conftest_hyperopt.py index af4039a3c..315b138cf 100644 --- a/tests/conftest_hyperopt.py +++ b/tests/conftest_hyperopt.py @@ -324,7 +324,8 @@ def hyperopt_test_result(): "profit_mean": None, "profit_median": None, "profit_total": 0, - "profit": 0.0, + "max_drawdown_account": 0.0, + "max_drawdown_abs": 0.0, "holding_avg": timedelta(), }, # noqa: E501 "results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501 diff --git a/tests/data/test_datahandler.py b/tests/data/test_datahandler.py index 1f66d1b1e..b8bb5661f 100644 --- a/tests/data/test_datahandler.py +++ b/tests/data/test_datahandler.py @@ -83,7 +83,7 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype): @pytest.mark.parametrize( - "input,expected", + "pair,expected", [ ("XMR_USDT", "XMR/USDT"), ("BTC_USDT", "BTC/USDT"), @@ -95,8 +95,8 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype): ("UNITTEST_USDT", "UNITTEST/USDT"), ], ) -def test_rebuild_pair_from_filename(input, expected): - assert IDataHandler.rebuild_pair_from_filename(input) == expected +def test_rebuild_pair_from_filename(pair, expected): + assert IDataHandler.rebuild_pair_from_filename(pair) == expected def test_datahandler_ohlcv_get_available_data(testdatadir): diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index 4d7b5da4b..8656da10b 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -286,7 +286,7 @@ def test_refresh(mocker, default_conf): refresh_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv") mock_refresh_trades = mocker.patch(f"{EXMS}.refresh_latest_trades") - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") timeframe = default_conf["timeframe"] pairs = [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe)] diff --git a/tests/data/test_download_data.py b/tests/data/test_download_data.py index 08d56458f..f2c8a51d4 100644 --- a/tests/data/test_download_data.py +++ b/tests/data/test_download_data.py @@ -14,7 +14,7 @@ def test_download_data_main_no_markets(mocker, caplog): "freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data", MagicMock(return_value=["ETH/BTC", "XRP/BTC"]), ) - patch_exchange(mocker, id="binance") + patch_exchange(mocker, exchange="binance") mocker.patch(f"{EXMS}.get_markets", return_value={}) config = setup_utils_configuration({"exchange": "binance"}, RunMode.UTIL_EXCHANGE) config.update({"days": 20, "pairs": ["ETH/BTC", "XRP/BTC"], "timeframes": ["5m", "1h"]}) @@ -91,7 +91,7 @@ def test_download_data_main_trades(mocker): def test_download_data_main_data_invalid(mocker): - patch_exchange(mocker, id="kraken") + patch_exchange(mocker, exchange="kraken") mocker.patch(f"{EXMS}.get_markets", return_value={}) config = setup_utils_configuration({"exchange": "kraken"}, RunMode.UTIL_EXCHANGE) config.update( diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py index 1a5309190..e7909c339 100644 --- a/tests/data/test_entryexitanalysis.py +++ b/tests/data/test_entryexitanalysis.py @@ -154,10 +154,10 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, use assert "-3.5" in captured.out assert "50" in captured.out assert "0" in captured.out - assert "0.01616" in captured.out + assert "0.016" in captured.out assert "34.049" in captured.out - assert "0.104411" in captured.out - assert "52.8292" in captured.out + assert "0.104" in captured.out + assert "52.829" in captured.out # test group 1 args = get_args(base_args + ["--analysis-groups", "1"]) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index d45d95e45..6b4f09ae2 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -555,7 +555,7 @@ def test_refresh_backtest_ohlcv_data( mocker.patch.object(Path, "unlink", MagicMock()) default_conf["trading_mode"] = trademode - ex = get_patched_exchange(mocker, default_conf, id="bybit") + ex = get_patched_exchange(mocker, default_conf, exchange="bybit") timerange = TimeRange.parse_timerange("20190101-20190102") refresh_backtest_ohlcv_data( exchange=ex, diff --git a/tests/data/test_trade_converter_kraken.py b/tests/data/test_trade_converter_kraken.py index 480ea93ca..d8d20fd88 100644 --- a/tests/data/test_trade_converter_kraken.py +++ b/tests/data/test_trade_converter_kraken.py @@ -17,7 +17,7 @@ def test_import_kraken_trades_from_csv(testdatadir, tmp_path, caplog, default_co default_conf_usdt["exchange"]["name"] = "kraken" - patch_exchange(mocker, id="kraken") + patch_exchange(mocker, exchange="kraken") mocker.patch( f"{EXMS}.markets", PropertyMock( diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index b961c2809..1de464097 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -12,7 +12,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers @pytest.mark.parametrize( - "side,type,time_in_force,expected", + "side,order_type,time_in_force,expected", [ ("buy", "limit", "gtc", {"timeInForce": "GTC"}), ("buy", "limit", "IOC", {"timeInForce": "IOC"}), @@ -22,9 +22,9 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers ("sell", "market", "PO", {}), ], ) -def test__get_params_binance(default_conf, mocker, side, type, time_in_force, expected): - exchange = get_patched_exchange(mocker, default_conf, id="binance") - assert exchange._get_params(side, type, 1, False, time_in_force) == expected +def test__get_params_binance(default_conf, mocker, side, order_type, time_in_force, expected): + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") + assert exchange._get_params(side, order_type, 1, False, time_in_force) == expected @pytest.mark.parametrize("trademode", [TradingMode.FUTURES, TradingMode.SPOT]) @@ -159,7 +159,7 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker): "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] ) def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") order = { "type": "stop_loss_limit", "price": 1500, @@ -378,7 +378,7 @@ def test_fill_leverage_tiers_binance(default_conf, mocker): default_conf["dry_run"] = False default_conf["trading_mode"] = TradingMode.FUTURES default_conf["margin_mode"] = MarginMode.ISOLATED - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="binance") exchange.fill_leverage_tiers() assert exchange._leverage_tiers == { @@ -497,7 +497,7 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers api_mock = MagicMock() default_conf["trading_mode"] = TradingMode.FUTURES default_conf["margin_mode"] = MarginMode.ISOLATED - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="binance") exchange.fill_leverage_tiers() assert len(exchange._leverage_tiers.keys()) > 100 for key, value in leverage_tiers.items(): @@ -518,10 +518,10 @@ def test_additional_exchange_init_binance(default_conf, mocker): OperationalException, match=r"Hedge Mode is not supported.*\nMulti-Asset Mode is not supported.*", ): - get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) + get_patched_exchange(mocker, default_conf, exchange="binance", api_mock=api_mock) api_mock.fapiPrivateGetPositionSideDual = MagicMock(return_value={"dualSidePosition": False}) api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": False}) - exchange = get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) + exchange = get_patched_exchange(mocker, default_conf, exchange="binance", api_mock=api_mock) assert exchange ccxt_exceptionhandlers( mocker, @@ -541,7 +541,7 @@ def test__set_leverage_binance(mocker, default_conf): default_conf["trading_mode"] = TradingMode.FUTURES default_conf["margin_mode"] = MarginMode.ISOLATED - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="binance") exchange._set_leverage(3.2, "BTC/USDT:USDT") assert api_mock.set_leverage.call_count == 1 # Leverage is rounded to 3. @@ -574,7 +574,7 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, c ] ] - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") # Monkey-patch async function exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) @@ -620,7 +620,7 @@ def test_get_maintenance_ratio_and_amt_binance( amt, ): mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") exchange._leverage_tiers = leverage_tiers (result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value) assert (round(result_ratio, 8), round(result_amt, 8)) == (mm_ratio, amt) diff --git a/tests/exchange/test_bitpanda.py b/tests/exchange/test_bitpanda.py index 83561b914..b007bea5c 100644 --- a/tests/exchange/test_bitpanda.py +++ b/tests/exchange/test_bitpanda.py @@ -39,7 +39,7 @@ def test_get_trades_for_order(default_conf, mocker): } ] ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) orders = exchange.get_trades_for_order(order_id, "LTC/BTC", since) assert len(orders) == 1 diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index 8f09b049d..8dc11667c 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -18,7 +18,7 @@ def test_additional_exchange_init_bybit(default_conf, mocker, caplog): api_mock.set_position_mode = MagicMock(return_value={"dualSidePosition": False}) api_mock.is_unified_enabled = MagicMock(return_value=[False, False]) - exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock) assert api_mock.set_position_mode.call_count == 1 assert api_mock.is_unified_enabled.call_count == 1 assert exchange.unified_account is False @@ -28,9 +28,9 @@ def test_additional_exchange_init_bybit(default_conf, mocker, caplog): api_mock.set_position_mode.reset_mock() api_mock.is_unified_enabled = MagicMock(return_value=[False, True]) with pytest.raises(OperationalException, match=r"Bybit: Unified account is not supported.*"): - get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock) assert log_has("Bybit: Unified account.", caplog) - # exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + # exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock) # assert api_mock.set_position_mode.call_count == 1 # assert api_mock.is_unified_enabled.call_count == 1 # assert exchange.unified_account is True @@ -45,7 +45,7 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): default_conf["margin_mode"] = "isolated" api_mock = MagicMock() api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[]) - exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock) limit = 200 # Test fetch_funding_rate_history (current data) await exchange._fetch_funding_rate_history( @@ -77,14 +77,14 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): def test_bybit_get_funding_fees(default_conf, mocker): now = datetime.now(timezone.utc) - exchange = get_patched_exchange(mocker, default_conf, id="bybit") + exchange = get_patched_exchange(mocker, default_conf, exchange="bybit") exchange._fetch_and_calculate_funding_fees = MagicMock() exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) assert exchange._fetch_and_calculate_funding_fees.call_count == 0 default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, id="bybit") + exchange = get_patched_exchange(mocker, default_conf, exchange="bybit") exchange._fetch_and_calculate_funding_fees = MagicMock() exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) @@ -105,13 +105,13 @@ def test_bybit_fetch_orders(default_conf, mocker, limit_order): mocker.patch(f"{EXMS}.exchange_has", return_value=True) start_time = datetime.now(timezone.utc) - timedelta(days=20) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="bybit") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bybit") # Not available in dry-run assert exchange.fetch_orders("mocked", start_time) == [] assert api_mock.fetch_orders.call_count == 0 default_conf["dry_run"] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="bybit") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bybit") res = exchange.fetch_orders("mocked", start_time) # Bybit will call the endpoint 3 times, as it has a limit of 7 days per call assert api_mock.fetch_orders.call_count == 3 @@ -136,7 +136,7 @@ def test_bybit_fetch_order_canceled_empty(default_conf_usdt, mocker): ) mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, id="bybit") + exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange="bybit") res = exchange.fetch_order("123", "BTC/USDT") assert res["remaining"] is None diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 690745d03..2d0915753 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -118,19 +118,19 @@ def ccxt_exceptionhandlers( with patch("freqtrade.exchange.common.time.sleep"): with pytest.raises(DDosProtection): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == retries with pytest.raises(TemporaryError): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.OperationFailed("DeaDBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == retries with pytest.raises(OperationalException): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 @@ -304,7 +304,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): def test_validate_order_time_in_force(default_conf, mocker, caplog): caplog.set_level(logging.INFO) # explicitly test bybit, exchanges implementing other policies need separate tests - ex = get_patched_exchange(mocker, default_conf, id="bybit") + ex = get_patched_exchange(mocker, default_conf, exchange="bybit") tif = { "buy": "gtc", "sell": "gtc", @@ -346,7 +346,7 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ) def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precision, expected): markets = PropertyMock(return_value={"ETH/BTC": {"precision": {"price": precision}}}) - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") mocker.patch(f"{EXMS}.markets", markets) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode)) pair = "ETH/BTC" @@ -354,7 +354,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio def test__get_stake_amount_limit(mocker, default_conf) -> None: - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") stoploss = -0.05 markets = {"ETH/BTC": {"symbol": "ETH/BTC"}} @@ -463,7 +463,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: markets["ETH/BTC"]["contractSize"] = "0.01" default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) # Contract size 0.01 @@ -484,7 +484,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") stoploss = -0.05 markets = {"ETH/BTC": {"symbol": "ETH/BTC"}} @@ -565,7 +565,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine): api_mock.load_markets = get_mock_coro(return_value=initial_markets) default_conf["exchange"]["markets_refresh_interval"] = 10 exchange = get_patched_exchange( - mocker, default_conf, api_mock, id="binance", mock_markets=False + mocker, default_conf, api_mock, exchange="binance", mock_markets=False ) lam_spy = mocker.spy(exchange, "_load_async_markets") assert exchange._last_markets_refresh == dt_ts() @@ -600,7 +600,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog): api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("LoadError")) default_conf["exchange"]["markets_refresh_interval"] = 10 exchange = get_patched_exchange( - mocker, default_conf, api_mock, id="binance", mock_markets=False + mocker, default_conf, api_mock, exchange="binance", mock_markets=False ) exchange._last_markets_refresh = 2 @@ -1153,7 +1153,7 @@ def test_exchange_has(default_conf, mocker): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverage): default_conf["dry_run"] = True - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) order = exchange.create_dry_run_order( pair="ETH/BTC", ordertype="limit", side=side, amount=1, rate=200, leverage=leverage @@ -1247,7 +1247,7 @@ def test_create_dry_run_order_limit_fill( leverage, ): default_conf["dry_run"] = True - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) mocker.patch.multiple( EXMS, exchange_has=MagicMock(return_value=True), @@ -1316,7 +1316,7 @@ def test_create_dry_run_order_market_fill( default_conf, mocker, side, rate, amount, endprice, exchange_name, order_book_l2_usd, leverage ): default_conf["dry_run"] = True - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) mocker.patch.multiple( EXMS, exchange_has=MagicMock(return_value=True), @@ -1365,7 +1365,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, default_conf["margin_mode"] = "isolated" mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() @@ -1393,7 +1393,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, "amount": 1, } ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.trading_mode = TradingMode.FUTURES exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() @@ -1412,7 +1412,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_buy_dry_run(default_conf, mocker, exchange_name): default_conf["dry_run"] = True - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) order = exchange.create_order( pair="ETH/BTC", @@ -1440,7 +1440,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): default_conf["dry_run"] = False mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) order = exchange.create_order( pair="ETH/BTC", @@ -1484,7 +1484,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype=order_type, @@ -1497,7 +1497,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype="limit", @@ -1510,7 +1510,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype="market", @@ -1523,7 +1523,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype=order_type, @@ -1536,7 +1536,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype=order_type, @@ -1559,7 +1559,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): default_conf["dry_run"] = False mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) order_type = "limit" time_in_force = "ioc" @@ -1638,7 +1638,7 @@ def test_sell_prod(default_conf, mocker, exchange_name): mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) order = exchange.create_order( pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 @@ -1670,14 +1670,14 @@ def test_sell_prod(default_conf, mocker, exchange_name): # test exception handling with pytest.raises(InsufficientFundsError): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 ) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype="limit", side="sell", amount=1, rate=200, leverage=1.0 ) @@ -1685,21 +1685,21 @@ def test_sell_prod(default_conf, mocker, exchange_name): # Market orders don't require price, so the behaviour is slightly different with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype="market", side="sell", amount=1, rate=200, leverage=1.0 ) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 ) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.create_order( pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 ) @@ -1716,7 +1716,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): default_conf["dry_run"] = False mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) order_type = "limit" time_in_force = "ioc" @@ -1778,7 +1778,7 @@ def test_get_balances_prod(default_conf, mocker, exchange_name): return_value={"1ST": balance_item, "2ND": balance_item, "3RD": balance_item} ) default_conf["dry_run"] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert len(exchange.get_balances()) == 3 assert exchange.get_balances()["1ST"]["free"] == 10.0 assert exchange.get_balances()["1ST"]["total"] == 10.0 @@ -1799,12 +1799,12 @@ def test_fetch_positions(default_conf, mocker, exchange_name): {"symbol": "XRP/USDT:USDT", "leverage": 5}, ] ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.fetch_positions() == [] default_conf["dry_run"] = False default_conf["trading_mode"] = "futures" - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) res = exchange.fetch_positions() assert len(res) == 2 @@ -1831,13 +1831,13 @@ def test_fetch_orders(default_conf, mocker, exchange_name, limit_order): if exchange_name == "bybit": expected = 3 - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) # Not available in dry-run assert exchange.fetch_orders("mocked", start_time) == [] assert api_mock.fetch_orders.call_count == 0 default_conf["dry_run"] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) res = exchange.fetch_orders("mocked", start_time) assert api_mock.fetch_orders.call_count == expected assert api_mock.fetch_open_orders.call_count == 0 @@ -1938,7 +1938,7 @@ def test_fetch_trading_fees(default_conf, mocker): default_conf["margin_mode"] = MarginMode.ISOLATED api_mock.fetch_trading_fees = MagicMock(return_value=tick) mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert "1INCH/USDT:USDT" in exchange._trading_fees assert "ETH/USDT:USDT" in exchange._trading_fees @@ -1953,7 +1953,7 @@ def test_fetch_trading_fees(default_conf, mocker): ) api_mock.fetch_trading_fees = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_trading_fees() mocker.patch(f"{EXMS}.exchange_has", return_value=True) assert exchange.fetch_trading_fees() == {} @@ -1978,7 +1978,7 @@ def test_fetch_bids_asks(default_conf, mocker): exchange_name = "binance" api_mock.fetch_bids_asks = MagicMock(return_value=tick) mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) # retrieve original ticker bidsasks = exchange.fetch_bids_asks() @@ -2005,11 +2005,11 @@ def test_fetch_bids_asks(default_conf, mocker): with pytest.raises(OperationalException): api_mock.fetch_bids_asks = MagicMock(side_effect=ccxt.NotSupported("DeadBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_bids_asks() api_mock.fetch_bids_asks = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_bids_asks() mocker.patch(f"{EXMS}.exchange_has", return_value=True) assert exchange.fetch_bids_asks() == {} @@ -2035,7 +2035,7 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): mocker.patch(f"{EXMS}.exchange_has", return_value=True) api_mock.fetch_tickers = MagicMock(return_value=tick) api_mock.fetch_bids_asks = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) # retrieve original ticker tickers = exchange.get_tickers() @@ -2065,19 +2065,19 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): with pytest.raises(OperationalException): api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported("DeadBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.get_tickers() caplog.clear() api_mock.fetch_tickers = MagicMock(side_effect=[ccxt.BadSymbol("SomeSymbol"), []]) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) x = exchange.get_tickers() assert x == [] assert log_has_re(r"Could not load tickers due to BadSymbol\..*SomeSymbol", caplog) caplog.clear() api_mock.fetch_tickers = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.get_tickers() api_mock.fetch_tickers.reset_mock() @@ -2085,7 +2085,7 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): default_conf["trading_mode"] = TradingMode.FUTURES default_conf["margin_mode"] = MarginMode.ISOLATED mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.get_tickers() assert api_mock.fetch_tickers.call_count == 1 @@ -2108,7 +2108,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name): } api_mock.fetch_ticker = MagicMock(return_value=tick) api_mock.markets = {"ETH/BTC": {"active": True}} - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) # retrieve original ticker ticker = exchange.fetch_ticker(pair="ETH/BTC") @@ -2123,7 +2123,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name): "last": 42, } api_mock.fetch_ticker = MagicMock(return_value=tick) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) # if not caching the result we should get the same ticker # if not fetching a new result we should get the cached ticker @@ -2144,7 +2144,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name): ) api_mock.fetch_ticker = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_ticker(pair="ETH/BTC") with pytest.raises(DependencyException, match=r"Pair XRP/ETH not available"): @@ -2153,7 +2153,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test___now_is_time_to_refresh(default_conf, mocker, exchange_name, time_machine): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) pair = "BTC/USDT" candle_type = CandleType.SPOT start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc) @@ -2182,7 +2182,7 @@ def test___now_is_time_to_refresh(default_conf, mocker, exchange_name, time_mach @pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("candle_type", ["mark", ""]) def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) ohlcv = [ [ dt_ts(), # unix timestamp ms @@ -2237,7 +2237,7 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_ 5, # volume (in quote currency) ] ] - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) # Monkey-patch async function exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) @@ -2693,7 +2693,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ ] caplog.set_level(logging.DEBUG) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) # Monkey-patch async function exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) @@ -2725,7 +2725,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ OperationalException, match=r"Could not fetch historical candle \(OHLCV\) data.*" ): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) await exchange._async_get_candle_history( pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000)) ) @@ -2737,7 +2737,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ match=r"Exchange.* does not support fetching " r"historical candle \(OHLCV\) data\..*", ): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) await exchange._async_get_candle_history( pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000)) ) @@ -2758,7 +2758,7 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): '{"code":"429000","msg":"Too Many Requests"}' ) ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kucoin") mocker.patch(f"{EXMS}.name", PropertyMock(return_value="KuCoin")) msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay" @@ -2880,7 +2880,7 @@ def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name) api_mock = MagicMock() api_mock.fetch_l2_order_book = order_book_l2 - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) order_book = exchange.fetch_l2_order_book(pair="ETH/BTC", limit=10) assert "bids" in order_book assert "asks" in order_book @@ -2908,15 +2908,15 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name): api_mock = MagicMock() with pytest.raises(OperationalException): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported("Not supported")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) with pytest.raises(TemporaryError): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError("DeadBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) with pytest.raises(OperationalException): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) @@ -3213,7 +3213,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867], ] - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) sort_mock = mocker.patch("freqtrade.exchange.exchange.sorted", MagicMock(side_effect=sort_data)) # Test the OHLCV data sort @@ -3283,7 +3283,7 @@ async def test__async_fetch_trades( default_conf, mocker, caplog, exchange_name, fetch_trades_result ): caplog.set_level(logging.DEBUG) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) # Monkey-patch async function exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result) @@ -3337,7 +3337,7 @@ async def test__async_fetch_trades( api_mock = MagicMock() with pytest.raises(OperationalException, match=r"Could not fetch trade data*"): api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000))) exchange.close() @@ -3346,7 +3346,7 @@ async def test__async_fetch_trades( match=r"Exchange.* does not support fetching " r"historical trade data\..*", ): api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000))) exchange.close() @@ -3358,7 +3358,7 @@ async def test__async_fetch_trades_contract_size( caplog.set_level(logging.DEBUG) default_conf["margin_mode"] = "isolated" default_conf["trading_mode"] = "futures" - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) # Monkey-patch async function exchange._api_async.fetch_trades = get_mock_coro( [ @@ -3401,7 +3401,7 @@ async def test__async_fetch_trades_contract_size( async def test__async_get_trade_history_id( default_conf, mocker, exchange_name, fetch_trades_result ): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) if exchange._trades_pagination != "id": exchange.close() pytest.skip("Exchange does not support pagination by trade id") @@ -3460,7 +3460,7 @@ async def test__async_get_trade_history_id( def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, trade_id, expected): if exchange_name == "kraken": pytest.skip("Kraken has a different pagination id format, and an explicit test.") - exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name) assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected @@ -3479,7 +3479,7 @@ async def test__async_get_trade_history_time( return fetch_trades_result[-1:] caplog.set_level(logging.DEBUG) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) if exchange._trades_pagination != "time": exchange.close() pytest.skip("Exchange does not support pagination by timestamp") @@ -3521,7 +3521,7 @@ async def test__async_get_trade_history_time_empty( return [], None caplog.set_level(logging.DEBUG) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) # Monkey-patch async function exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist) pair = "ETH/BTC" @@ -3542,7 +3542,7 @@ async def test__async_get_trade_history_time_empty( @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history): mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) pair = "ETH/BTC" @@ -3573,7 +3573,7 @@ def test_get_historic_trades_notsupported( default_conf, mocker, caplog, exchange_name, trades_history ): mocker.patch(f"{EXMS}.exchange_has", return_value=False) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) pair = "ETH/BTC" @@ -3587,7 +3587,7 @@ def test_get_historic_trades_notsupported( @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf["dry_run"] = True - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True) assert exchange.cancel_order(order_id="123", pair="TKN/BTC") == {} assert exchange.cancel_stoploss_order(order_id="123", pair="TKN/BTC") == {} @@ -3622,7 +3622,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name): ], ) def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) assert exchange.check_order_canceled_empty(order) == result @@ -3642,7 +3642,7 @@ def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, ], ) def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, order, result): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) assert exchange.is_cancel_order_result_suitable(order) == result @@ -3662,7 +3662,7 @@ def test_cancel_order_with_result( api_mock = MagicMock() api_mock.cancel_order = MagicMock(return_value=corder) api_mock.fetch_order = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1234) assert isinstance(res, dict) assert api_mock.cancel_order.call_count == call_corder @@ -3676,7 +3676,7 @@ def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, cap api_mock = MagicMock() api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1541) assert isinstance(res, dict) @@ -3691,12 +3691,12 @@ def test_cancel_order(default_conf, mocker, exchange_name): default_conf["dry_run"] = False api_mock = MagicMock() api_mock.cancel_order = MagicMock(return_value={"id": "123"}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.cancel_order(order_id="_", pair="TKN/BTC") == {"id": "123"} with pytest.raises(InvalidOrderException): api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.cancel_order(order_id="_", pair="TKN/BTC") assert api_mock.cancel_order.call_count == 1 @@ -3717,12 +3717,12 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name): default_conf["dry_run"] = False api_mock = MagicMock() api_mock.cancel_order = MagicMock(return_value={"id": "123"}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.cancel_stoploss_order(order_id="_", pair="TKN/BTC") == {"id": "123"} with pytest.raises(InvalidOrderException): api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.cancel_stoploss_order(order_id="_", pair="TKN/BTC") assert api_mock.cancel_order.call_count == 1 @@ -3746,7 +3746,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): mock_prefix = "freqtrade.exchange.okx.Okx" mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value={"for": 123}) mocker.patch(f"{mock_prefix}.fetch_stoploss_order", return_value={"for": 123}) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) res = {"fee": {}, "status": "canceled", "amount": 1234} mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=res) @@ -3771,7 +3771,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): exc = InvalidOrderException("Did not find order") mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=exc) mocker.patch(f"{mock_prefix}.cancel_stoploss_order", side_effect=exc) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=123) @@ -3785,7 +3785,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): order.symbol = "TKN/BTC" mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) exchange._dry_run_open_orders["X"] = order assert exchange.fetch_order("X", "TKN/BTC").myid == 123 @@ -3795,18 +3795,18 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): default_conf["dry_run"] = False api_mock = MagicMock() api_mock.fetch_order = MagicMock(return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert log_has(("API fetch_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog) with pytest.raises(InvalidOrderException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_order(order_id="_", pair="TKN/BTC") assert api_mock.fetch_order.call_count == 1 api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) with patch("freqtrade.exchange.common.time.sleep") as tm: with pytest.raises(InvalidOrderException): exchange.fetch_order(order_id="_", pair="TKN/BTC") @@ -3841,7 +3841,7 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): order.myid = 123 order.symbol = "TKN/BTC" - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) mocker.patch(f"{EXMS}.exchange_has", return_value=False) exchange._dry_run_open_orders["X"] = order # Dry run - regular fetch_order behavior @@ -3859,7 +3859,7 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): api_mock.fetch_closed_order = MagicMock( return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"} ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert log_has( ("API fetch_open_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog @@ -3873,7 +3873,7 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): api_mock.fetch_closed_order = MagicMock( return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"} ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert log_has( ("API fetch_closed_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog @@ -3885,12 +3885,12 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): with pytest.raises(InvalidOrderException): api_mock.fetch_open_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.fetch_closed_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_order(order_id="_", pair="TKN/BTC") assert api_mock.fetch_open_order.call_count == 1 api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) ccxt_exceptionhandlers( mocker, @@ -3913,7 +3913,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name): mocker.patch(f"{EXMS}.exchange_has", return_value=True) order = MagicMock() order.myid = 123 - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) exchange._dry_run_open_orders["X"] = order assert exchange.fetch_stoploss_order("X", "TKN/BTC").myid == 123 @@ -3923,7 +3923,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name): default_conf["dry_run"] = False api_mock = MagicMock() api_mock.fetch_order = MagicMock(return_value={"id": "123", "symbol": "TKN/BTC"}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) res = {"id": "123", "symbol": "TKN/BTC"} if exchange_name == "okx": res = {"id": "123", "symbol": "TKN/BTC", "type": "stoploss"} @@ -3934,7 +3934,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name): return with pytest.raises(InvalidOrderException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange.fetch_stoploss_order(order_id="_", pair="TKN/BTC") assert api_mock.fetch_order.call_count == 1 @@ -3952,7 +3952,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name): def test_fetch_order_or_stoploss_order(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") fetch_order_mock = MagicMock() fetch_stoploss_order_mock = MagicMock() mocker.patch.multiple( @@ -3979,7 +3979,7 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_name(default_conf, mocker, exchange_name): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) assert exchange.name == exchange_name.title() assert exchange.id == exchange_name @@ -4030,7 +4030,7 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, ] ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) orders = exchange.get_trades_for_order(order_id, "ETH/USDT:USDT", since) assert len(orders) == 1 @@ -4069,7 +4069,7 @@ def test_get_fee(default_conf, mocker, exchange_name): api_mock.calculate_fee = MagicMock( return_value={"type": "taker", "currency": "BTC", "rate": 0.025, "cost": 0.05} ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange._config.pop("fee", None) assert exchange.get_fee("ETH/BTC") == 0.025 @@ -4087,7 +4087,7 @@ def test_get_fee(default_conf, mocker, exchange_name): def test_stoploss_order_unsupported_exchange(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id="bitpanda") + exchange = get_patched_exchange(mocker, default_conf, exchange="bitpanda") with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.create_stoploss( pair="ETH/BTC", amount=1, stop_price=220, order_types={}, side="sell", leverage=1.0 @@ -4111,7 +4111,7 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): ], ) def test__get_stop_limit_rate(default_conf_usdt, mocker, side, ratio, expected): - exchange = get_patched_exchange(mocker, default_conf_usdt, id="binance") + exchange = get_patched_exchange(mocker, default_conf_usdt, exchange="binance") order_types = {"stoploss_on_exchange_limit_ratio": ratio} if isinstance(expected, type) and issubclass(expected, Exception): @@ -4469,7 +4469,7 @@ def test_get_markets_error(default_conf, mocker): def test_ohlcv_candle_limit(default_conf, mocker, exchange_name): if exchange_name == "okx": pytest.skip("Tested separately for okx") - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) timeframes = ("1m", "5m", "1h") expected = exchange._ft_has["ohlcv_candle_limit"] for timeframe in timeframes: @@ -4538,7 +4538,7 @@ def test_market_is_tradable( ) -> None: default_conf["trading_mode"] = trademode mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode") - ex = get_patched_exchange(mocker, default_conf, id=exchange) + ex = get_patched_exchange(mocker, default_conf, exchange=exchange) market = { "symbol": market_symbol, "base": base, @@ -4809,7 +4809,7 @@ def test_get_funding_fees(default_conf_usdt, mocker, exchange_name, caplog): now = datetime.now(timezone.utc) default_conf_usdt["trading_mode"] = "futures" default_conf_usdt["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name) exchange._fetch_and_calculate_funding_fees = MagicMock(side_effect=ExchangeError) assert exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) == 0.0 assert exchange._fetch_and_calculate_funding_fees.call_count == 1 @@ -4862,7 +4862,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): type(api_mock).has = PropertyMock(return_value={"fetchFundingHistory": True}) # mocker.patch(f'{EXMS}.get_funding_fees', lambda pair, since: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) date_time = datetime.strptime("2021-09-01T00:00:01.000Z", "%Y-%m-%dT%H:%M:%S.%fZ") unix_time = int(date_time.timestamp()) expected_fees = -0.001 # 0.14542341 + -0.14642341 @@ -4892,7 +4892,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): def test_get_stake_amount_considering_leverage( exchange, stake_amount, leverage, min_stake_with_lev, mocker, default_conf ): - exchange = get_patched_exchange(mocker, default_conf, id=exchange) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange) assert ( exchange._get_stake_amount_considering_leverage(stake_amount, leverage) == min_stake_with_lev @@ -4959,7 +4959,7 @@ def test_validate_trading_mode_and_margin_mode( default_conf, mocker, exchange_name, trading_mode, margin_mode, exception_thrown ): exchange = get_patched_exchange( - mocker, default_conf, id=exchange_name, mock_supported_modes=False + mocker, default_conf, exchange=exchange_name, mock_supported_modes=False ) if exception_thrown: with pytest.raises(OperationalException): @@ -4986,7 +4986,7 @@ def test_validate_trading_mode_and_margin_mode( def test__ccxt_config(default_conf, mocker, exchange_name, trading_mode, ccxt_config): default_conf["trading_mode"] = trading_mode default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) assert exchange._ccxt_config == ccxt_config @@ -5005,7 +5005,7 @@ def test_get_max_leverage_from_margin(default_conf, mocker, pair, nominal_value, default_conf["margin_mode"] = "isolated" api_mock = MagicMock() type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gate") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="gate") assert exchange.get_max_leverage(pair, nominal_value) == max_lev @@ -5022,7 +5022,7 @@ def test_calculate_funding_fees( default_conf, mocker, size, funding_rate, mark_price, funding_fee, kraken_fee, time_in_ratio ): exchange = get_patched_exchange(mocker, default_conf) - kraken = get_patched_exchange(mocker, default_conf, id="kraken") + kraken = get_patched_exchange(mocker, default_conf, exchange="kraken") prior_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc) - timedelta(hours=1)) trade_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc)) funding_rates = DataFrame( @@ -5247,7 +5247,7 @@ def test__fetch_and_calculate_funding_fees( type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True}) type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True}) - ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) + ex = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange) mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["1h", "4h", "8h"])) funding_fees = ex._fetch_and_calculate_funding_fees( pair="ADA/USDT:USDT", amount=amount, is_short=True, open_date=d1, close_date=d2 @@ -5261,7 +5261,7 @@ def test__fetch_and_calculate_funding_fees( # Return empty "refresh_latest" mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value={}) - ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) + ex = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange) with pytest.raises(ExchangeError, match="Could not find funding rates."): ex._fetch_and_calculate_funding_fees( pair="ADA/USDT:USDT", amount=amount, is_short=False, open_date=d1, close_date=d2 @@ -5292,7 +5292,7 @@ def test__fetch_and_calculate_funding_fees_datetime_called( type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True}) type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True}) mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["4h", "8h"])) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange) d1 = datetime.strptime("2021-08-31 23:00:01 +0000", "%Y-%m-%d %H:%M:%S %z") time_machine.move_to("2021-09-01 08:00:00 +00:00") @@ -5609,7 +5609,7 @@ def test_liquidation_price_is_none( ): default_conf["trading_mode"] = trading_mode default_conf["margin_mode"] = margin_mode - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) assert ( exchange.get_liquidation_price( pair="DOGE/USDT", @@ -5708,7 +5708,7 @@ def test_liquidation_price_binance( default_conf["trading_mode"] = trading_mode default_conf["margin_mode"] = margin_mode default_conf["liquidation_buffer"] = 0.0 - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) assert ( pytest.approx( @@ -5858,7 +5858,7 @@ def test_load_leverage_tiers(mocker, default_conf, exchange_name): ) # SPOT - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.load_leverage_tiers() == {} default_conf["trading_mode"] = "futures" @@ -5867,12 +5867,12 @@ def test_load_leverage_tiers(mocker, default_conf, exchange_name): if exchange_name != "binance": # FUTURES has.fetchLeverageTiers == False type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.load_leverage_tiers() == {} # FUTURES regular type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": True}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) assert exchange.load_leverage_tiers() == { "ADA/USDT:USDT": [ { @@ -6024,13 +6024,13 @@ def test_get_maintenance_ratio_and_amt( def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): # Test Spot - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") assert exchange.get_max_leverage("BNB/USDT", 100.0) == 1.0 # Test Futures default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange = get_patched_exchange(mocker, default_conf, exchange="binance") exchange._leverage_tiers = leverage_tiers @@ -6054,7 +6054,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): def test__get_params(mocker, default_conf, exchange_name): api_mock = MagicMock() mocker.patch(f"{EXMS}.exchange_has", return_value=True) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange._params = {"test": True} params1 = {"test": True} @@ -6109,7 +6109,7 @@ def test__get_params(mocker, default_conf, exchange_name): default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name) exchange._params = {"test": True} assert ( @@ -6326,7 +6326,7 @@ def test_get_liquidation_price( default_conf_usdt["exchange"]["name"] = exchange_name default_conf_usdt["margin_mode"] = margin_mode mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes") - exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) + exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) exchange.name = exchange_name diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index ec238000a..8b97ae1b8 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -56,11 +56,11 @@ async def test_exchangews_ohlcv(mocker): assert exchange_ws._klines_scheduled == set() exchange_ws.schedule_ohlcv("ETH/BTC", "1m", CandleType.SPOT) - asyncio.sleep(0.5) + await asyncio.sleep(0.5) assert exchange_ws._klines_watching == {("ETH/BTC", "1m", CandleType.SPOT)} assert exchange_ws._klines_scheduled == {("ETH/BTC", "1m", CandleType.SPOT)} - asyncio.sleep(0.1) + await asyncio.sleep(0.1) assert ccxt_object.watch_ohlcv.call_count == 1 except Exception as e: print(e) diff --git a/tests/exchange/test_gate.py b/tests/exchange/test_gate.py index b4e021a5d..3f5ecacf6 100644 --- a/tests/exchange/test_gate.py +++ b/tests/exchange/test_gate.py @@ -9,7 +9,7 @@ from tests.conftest import EXMS, get_patched_exchange @pytest.mark.usefixtures("init_persistence") def test_fetch_stoploss_order_gate(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id="gate") + exchange = get_patched_exchange(mocker, default_conf, exchange="gate") fetch_order_mock = MagicMock() exchange.fetch_order = fetch_order_mock @@ -23,7 +23,7 @@ def test_fetch_stoploss_order_gate(default_conf, mocker): default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, id="gate") + exchange = get_patched_exchange(mocker, default_conf, exchange="gate") exchange.fetch_order = MagicMock( return_value={ @@ -41,7 +41,7 @@ def test_fetch_stoploss_order_gate(default_conf, mocker): def test_cancel_stoploss_order_gate(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id="gate") + exchange = get_patched_exchange(mocker, default_conf, exchange="gate") cancel_order_mock = MagicMock() exchange.cancel_order = cancel_order_mock @@ -57,7 +57,7 @@ def test_cancel_stoploss_order_gate(default_conf, mocker): "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] ) def test_stoploss_adjust_gate(mocker, default_conf, sl1, sl2, sl3, side): - exchange = get_patched_exchange(mocker, default_conf, id="gate") + exchange = get_patched_exchange(mocker, default_conf, exchange="gate") order = { "price": 1500, "stopPrice": 1500, @@ -111,7 +111,7 @@ def test_fetch_my_trades_gate(mocker, default_conf, takerormaker, rate, cost): } ] ) - exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, id="gate") + exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, exchange="gate") exchange._trading_fees = tick trades = exchange.get_trades_for_order("22255", "ETH/USDT:USDT", datetime.now(timezone.utc)) trade = trades[0] diff --git a/tests/exchange/test_htx.py b/tests/exchange/test_htx.py index 807d9b28f..03099c3de 100644 --- a/tests/exchange/test_htx.py +++ b/tests/exchange/test_htx.py @@ -128,7 +128,7 @@ def test_create_stoploss_order_dry_run_htx(default_conf, mocker): def test_stoploss_adjust_htx(mocker, default_conf): - exchange = get_patched_exchange(mocker, default_conf, id="htx") + exchange = get_patched_exchange(mocker, default_conf, exchange="htx") order = { "type": "stop", "price": 1500, diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 932677c68..5715c9cc0 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -32,7 +32,7 @@ def test_kraken_trading_agreement(default_conf, mocker, order_type, time_in_forc mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kraken") order = exchange.create_order( pair="ETH/BTC", @@ -121,7 +121,7 @@ def test_get_balances_prod(default_conf, mocker): ] api_mock.fetch_open_orders = MagicMock(return_value=kraken_open_orders) default_conf["dry_run"] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kraken") balances = exchange.get_balances() assert len(balances) == 6 @@ -256,7 +256,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side): "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] ) def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side): - exchange = get_patched_exchange(mocker, default_conf, id="kraken") + exchange = get_patched_exchange(mocker, default_conf, exchange="kraken") order = { "type": "market", "stopLossPrice": 1500, @@ -278,5 +278,5 @@ def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side): ], ) def test__valid_trade_pagination_id_kraken(mocker, default_conf_usdt, trade_id, expected): - exchange = get_patched_exchange(mocker, default_conf_usdt, id="kraken") + exchange = get_patched_exchange(mocker, default_conf_usdt, exchange="kraken") assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py index 1d297505c..c2d245927 100644 --- a/tests/exchange/test_kucoin.py +++ b/tests/exchange/test_kucoin.py @@ -134,7 +134,7 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker): def test_stoploss_adjust_kucoin(mocker, default_conf): - exchange = get_patched_exchange(mocker, default_conf, id="kucoin") + exchange = get_patched_exchange(mocker, default_conf, exchange="kucoin") order = { "type": "limit", "price": 1500, @@ -161,7 +161,7 @@ def test_kucoin_create_order(default_conf, mocker, side, ordertype, rate): default_conf["dry_run"] = False mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kucoin") exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index 305b16ea2..df428010f 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -12,7 +12,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers def test_okx_ohlcv_candle_limit(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange = get_patched_exchange(mocker, default_conf, exchange="okx") timeframes = ("1m", "5m", "1h") start_time = int(datetime(2021, 1, 1, tzinfo=timezone.utc).timestamp() * 1000) @@ -188,7 +188,7 @@ def test_get_maintenance_ratio_and_amt_okx( } ), ) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx") assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 2000) == (0.01, None) assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 2001) == (0.015, None) assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 4001) == (0.02, None) @@ -199,12 +199,12 @@ def test_get_maintenance_ratio_and_amt_okx( def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers): - exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange = get_patched_exchange(mocker, default_conf, exchange="okx") assert exchange.get_max_pair_stake_amount("BNB/BUSD", 1.0) == float("inf") default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange = get_patched_exchange(mocker, default_conf, exchange="okx") exchange._leverage_tiers = leverage_tiers assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 1.0) == 30000000 @@ -229,7 +229,7 @@ def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers): ], ) def test__get_posSide(default_conf, mocker, mode, side, reduceonly, result): - exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange = get_patched_exchange(mocker, default_conf, exchange="okx") exchange.net_only = mode == "net" assert exchange._get_posSide(side, reduceonly) == result @@ -257,7 +257,7 @@ def test_additional_exchange_init_okx(default_conf, mocker): ] ) default_conf["dry_run"] = False - exchange = get_patched_exchange(mocker, default_conf, id="okx", api_mock=api_mock) + exchange = get_patched_exchange(mocker, default_conf, exchange="okx", api_mock=api_mock) assert api_mock.fetch_accounts.call_count == 0 exchange.trading_mode = TradingMode.FUTURES # Default to netOnly @@ -438,7 +438,7 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmp_path, caplog default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" default_conf["stake_currency"] = "USDT" - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx") exchange.trading_mode = TradingMode.FUTURES exchange.margin_mode = MarginMode.ISOLATED exchange.markets = markets @@ -520,7 +520,7 @@ def test__set_leverage_okx(mocker, default_conf): default_conf["trading_mode"] = TradingMode.FUTURES default_conf["margin_mode"] = MarginMode.ISOLATED - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx") exchange._lev_prep("BTC/USDT:USDT", 3.2, "buy") assert api_mock.set_leverage.call_count == 1 # Leverage is rounded to 3. @@ -554,7 +554,7 @@ def test_fetch_stoploss_order_okx(default_conf, mocker): api_mock = MagicMock() api_mock.fetch_order = MagicMock() - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx") exchange.fetch_stoploss_order("1234", "ETH/BTC") assert api_mock.fetch_order.call_count == 1 @@ -594,7 +594,7 @@ def test_fetch_stoploss_order_okx(default_conf, mocker): assert resp["type"] == "stoploss" default_conf["dry_run"] = True - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx") dro_mock = mocker.patch(f"{EXMS}.fetch_dry_run_order", MagicMock(return_value={"id": "123455"})) api_mock.fetch_order.reset_mock() @@ -614,7 +614,7 @@ def test_fetch_stoploss_order_okx(default_conf, mocker): "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] ) def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side): - exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange = get_patched_exchange(mocker, default_conf, exchange="okx") order = { "type": "stoploss", "price": 1500, @@ -625,7 +625,7 @@ def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side): def test_stoploss_cancel_okx(mocker, default_conf): - exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange = get_patched_exchange(mocker, default_conf, exchange="okx") exchange.cancel_order = MagicMock() @@ -639,7 +639,7 @@ def test_stoploss_cancel_okx(mocker, default_conf): def test__get_stop_params_okx(mocker, default_conf): default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" - exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange = get_patched_exchange(mocker, default_conf, exchange="okx") params = exchange._get_stop_params("ETH/USDT:USDT", 1500, "sell") assert params["tdMode"] == "isolated" @@ -660,13 +660,13 @@ def test_fetch_orders_okx(default_conf, mocker, limit_order): mocker.patch(f"{EXMS}.exchange_has", return_value=True) start_time = datetime.now(timezone.utc) - timedelta(days=20) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx") # Not available in dry-run assert exchange.fetch_orders("mocked", start_time) == [] assert api_mock.fetch_orders.call_count == 0 default_conf["dry_run"] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx") def has_resp(_, endpoint): if endpoint == "fetchOrders": diff --git a/tests/exchange_online/test_ccxt_ws_compat.py b/tests/exchange_online/test_ccxt_ws_compat.py index 49c46f516..ed449bb58 100644 --- a/tests/exchange_online/test_ccxt_ws_compat.py +++ b/tests/exchange_online/test_ccxt_ws_compat.py @@ -21,8 +21,8 @@ from tests.exchange_online.conftest import EXCHANGE_WS_FIXTURE_TYPE @pytest.mark.longrun @pytest.mark.timeout(3 * 60) class TestCCXTExchangeWs: - def test_ccxt_ohlcv(self, exchange_ws: EXCHANGE_WS_FIXTURE_TYPE, caplog, mocker): - exch, exchangename, pair = exchange_ws + def test_ccxt_watch_ohlcv(self, exchange_ws: EXCHANGE_WS_FIXTURE_TYPE, caplog, mocker): + exch, _exchangename, pair = exchange_ws assert exch._ws_async is not None timeframe = "1m" diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index db2e96b37..23dfbb785 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -915,7 +915,7 @@ def test_execute_entry( default_conf_usdt["margin_mode"] = margin_mode mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes") patch_RPCManager(mocker) - patch_exchange(mocker, id=exchange_name) + patch_exchange(mocker, exchange=exchange_name) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) freqtrade.strategy.leverage = MagicMock(return_value=leverage) @@ -3810,6 +3810,9 @@ def test_get_real_amount_quote_dust( def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mocker, fee): mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[]) + # Invalid nested trade object + buy_order_fee["trades"] = [{"amount": None, "cost": 22}] + amount = buy_order_fee["amount"] trade = Trade( pair="LTC/ETH", diff --git a/tests/leverage/test_candletype.py b/tests/leverage/test_candletype.py index a424012d7..8a2c4ba3e 100644 --- a/tests/leverage/test_candletype.py +++ b/tests/leverage/test_candletype.py @@ -4,7 +4,7 @@ from freqtrade.enums import CandleType @pytest.mark.parametrize( - "input,expected", + "candle_type,expected", [ ("", CandleType.SPOT), ("spot", CandleType.SPOT), @@ -17,17 +17,17 @@ from freqtrade.enums import CandleType ("premiumIndex", CandleType.PREMIUMINDEX), ], ) -def test_CandleType_from_string(input, expected): - assert CandleType.from_string(input) == expected +def test_CandleType_from_string(candle_type, expected): + assert CandleType.from_string(candle_type) == expected @pytest.mark.parametrize( - "input,expected", + "candle_type,expected", [ ("futures", CandleType.FUTURES), ("spot", CandleType.SPOT), ("margin", CandleType.SPOT), ], ) -def test_CandleType_get_default(input, expected): - assert CandleType.get_default(input) == expected +def test_CandleType_get_default(candle_type, expected): + assert CandleType.get_default(candle_type) == expected diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 2e9ae8d35..a9f697629 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -291,9 +291,10 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: "is_best": True, } ) + hyperopt._hyper_out.print() out, _err = capsys.readouterr() assert all( - x in out for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "00:20:00"] + x in out for x in ["Best", "2/2", "1", "0.10%", "0.00100000 BTC (1.00%)", "0:20:00"] ) diff --git a/tests/optimize/test_lookahead_analysis.py b/tests/optimize/test_lookahead_analysis.py index 88e3ad877..f7d38b24b 100644 --- a/tests/optimize/test_lookahead_analysis.py +++ b/tests/optimize/test_lookahead_analysis.py @@ -147,7 +147,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf instance = LookaheadAnalysis(lookahead_conf, strategy_obj) instance.current_analysis = analysis - _table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( + data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( lookahead_conf, [instance] ) @@ -163,14 +163,14 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf analysis.false_exit_signals = 10 instance = LookaheadAnalysis(lookahead_conf, strategy_obj) instance.current_analysis = analysis - _table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( + data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( lookahead_conf, [instance] ) assert data[0][2].__contains__("error") # edit it into not showing an error instance.failed_bias_check = False - _table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( + data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( lookahead_conf, [instance] ) assert data[0][0] == "strategy_test_v3_with_lookahead_bias.py" @@ -183,7 +183,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf analysis.false_indicators.append("falseIndicator1") analysis.false_indicators.append("falseIndicator2") - _table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( + data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( lookahead_conf, [instance] ) @@ -193,7 +193,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf assert len(data) == 1 # check amount of multiple rows - _table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( + data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( lookahead_conf, [instance, instance, instance] ) assert len(data) == 3 diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index b26f15be5..4c7ce06e8 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -59,7 +59,7 @@ def _backup_file(file: Path, copy_file: bool = False) -> None: copyfile(file_swp, file) -def test_text_table_bt_results(): +def test_text_table_bt_results(capsys): results = pd.DataFrame( { "pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"], @@ -69,21 +69,23 @@ def test_text_table_bt_results(): } ) - result_str = ( - "| Pair | Trades | Avg Profit % | Tot Profit BTC | " - "Tot Profit % | Avg Duration | Win Draw Loss Win% |\n" - "|---------+----------+----------------+------------------+" - "----------------+----------------+-------------------------|\n" - "| ETH/BTC | 3 | 8.33 | 0.50000000 | " - "12.50 | 0:20:00 | 2 0 1 66.7 |\n" - "| TOTAL | 3 | 8.33 | 0.50000000 | " - "12.50 | 0:20:00 | 2 0 1 66.7 |" - ) - pair_results = generate_pair_metrics( ["ETH/BTC"], stake_currency="BTC", starting_balance=4, results=results ) - assert text_table_bt_results(pair_results, stake_currency="BTC") == result_str + text_table_bt_results(pair_results, stake_currency="BTC", title="title") + text = capsys.readouterr().out + re.search( + r".* Pair .* Trades .* Avg Profit % .* Tot Profit BTC .* Tot Profit % .* " + r"Avg Duration .* Win Draw Loss Win% .*", + text, + ) + re.search( + r".* ETH/BTC .* 3 .* 8.33 .* 0.50000000 .* 12.50 .* 0:20:00 .* 2 0 1 66.7 .*", + text, + ) + re.search( + r".* TOTAL .* 3 .* 8.33 .* 0.50000000 .* 12.50 .* 0:20:00 .* 2 0 1 66.7 .*", text + ) def test_generate_backtest_stats(default_conf, testdatadir, tmp_path): @@ -434,7 +436,7 @@ def test_calc_streak(testdatadir): assert calc_streak(bt_data) == (7, 18) -def test_text_table_exit_reason(): +def test_text_table_exit_reason(capsys): results = pd.DataFrame( { "pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"], @@ -448,23 +450,28 @@ def test_text_table_exit_reason(): } ) - result_str = ( - "| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |" - " Avg Duration | Win Draw Loss Win% |\n" - "|---------------+---------+----------------+------------------+----------------+" - "----------------+-------------------------|\n" - "| roi | 2 | 15.00 | 0.60000000 | 2.73 |" - " 0:20:00 | 2 0 0 100 |\n" - "| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |" - " 0:10:00 | 0 0 1 0 |\n" - "| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |" - " 0:17:00 | 2 0 1 66.7 |" - ) - exit_reason_stats = generate_tag_metrics( "exit_reason", starting_balance=22, results=results, skip_nan=False ) - assert text_table_tags("exit_tag", exit_reason_stats, "BTC") == result_str + text_table_tags("exit_tag", exit_reason_stats, "BTC") + text = capsys.readouterr().out + + assert re.search( + r".* Exit Reason .* Exits .* Avg Profit % .* Tot Profit BTC .* Tot Profit % .* " + r"Avg Duration .* Win Draw Loss Win% .*", + text, + ) + assert re.search( + r".* roi .* 2 .* 15.0 .* 0.60000000 .* 2.73 .* 0:20:00 .* 2 0 0 100 .*", + text, + ) + assert re.search( + r".* stop_loss .* 1 .* -10.0 .* -0.20000000 .* -0.91 .* 0:10:00 .* 0 0 1 0 .*", + text, + ) + assert re.search( + r".* TOTAL .* 3 .* 6.67 .* 0.40000000 .* 1.82 .* 0:17:00 .* 2 0 1 66.7 .*", text + ) def test_generate_sell_reason_stats(): @@ -502,39 +509,42 @@ def test_generate_sell_reason_stats(): assert stop_result["profit_mean_pct"] == round(stop_result["profit_mean"] * 100, 2) -def test_text_table_strategy(testdatadir): +def test_text_table_strategy(testdatadir, capsys): filename = testdatadir / "backtest_results/backtest-result_multistrat.json" bt_res_data = load_backtest_stats(filename) bt_res_data_comparison = bt_res_data.pop("strategy_comparison") - result_str = ( - "| Strategy | Trades | Avg Profit % | Tot Profit BTC |" - " Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n" - "|----------------+----------+----------------+------------------+" - "----------------+----------------+-------------------------+-----------------------|\n" - "| StrategyTestV2 | 179 | 0.08 | 0.02608550 |" - " 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n" - "| TestStrategy | 179 | 0.08 | 0.02608550 |" - " 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |" - ) - strategy_results = generate_strategy_comparison(bt_stats=bt_res_data["strategy"]) assert strategy_results == bt_res_data_comparison - assert text_table_strategy(strategy_results, "BTC") == result_str + text_table_strategy(strategy_results, "BTC", "STRATEGY SUMMARY") + + captured = capsys.readouterr() + text = captured.out + assert re.search( + r".* Strategy .* Trades .* Avg Profit % .* Tot Profit BTC .* Tot Profit % .* " + r"Avg Duration .* Win Draw Loss Win% .* Drawdown .*", + text, + ) + assert re.search( + r".*StrategyTestV2 .* 179 .* 0.08 .* 0.02608550 .* " + r"260.85 .* 3:40:00 .* 170 0 9 95.0 .* 0.00308222 BTC 8.67%.*", + text, + ) + assert re.search( + r".*TestStrategy .* 179 .* 0.08 .* 0.02608550 .* " + r"260.85 .* 3:40:00 .* 170 0 9 95.0 .* 0.00308222 BTC 8.67%.*", + text, + ) -def test_generate_edge_table(): +def test_generate_edge_table(capsys): results = {} results["ETH/BTC"] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60) - assert generate_edge_table(results).count("+") == 7 - assert generate_edge_table(results).count("| ETH/BTC |") == 1 - assert ( - generate_edge_table(results).count( - "| Risk Reward Ratio | Required Risk Reward | Expectancy |" - ) - == 1 - ) + generate_edge_table(results) + text = capsys.readouterr().out + assert re.search(r".* ETH/BTC .*", text) + assert re.search(r".* Risk Reward Ratio .* Required Risk Reward .* Expectancy .*", text) def test_generate_periodic_breakdown_stats(testdatadir): diff --git a/tests/optimize/test_recursive_analysis.py b/tests/optimize/test_recursive_analysis.py index 2969b4153..e16c82d24 100644 --- a/tests/optimize/test_recursive_analysis.py +++ b/tests/optimize/test_recursive_analysis.py @@ -105,9 +105,7 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf instance = RecursiveAnalysis(recursive_conf, strategy_obj) instance.dict_recursive = dict_diff - _table, _headers, data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances( - [instance] - ) + data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances([instance]) # check row contents for a try that has too few signals assert data[0][0] == "rsi" @@ -118,9 +116,7 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf dict_diff = dict() instance = RecursiveAnalysis(recursive_conf, strategy_obj) instance.dict_recursive = dict_diff - _table, _headers, data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances( - [instance] - ) + data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances([instance]) assert len(data) == 0