From 2bed41da5dcc899b45c0d984b81b44cfc5094340 Mon Sep 17 00:00:00 2001 From: rextea Date: Fri, 26 Mar 2021 18:40:50 +0300 Subject: [PATCH 01/12] Add days breakdown table to backtesting --- docs/backtesting.md | 1 + freqtrade/commands/arguments.py | 2 +- freqtrade/commands/cli_options.py | 6 ++ freqtrade/configuration/configuration.py | 3 + freqtrade/optimize/optimize_reports.py | 71 +++++++++++++++++++++--- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index d02c59f05..91faa07bb 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -67,6 +67,7 @@ optional arguments: Requires `--export` to be set as well. Example: `--export-filename=user_data/backtest_results/backtest _today.json` + --show-days Print a days breakdown table of the backtest results Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 9468a7f7d..b71819ef2 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -21,7 +21,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "enable_protections", "dry_run_wallet", - "strategy_list", "export", "exportfilename"] + "strategy_list", "export", "exportfilename", "show_days"] ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "position_stacking", "use_max_market_positions", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 15c13cec9..dc193ee4f 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -183,6 +183,12 @@ AVAILABLE_CLI_OPTIONS = { type=float, metavar='FLOAT', ), + "show_days": Arg( + '--show-days', + help='Print days breakdown for backtest results', + action='store_true', + default=False, + ), # Edge "stoploss_range": Arg( '--stoplosses', diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index a40a4fd83..1eb6351d0 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -260,6 +260,9 @@ class Configuration: self._args_to_config(config, argname='export', logstring='Parameter --export detected: {} ...') + self._args_to_config(config, argname='show_days', + logstring='Parameter --show-days detected ...') + # Edge section: if 'stoploss_range' in self.args and self.args["stoploss_range"]: txt_range = eval(self.args["stoploss_range"]) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 099976aa9..d15988669 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -13,7 +13,6 @@ from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, calculate_max_drawdown) from freqtrade.misc import decimals_per_coin, file_dump_json, round_coin_value - logger = logging.getLogger(__name__) @@ -32,7 +31,7 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N filename = Path.joinpath( recordfilename.parent, f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' - ).with_suffix(recordfilename.suffix) + ).with_suffix(recordfilename.suffix) file_dump_json(filename, stats) latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) @@ -75,8 +74,8 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column 'profit_total': profit_total, 'profit_total_pct': round(profit_total * 100.0, 2), 'duration_avg': str(timedelta( - minutes=round(result['trade_duration'].mean())) - ) if not result.empty else '0:00', + minutes=round(result['trade_duration'].mean())) + ) if not result.empty else '0:00', # 'duration_max': str(timedelta( # minutes=round(result['trade_duration'].max())) # ) if not result.empty else '0:00', @@ -161,12 +160,11 @@ def generate_strategy_metrics(all_results: Dict) -> List[Dict]: for strategy, results in all_results.items(): tabular_data.append(_generate_result_line( results['results'], results['config']['dry_run_wallet'], strategy) - ) + ) return tabular_data def generate_edge_table(results: dict) -> str: - floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', 'd', 'd') tabular_data = [] headers = ['Pair', 'Stoploss', 'Win Rate', 'Risk Reward Ratio', @@ -191,6 +189,29 @@ def generate_edge_table(results: dict) -> str: floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore +def generate_days_breakdown_stats(results: DataFrame, starting_balance: int) -> Dict[str, Any]: + days = results.resample('1d', on='close_date') + days_stats = [] + for name, day in days: + profit_abs = day['profit_abs'].sum().round(10) + profit_total = day['profit_abs'].sum() / starting_balance + wins = sum(day['profit_abs'] > 0) + draws = sum(day['profit_abs'] == 0) + loses = sum(day['profit_abs'] < 0) + profit_percentage = round(profit_total * 100.0, 2) + days_stats.append( + { + 'date': name.strftime('%d/%m/%Y'), + 'profit_percentage': profit_percentage, + 'profit_abs': profit_abs, + 'wins': wins, + 'draws': draws, + 'loses': loses + } + ) + return days_stats + + def generate_daily_stats(results: DataFrame) -> Dict[str, Any]: if len(results) == 0: return { @@ -266,6 +287,8 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], starting_balance=starting_balance, results=results.loc[results['is_open']], skip_nan=True) + days_breakdown_stats = generate_days_breakdown_stats(results=results, + starting_balance=starting_balance) daily_stats = generate_daily_stats(results) best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'], key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None @@ -283,6 +306,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], 'results_per_pair': pair_results, 'sell_reason_summary': sell_reason_stats, 'left_open_trades': left_open_results, + 'days_breakdown_stats': days_breakdown_stats, 'total_trades': len(results), 'total_volume': float(results['stake_amount'].sum()), 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0, @@ -425,6 +449,28 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") +def text_table_days_breakdown(days_breakdown_stats: List[Dict[str, Any]], stake_currency: str) -> str: + """ + 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 = [ + 'Day', + 'Profit %', + f'Tot Profit {stake_currency}', + 'Wins', + 'Draws', + 'Losses', + ] + output = [[ + d['date'], d['profit_percentage'], round_coin_value(d['profit_abs'], stake_currency, False), + d['wins'], d['draws'], d['loses'], + ] for d in days_breakdown_stats] + return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") + + def text_table_strategy(strategy_results, stake_currency: str) -> str: """ Generate summary table per strategy @@ -463,6 +509,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency'])), ('Total profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"), ('Trades per day', strat_results['trades_per_day']), + ('Avg. daily profit %', + f"{round(strat_results['profit_total'] / strat_results['backtest_days'] * 100, 2)}%"), ('Avg. stake amount', round_coin_value(strat_results['avg_stake_amount'], strat_results['stake_currency'])), ('Total trade volume', round_coin_value(strat_results['total_volume'], @@ -482,7 +530,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Worst day', round_coin_value(strat_results['backtest_worst_day_abs'], strat_results['stake_currency'])), ('Days win/draw/lose', f"{strat_results['winning_days']} / " - f"{strat_results['draw_days']} / {strat_results['losing_days']}"), + f"{strat_results['draw_days']} / {strat_results['losing_days']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), ('', ''), # Empty line to improve readability @@ -510,7 +558,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency']) stake_amount = round_coin_value( strat_results['stake_amount'], strat_results['stake_currency'] - ) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited' + ) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited' message = ("No trades made. " f"Your starting balance was {start_balance}, " @@ -542,6 +590,13 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(table) + if config.get('show_days', False): + table = text_table_days_breakdown(days_breakdown_stats=results['days_breakdown_stats'], + stake_currency=stake_currency) + if isinstance(table, str) and len(table) > 0: + print(' DAYS 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]), '=')) From 76a02ff70aa4016ed6755fa1f00cbb6246edab97 Mon Sep 17 00:00:00 2001 From: rextea Date: Fri, 26 Mar 2021 18:49:17 +0300 Subject: [PATCH 02/12] fix indentations --- freqtrade/optimize/optimize_reports.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index d15988669..286fa5c46 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -13,6 +13,7 @@ from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, calculate_max_drawdown) from freqtrade.misc import decimals_per_coin, file_dump_json, round_coin_value + logger = logging.getLogger(__name__) @@ -31,7 +32,7 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N filename = Path.joinpath( recordfilename.parent, f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' - ).with_suffix(recordfilename.suffix) + ).with_suffix(recordfilename.suffix) file_dump_json(filename, stats) latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) @@ -74,8 +75,8 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column 'profit_total': profit_total, 'profit_total_pct': round(profit_total * 100.0, 2), 'duration_avg': str(timedelta( - minutes=round(result['trade_duration'].mean())) - ) if not result.empty else '0:00', + minutes=round(result['trade_duration'].mean())) + ) if not result.empty else '0:00', # 'duration_max': str(timedelta( # minutes=round(result['trade_duration'].max())) # ) if not result.empty else '0:00', @@ -530,7 +531,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Worst day', round_coin_value(strat_results['backtest_worst_day_abs'], strat_results['stake_currency'])), ('Days win/draw/lose', f"{strat_results['winning_days']} / " - f"{strat_results['draw_days']} / {strat_results['losing_days']}"), + f"{strat_results['draw_days']} / {strat_results['losing_days']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), ('', ''), # Empty line to improve readability @@ -558,7 +559,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency']) stake_amount = round_coin_value( strat_results['stake_amount'], strat_results['stake_currency'] - ) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited' + ) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited' message = ("No trades made. " f"Your starting balance was {start_balance}, " From 7d8cd736b8113904c427ccba13b2a5113e959be3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Oct 2021 16:48:31 +0200 Subject: [PATCH 03/12] Support days-breakdown also for hyperopt results --- freqtrade/commands/arguments.py | 2 +- freqtrade/commands/hyperopt_commands.py | 2 +- freqtrade/optimize/optimize_reports.py | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 00aa0ded0..11c1e9191 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -89,7 +89,7 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperoptexportfilename", "hyperopt_show_no_header", - "disableparamexport"] + "disableparamexport", "show_days"] NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets", "list-pairs", "list-strategies", "list-data", diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 614c4b3f5..d2f8c188c 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -96,7 +96,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: if 'strategy_name' in metrics: strategy_name = metrics['strategy_name'] show_backtest_result(strategy_name, metrics, - metrics['stake_currency']) + metrics['stake_currency'], config.get('show_days', False)) HyperoptTools.try_export_params(config, strategy_name, val) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 384ca006b..a6eedc6c7 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import Any, Dict, List, Union from numpy import int64 -from pandas import DataFrame +from pandas import DataFrame, to_datetime from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT @@ -213,7 +213,9 @@ def generate_edge_table(results: dict) -> str: floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore -def generate_days_breakdown_stats(results: DataFrame, starting_balance: int) -> Dict[str, Any]: +def generate_days_breakdown_stats(trade_list: List, starting_balance: int) -> List[Dict[str, Any]]: + results = DataFrame.from_records(trade_list) + results['close_date'] = to_datetime(results['close_date'], utc=True) days = results.resample('1d', on='close_date') days_stats = [] for name, day in days: @@ -341,8 +343,6 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], starting_balance=starting_balance, results=results.loc[results['is_open']], skip_nan=True) - days_breakdown_stats = generate_days_breakdown_stats( - results=results, starting_balance=starting_balance) daily_stats = generate_daily_stats(results) trade_stats = generate_trading_stats(results) best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'], @@ -362,7 +362,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'results_per_pair': pair_results, 'sell_reason_summary': sell_reason_stats, 'left_open_trades': left_open_results, - 'days_breakdown_stats': days_breakdown_stats, + # 'days_breakdown_stats': days_breakdown_stats, 'total_trades': len(results), 'total_volume': float(results['stake_amount'].sum()), @@ -690,7 +690,9 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: print(table) if show_days: - table = text_table_days_breakdown(days_breakdown_stats=results['days_breakdown_stats'], + days_breakdown_stats = generate_days_breakdown_stats( + trade_list=results['trades'], starting_balance=results['starting_balance']) + table = text_table_days_breakdown(days_breakdown_stats=days_breakdown_stats, stake_currency=stake_currency) if isinstance(table, str) and len(table) > 0: print(' DAYS BREAKDOWN '.center(len(table.splitlines()[0]), '=')) From 0329da1a57a83ef49a2c44b5d0e3a672ab5b099f Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 17 Oct 2021 07:06:55 -0600 Subject: [PATCH 04/12] updated get_max_leverage to use new ccxt unified property --- freqtrade/exchange/binance.py | 8 ++++++- freqtrade/exchange/exchange.py | 16 +++++++++----- freqtrade/exchange/ftx.py | 17 +------------- freqtrade/exchange/kraken.py | 34 ---------------------------- tests/conftest.py | 34 ++++++++++++---------------- tests/exchange/test_exchange.py | 13 +++++++++++ tests/exchange/test_ftx.py | 17 -------------- tests/exchange/test_kraken.py | 39 --------------------------------- 8 files changed, 45 insertions(+), 133 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index d23f84e7b..231dc1a95 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -2,7 +2,7 @@ import json import logging from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import arrow import ccxt @@ -38,6 +38,12 @@ class Binance(Exchange): # (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported ] + def __init__(self, config: Dict[str, Any], validate: bool = True) -> None: + super().__init__(config, validate) + self._leverage_brackets: Dict = {} + if self.trading_mode != TradingMode.SPOT: + self.fill_leverage_brackets() + @property def _ccxt_config(self) -> Dict: # Parameters to add directly to ccxt sync/async initialization. diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f711bc258..ad74fa0c1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -89,7 +89,6 @@ class Exchange: self._api: ccxt.Exchange = None self._api_async: ccxt_async.Exchange = None self._markets: Dict = {} - self._leverage_brackets: Dict = {} self._config.update(config) @@ -158,9 +157,6 @@ class Exchange: self._api_async = self._init_ccxt( exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) - if self.trading_mode != TradingMode.SPOT: - self.fill_leverage_brackets() - logger.info('Using Exchange "%s"', self.name) if validate: @@ -1637,9 +1633,9 @@ class Exchange: def fill_leverage_brackets(self): """ - # TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken Assigns property _leverage_brackets to a dictionary of information about the leverage allowed on each pair + Not used by most exchanges, only used by Binance at time of writing """ return @@ -1649,7 +1645,15 @@ class Exchange: :param pair: The base/quote currency pair being traded :nominal_value: The total value of the trade in quote currency (collateral + debt) """ - return 1.0 + market = self.markets[pair] + if ( + 'limits' in market and + 'leverage' in market['limits'] and + 'max' in market['limits']['leverage'] + ): + return market['limits']['leverage']['max'] + else: + return 1.0 @retrier def _set_leverage( diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 5072d653e..2acf32ba3 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -1,6 +1,6 @@ """ FTX exchange subclass """ import logging -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Tuple import ccxt @@ -168,18 +168,3 @@ class Ftx(Exchange): if order['type'] == 'stop': return safe_value_fallback2(order, order, 'id_stop', 'id') return order['id'] - - def fill_leverage_brackets(self): - """ - FTX leverage is static across the account, and doesn't change from pair to pair, - so _leverage_brackets doesn't need to be set - """ - return - - def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: - """ - Returns the maximum leverage that a pair can be traded at, which is always 20 on ftx - :param pair: Here for super method, not used on FTX - :nominal_value: Here for super method, not used on FTX - """ - return 20.0 diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 710260c76..d2cbcd347 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -139,40 +139,6 @@ class Kraken(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - def fill_leverage_brackets(self): - """ - Assigns property _leverage_brackets to a dictionary of information about the leverage - allowed on each pair - """ - leverages = {} - - for pair, market in self.markets.items(): - leverages[pair] = [1] - info = market['info'] - leverage_buy = info.get('leverage_buy', []) - leverage_sell = info.get('leverage_sell', []) - if len(leverage_buy) > 0 or len(leverage_sell) > 0: - if leverage_buy != leverage_sell: - logger.warning( - f"The buy({leverage_buy}) and sell({leverage_sell}) leverage are not equal" - "for {pair}. Please notify freqtrade because this has never happened before" - ) - if max(leverage_buy) <= max(leverage_sell): - leverages[pair] += [int(lev) for lev in leverage_buy] - else: - leverages[pair] += [int(lev) for lev in leverage_sell] - else: - leverages[pair] += [int(lev) for lev in leverage_buy] - self._leverage_brackets = leverages - - def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: - """ - Returns the maximum leverage that a pair can be traded at - :param pair: The base/quote currency pair being traded - :nominal_value: Here for super class, not needed on Kraken - """ - return float(max(self._leverage_brackets[pair])) - def _set_leverage( self, leverage: float, diff --git a/tests/conftest.py b/tests/conftest.py index 1cb4c186e..6d424c246 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -590,10 +590,10 @@ def get_markets(): 'min': 0.0001, 'max': 500000, }, - }, - 'info': { - 'leverage_buy': ['2'], - 'leverage_sell': ['2'], + 'leverage': { + 'min': 1.0, + 'max': 2.0 + } }, }, 'TKN/BTC': { @@ -619,10 +619,10 @@ def get_markets(): 'min': 0.0001, 'max': 500000, }, - }, - 'info': { - 'leverage_buy': ['2', '3', '4', '5'], - 'leverage_sell': ['2', '3', '4', '5'], + 'leverage': { + 'min': 1.0, + 'max': 5.0 + } }, }, 'BLK/BTC': { @@ -647,10 +647,10 @@ def get_markets(): 'min': 0.0001, 'max': 500000, }, - }, - 'info': { - 'leverage_buy': ['2', '3'], - 'leverage_sell': ['2', '3'], + 'leverage': { + 'min': 1.0, + 'max': 3.0 + }, }, }, 'LTC/BTC': { @@ -676,10 +676,7 @@ def get_markets(): 'max': 500000, }, }, - 'info': { - 'leverage_buy': [], - 'leverage_sell': [], - }, + 'info': {}, }, 'XRP/BTC': { 'id': 'xrpbtc', @@ -757,10 +754,7 @@ def get_markets(): 'max': None } }, - 'info': { - 'leverage_buy': [], - 'leverage_sell': [], - }, + 'info': {}, }, 'ETH/USDT': { 'id': 'USDT-ETH', diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 430c648d0..de1328f3e 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3267,3 +3267,16 @@ def test__ccxt_config( default_conf['collateral'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) assert exchange._ccxt_config == ccxt_config + + +@pytest.mark.parametrize('pair,nominal_value,max_lev', [ + ("ETH/BTC", 0.0, 2.0), + ("TKN/BTC", 100.0, 5.0), + ("BLK/BTC", 173.31, 3.0), + ("LTC/BTC", 0.0, 1.0), + ("TKN/USDT", 210.30, 1.0), +]) +def test_get_max_leverage(default_conf, mocker, pair, nominal_value, max_lev): + # Binance has a different method of getting the max leverage + exchange = get_patched_exchange(mocker, default_conf, id="kraken") + assert exchange.get_max_leverage(pair, nominal_value) == max_lev diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index ca6b24d64..97093bdcb 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -250,20 +250,3 @@ def test_get_order_id(mocker, default_conf): } } assert exchange.get_order_id_conditional(order) == '1111' - - -@pytest.mark.parametrize('pair,nominal_value,max_lev', [ - ("ADA/BTC", 0.0, 20.0), - ("BTC/EUR", 100.0, 20.0), - ("ZEC/USD", 173.31, 20.0), -]) -def test_get_max_leverage_ftx(default_conf, mocker, pair, nominal_value, max_lev): - exchange = get_patched_exchange(mocker, default_conf, id="ftx") - assert exchange.get_max_leverage(pair, nominal_value) == max_lev - - -def test_fill_leverage_brackets_ftx(default_conf, mocker): - # FTX only has one account wide leverage, so there's no leverage brackets - exchange = get_patched_exchange(mocker, default_conf, id="ftx") - exchange.fill_leverage_brackets() - assert exchange._leverage_brackets == {} diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 641d2f263..0e7233cb4 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -295,42 +295,3 @@ def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side): # Test with invalid order case ... order['type'] = 'stop_loss_limit' assert not exchange.stoploss_adjust(sl3, order, side=side) - - -@pytest.mark.parametrize('pair,nominal_value,max_lev', [ - ("ADA/BTC", 0.0, 3.0), - ("BTC/EUR", 100.0, 5.0), - ("ZEC/USD", 173.31, 2.0), -]) -def test_get_max_leverage_kraken(default_conf, mocker, pair, nominal_value, max_lev): - exchange = get_patched_exchange(mocker, default_conf, id="kraken") - exchange._leverage_brackets = { - 'ADA/BTC': ['2', '3'], - 'BTC/EUR': ['2', '3', '4', '5'], - 'ZEC/USD': ['2'] - } - assert exchange.get_max_leverage(pair, nominal_value) == max_lev - - -def test_fill_leverage_brackets_kraken(default_conf, mocker): - api_mock = MagicMock() - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") - exchange.fill_leverage_brackets() - - assert exchange._leverage_brackets == { - 'BLK/BTC': [1, 2, 3], - 'TKN/BTC': [1, 2, 3, 4, 5], - 'ETH/BTC': [1, 2], - 'LTC/BTC': [1], - 'XRP/BTC': [1], - 'NEO/BTC': [1], - 'BTT/BTC': [1], - 'ETH/USDT': [1], - 'LTC/USDT': [1], - 'LTC/USD': [1], - 'XLTCUSDT': [1], - 'LTC/ETH': [1], - 'NEO/USDT': [1], - 'TKN/USDT': [1], - 'XRP/USDT': [1] - } From 028e5de9358d01fafbd1ef5d93f324d2434ef49b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Oct 2021 16:50:56 +0200 Subject: [PATCH 05/12] Remove space after @ decorator in tests --- tests/test_freqtradebot.py | 104 ++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 6d784d9d1..3d91d738b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1969,7 +1969,7 @@ def test_handle_trade( assert trade.close_date is not None -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_handle_overlapping_signals( default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short ) -> None: @@ -2045,7 +2045,7 @@ def test_handle_overlapping_signals( assert freqtrade.handle_trade(trades[0]) is True -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short) -> None: @@ -2087,7 +2087,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, caplog) -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_handle_trade_use_sell_signal( default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short ) -> None: @@ -2129,7 +2129,7 @@ def test_handle_trade_use_sell_signal( caplog) -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_close_trade( default_conf_usdt, ticker_usdt, limit_order_open, limit_order, fee, mocker, is_short @@ -2176,7 +2176,7 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog): assert ftbot.strategy.analyze.call_count == 1 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_buy_usercustom( default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, limit_sell_order_old, fee, mocker, is_short @@ -2251,7 +2251,7 @@ def test_check_handle_timedout_buy_usercustom( assert freqtrade.strategy.check_buy_timeout.call_count == 1 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_buy( default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, limit_sell_order_old, fee, mocker, is_short @@ -2292,7 +2292,7 @@ def test_check_handle_timedout_buy( assert freqtrade.strategy.check_buy_timeout.call_count == 0 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_cancelled_buy( default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, limit_sell_order_old, fee, mocker, caplog, is_short @@ -2325,7 +2325,7 @@ def test_check_handle_cancelled_buy( f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog) -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_buy_exception( default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, is_short, fee, mocker @@ -2354,7 +2354,7 @@ def test_check_handle_timedout_buy_exception( assert nb_trades == 1 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_sell_usercustom( default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt @@ -2406,7 +2406,7 @@ def test_check_handle_timedout_sell_usercustom( assert freqtrade.strategy.check_sell_timeout.call_count == 1 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_sell( default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt @@ -2439,7 +2439,7 @@ def test_check_handle_timedout_sell( assert freqtrade.strategy.check_sell_timeout.call_count == 0 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_cancelled_sell( default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt, is_short, mocker, caplog @@ -2471,7 +2471,7 @@ def test_check_handle_cancelled_sell( assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog) -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_partial( default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short, open_trade, mocker @@ -2503,7 +2503,7 @@ def test_check_handle_timedout_partial( assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_partial_fee( default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short, limit_buy_order_old_partial, trades_for_order, @@ -2545,7 +2545,7 @@ def test_check_handle_timedout_partial_fee( assert pytest.approx(trades[0].fee_open) == 0.001 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_check_handle_timedout_partial_except( default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short, limit_buy_order_old_partial, trades_for_order, @@ -2619,7 +2619,7 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr caplog) -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_short) -> None: patch_RPCManager(mocker) @@ -2667,9 +2667,9 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, assert log_has_re(r"Order .* for .* not cancelled.", caplog) -@ pytest.mark.parametrize("is_short", [False, True]) -@ pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], - indirect=['limit_buy_order_canceled_empty']) +@pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], + indirect=['limit_buy_order_canceled_empty']) def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short, limit_buy_order_canceled_empty) -> None: patch_RPCManager(mocker) @@ -2690,8 +2690,8 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho assert nofiy_mock.call_count == 1 -@ pytest.mark.parametrize("is_short", [False, True]) -@ pytest.mark.parametrize('cancelorder', [ +@pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize('cancelorder', [ {}, {'remaining': None}, 'String Return value', @@ -2789,7 +2789,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None: assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order' -@ pytest.mark.parametrize("is_short, open_rate, amt", [ +@pytest.mark.parametrize("is_short, open_rate, amt", [ (False, 2.0, 30.0), (True, 2.02, 29.70297029), ]) @@ -2864,7 +2864,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ } == last_msg -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, ticker_usdt_sell_up, mocker, is_short) -> None: rpc_mock = patch_RPCManager(mocker) @@ -2993,7 +2993,7 @@ def test_execute_trade_exit_custom_exit_price( } == last_msg -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down, ticker_usdt_sell_up, mocker) -> None: @@ -3091,7 +3091,7 @@ def test_execute_trade_exit_sloe_cancel_exception( assert log_has('Could not cancel stoploss order abcd', caplog) -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_execute_trade_exit_with_stoploss_on_exchange( default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None: @@ -3309,7 +3309,7 @@ def test_execute_trade_exit_market_order( } == last_msg -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_up, mocker) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) @@ -3345,7 +3345,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u assert mock_insuf.call_count == 1 -@ pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [ +@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [ # Enable profit (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, False), (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, True), @@ -3439,7 +3439,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_ope assert trade.amount != amnt -@ pytest.mark.parametrize('amount_wallet,has_err', [ +@pytest.mark.parametrize('amount_wallet,has_err', [ (95.29, False), (91.29, True) ]) @@ -3476,7 +3476,7 @@ def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet assert wallet_update.call_count == 1 -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker, caplog, is_short) -> None: patch_RPCManager(mocker) @@ -3515,7 +3515,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog) -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.parametrize("is_short", [False, True]) def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short, fee, mocker) -> None: patch_RPCManager(mocker) @@ -3561,7 +3561,7 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_op assert trade.sell_reason == SellType.ROI.value -@ pytest.mark.parametrize("is_short,val1,val2", [ +@pytest.mark.parametrize("is_short,val1,val2", [ (False, 1.5, 1.1), (True, 0.5, 0.9) ]) @@ -3623,7 +3623,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open, assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value -@ pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [ +@pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [ (0, False, 2.0394, False), (0.011, False, 2.0394, False), (0.055, True, 1.8, False), @@ -3845,7 +3845,7 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock ) -@ pytest.mark.parametrize( +@pytest.mark.parametrize( 'fee_par,fee_reduction_amount,use_ticker_usdt_rate,expected_log', [ # basic, amount does not change ({'cost': 0.008, 'currency': 'ETH'}, 0, False, None), @@ -3898,7 +3898,7 @@ def test_get_real_amount( assert log_has(expected_log, caplog) -@ pytest.mark.parametrize( +@pytest.mark.parametrize( 'fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount', [ # basic, amount is reduced by fee (None, None, 0.001, 0.001, 7.992), @@ -4050,7 +4050,7 @@ def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker): assert freqtrade.get_real_amount(trade, order) == amount -@ pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [ +@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [ (8.0, 0.0, 10, 8), (8.0, 0.0, 0, 8), (8.0, 0.1, 0, 7.9), @@ -4079,11 +4079,11 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker, assert walletmock.call_count == 1 -@ pytest.mark.parametrize("delta, is_high_delta", [ +@pytest.mark.parametrize("delta, is_high_delta", [ (0.1, False), (100, True), ]) -@ pytest.mark.parametrize('is_short, open_rate', [ +@pytest.mark.parametrize('is_short, open_rate', [ (False, 2.0), (True, 2.02), ]) @@ -4129,7 +4129,7 @@ def test_order_book_depth_of_market( assert whitelist == default_conf_usdt['exchange']['pair_whitelist'] -@ pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [ +@pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [ (False, 0.045, 0.046, 2, None), (True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]}) ]) @@ -4182,7 +4182,7 @@ def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False -@ pytest.mark.parametrize('is_short', [False, True]) +@pytest.mark.parametrize('is_short', [False, True]) def test_order_book_ask_strategy( default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short, limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None: @@ -4263,7 +4263,7 @@ def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker): assert reinit_mock.call_count == 0 -@ pytest.mark.usefixtures("init_persistence") +@pytest.mark.usefixtures("init_persistence") def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_buy_order_usdt_open, caplog): default_conf_usdt['dry_run'] = True @@ -4296,8 +4296,8 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_ caplog) -@ pytest.mark.usefixtures("init_persistence") -@ pytest.mark.parametrize("is_short,buy_calls,sell_calls", [ +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short,buy_calls,sell_calls", [ (False, 1, 2), (True, 2, 1), ]) @@ -4325,8 +4325,8 @@ def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, lim assert sell_mock.call_count == sell_calls -@ pytest.mark.usefixtures("init_persistence") -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) @@ -4343,8 +4343,8 @@ def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short): assert 'Handle these trades manually' in freqtrade.rpc.send_msg.call_args[0][0]['status'] -@ pytest.mark.parametrize("is_short", [False, True]) -@ pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.usefixtures("init_persistence") def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) create_mock_trades(fee, is_short=is_short) @@ -4370,8 +4370,8 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s assert len(Order.get_open_orders()) == 2 -@ pytest.mark.usefixtures("init_persistence") -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) @@ -4434,8 +4434,8 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f assert trade.fee_close_currency is not None -@ pytest.mark.usefixtures("init_persistence") -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') @@ -4483,7 +4483,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh r".* for order .*\.", caplog) -@ pytest.mark.usefixtures("init_persistence") +@pytest.mark.usefixtures("init_persistence") def test_handle_insufficient_funds(mocker, default_conf_usdt, fee): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order') @@ -4521,8 +4521,8 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee): assert mock_bof.call_count == 1 -@ pytest.mark.usefixtures("init_persistence") -@ pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short): caplog.set_level(logging.DEBUG) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) From de5497c76660242d41839df07c22b18ed48ba7b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Oct 2021 19:39:37 +0200 Subject: [PATCH 06/12] backtest_days cannot be below 1 --- freqtrade/optimize/optimize_reports.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index a6eedc6c7..abfccaa86 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -353,7 +353,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], results['open_timestamp'] = results['open_date'].view(int64) // 1e6 results['close_timestamp'] = results['close_date'].view(int64) // 1e6 - backtest_days = (max_date - min_date).days + backtest_days = (max_date - min_date).days or 1 strat_stats = { 'trades': results.to_dict(orient='records'), 'locks': [lock.to_json() for lock in content['locks']], @@ -380,7 +380,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'backtest_run_start_ts': content['backtest_start_time'], 'backtest_run_end_ts': content['backtest_end_time'], - 'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0, + 'trades_per_day': round(len(results) / backtest_days, 2), 'market_change': market_change, 'pairlist': list(btdata.keys()), 'stake_amount': config['stake_amount'], From 7197f4ce77d0fe331fbfcdb7edd64b2de16f6c60 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Oct 2021 20:01:31 +0200 Subject: [PATCH 07/12] Don't show daily % profit (it's wrong) --- freqtrade/optimize/optimize_reports.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index abfccaa86..9a4591e67 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -220,15 +220,12 @@ def generate_days_breakdown_stats(trade_list: List, starting_balance: int) -> Li days_stats = [] for name, day in days: profit_abs = day['profit_abs'].sum().round(10) - profit_total = day['profit_abs'].sum() / starting_balance wins = sum(day['profit_abs'] > 0) draws = sum(day['profit_abs'] == 0) loses = sum(day['profit_abs'] < 0) - profit_percentage = round(profit_total * 100.0, 2) days_stats.append( { 'date': name.strftime('%d/%m/%Y'), - 'profit_percentage': profit_percentage, 'profit_abs': profit_abs, 'wins': wins, 'draws': draws, @@ -542,14 +539,13 @@ def text_table_days_breakdown(days_breakdown_stats: List[Dict[str, Any]], """ headers = [ 'Day', - 'Profit %', f'Tot Profit {stake_currency}', 'Wins', 'Draws', 'Losses', ] output = [[ - d['date'], d['profit_percentage'], round_coin_value(d['profit_abs'], stake_currency, False), + d['date'], round_coin_value(d['profit_abs'], stake_currency, False), d['wins'], d['draws'], d['loses'], ] for d in days_breakdown_stats] return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") From fa028c2134440bbf794920d52e7822a128cdccf7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Oct 2021 06:58:40 +0200 Subject: [PATCH 08/12] Support day/week/month breakdowns --- freqtrade/commands/arguments.py | 4 +- freqtrade/commands/cli_options.py | 10 ++--- freqtrade/commands/hyperopt_commands.py | 2 +- freqtrade/configuration/configuration.py | 4 +- freqtrade/constants.py | 5 +++ freqtrade/optimize/optimize_reports.py | 47 +++++++++++++++--------- 6 files changed, 45 insertions(+), 27 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 11c1e9191..53cdda95d 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -23,7 +23,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "enable_protections", "dry_run_wallet", "timeframe_detail", - "strategy_list", "export", "exportfilename", "show_days"] + "strategy_list", "export", "exportfilename", "backtest_breakdown"] ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "position_stacking", "use_max_market_positions", @@ -89,7 +89,7 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperoptexportfilename", "hyperopt_show_no_header", - "disableparamexport", "show_days"] + "disableparamexport", "backtest_breakdown"] NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets", "list-pairs", "list-strategies", "list-data", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 758e1d9ec..2c2c957df 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -193,11 +193,11 @@ AVAILABLE_CLI_OPTIONS = { type=float, metavar='FLOAT', ), - "show_days": Arg( - '--show-days', - help='Print days breakdown for backtest results', - action='store_true', - default=False, + "backtest_breakdown": Arg( + '--breakdown', + help='Show backtesting breakdown per [day, week, month].', + nargs='+', + choices=constants.BACKTEST_BREAKDOWNS ), # Edge "stoploss_range": Arg( diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index d2f8c188c..344828282 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -96,7 +96,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: if 'strategy_name' in metrics: strategy_name = metrics['strategy_name'] show_backtest_result(strategy_name, metrics, - metrics['stake_currency'], config.get('show_days', False)) + metrics['stake_currency'], config.get('backtest_breakdown', [])) HyperoptTools.try_export_params(config, strategy_name, val) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 845e87b83..c6bad9305 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -269,8 +269,8 @@ class Configuration: self._args_to_config(config, argname='export', logstring='Parameter --export detected: {} ...') - self._args_to_config(config, argname='show_days', - logstring='Parameter --show-days detected ...') + self._args_to_config(config, argname='backtest_breakdown', + logstring='Parameter --breakdown detected ...') self._args_to_config(config, argname='disableparamexport', logstring='Parameter --disableparamexport detected: {} ...') diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c6b8f0e62..8bef6610c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -32,6 +32,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] +BACKTEST_BREAKDOWNS = ['day', 'week', 'month'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons @@ -146,6 +147,10 @@ CONF_SCHEMA = { 'sell_profit_offset': {'type': 'number'}, 'ignore_roi_if_buy_signal': {'type': 'boolean'}, 'ignore_buying_expired_candle_after': {'type': 'number'}, + 'backtest_breakdown': { + 'type': 'array', + 'items': {'type': 'string', 'enum': BACKTEST_BREAKDOWNS} + }, 'bot_name': {'type': 'string'}, 'unfilledtimeout': { 'type': 'object', diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 9a4591e67..a97f85637 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -213,17 +213,28 @@ def generate_edge_table(results: dict) -> str: floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore -def generate_days_breakdown_stats(trade_list: List, starting_balance: int) -> List[Dict[str, Any]]: +def _get_resample_from_period(period: str) -> str: + if period == 'day': + return '1d' + if period == 'week': + return '1w' + if period == 'month': + return '1m' + raise ValueError(f"Period {period} is not supported.") + + +def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dict[str, Any]]: results = DataFrame.from_records(trade_list) results['close_date'] = to_datetime(results['close_date'], utc=True) - days = results.resample('1d', on='close_date') - days_stats = [] - for name, day in days: + resample = _get_resample_from_period(period) + period = results.resample(resample, on='close_date') + stats = [] + for name, day in period: profit_abs = day['profit_abs'].sum().round(10) wins = sum(day['profit_abs'] > 0) draws = sum(day['profit_abs'] == 0) loses = sum(day['profit_abs'] < 0) - days_stats.append( + stats.append( { 'date': name.strftime('%d/%m/%Y'), 'profit_abs': profit_abs, @@ -232,7 +243,7 @@ def generate_days_breakdown_stats(trade_list: List, starting_balance: int) -> Li 'loses': loses } ) - return days_stats + return stats def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: @@ -529,8 +540,8 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") -def text_table_days_breakdown(days_breakdown_stats: List[Dict[str, Any]], - stake_currency: str) -> str: +def text_table_periodic_breakdown(days_breakdown_stats: List[Dict[str, Any]], + stake_currency: str, period: str) -> str: """ Generate small table with Backtest results by days :param days_breakdown_stats: Days breakdown metrics @@ -538,7 +549,7 @@ def text_table_days_breakdown(days_breakdown_stats: List[Dict[str, Any]], :return: pretty printed table with tabulate as string """ headers = [ - 'Day', + period.capitalize(), f'Tot Profit {stake_currency}', 'Wins', 'Draws', @@ -663,7 +674,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str, - show_days=False): + backtest_breakdown=[]): """ Print results for one strategy """ @@ -685,13 +696,13 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(table) - if show_days: - days_breakdown_stats = generate_days_breakdown_stats( - trade_list=results['trades'], starting_balance=results['starting_balance']) - table = text_table_days_breakdown(days_breakdown_stats=days_breakdown_stats, - stake_currency=stake_currency) + for period in backtest_breakdown: + days_breakdown_stats = generate_periodic_breakdown_stats( + trade_list=results['trades'], period=period) + table = 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(' DAYS BREAKDOWN '.center(len(table.splitlines()[0]), '=')) + print(f' {period.upper()} BREAKDOWN '.center(len(table.splitlines()[0]), '=')) print(table) table = text_table_add_metrics(results) @@ -708,7 +719,9 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): stake_currency = config['stake_currency'] for strategy, results in backtest_stats['strategy'].items(): - show_backtest_result(strategy, results, stake_currency, config.get('show_days', False)) + show_backtest_result( + strategy, results, stake_currency, + config.get('backtest_breakdown', [])) if len(backtest_stats['strategy']) > 1: # Print Strategy summary table From 7b5346b984508fb48f646a3f36e8aea566d51f0c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Oct 2021 07:09:17 +0200 Subject: [PATCH 09/12] Add test for breakdown-stats --- freqtrade/optimize/optimize_reports.py | 4 ++- tests/optimize/test_backtesting.py | 2 ++ tests/optimize/test_optimize_reports.py | 34 ++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index a97f85637..a2590c10b 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -219,12 +219,14 @@ def _get_resample_from_period(period: str) -> str: if period == 'week': return '1w' if period == 'month': - return '1m' + return '1M' raise ValueError(f"Period {period} is not supported.") def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dict[str, Any]]: results = DataFrame.from_records(trade_list) + if len(results) == 0: + return [] results['close_date'] = to_datetime(results['close_date'], utc=True) resample = _get_resample_from_period(period) period = results.resample(resample, on='close_date') diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 2248cd4c1..b5fa44d01 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1102,6 +1102,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat '--timerange', '1510694220-1510700340', '--enable-position-stacking', '--disable-max-market-positions', + '--breakdown', 'day', '--strategy-list', 'StrategyTestV2', 'TestStrategyLegacyV1', @@ -1130,6 +1131,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat captured = capsys.readouterr() assert 'BACKTESTING REPORT' in captured.out assert 'SELL REASON STATS' in captured.out + assert 'DAY BREAKDOWN' in captured.out assert 'LEFT OPEN TRADES REPORT' in captured.out assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out assert 'STRATEGY SUMMARY' in captured.out diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 83caefd2d..4bf20e547 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -13,9 +13,9 @@ from freqtrade.data import history from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data from freqtrade.edge import PairInfo from freqtrade.enums import SellType -from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats, - generate_edge_table, generate_pair_metrics, - generate_sell_reason_stats, +from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats, + generate_daily_stats, generate_edge_table, + generate_pair_metrics, generate_periodic_breakdown_stats, generate_sell_reason_stats, generate_strategy_comparison, generate_trading_stats, store_backtest_stats, text_table_bt_results, text_table_sell_reason, @@ -377,3 +377,31 @@ def test_generate_edge_table(): assert generate_edge_table(results).count('| ETH/BTC |') == 1 assert generate_edge_table(results).count( '| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1 + + +def test_generate_periodic_breakdown_stats(testdatadir): + filename = testdatadir / "backtest-result_new.json" + bt_data = load_backtest_data(filename).to_dict(orient='records') + + res = generate_periodic_breakdown_stats(bt_data, 'day') + assert isinstance(res, list) + assert len(res) == 21 + day = res[0] + assert 'date' in day + assert 'draws' in day + assert 'loses' in day + assert 'wins' in day + assert 'profit_abs' in day + + # Select empty dataframe! + res = generate_periodic_breakdown_stats([], 'day') + assert res == [] + + +def test__get_resample_from_period(): + + assert _get_resample_from_period('day') == '1d' + assert _get_resample_from_period('week') == '1w' + assert _get_resample_from_period('month') == '1M' + with pytest.raises(ValueError, match=r"Period noooo is not supported."): + _get_resample_from_period('noooo') From e458c9867a54991ac5067f517fe2d088187e7674 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Oct 2021 07:42:19 +0200 Subject: [PATCH 10/12] Styling fixes --- freqtrade/commands/arguments.py | 3 ++- freqtrade/optimize/optimize_reports.py | 6 +++--- tests/optimize/test_optimize_reports.py | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 53cdda95d..87ef49a9b 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -23,7 +23,8 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "enable_protections", "dry_run_wallet", "timeframe_detail", - "strategy_list", "export", "exportfilename", "backtest_breakdown"] + "strategy_list", "export", "exportfilename", + "backtest_breakdown"] ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "position_stacking", "use_max_market_positions", diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index a2590c10b..96549316d 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -228,10 +228,10 @@ def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dic if len(results) == 0: return [] results['close_date'] = to_datetime(results['close_date'], utc=True) - resample = _get_resample_from_period(period) - period = results.resample(resample, on='close_date') + resample_period = _get_resample_from_period(period) + resampled = results.resample(resample_period, on='close_date') stats = [] - for name, day in period: + for name, day in resampled: profit_abs = day['profit_abs'].sum().round(10) wins = sum(day['profit_abs'] > 0) draws = sum(day['profit_abs'] == 0) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 4bf20e547..b5eb09923 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -15,7 +15,9 @@ from freqtrade.edge import PairInfo from freqtrade.enums import SellType from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats, generate_daily_stats, generate_edge_table, - generate_pair_metrics, generate_periodic_breakdown_stats, generate_sell_reason_stats, + generate_pair_metrics, + generate_periodic_breakdown_stats, + generate_sell_reason_stats, generate_strategy_comparison, generate_trading_stats, store_backtest_stats, text_table_bt_results, text_table_sell_reason, From 053fb076e42124285ee9cedf5848a0da2f1b5f75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Oct 2021 10:56:18 +0200 Subject: [PATCH 11/12] Add documentation for breakdown command --- docs/backtesting.md | 34 ++++++++++++++++++++++++++++++---- docs/utils.md | 3 +++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 4a9532894..37724b02a 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -21,6 +21,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--timeframe-detail TIMEFRAME_DETAIL] [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] [--export {none,trades}] [--export-filename PATH] + [--breakdown {day,week,month} [{day,week,month} ...]] optional arguments: -h, --help show this help message and exit @@ -30,7 +31,7 @@ optional arguments: Specify what timerange of data to use. --data-format-ohlcv {json,jsongz,hdf5} Storage format for downloaded candle (OHLCV) data. - (default: `None`). + (default: `json`). --max-open-trades INT Override the value of the `max_open_trades` configuration setting. @@ -65,8 +66,7 @@ optional arguments: set either in config or via command line. When using this together with `--export trades`, the strategy- name is injected into the filename (so `backtest- - data.json` becomes `backtest-data- - SampleStrategy.json` + data.json` becomes `backtest-data-SampleStrategy.json` --export {none,trades} Export backtest results (default: trades). --export-filename PATH @@ -74,7 +74,8 @@ optional arguments: Requires `--export` to be set as well. Example: `--export-filename=user_data/backtest_results/backtest _today.json` - --show-days Print a days breakdown table of the backtest results + --breakdown {day,week,month} [{day,week,month} ...] + Show backtesting breakdown per [day, week, month]. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -430,6 +431,31 @@ It contains some useful key metrics about performance of your strategy on backte - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. +### Daily / Weekly / Monthly breakdown + +You can get an overview over daily / weekly or monthly results by using the `--breakdown <>` switch. + +To visualize daily and weekly breakdowns, you can use the following: + +``` bash +freqtrade backtesting --strategy MyAwesomeStrategy --breakdown day month +``` + +``` output +======================== DAY BREAKDOWN ========================= +| Day | Tot Profit USDT | Wins | Draws | Losses | +|------------+-------------------+--------+---------+----------| +| 03/07/2021 | 200.0 | 2 | 0 | 0 | +| 04/07/2021 | -50.31 | 0 | 0 | 2 | +| 05/07/2021 | 220.611 | 3 | 2 | 0 | +| 06/07/2021 | 150.974 | 3 | 0 | 2 | +| 07/07/2021 | -70.193 | 1 | 0 | 2 | +| 08/07/2021 | 212.413 | 2 | 0 | 3 | + +``` + +The output will show a table containing the realized absolute Profit (in stake currency) for the given timeperiod, as well as wins, draws and losses that materialized (closed) on this day. + ### Further backtest-result analysis To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file). diff --git a/docs/utils.md b/docs/utils.md index d8fbcacb7..4845828ab 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -667,6 +667,7 @@ usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--profitable] [-n INT] [--print-json] [--hyperopt-filename FILENAME] [--no-header] [--disable-param-export] + [--breakdown {day,week,month} [{day,week,month} ...]] optional arguments: -h, --help show this help message and exit @@ -680,6 +681,8 @@ optional arguments: --no-header Do not print epoch details header. --disable-param-export Disable automatic hyperopt parameter export. + --breakdown {day,week,month} [{day,week,month} ...] + Show backtesting breakdown per [day, week, month]. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). From f07555fc84ce4dbbcb88858591d2ecbe1f569c91 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 22 Oct 2021 06:37:56 -0600 Subject: [PATCH 12/12] removed binance constructor, added fill_leverage_brackets call to exchange constructor --- freqtrade/exchange/binance.py | 8 +------- freqtrade/exchange/exchange.py | 6 +++++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 231dc1a95..d23f84e7b 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -2,7 +2,7 @@ import json import logging from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple import arrow import ccxt @@ -38,12 +38,6 @@ class Binance(Exchange): # (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported ] - def __init__(self, config: Dict[str, Any], validate: bool = True) -> None: - super().__init__(config, validate) - self._leverage_brackets: Dict = {} - if self.trading_mode != TradingMode.SPOT: - self.fill_leverage_brackets() - @property def _ccxt_config(self) -> Dict: # Parameters to add directly to ccxt sync/async initialization. diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ad74fa0c1..de9711ddd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -179,6 +179,10 @@ class Exchange: self.markets_refresh_interval: int = exchange_config.get( "markets_refresh_interval", 60) * 60 + self._leverage_brackets: Dict = {} + if self.trading_mode != TradingMode.SPOT: + self.fill_leverage_brackets() + def __del__(self): """ Destructor - clean up async stuff @@ -1635,7 +1639,7 @@ class Exchange: """ Assigns property _leverage_brackets to a dictionary of information about the leverage allowed on each pair - Not used by most exchanges, only used by Binance at time of writing + Not used if the exchange has a static max leverage value for the account or each pair """ return