Merge branch 'develop' into feat/short
This commit is contained in:
		| @@ -1,4 +1,4 @@ | |||||||
| FROM python:3.9.6-slim-buster as base | FROM python:3.9.7-slim-buster as base | ||||||
|  |  | ||||||
| # Setup env | # Setup env | ||||||
| ENV LANG C.UTF-8 | ENV LANG C.UTF-8 | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_I | |||||||
| docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM | docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM | ||||||
|  |  | ||||||
| # Run backtest | # Run backtest | ||||||
| docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy | docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 | ||||||
|  |  | ||||||
| if [ $? -ne 0 ]; then | if [ $? -ne 0 ]; then | ||||||
|     echo "failed running backtest" |     echo "failed running backtest" | ||||||
|   | |||||||
| @@ -53,7 +53,7 @@ docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE | |||||||
| docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT | docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT | ||||||
|  |  | ||||||
| # Run backtest | # Run backtest | ||||||
| docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy | docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 | ||||||
|  |  | ||||||
| if [ $? -ne 0 ]; then | if [ $? -ne 0 ]; then | ||||||
|     echo "failed running backtest" |     echo "failed running backtest" | ||||||
|   | |||||||
| @@ -174,7 +174,7 @@ | |||||||
|         "heartbeat_interval": 60 |         "heartbeat_interval": 60 | ||||||
|     }, |     }, | ||||||
|     "disable_dataframe_checks": false, |     "disable_dataframe_checks": false, | ||||||
|     "strategy": "DefaultStrategy", |     "strategy": "SampleStrategy", | ||||||
|     "strategy_path": "user_data/strategies/", |     "strategy_path": "user_data/strategies/", | ||||||
|     "dataformat_ohlcv": "json", |     "dataformat_ohlcv": "json", | ||||||
|     "dataformat_trades": "jsongz" |     "dataformat_trades": "jsongz" | ||||||
|   | |||||||
| @@ -335,7 +335,7 @@ Once the optimized parameters and conditions have been implemented into your str | |||||||
|  |  | ||||||
| To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting. | To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting. | ||||||
|  |  | ||||||
| Should results don't match, please double-check to make sure you transferred all conditions correctly. | Should results not match, please double-check to make sure you transferred all conditions correctly. | ||||||
| Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy. | Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy. | ||||||
| You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`). | You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`). | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] | |||||||
|                              [-p PAIRS [PAIRS ...]] [--eps] [--dmmp] |                              [-p PAIRS [PAIRS ...]] [--eps] [--dmmp] | ||||||
|                              [--enable-protections] |                              [--enable-protections] | ||||||
|                              [--dry-run-wallet DRY_RUN_WALLET] |                              [--dry-run-wallet DRY_RUN_WALLET] | ||||||
|  |                              [--timeframe-detail TIMEFRAME_DETAIL] | ||||||
|                              [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] |                              [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] | ||||||
|                              [--export {none,trades}] [--export-filename PATH] |                              [--export {none,trades}] [--export-filename PATH] | ||||||
|  |  | ||||||
| @@ -55,6 +56,9 @@ optional arguments: | |||||||
|   --dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET |   --dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET | ||||||
|                         Starting balance, used for backtesting / hyperopt and |                         Starting balance, used for backtesting / hyperopt and | ||||||
|                         dry-runs. |                         dry-runs. | ||||||
|  |   --timeframe-detail TIMEFRAME_DETAIL | ||||||
|  |                         Specify detail timeframe for backtesting (`1m`, `5m`, | ||||||
|  |                         `30m`, `1h`, `1d`). | ||||||
|   --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] |   --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] | ||||||
|                         Provide a space-separated list of strategies to |                         Provide a space-separated list of strategies to | ||||||
|                         backtest. Please note that ticker-interval needs to be |                         backtest. Please note that ticker-interval needs to be | ||||||
| @@ -62,7 +66,7 @@ optional arguments: | |||||||
|                         this together with `--export trades`, the strategy- |                         this together with `--export trades`, the strategy- | ||||||
|                         name is injected into the filename (so `backtest- |                         name is injected into the filename (so `backtest- | ||||||
|                         data.json` becomes `backtest-data- |                         data.json` becomes `backtest-data- | ||||||
|                         DefaultStrategy.json` |                         SampleStrategy.json` | ||||||
|   --export {none,trades} |   --export {none,trades} | ||||||
|                         Export backtest results (default: trades). |                         Export backtest results (default: trades). | ||||||
|   --export-filename PATH |   --export-filename PATH | ||||||
| @@ -425,7 +429,12 @@ 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). | - `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. | - `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. | ||||||
|  |  | ||||||
| ### Assumptions made by backtesting | ### Further backtest-result analysis | ||||||
|  |  | ||||||
|  | To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file). | ||||||
|  | You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) backtesting section. | ||||||
|  |  | ||||||
|  | ## Assumptions made by backtesting | ||||||
|  |  | ||||||
| Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions: | Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions: | ||||||
|  |  | ||||||
| @@ -456,10 +465,30 @@ Also, keep in mind that past results don't guarantee future success. | |||||||
|  |  | ||||||
| In addition to the above assumptions, strategy authors should carefully read the [Common Mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) section, to avoid using data in backtesting which is not available in real market conditions. | In addition to the above assumptions, strategy authors should carefully read the [Common Mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) section, to avoid using data in backtesting which is not available in real market conditions. | ||||||
|  |  | ||||||
| ### Further backtest-result analysis | ### Improved backtest accuracy | ||||||
|  |  | ||||||
| To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file). | One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?). | ||||||
| You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) backtesting section. | So assuming you run backtesting with a 1h timeframe, there will be 4 prices for that candle (Open, High, Low, Close). | ||||||
|  |  | ||||||
|  | While backtesting does take some assumptions (read above) about this - this can never be perfect, and will always be biased in one way or the other. | ||||||
|  | To mitigate this, freqtrade can use a lower (faster) timeframe to simulate intra-candle movements. | ||||||
|  |  | ||||||
|  | To utilize this, you can append `--timeframe-detail 5m` to your regular backtesting command. | ||||||
|  |  | ||||||
|  | ``` bash | ||||||
|  | freqtrade backtesting --strategy AwesomeStrategy --timeframe 1h --timeframe-detail 5m | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | This will load 1h data as well as 5m data for the timeframe. The strategy will be analyzed with the 1h timeframe - and for every "open trade candle" (candles where a trade is open) the 5m data will be used to simulate intra-candle movements. | ||||||
|  | All callback functions (`custom_sell()`, `custom_stoploss()`, ... ) will be running for each 5m candle once the trade is opened (so 12 times in the above example of 1h timeframe, and 5m detailed timeframe). | ||||||
|  |  | ||||||
|  | `--timeframe-detail` must be smaller than the original timeframe, otherwise backtesting will fail to start. | ||||||
|  |  | ||||||
|  | Obviously this will require more memory (5m data is bigger than 1h data), and will also impact runtime (depending on the amount of trades and trade durations). | ||||||
|  | Also, data must be available / downloaded already. | ||||||
|  |  | ||||||
|  | !!! Tip | ||||||
|  |     You can use this function as the last part of strategy development, to ensure your strategy is not exploiting one of the [backtesting assumptions](#assumptions-made-by-backtesting). Strategies that perform similarly well with this mode have a good chance to perform well in dry/live modes too (although only forward-testing (dry-mode) can really confirm a strategy). | ||||||
|  |  | ||||||
| ## Backtesting multiple strategies | ## Backtesting multiple strategies | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ This page provides you some basic concepts on how Freqtrade works and operates. | |||||||
| * **Strategy**: Your trading strategy, telling the bot what to do. | * **Strategy**: Your trading strategy, telling the bot what to do. | ||||||
| * **Trade**: Open position. | * **Trade**: Open position. | ||||||
| * **Open Order**: Order which is currently placed on the exchange, and is not yet complete. | * **Open Order**: Order which is currently placed on the exchange, and is not yet complete. | ||||||
| * **Pair**: Tradable pair, usually in the format of Quote/Base (e.g. XRP/USDT). | * **Pair**: Tradable pair, usually in the format of Base/Quote (e.g. XRP/USDT). | ||||||
| * **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...). | * **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...). | ||||||
| * **Indicators**: Technical indicators (SMA, EMA, RSI, ...). | * **Indicators**: Technical indicators (SMA, EMA, RSI, ...). | ||||||
| * **Limit order**: Limit orders which execute at the defined limit price or better. | * **Limit order**: Limit orders which execute at the defined limit price or better. | ||||||
|   | |||||||
| @@ -456,7 +456,7 @@ class MyAwesomeStrategy(IStrategy): | |||||||
|                 "only_per_pair": False |                 "only_per_pair": False | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|         return protection |         return prot | ||||||
|  |  | ||||||
|     def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
|         # ... |         # ... | ||||||
| @@ -827,8 +827,8 @@ After you run Hyperopt for the desired amount of epochs, you can later list all | |||||||
|  |  | ||||||
| Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected. | Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected. | ||||||
|  |  | ||||||
| To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting. | To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting. | ||||||
|  |  | ||||||
| Should results don't match, please double-check to make sure you transferred all conditions correctly. | Should results not match, please double-check to make sure you transferred all conditions correctly. | ||||||
| Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy. | Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy. | ||||||
| You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`). | You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`). | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| mkdocs==1.2.2 | mkdocs==1.2.2 | ||||||
| mkdocs-material==7.2.4 | mkdocs-material==7.2.5 | ||||||
| mdx_truly_sane_lists==1.2 | mdx_truly_sane_lists==1.2 | ||||||
| pymdown-extensions==8.2 | pymdown-extensions==8.2 | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", | |||||||
|                         "max_open_trades", "stake_amount", "fee", "pairs"] |                         "max_open_trades", "stake_amount", "fee", "pairs"] | ||||||
|  |  | ||||||
| ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", | ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", | ||||||
|                                         "enable_protections", "dry_run_wallet", |                                         "enable_protections", "dry_run_wallet", "timeframe_detail", | ||||||
|                                         "strategy_list", "export", "exportfilename"] |                                         "strategy_list", "export", "exportfilename"] | ||||||
|  |  | ||||||
| ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", | ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", | ||||||
|   | |||||||
| @@ -66,16 +66,22 @@ def ask_user_config() -> Dict[str, Any]: | |||||||
|         { |         { | ||||||
|             "type": "text", |             "type": "text", | ||||||
|             "name": "stake_amount", |             "name": "stake_amount", | ||||||
|             "message": "Please insert your stake amount:", |             "message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):", | ||||||
|             "default": "0.01", |             "default": "0.01", | ||||||
|             "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val), |             "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val), | ||||||
|  |             "filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"' | ||||||
|  |             if val == UNLIMITED_STAKE_AMOUNT | ||||||
|  |             else val | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "type": "text", |             "type": "text", | ||||||
|             "name": "max_open_trades", |             "name": "max_open_trades", | ||||||
|             "message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):", |             "message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):", | ||||||
|             "default": "3", |             "default": "3", | ||||||
|             "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val) |             "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val), | ||||||
|  |             "filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"' | ||||||
|  |             if val == UNLIMITED_STAKE_AMOUNT | ||||||
|  |             else val | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "type": "text", |             "type": "text", | ||||||
|   | |||||||
| @@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = { | |||||||
|         help='Override the value of the `stake_amount` configuration setting.', |         help='Override the value of the `stake_amount` configuration setting.', | ||||||
|     ), |     ), | ||||||
|     # Backtesting |     # Backtesting | ||||||
|  |     "timeframe_detail": Arg( | ||||||
|  |         '--timeframe-detail', | ||||||
|  |         help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).', | ||||||
|  |     ), | ||||||
|     "position_stacking": Arg( |     "position_stacking": Arg( | ||||||
|         '--eps', '--enable-position-stacking', |         '--eps', '--enable-position-stacking', | ||||||
|         help='Allow buying the same pair multiple times (position stacking).', |         help='Allow buying the same pair multiple times (position stacking).', | ||||||
| @@ -162,7 +166,7 @@ AVAILABLE_CLI_OPTIONS = { | |||||||
|         'Please note that ticker-interval needs to be set either in config ' |         'Please note that ticker-interval needs to be set either in config ' | ||||||
|         'or via command line. When using this together with `--export trades`, ' |         'or via command line. When using this together with `--export trades`, ' | ||||||
|         'the strategy-name is injected into the filename ' |         'the strategy-name is injected into the filename ' | ||||||
|         '(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`', |         '(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`', | ||||||
|         nargs='+', |         nargs='+', | ||||||
|     ), |     ), | ||||||
|     "export": Arg( |     "export": Arg( | ||||||
|   | |||||||
| @@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None: | |||||||
|     config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) |     config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) | ||||||
|  |  | ||||||
|     if "strategy" in args and args["strategy"]: |     if "strategy" in args and args["strategy"]: | ||||||
|         if args["strategy"] == "DefaultStrategy": |  | ||||||
|             raise OperationalException("DefaultStrategy is not allowed as name.") |  | ||||||
|  |  | ||||||
|         new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py') |         new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py') | ||||||
|  |  | ||||||
| @@ -128,8 +126,6 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: | |||||||
|     config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) |     config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) | ||||||
|  |  | ||||||
|     if 'hyperopt' in args and args['hyperopt']: |     if 'hyperopt' in args and args['hyperopt']: | ||||||
|         if args['hyperopt'] == 'DefaultHyperopt': |  | ||||||
|             raise OperationalException("DefaultHyperopt is not allowed as name.") |  | ||||||
|  |  | ||||||
|         new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py') |         new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py') | ||||||
|  |  | ||||||
|   | |||||||
| @@ -242,6 +242,9 @@ class Configuration: | |||||||
|             except ValueError: |             except ValueError: | ||||||
|                 pass |                 pass | ||||||
|  |  | ||||||
|  |         self._args_to_config(config, argname='timeframe_detail', | ||||||
|  |                              logstring='Parameter --timeframe-detail detected, ' | ||||||
|  |                              'using {} for intra-candle backtesting ...') | ||||||
|         self._args_to_config(config, argname='stake_amount', |         self._args_to_config(config, argname='stake_amount', | ||||||
|                              logstring='Parameter --stake-amount detected, ' |                              logstring='Parameter --stake-amount detected, ' | ||||||
|                              'overriding stake_amount to: {} ...') |                              'overriding stake_amount to: {} ...') | ||||||
|   | |||||||
| @@ -49,6 +49,8 @@ USERPATH_NOTEBOOKS = 'notebooks' | |||||||
| TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent'] | TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent'] | ||||||
| ENV_VAR_PREFIX = 'FREQTRADE__' | ENV_VAR_PREFIX = 'FREQTRADE__' | ||||||
|  |  | ||||||
|  | NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired') | ||||||
|  |  | ||||||
|  |  | ||||||
| # Define decimals per coin for outputs | # Define decimals per coin for outputs | ||||||
| # Only used for outputs. | # Only used for outputs. | ||||||
|   | |||||||
| @@ -19,7 +19,8 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU | |||||||
|                                             decimal_to_precision) |                                             decimal_to_precision) | ||||||
| from pandas import DataFrame | from pandas import DataFrame | ||||||
|  |  | ||||||
| from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes | from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, | ||||||
|  |                                  ListPairsWithTimeframes) | ||||||
| from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list | from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list | ||||||
| from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, | from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, | ||||||
|                                   InvalidOrderException, OperationalException, PricingError, |                                   InvalidOrderException, OperationalException, PricingError, | ||||||
| @@ -351,9 +352,16 @@ class Exchange: | |||||||
|     def validate_stakecurrency(self, stake_currency: str) -> None: |     def validate_stakecurrency(self, stake_currency: str) -> None: | ||||||
|         """ |         """ | ||||||
|         Checks stake-currency against available currencies on the exchange. |         Checks stake-currency against available currencies on the exchange. | ||||||
|  |         Only runs on startup. If markets have not been loaded, there's been a problem with | ||||||
|  |         the connection to the exchange. | ||||||
|         :param stake_currency: Stake-currency to validate |         :param stake_currency: Stake-currency to validate | ||||||
|         :raise: OperationalException if stake-currency is not available. |         :raise: OperationalException if stake-currency is not available. | ||||||
|         """ |         """ | ||||||
|  |         if not self._markets: | ||||||
|  |             raise OperationalException( | ||||||
|  |                 'Could not load markets, therefore cannot start. ' | ||||||
|  |                 'Please investigate the above error for more details.' | ||||||
|  |                 ) | ||||||
|         quote_currencies = self.get_quote_currencies() |         quote_currencies = self.get_quote_currencies() | ||||||
|         if stake_currency not in quote_currencies: |         if stake_currency not in quote_currencies: | ||||||
|             raise OperationalException( |             raise OperationalException( | ||||||
| @@ -804,7 +812,7 @@ class Exchange: | |||||||
|         :param order: Order dict as returned from fetch_order() |         :param order: Order dict as returned from fetch_order() | ||||||
|         :return: True if order has been cancelled without being filled, False otherwise. |         :return: True if order has been cancelled without being filled, False otherwise. | ||||||
|         """ |         """ | ||||||
|         return (order.get('status') in ('closed', 'canceled', 'cancelled') |         return (order.get('status') in NON_OPEN_EXCHANGE_STATES | ||||||
|                 and order.get('filled') == 0.0) |                 and order.get('filled') == 0.0) | ||||||
|  |  | ||||||
|     @retrier |     @retrier | ||||||
|   | |||||||
| @@ -433,11 +433,11 @@ class FreqtradeBot(LoggingMixin): | |||||||
|             if ((bid_check_dom.get('enabled', False)) and |             if ((bid_check_dom.get('enabled', False)) and | ||||||
|                     (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): |                     (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): | ||||||
|                 if self._check_depth_of_market_buy(pair, bid_check_dom): |                 if self._check_depth_of_market_buy(pair, bid_check_dom): | ||||||
|                     return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) |                     return self.execute_entry(pair, stake_amount, buy_tag=buy_tag) | ||||||
|                 else: |                 else: | ||||||
|                     return False |                     return False | ||||||
|  |  | ||||||
|             return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) |             return self.execute_entry(pair, stake_amount, buy_tag=buy_tag) | ||||||
|         else: |         else: | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
| @@ -465,8 +465,8 @@ class FreqtradeBot(LoggingMixin): | |||||||
|             logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") |             logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|     def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, |     def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, | ||||||
|                     forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: |                       forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: | ||||||
|         """ |         """ | ||||||
|         Executes a limit buy for the given pair |         Executes a limit buy for the given pair | ||||||
|         :param pair: pair for which we want to create a LIMIT_BUY |         :param pair: pair for which we want to create a LIMIT_BUY | ||||||
| @@ -745,7 +745,7 @@ class FreqtradeBot(LoggingMixin): | |||||||
|             trade.stoploss_order_id = None |             trade.stoploss_order_id = None | ||||||
|             logger.error(f'Unable to place a stoploss order on exchange. {e}') |             logger.error(f'Unable to place a stoploss order on exchange. {e}') | ||||||
|             logger.warning('Selling the trade forcefully') |             logger.warning('Selling the trade forcefully') | ||||||
|             self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple( |             self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple( | ||||||
|                 sell_type=SellType.EMERGENCY_SELL)) |                 sell_type=SellType.EMERGENCY_SELL)) | ||||||
|  |  | ||||||
|         except ExchangeError: |         except ExchangeError: | ||||||
| @@ -863,7 +863,7 @@ class FreqtradeBot(LoggingMixin): | |||||||
|  |  | ||||||
|         if should_sell.sell_flag: |         if should_sell.sell_flag: | ||||||
|             logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') |             logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') | ||||||
|             self.execute_sell(trade, sell_rate, should_sell) |             self.execute_trade_exit(trade, sell_rate, should_sell) | ||||||
|             return True |             return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
| @@ -945,7 +945,7 @@ class FreqtradeBot(LoggingMixin): | |||||||
|         was_trade_fully_canceled = False |         was_trade_fully_canceled = False | ||||||
|  |  | ||||||
|         # Cancelled orders may have the status of 'canceled' or 'closed' |         # Cancelled orders may have the status of 'canceled' or 'closed' | ||||||
|         if order['status'] not in ('cancelled', 'canceled', 'closed'): |         if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES: | ||||||
|             filled_val = order.get('filled', 0.0) or 0.0 |             filled_val = order.get('filled', 0.0) or 0.0 | ||||||
|             filled_stake = filled_val * trade.open_rate |             filled_stake = filled_val * trade.open_rate | ||||||
|             minstake = self.exchange.get_min_pair_stake_amount( |             minstake = self.exchange.get_min_pair_stake_amount( | ||||||
| @@ -961,7 +961,7 @@ class FreqtradeBot(LoggingMixin): | |||||||
|             # Avoid race condition where the order could not be cancelled coz its already filled. |             # Avoid race condition where the order could not be cancelled coz its already filled. | ||||||
|             # Simply bailing here is the only safe way - as this order will then be |             # Simply bailing here is the only safe way - as this order will then be | ||||||
|             # handled in the next iteration. |             # handled in the next iteration. | ||||||
|             if corder.get('status') not in ('cancelled', 'canceled', 'closed'): |             if corder.get('status') not in constants.NON_OPEN_EXCHANGE_STATES: | ||||||
|                 logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.") |                 logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.") | ||||||
|                 return False |                 return False | ||||||
|         else: |         else: | ||||||
| @@ -1064,9 +1064,9 @@ class FreqtradeBot(LoggingMixin): | |||||||
|             raise DependencyException( |             raise DependencyException( | ||||||
|                 f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") |                 f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") | ||||||
|  |  | ||||||
|     def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: |     def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: | ||||||
|         """ |         """ | ||||||
|         Executes a limit sell for the given trade and limit |         Executes a trade exit for the given trade and limit | ||||||
|         :param trade: Trade instance |         :param trade: Trade instance | ||||||
|         :param limit: limit rate for the sell order |         :param limit: limit rate for the sell order | ||||||
|         :param sell_reason: Reason the sell was triggered |         :param sell_reason: Reason the sell was triggered | ||||||
| @@ -1142,7 +1142,7 @@ class FreqtradeBot(LoggingMixin): | |||||||
|         trade.close_rate_requested = limit |         trade.close_rate_requested = limit | ||||||
|         trade.sell_reason = sell_reason.sell_reason |         trade.sell_reason = sell_reason.sell_reason | ||||||
|         # In case of market sell orders the order can be closed immediately |         # In case of market sell orders the order can be closed immediately | ||||||
|         if order.get('status', 'unknown') == 'closed': |         if order.get('status', 'unknown') in ('closed', 'expired'): | ||||||
|             self.update_trade_state(trade, trade.open_order_id, order) |             self.update_trade_state(trade, trade.open_order_id, order) | ||||||
|         Trade.commit() |         Trade.commit() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -86,6 +86,17 @@ class Backtesting: | |||||||
|                                        "configuration or as cli argument `--timeframe 5m`") |                                        "configuration or as cli argument `--timeframe 5m`") | ||||||
|         self.timeframe = str(self.config.get('timeframe')) |         self.timeframe = str(self.config.get('timeframe')) | ||||||
|         self.timeframe_min = timeframe_to_minutes(self.timeframe) |         self.timeframe_min = timeframe_to_minutes(self.timeframe) | ||||||
|  |         # Load detail timeframe if specified | ||||||
|  |         self.timeframe_detail = str(self.config.get('timeframe_detail', '')) | ||||||
|  |         if self.timeframe_detail: | ||||||
|  |             self.timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail) | ||||||
|  |             if self.timeframe_min <= self.timeframe_detail_min: | ||||||
|  |                 raise OperationalException( | ||||||
|  |                     "Detail timeframe must be smaller than strategy timeframe.") | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             self.timeframe_detail_min = 0 | ||||||
|  |         self.detail_data: Dict[str, DataFrame] = {} | ||||||
|  |  | ||||||
|         self.pairlists = PairListManager(self.exchange, self.config) |         self.pairlists = PairListManager(self.exchange, self.config) | ||||||
|         if 'VolumePairList' in self.pairlists.name_list: |         if 'VolumePairList' in self.pairlists.name_list: | ||||||
| @@ -188,6 +199,23 @@ class Backtesting: | |||||||
|         self.progress.set_new_value(1) |         self.progress.set_new_value(1) | ||||||
|         return data, self.timerange |         return data, self.timerange | ||||||
|  |  | ||||||
|  |     def load_bt_data_detail(self) -> None: | ||||||
|  |         """ | ||||||
|  |         Loads backtest detail data (smaller timeframe) if necessary. | ||||||
|  |         """ | ||||||
|  |         if self.timeframe_detail: | ||||||
|  |             self.detail_data = history.load_data( | ||||||
|  |                 datadir=self.config['datadir'], | ||||||
|  |                 pairs=self.pairlists.whitelist, | ||||||
|  |                 timeframe=self.timeframe_detail, | ||||||
|  |                 timerange=self.timerange, | ||||||
|  |                 startup_candles=0, | ||||||
|  |                 fail_without_data=True, | ||||||
|  |                 data_format=self.config.get('dataformat_ohlcv', 'json'), | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             self.detail_data = {} | ||||||
|  |  | ||||||
|     def prepare_backtest(self, enable_protections): |     def prepare_backtest(self, enable_protections): | ||||||
|         """ |         """ | ||||||
|         Backtesting setup method - called once for every call to "backtest()". |         Backtesting setup method - called once for every call to "backtest()". | ||||||
| @@ -320,7 +348,8 @@ class Backtesting: | |||||||
|         else: |         else: | ||||||
|             return sell_row[OPEN_IDX] |             return sell_row[OPEN_IDX] | ||||||
|  |  | ||||||
|     def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: |     def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, | ||||||
|  |                                          sell_row: Tuple) -> Optional[LocalTrade]: | ||||||
|         sell_candle_time = sell_row[DATE_IDX].to_pydatetime() |         sell_candle_time = sell_row[DATE_IDX].to_pydatetime() | ||||||
|         sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX],  # type: ignore |         sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX],  # type: ignore | ||||||
|                                          sell_candle_time, sell_row[BUY_IDX], |                                          sell_candle_time, sell_row[BUY_IDX], | ||||||
| @@ -348,6 +377,32 @@ class Backtesting: | |||||||
|  |  | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |     def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: | ||||||
|  |         if self.timeframe_detail and trade.pair in self.detail_data: | ||||||
|  |             sell_candle_time = sell_row[DATE_IDX].to_pydatetime() | ||||||
|  |             sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min) | ||||||
|  |  | ||||||
|  |             detail_data = self.detail_data[trade.pair] | ||||||
|  |             detail_data = detail_data.loc[ | ||||||
|  |                 (detail_data['date'] >= sell_candle_time) & | ||||||
|  |                 (detail_data['date'] < sell_candle_end) | ||||||
|  |              ] | ||||||
|  |             if len(detail_data) == 0: | ||||||
|  |                 # Fall back to "regular" data if no detail data was found for this candle | ||||||
|  |                 return self._get_sell_trade_entry_for_candle(trade, sell_row) | ||||||
|  |             detail_data['buy'] = sell_row[BUY_IDX] | ||||||
|  |             detail_data['sell'] = sell_row[SELL_IDX] | ||||||
|  |             headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] | ||||||
|  |             for det_row in detail_data[headers].values.tolist(): | ||||||
|  |                 res = self._get_sell_trade_entry_for_candle(trade, det_row) | ||||||
|  |                 if res: | ||||||
|  |                     return res | ||||||
|  |  | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             return self._get_sell_trade_entry_for_candle(trade, sell_row) | ||||||
|  |  | ||||||
|     def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]: |     def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]: | ||||||
|         try: |         try: | ||||||
|             stake_amount = self.wallets.get_trade_stake_amount(pair, None) |             stake_amount = self.wallets.get_trade_stake_amount(pair, None) | ||||||
| @@ -594,6 +649,7 @@ class Backtesting: | |||||||
|         data: Dict[str, Any] = {} |         data: Dict[str, Any] = {} | ||||||
|  |  | ||||||
|         data, timerange = self.load_bt_data() |         data, timerange = self.load_bt_data() | ||||||
|  |         self.load_bt_data_detail() | ||||||
|         logger.info("Dataload complete. Calculating indicators") |         logger.info("Dataload complete. Calculating indicators") | ||||||
|  |  | ||||||
|         for strat in self.strategylist: |         for strat in self.strategylist: | ||||||
|   | |||||||
| @@ -368,6 +368,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], | |||||||
|         'max_open_trades_setting': (config['max_open_trades'] |         'max_open_trades_setting': (config['max_open_trades'] | ||||||
|                                     if config['max_open_trades'] != float('inf') else -1), |                                     if config['max_open_trades'] != float('inf') else -1), | ||||||
|         'timeframe': config['timeframe'], |         'timeframe': config['timeframe'], | ||||||
|  |         'timeframe_detail': config.get('timeframe_detail', ''), | ||||||
|         'timerange': config.get('timerange', ''), |         'timerange': config.get('timerange', ''), | ||||||
|         'enable_protections': config.get('enable_protections', False), |         'enable_protections': config.get('enable_protections', False), | ||||||
|         'strategy_name': strategy, |         'strategy_name': strategy, | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session | |||||||
| from sqlalchemy.pool import StaticPool | from sqlalchemy.pool import StaticPool | ||||||
| from sqlalchemy.sql.schema import UniqueConstraint | from sqlalchemy.sql.schema import UniqueConstraint | ||||||
|  |  | ||||||
| from freqtrade.constants import DATETIME_PRINT_FORMAT | from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES | ||||||
| from freqtrade.enums import SellType | from freqtrade.enums import SellType | ||||||
| from freqtrade.exceptions import DependencyException, OperationalException | from freqtrade.exceptions import DependencyException, OperationalException | ||||||
| from freqtrade.leverage import interest | from freqtrade.leverage import interest | ||||||
| @@ -164,7 +164,7 @@ class Order(_DECL_BASE): | |||||||
|             self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc) |             self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc) | ||||||
|  |  | ||||||
|         self.ft_is_open = True |         self.ft_is_open = True | ||||||
|         if self.status in ('closed', 'canceled', 'cancelled'): |         if self.status in NON_OPEN_EXCHANGE_STATES: | ||||||
|             self.ft_is_open = False |             self.ft_is_open = False | ||||||
|             if (order.get('filled', 0.0) or 0.0) > 0: |             if (order.get('filled', 0.0) or 0.0) > 0: | ||||||
|                 self.order_filled_date = datetime.now(timezone.utc) |                 self.order_filled_date = datetime.now(timezone.utc) | ||||||
|   | |||||||
| @@ -46,11 +46,14 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac | |||||||
|             if ( |             if ( | ||||||
|                 not ApiServer._bt |                 not ApiServer._bt | ||||||
|                 or lastconfig.get('timeframe') != strat.timeframe |                 or lastconfig.get('timeframe') != strat.timeframe | ||||||
|  |                 or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail') | ||||||
|                 or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0) |                 or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0) | ||||||
|                 or lastconfig.get('timerange') != btconfig['timerange'] |                 or lastconfig.get('timerange') != btconfig['timerange'] | ||||||
|             ): |             ): | ||||||
|                 from freqtrade.optimize.backtesting import Backtesting |                 from freqtrade.optimize.backtesting import Backtesting | ||||||
|                 ApiServer._bt = Backtesting(btconfig) |                 ApiServer._bt = Backtesting(btconfig) | ||||||
|  |                 if ApiServer._bt.timeframe_detail: | ||||||
|  |                     ApiServer._bt.load_bt_data_detail() | ||||||
|  |  | ||||||
|             # Only reload data if timeframe changed. |             # Only reload data if timeframe changed. | ||||||
|             if ( |             if ( | ||||||
|   | |||||||
| @@ -324,6 +324,7 @@ class PairHistory(BaseModel): | |||||||
| class BacktestRequest(BaseModel): | class BacktestRequest(BaseModel): | ||||||
|     strategy: str |     strategy: str | ||||||
|     timeframe: Optional[str] |     timeframe: Optional[str] | ||||||
|  |     timeframe_detail: Optional[str] | ||||||
|     timerange: Optional[str] |     timerange: Optional[str] | ||||||
|     max_open_trades: Optional[int] |     max_open_trades: Optional[int] | ||||||
|     stake_amount: Optional[Union[float, str]] |     stake_amount: Optional[Union[float, str]] | ||||||
|   | |||||||
| @@ -557,7 +557,7 @@ class RPC: | |||||||
|                 current_rate = self._freqtrade.exchange.get_rate( |                 current_rate = self._freqtrade.exchange.get_rate( | ||||||
|                     trade.pair, refresh=False, side="sell") |                     trade.pair, refresh=False, side="sell") | ||||||
|                 sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) |                 sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) | ||||||
|                 self._freqtrade.execute_sell(trade, current_rate, sell_reason) |                 self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason) | ||||||
|         # ---- EOF def _exec_forcesell ---- |         # ---- EOF def _exec_forcesell ---- | ||||||
|  |  | ||||||
|         if self._freqtrade.state != State.RUNNING: |         if self._freqtrade.state != State.RUNNING: | ||||||
| @@ -613,7 +613,7 @@ class RPC: | |||||||
|         stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) |         stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) | ||||||
|  |  | ||||||
|         # execute buy |         # execute buy | ||||||
|         if self._freqtrade.execute_buy(pair, stakeamount, price, forcebuy=True): |         if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True): | ||||||
|             Trade.commit() |             Trade.commit() | ||||||
|             trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() |             trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() | ||||||
|             return trade |             return trade | ||||||
|   | |||||||
| @@ -120,6 +120,8 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|     # and wallets - access to the current balance. |     # and wallets - access to the current balance. | ||||||
|     dp: Optional[DataProvider] = None |     dp: Optional[DataProvider] = None | ||||||
|     wallets: Optional[Wallets] = None |     wallets: Optional[Wallets] = None | ||||||
|  |     # Filled from configuration | ||||||
|  |     stake_currency: str | ||||||
|     # container variable for strategy source code |     # container variable for strategy source code | ||||||
|     __source__: str = '' |     __source__: str = '' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,6 +36,6 @@ | |||||||
|         "BNB/TUSD", |         "BNB/TUSD", | ||||||
|         "BNB/USDC", |         "BNB/USDC", | ||||||
|         "BNB/USDS", |         "BNB/USDS", | ||||||
|         "BNB/USDT", |         "BNB/USDT" | ||||||
|     ] |     ] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| # Include all requirements to run the bot. | # Include all requirements to run the bot. | ||||||
| -r requirements.txt | -r requirements.txt | ||||||
|  |  | ||||||
| plotly==5.2.1 | plotly==5.3.0 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| numpy==1.21.2 | numpy==1.21.2 | ||||||
| pandas==1.3.2 | pandas==1.3.2 | ||||||
|  |  | ||||||
| ccxt==1.55.28 | ccxt==1.55.56 | ||||||
| # Pin cryptography for now due to rust build errors with piwheels | # Pin cryptography for now due to rust build errors with piwheels | ||||||
| cryptography==3.4.7 | cryptography==3.4.8 | ||||||
| aiohttp==3.7.4.post0 | aiohttp==3.7.4.post0 | ||||||
| SQLAlchemy==1.4.23 | SQLAlchemy==1.4.23 | ||||||
| python-telegram-bot==13.7 | python-telegram-bot==13.7 | ||||||
| @@ -31,7 +31,7 @@ python-rapidjson==1.4 | |||||||
| sdnotify==0.3.2 | sdnotify==0.3.2 | ||||||
|  |  | ||||||
| # API Server | # API Server | ||||||
| fastapi==0.68.0 | fastapi==0.68.1 | ||||||
| uvicorn==0.15.0 | uvicorn==0.15.0 | ||||||
| pyjwt==2.1.0 | pyjwt==2.1.0 | ||||||
| aiofiles==0.7.0 | aiofiles==0.7.0 | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								setup.sh
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.sh
									
									
									
									
									
								
							| @@ -119,6 +119,7 @@ function install_mac_newer_python_dependencies() { | |||||||
|         echo "-------------------------" |         echo "-------------------------" | ||||||
|         brew install hdf5 |         brew install hdf5 | ||||||
|     fi |     fi | ||||||
|  |     export HDF5_DIR=$(brew --prefix) | ||||||
|  |  | ||||||
|     if [ ! $(brew --prefix --installed c-blosc 2>/dev/null) ] |     if [ ! $(brew --prefix --installed c-blosc 2>/dev/null) ] | ||||||
|     then |     then | ||||||
| @@ -127,6 +128,7 @@ function install_mac_newer_python_dependencies() { | |||||||
|         echo "-------------------------" |         echo "-------------------------" | ||||||
|         brew install c-blosc |         brew install c-blosc | ||||||
|     fi     |     fi     | ||||||
|  |     export CBLOSC_DIR=$(brew --prefix) | ||||||
| } | } | ||||||
|  |  | ||||||
| # Install bot MacOS | # Install bot MacOS | ||||||
|   | |||||||
| @@ -510,17 +510,6 @@ def test_start_new_strategy(mocker, caplog): | |||||||
|         start_new_strategy(get_args(args)) |         start_new_strategy(get_args(args)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_start_new_strategy_DefaultStrat(mocker, caplog): |  | ||||||
|     args = [ |  | ||||||
|         "new-strategy", |  | ||||||
|         "--strategy", |  | ||||||
|         "DefaultStrategy" |  | ||||||
|     ] |  | ||||||
|     with pytest.raises(OperationalException, |  | ||||||
|                        match=r"DefaultStrategy is not allowed as name\."): |  | ||||||
|         start_new_strategy(get_args(args)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_start_new_strategy_no_arg(mocker, caplog): | def test_start_new_strategy_no_arg(mocker, caplog): | ||||||
|     args = [ |     args = [ | ||||||
|         "new-strategy", |         "new-strategy", | ||||||
| @@ -552,17 +541,6 @@ def test_start_new_hyperopt(mocker, caplog): | |||||||
|         start_new_hyperopt(get_args(args)) |         start_new_hyperopt(get_args(args)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_start_new_hyperopt_DefaultHyperopt(mocker, caplog): |  | ||||||
|     args = [ |  | ||||||
|         "new-hyperopt", |  | ||||||
|         "--hyperopt", |  | ||||||
|         "DefaultHyperopt" |  | ||||||
|     ] |  | ||||||
|     with pytest.raises(OperationalException, |  | ||||||
|                        match=r"DefaultHyperopt is not allowed as name\."): |  | ||||||
|         start_new_hyperopt(get_args(args)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_start_new_hyperopt_no_arg(mocker): | def test_start_new_hyperopt_no_arg(mocker): | ||||||
|     args = [ |     args = [ | ||||||
|         "new-hyperopt", |         "new-hyperopt", | ||||||
| @@ -827,9 +805,9 @@ def test_start_list_strategies(mocker, caplog, capsys): | |||||||
|     # pargs['config'] = None |     # pargs['config'] = None | ||||||
|     start_list_strategies(pargs) |     start_list_strategies(pargs) | ||||||
|     captured = capsys.readouterr() |     captured = capsys.readouterr() | ||||||
|     assert "TestStrategyLegacy" in captured.out |     assert "TestStrategyLegacyV1" in captured.out | ||||||
|     assert "legacy_strategy.py" not in captured.out |     assert "legacy_strategy_v1.py" not in captured.out | ||||||
|     assert "DefaultStrategy" in captured.out |     assert "StrategyTestV2" in captured.out | ||||||
|  |  | ||||||
|     # Test regular output |     # Test regular output | ||||||
|     args = [ |     args = [ | ||||||
| @@ -842,9 +820,9 @@ def test_start_list_strategies(mocker, caplog, capsys): | |||||||
|     # pargs['config'] = None |     # pargs['config'] = None | ||||||
|     start_list_strategies(pargs) |     start_list_strategies(pargs) | ||||||
|     captured = capsys.readouterr() |     captured = capsys.readouterr() | ||||||
|     assert "TestStrategyLegacy" in captured.out |     assert "TestStrategyLegacyV1" in captured.out | ||||||
|     assert "legacy_strategy.py" in captured.out |     assert "legacy_strategy_v1.py" in captured.out | ||||||
|     assert "DefaultStrategy" in captured.out |     assert "StrategyTestV2" in captured.out | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_start_list_hyperopts(mocker, caplog, capsys): | def test_start_list_hyperopts(mocker, caplog, capsys): | ||||||
| @@ -861,7 +839,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys): | |||||||
|     captured = capsys.readouterr() |     captured = capsys.readouterr() | ||||||
|     assert "TestHyperoptLegacy" not in captured.out |     assert "TestHyperoptLegacy" not in captured.out | ||||||
|     assert "legacy_hyperopt.py" not in captured.out |     assert "legacy_hyperopt.py" not in captured.out | ||||||
|     assert "DefaultHyperOpt" in captured.out |     assert "HyperoptTestSepFile" in captured.out | ||||||
|     assert "test_hyperopt.py" not in captured.out |     assert "test_hyperopt.py" not in captured.out | ||||||
|  |  | ||||||
|     # Test regular output |     # Test regular output | ||||||
| @@ -876,7 +854,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys): | |||||||
|     captured = capsys.readouterr() |     captured = capsys.readouterr() | ||||||
|     assert "TestHyperoptLegacy" not in captured.out |     assert "TestHyperoptLegacy" not in captured.out | ||||||
|     assert "legacy_hyperopt.py" not in captured.out |     assert "legacy_hyperopt.py" not in captured.out | ||||||
|     assert "DefaultHyperOpt" in captured.out |     assert "HyperoptTestSepFile" in captured.out | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): | def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): | ||||||
|   | |||||||
| @@ -360,7 +360,7 @@ def get_default_conf(testdatadir): | |||||||
|         "user_data_dir": Path("user_data"), |         "user_data_dir": Path("user_data"), | ||||||
|         "verbosity": 3, |         "verbosity": 3, | ||||||
|         "strategy_path": str(Path(__file__).parent / "strategy" / "strats"), |         "strategy_path": str(Path(__file__).parent / "strategy" / "strats"), | ||||||
|         "strategy": "DefaultStrategy", |         "strategy": "StrategyTestV2", | ||||||
|         "disableparamexport": True, |         "disableparamexport": True, | ||||||
|         "internals": {}, |         "internals": {}, | ||||||
|         "export": "none", |         "export": "none", | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ def mock_trade_1(fee): | |||||||
|         open_rate=0.123, |         open_rate=0.123, | ||||||
|         exchange='binance', |         exchange='binance', | ||||||
|         open_order_id='dry_run_buy_12345', |         open_order_id='dry_run_buy_12345', | ||||||
|         strategy='DefaultStrategy', |         strategy='StrategyTestV2', | ||||||
|         timeframe=5, |         timeframe=5, | ||||||
|     ) |     ) | ||||||
|     o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy') |     o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy') | ||||||
| @@ -87,7 +87,7 @@ def mock_trade_2(fee): | |||||||
|         exchange='binance', |         exchange='binance', | ||||||
|         is_open=False, |         is_open=False, | ||||||
|         open_order_id='dry_run_sell_12345', |         open_order_id='dry_run_sell_12345', | ||||||
|         strategy='DefaultStrategy', |         strategy='StrategyTestV2', | ||||||
|         timeframe=5, |         timeframe=5, | ||||||
|         sell_reason='sell_signal', |         sell_reason='sell_signal', | ||||||
|         open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), |         open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), | ||||||
| @@ -146,7 +146,7 @@ def mock_trade_3(fee): | |||||||
|         close_profit_abs=0.000155, |         close_profit_abs=0.000155, | ||||||
|         exchange='binance', |         exchange='binance', | ||||||
|         is_open=False, |         is_open=False, | ||||||
|         strategy='DefaultStrategy', |         strategy='StrategyTestV2', | ||||||
|         timeframe=5, |         timeframe=5, | ||||||
|         sell_reason='roi', |         sell_reason='roi', | ||||||
|         open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), |         open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), | ||||||
| @@ -189,7 +189,7 @@ def mock_trade_4(fee): | |||||||
|         open_rate=0.123, |         open_rate=0.123, | ||||||
|         exchange='binance', |         exchange='binance', | ||||||
|         open_order_id='prod_buy_12345', |         open_order_id='prod_buy_12345', | ||||||
|         strategy='DefaultStrategy', |         strategy='StrategyTestV2', | ||||||
|         timeframe=5, |         timeframe=5, | ||||||
|     ) |     ) | ||||||
|     o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy') |     o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy') | ||||||
|   | |||||||
| @@ -93,7 +93,7 @@ def test_load_backtest_data_new_format(testdatadir): | |||||||
| def test_load_backtest_data_multi(testdatadir): | def test_load_backtest_data_multi(testdatadir): | ||||||
|  |  | ||||||
|     filename = testdatadir / "backtest-result_multistrat.json" |     filename = testdatadir / "backtest-result_multistrat.json" | ||||||
|     for strategy in ('DefaultStrategy', 'TestStrategy'): |     for strategy in ('StrategyTestV2', 'TestStrategy'): | ||||||
|         bt_data = load_backtest_data(filename, strategy=strategy) |         bt_data = load_backtest_data(filename, strategy=strategy) | ||||||
|         assert isinstance(bt_data, DataFrame) |         assert isinstance(bt_data, DataFrame) | ||||||
|         assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID) |         assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID) | ||||||
| @@ -128,7 +128,7 @@ def test_load_trades_from_db(default_conf, fee, mocker): | |||||||
|     for col in BT_DATA_COLUMNS: |     for col in BT_DATA_COLUMNS: | ||||||
|         if col not in ['index', 'open_at_end']: |         if col not in ['index', 'open_at_end']: | ||||||
|             assert col in trades.columns |             assert col in trades.columns | ||||||
|     trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='DefaultStrategy') |     trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='StrategyTestV2') | ||||||
|     assert len(trades) == 4 |     assert len(trades) == 4 | ||||||
|     trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy') |     trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy') | ||||||
|     assert len(trades) == 0 |     assert len(trades) == 0 | ||||||
| @@ -186,7 +186,7 @@ def test_load_trades(default_conf, mocker): | |||||||
|                 db_url=default_conf.get('db_url'), |                 db_url=default_conf.get('db_url'), | ||||||
|                 exportfilename=default_conf.get('exportfilename'), |                 exportfilename=default_conf.get('exportfilename'), | ||||||
|                 no_trades=False, |                 no_trades=False, | ||||||
|                 strategy="DefaultStrategy", |                 strategy="StrategyTestV2", | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|     assert db_mock.call_count == 1 |     assert db_mock.call_count == 1 | ||||||
|   | |||||||
| @@ -380,7 +380,7 @@ def test_file_dump_json_tofile(testdatadir) -> None: | |||||||
| def test_get_timerange(default_conf, mocker, testdatadir) -> None: | def test_get_timerange(default_conf, mocker, testdatadir) -> None: | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|  |  | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     data = strategy.advise_all_indicators( |     data = strategy.advise_all_indicators( | ||||||
| @@ -398,7 +398,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None: | |||||||
| def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None: | def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None: | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|  |  | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     data = strategy.advise_all_indicators( |     data = strategy.advise_all_indicators( | ||||||
| @@ -422,7 +422,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) | |||||||
| def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None: | def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None: | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|  |  | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     timerange = TimeRange('index', 'index', 200, 250) |     timerange = TimeRange('index', 'index', 200, 250) | ||||||
|   | |||||||
| @@ -557,7 +557,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog): | |||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT']) | @pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT']) | ||||||
| def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog): | def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): | ||||||
|     default_conf['stake_currency'] = stake_currency |     default_conf['stake_currency'] = stake_currency | ||||||
|     api_mock = MagicMock() |     api_mock = MagicMock() | ||||||
|     type(api_mock).load_markets = MagicMock(return_value={ |     type(api_mock).load_markets = MagicMock(return_value={ | ||||||
| @@ -571,7 +571,7 @@ def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog): | |||||||
|     Exchange(default_conf) |     Exchange(default_conf) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_validate_stake_currency_error(default_conf, mocker, caplog): | def test_validate_stakecurrency_error(default_conf, mocker, caplog): | ||||||
|     default_conf['stake_currency'] = 'XRP' |     default_conf['stake_currency'] = 'XRP' | ||||||
|     api_mock = MagicMock() |     api_mock = MagicMock() | ||||||
|     type(api_mock).load_markets = MagicMock(return_value={ |     type(api_mock).load_markets = MagicMock(return_value={ | ||||||
| @@ -587,6 +587,13 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog): | |||||||
|                        'Available currencies are: BTC, ETH, USDT'): |                        'Available currencies are: BTC, ETH, USDT'): | ||||||
|         Exchange(default_conf) |         Exchange(default_conf) | ||||||
|  |  | ||||||
|  |     type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError('No connection.')) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) | ||||||
|  |  | ||||||
|  |     with pytest.raises(OperationalException, | ||||||
|  |                        match=r'Could not load markets, therefore cannot start\. Please.*'): | ||||||
|  |         Exchange(default_conf) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_quote_currencies(default_conf, mocker): | def test_get_quote_currencies(default_conf, mocker): | ||||||
|     ex = get_patched_exchange(mocker, default_conf) |     ex = get_patched_exchange(mocker, default_conf) | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ def hyperopt_conf(default_conf): | |||||||
|     hyperconf.update({ |     hyperconf.update({ | ||||||
|         'datadir': Path(default_conf['datadir']), |         'datadir': Path(default_conf['datadir']), | ||||||
|         'runmode': RunMode.HYPEROPT, |         'runmode': RunMode.HYPEROPT, | ||||||
|         'hyperopt': 'DefaultHyperOpt', |         'hyperopt': 'HyperoptTestSepFile', | ||||||
|         'hyperopt_loss': 'ShortTradeDurHyperOptLoss', |         'hyperopt_loss': 'ShortTradeDurHyperOptLoss', | ||||||
|                          'hyperopt_path': str(Path(__file__).parent / 'hyperopts'), |                          'hyperopt_path': str(Path(__file__).parent / 'hyperopts'), | ||||||
|                          'epochs': 1, |                          'epochs': 1, | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib | |||||||
| from freqtrade.optimize.hyperopt_interface import IHyperOpt | from freqtrade.optimize.hyperopt_interface import IHyperOpt | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DefaultHyperOpt(IHyperOpt): | class HyperoptTestSepFile(IHyperOpt): | ||||||
|     """ |     """ | ||||||
|     Default hyperopt provided by the Freqtrade bot. |     Default hyperopt provided by the Freqtrade bot. | ||||||
|     You can override it with your own Hyperopt |     You can override it with your own Hyperopt | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument | # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument | ||||||
|  |  | ||||||
| import random | import random | ||||||
| from datetime import timedelta | from datetime import datetime, timedelta, timezone | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from unittest.mock import MagicMock, PropertyMock | from unittest.mock import MagicMock, PropertyMock | ||||||
|  |  | ||||||
| @@ -155,7 +155,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca | |||||||
|     args = [ |     args = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--export', 'none' |         '--export', 'none' | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
| @@ -190,7 +190,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> | |||||||
|     args = [ |     args = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--datadir', '/foo/bar', |         '--datadir', '/foo/bar', | ||||||
|         '--timeframe', '1m', |         '--timeframe', '1m', | ||||||
|         '--enable-position-stacking', |         '--enable-position-stacking', | ||||||
| @@ -240,7 +240,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) | |||||||
|     args = [ |     args = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--stake-amount', '1', |         '--stake-amount', '1', | ||||||
|         '--starting-balance', '2' |         '--starting-balance', '2' | ||||||
|     ] |     ] | ||||||
| @@ -251,7 +251,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) | |||||||
|     args = [ |     args = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--stake-amount', '1', |         '--stake-amount', '1', | ||||||
|         '--starting-balance', '0.5' |         '--starting-balance', '0.5' | ||||||
|     ] |     ] | ||||||
| @@ -269,7 +269,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None: | |||||||
|     args = [ |     args = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|     ] |     ] | ||||||
|     pargs = get_args(args) |     pargs = get_args(args) | ||||||
|     start_backtesting(pargs) |     start_backtesting(pargs) | ||||||
| @@ -302,7 +302,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: | |||||||
| def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: | def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     del default_conf['timeframe'] |     del default_conf['timeframe'] | ||||||
|     default_conf['strategy_list'] = ['DefaultStrategy', |     default_conf['strategy_list'] = ['StrategyTestV2', | ||||||
|                                      'SampleStrategy'] |                                      'SampleStrategy'] | ||||||
|  |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) |     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) | ||||||
| @@ -340,7 +340,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: | |||||||
|     assert len(processed['UNITTEST/BTC']) == 102 |     assert len(processed['UNITTEST/BTC']) == 102 | ||||||
|  |  | ||||||
|     # Load strategy to compare the result between Backtesting function and strategy are the same |     # Load strategy to compare the result between Backtesting function and strategy are the same | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     processed2 = strategy.advise_all_indicators(data) |     processed2 = strategy.advise_all_indicators(data) | ||||||
| @@ -441,6 +441,15 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> | |||||||
|     with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'): |     with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'): | ||||||
|         Backtesting(default_conf) |         Backtesting(default_conf) | ||||||
|  |  | ||||||
|  |     default_conf.update({ | ||||||
|  |         'pairlists': [{"method": "StaticPairList"}], | ||||||
|  |         'timeframe_detail': '1d', | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     with pytest.raises(OperationalException, | ||||||
|  |                        match='Detail timeframe must be smaller than strategy timeframe.'): | ||||||
|  |         Backtesting(default_conf) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: | def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) |     mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) | ||||||
| @@ -473,7 +482,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti | |||||||
|     Backtesting(default_conf) |     Backtesting(default_conf) | ||||||
|  |  | ||||||
|     # Multiple strategies |     # Multiple strategies | ||||||
|     default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy'] |     default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1'] | ||||||
|     with pytest.raises(OperationalException, |     with pytest.raises(OperationalException, | ||||||
|                        match='PrecisionFilter not allowed for backtesting multiple strategies.'): |                        match='PrecisionFilter not allowed for backtesting multiple strategies.'): | ||||||
|         Backtesting(default_conf) |         Backtesting(default_conf) | ||||||
| @@ -491,7 +500,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: | |||||||
|     pair = 'UNITTEST/BTC' |     pair = 'UNITTEST/BTC' | ||||||
|     row = [ |     row = [ | ||||||
|         pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0), |         pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0), | ||||||
|         1,  # Sell |         1,  # Buy | ||||||
|         0.001,  # Open |         0.001,  # Open | ||||||
|         0.0011,  # Close |         0.0011,  # Close | ||||||
|         0,  # Sell |         0,  # Sell | ||||||
| @@ -539,6 +548,88 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: | |||||||
|     backtesting.cleanup() |     backtesting.cleanup() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: | ||||||
|  |     default_conf['use_sell_signal'] = False | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) | ||||||
|  |     mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) | ||||||
|  |     patch_exchange(mocker) | ||||||
|  |     default_conf['timeframe_detail'] = '1m' | ||||||
|  |     default_conf['max_open_trades'] = 2 | ||||||
|  |     backtesting = Backtesting(default_conf) | ||||||
|  |     backtesting._set_strategy(backtesting.strategylist[0]) | ||||||
|  |     pair = 'UNITTEST/BTC' | ||||||
|  |     row = [ | ||||||
|  |         pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc), | ||||||
|  |         1,  # Buy | ||||||
|  |         200,  # Open | ||||||
|  |         201,  # Close | ||||||
|  |         0,  # Sell | ||||||
|  |         195,  # Low | ||||||
|  |         201.5,  # High | ||||||
|  |         '',  # Buy Signal Name | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     trade = backtesting._enter_trade(pair, row=row) | ||||||
|  |     assert isinstance(trade, LocalTrade) | ||||||
|  |  | ||||||
|  |     row_sell = [ | ||||||
|  |         pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc), | ||||||
|  |         0,  # Buy | ||||||
|  |         200,  # Open | ||||||
|  |         201,  # Close | ||||||
|  |         0,  # Sell | ||||||
|  |         195,  # Low | ||||||
|  |         210.5,  # High | ||||||
|  |         '',  # Buy Signal Name | ||||||
|  |     ] | ||||||
|  |     row_detail = pd.DataFrame( | ||||||
|  |         [ | ||||||
|  |             [ | ||||||
|  |                 pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc), | ||||||
|  |                 1, 200, 199, 0, 197, 200.1, '', | ||||||
|  |             ], [ | ||||||
|  |                 pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc), | ||||||
|  |                 0, 199, 199.5, 0, 199, 199.7, '', | ||||||
|  |             ], [ | ||||||
|  |                 pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc), | ||||||
|  |                 0, 199.5, 200.5, 0, 199, 200.8, '', | ||||||
|  |             ], [ | ||||||
|  |                 pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc), | ||||||
|  |                 0, 200.5, 210.5, 0, 193, 210.5, '',  # ROI sell (?) | ||||||
|  |             ], [ | ||||||
|  |                 pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc), | ||||||
|  |                 0, 200, 199, 0, 193, 200.1, '', | ||||||
|  |             ], | ||||||
|  |         ], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # No data available. | ||||||
|  |     res = backtesting._get_sell_trade_entry(trade, row_sell) | ||||||
|  |     assert res is not None | ||||||
|  |     assert res.sell_reason == SellType.ROI.value | ||||||
|  |     assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc) | ||||||
|  |  | ||||||
|  |     # Enter new trade | ||||||
|  |     trade = backtesting._enter_trade(pair, row=row) | ||||||
|  |     assert isinstance(trade, LocalTrade) | ||||||
|  |     # Assign empty ... no result. | ||||||
|  |     backtesting.detail_data[pair] = pd.DataFrame( | ||||||
|  |         [], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"]) | ||||||
|  |  | ||||||
|  |     res = backtesting._get_sell_trade_entry(trade, row) | ||||||
|  |     assert res is None | ||||||
|  |  | ||||||
|  |     # Assign backtest-detail data | ||||||
|  |     backtesting.detail_data[pair] = row_detail | ||||||
|  |  | ||||||
|  |     res = backtesting._get_sell_trade_entry(trade, row_sell) | ||||||
|  |     assert res is not None | ||||||
|  |     assert res.sell_reason == SellType.ROI.value | ||||||
|  |     # Sell at minute 3 (not available above!) | ||||||
|  |     assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc) | ||||||
|  |     assert round(res.close_rate, 3) == round(209.0225, 3) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: | def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: | ||||||
|     default_conf['use_sell_signal'] = False |     default_conf['use_sell_signal'] = False | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) |     mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) | ||||||
| @@ -694,7 +785,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir, | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): | def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): | ||||||
|     # Override the default buy trend function in our default_strategy |     # Override the default buy trend function in our StrategyTestV2 | ||||||
|     def fun(dataframe=None, pair=None): |     def fun(dataframe=None, pair=None): | ||||||
|         buy_value = 1 |         buy_value = 1 | ||||||
|         sell_value = 1 |         sell_value = 1 | ||||||
| @@ -710,7 +801,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_backtest_only_sell(mocker, default_conf, testdatadir): | def test_backtest_only_sell(mocker, default_conf, testdatadir): | ||||||
|     # Override the default buy trend function in our default_strategy |     # Override the default buy trend function in our StrategyTestV2 | ||||||
|     def fun(dataframe=None, pair=None): |     def fun(dataframe=None, pair=None): | ||||||
|         buy_value = 0 |         buy_value = 0 | ||||||
|         sell_value = 1 |         sell_value = 1 | ||||||
| @@ -837,7 +928,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): | |||||||
|     args = [ |     args = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--datadir', str(testdatadir), |         '--datadir', str(testdatadir), | ||||||
|         '--timeframe', '1m', |         '--timeframe', '1m', | ||||||
|         '--timerange', '1510694220-1510700340', |         '--timerange', '1510694220-1510700340', | ||||||
| @@ -908,8 +999,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): | |||||||
|         '--enable-position-stacking', |         '--enable-position-stacking', | ||||||
|         '--disable-max-market-positions', |         '--disable-max-market-positions', | ||||||
|         '--strategy-list', |         '--strategy-list', | ||||||
|         'DefaultStrategy', |         'StrategyTestV2', | ||||||
|         'TestStrategyLegacy', |         'TestStrategyLegacyV1', | ||||||
|     ] |     ] | ||||||
|     args = get_args(args) |     args = get_args(args) | ||||||
|     start_backtesting(args) |     start_backtesting(args) | ||||||
| @@ -931,8 +1022,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): | |||||||
|         'Backtesting with data from 2017-11-14 21:17:00 ' |         'Backtesting with data from 2017-11-14 21:17:00 ' | ||||||
|         'up to 2017-11-14 22:58:00 (0 days).', |         'up to 2017-11-14 22:58:00 (0 days).', | ||||||
|         'Parameter --enable-position-stacking detected ...', |         'Parameter --enable-position-stacking detected ...', | ||||||
|         'Running backtesting for Strategy DefaultStrategy', |         'Running backtesting for Strategy StrategyTestV2', | ||||||
|         'Running backtesting for Strategy TestStrategyLegacy', |         'Running backtesting for Strategy TestStrategyLegacyV1', | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     for line in exists: |     for line in exists: | ||||||
| @@ -1012,8 +1103,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat | |||||||
|         '--enable-position-stacking', |         '--enable-position-stacking', | ||||||
|         '--disable-max-market-positions', |         '--disable-max-market-positions', | ||||||
|         '--strategy-list', |         '--strategy-list', | ||||||
|         'DefaultStrategy', |         'StrategyTestV2', | ||||||
|         'TestStrategyLegacy', |         'TestStrategyLegacyV1', | ||||||
|     ] |     ] | ||||||
|     args = get_args(args) |     args = get_args(args) | ||||||
|     start_backtesting(args) |     start_backtesting(args) | ||||||
| @@ -1029,8 +1120,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat | |||||||
|         'Backtesting with data from 2017-11-14 21:17:00 ' |         'Backtesting with data from 2017-11-14 21:17:00 ' | ||||||
|         'up to 2017-11-14 22:58:00 (0 days).', |         'up to 2017-11-14 22:58:00 (0 days).', | ||||||
|         'Parameter --enable-position-stacking detected ...', |         'Parameter --enable-position-stacking detected ...', | ||||||
|         'Running backtesting for Strategy DefaultStrategy', |         'Running backtesting for Strategy StrategyTestV2', | ||||||
|         'Running backtesting for Strategy TestStrategyLegacy', |         'Running backtesting for Strategy TestStrategyLegacyV1', | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     for line in exists: |     for line in exists: | ||||||
| @@ -1042,3 +1133,102 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat | |||||||
|     assert 'LEFT OPEN TRADES REPORT' 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 '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 |     assert 'STRATEGY SUMMARY' in captured.out | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.filterwarnings("ignore:deprecated") | ||||||
|  | def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, | ||||||
|  |                                                   caplog, testdatadir, capsys): | ||||||
|  |     # Tests detail-data loading | ||||||
|  |     default_conf.update({ | ||||||
|  |         "use_sell_signal": True, | ||||||
|  |         "sell_profit_only": False, | ||||||
|  |         "sell_profit_offset": 0.0, | ||||||
|  |         "ignore_roi_if_buy_signal": False, | ||||||
|  |     }) | ||||||
|  |     patch_exchange(mocker) | ||||||
|  |     result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'], | ||||||
|  |                             'profit_ratio': [0.0, 0.0], | ||||||
|  |                             'profit_abs': [0.0, 0.0], | ||||||
|  |                             'open_date': pd.to_datetime(['2018-01-29 18:40:00', | ||||||
|  |                                                          '2018-01-30 03:30:00', ], utc=True | ||||||
|  |                                                         ), | ||||||
|  |                             'close_date': pd.to_datetime(['2018-01-29 20:45:00', | ||||||
|  |                                                           '2018-01-30 05:35:00', ], utc=True), | ||||||
|  |                             'trade_duration': [235, 40], | ||||||
|  |                             'is_open': [False, False], | ||||||
|  |                             'stake_amount': [0.01, 0.01], | ||||||
|  |                             'open_rate': [0.104445, 0.10302485], | ||||||
|  |                             'close_rate': [0.104969, 0.103541], | ||||||
|  |                             'sell_reason': [SellType.ROI, SellType.ROI] | ||||||
|  |                             }) | ||||||
|  |     result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], | ||||||
|  |                             'profit_ratio': [0.03, 0.01, 0.1], | ||||||
|  |                             'profit_abs': [0.01, 0.02, 0.2], | ||||||
|  |                             'open_date': pd.to_datetime(['2018-01-29 18:40:00', | ||||||
|  |                                                          '2018-01-30 03:30:00', | ||||||
|  |                                                          '2018-01-30 05:30:00'], utc=True | ||||||
|  |                                                         ), | ||||||
|  |                             'close_date': pd.to_datetime(['2018-01-29 20:45:00', | ||||||
|  |                                                           '2018-01-30 05:35:00', | ||||||
|  |                                                           '2018-01-30 08:30:00'], utc=True), | ||||||
|  |                             'trade_duration': [47, 40, 20], | ||||||
|  |                             'is_open': [False, False, False], | ||||||
|  |                             'stake_amount': [0.01, 0.01, 0.01], | ||||||
|  |                             'open_rate': [0.104445, 0.10302485, 0.122541], | ||||||
|  |                             'close_rate': [0.104969, 0.103541, 0.123541], | ||||||
|  |                             'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] | ||||||
|  |                             }) | ||||||
|  |     backtestmock = MagicMock(side_effect=[ | ||||||
|  |         { | ||||||
|  |             'results': result1, | ||||||
|  |             'config': default_conf, | ||||||
|  |             'locks': [], | ||||||
|  |             'rejected_signals': 20, | ||||||
|  |             'final_balance': 1000, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             'results': result2, | ||||||
|  |             'config': default_conf, | ||||||
|  |             'locks': [], | ||||||
|  |             'rejected_signals': 20, | ||||||
|  |             'final_balance': 1000, | ||||||
|  |         } | ||||||
|  |     ]) | ||||||
|  |     mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', | ||||||
|  |                  PropertyMock(return_value=['XRP/ETH'])) | ||||||
|  |     mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) | ||||||
|  |  | ||||||
|  |     patched_configuration_load_config_file(mocker, default_conf) | ||||||
|  |  | ||||||
|  |     args = [ | ||||||
|  |         'backtesting', | ||||||
|  |         '--config', 'config.json', | ||||||
|  |         '--datadir', str(testdatadir), | ||||||
|  |         '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), | ||||||
|  |         '--timeframe', '5m', | ||||||
|  |         '--timeframe-detail', '1m', | ||||||
|  |         '--strategy-list', | ||||||
|  |         'StrategyTestV2' | ||||||
|  |     ] | ||||||
|  |     args = get_args(args) | ||||||
|  |     start_backtesting(args) | ||||||
|  |  | ||||||
|  |     # check the logs, that will contain the backtest result | ||||||
|  |     exists = [ | ||||||
|  |         'Parameter -i/--timeframe detected ... Using timeframe: 5m ...', | ||||||
|  |         'Parameter --timeframe-detail detected, using 1m for intra-candle backtesting ...', | ||||||
|  |         f'Using data directory: {testdatadir} ...', | ||||||
|  |         'Loading data from 2019-10-11 00:00:00 ' | ||||||
|  |         'up to 2019-10-13 11:10:00 (2 days).', | ||||||
|  |         'Backtesting with data from 2019-10-11 01:40:00 ' | ||||||
|  |         'up to 2019-10-13 11:10:00 (2 days).', | ||||||
|  |         'Running backtesting for Strategy StrategyTestV2', | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     for line in exists: | ||||||
|  |         assert log_has(line, caplog) | ||||||
|  |  | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     assert 'BACKTESTING REPORT' in captured.out | ||||||
|  |     assert 'SELL REASON STATS' in captured.out | ||||||
|  |     assert 'LEFT OPEN TRADES REPORT' in captured.out | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca | |||||||
|     args = [ |     args = [ | ||||||
|         'edge', |         'edge', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     config = setup_optimize_configuration(get_args(args), RunMode.EDGE) |     config = setup_optimize_configuration(get_args(args), RunMode.EDGE) | ||||||
| @@ -46,7 +46,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N | |||||||
|     args = [ |     args = [ | ||||||
|         'edge', |         'edge', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--datadir', '/foo/bar', |         '--datadir', '/foo/bar', | ||||||
|         '--timeframe', '1m', |         '--timeframe', '1m', | ||||||
|         '--timerange', ':100', |         '--timerange', ':100', | ||||||
| @@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None: | |||||||
|     args = [ |     args = [ | ||||||
|         'edge', |         'edge', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|     ] |     ] | ||||||
|     pargs = get_args(args) |     pargs = get_args(args) | ||||||
|     start_edge(pargs) |     start_edge(pargs) | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ from freqtrade.strategy.hyper import IntParameter | |||||||
| from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, | from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, | ||||||
|                             patched_configuration_load_config_file) |                             patched_configuration_load_config_file) | ||||||
|  |  | ||||||
| from .hyperopts.default_hyperopt import DefaultHyperOpt | from .hyperopts.hyperopt_test_sep_file import HyperoptTestSepFile | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: | def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: | ||||||
| @@ -31,7 +31,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--hyperopt', 'DefaultHyperOpt', |         '--hyperopt', 'HyperoptTestSepFile', | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT) |     config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT) | ||||||
| @@ -63,7 +63,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--hyperopt', 'DefaultHyperOpt', |         '--hyperopt', 'HyperoptTestSepFile', | ||||||
|         '--datadir', '/foo/bar', |         '--datadir', '/foo/bar', | ||||||
|         '--timeframe', '1m', |         '--timeframe', '1m', | ||||||
|         '--timerange', ':100', |         '--timerange', ':100', | ||||||
| @@ -115,7 +115,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--hyperopt', 'DefaultHyperOpt', |         '--hyperopt', 'HyperoptTestSepFile', | ||||||
|         '--stake-amount', '1', |         '--stake-amount', '1', | ||||||
|         '--starting-balance', '2' |         '--starting-balance', '2' | ||||||
|     ] |     ] | ||||||
| @@ -125,7 +125,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--stake-amount', '1', |         '--stake-amount', '1', | ||||||
|         '--starting-balance', '0.5' |         '--starting-balance', '0.5' | ||||||
|     ] |     ] | ||||||
| @@ -136,7 +136,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None | |||||||
| def test_hyperoptresolver(mocker, default_conf, caplog) -> None: | def test_hyperoptresolver(mocker, default_conf, caplog) -> None: | ||||||
|     patched_configuration_load_config_file(mocker, default_conf) |     patched_configuration_load_config_file(mocker, default_conf) | ||||||
|  |  | ||||||
|     hyperopt = DefaultHyperOpt |     hyperopt = HyperoptTestSepFile | ||||||
|     delattr(hyperopt, 'populate_indicators') |     delattr(hyperopt, 'populate_indicators') | ||||||
|     delattr(hyperopt, 'populate_buy_trend') |     delattr(hyperopt, 'populate_buy_trend') | ||||||
|     delattr(hyperopt, 'populate_sell_trend') |     delattr(hyperopt, 'populate_sell_trend') | ||||||
| @@ -144,7 +144,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: | |||||||
|         'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object', |         'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object', | ||||||
|         MagicMock(return_value=hyperopt(default_conf)) |         MagicMock(return_value=hyperopt(default_conf)) | ||||||
|     ) |     ) | ||||||
|     default_conf.update({'hyperopt': 'DefaultHyperOpt'}) |     default_conf.update({'hyperopt': 'HyperoptTestSepFile'}) | ||||||
|     x = HyperOptResolver.load_hyperopt(default_conf) |     x = HyperOptResolver.load_hyperopt(default_conf) | ||||||
|     assert not hasattr(x, 'populate_indicators') |     assert not hasattr(x, 'populate_indicators') | ||||||
|     assert not hasattr(x, 'populate_buy_trend') |     assert not hasattr(x, 'populate_buy_trend') | ||||||
| @@ -184,7 +184,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None: | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--hyperopt', 'DefaultHyperOpt', |         '--hyperopt', 'HyperoptTestSepFile', | ||||||
|         '--hyperopt-path', |         '--hyperopt-path', | ||||||
|         str(Path(__file__).parent / "hyperopts"), |         str(Path(__file__).parent / "hyperopts"), | ||||||
|         '--epochs', '5', |         '--epochs', '5', | ||||||
| @@ -205,7 +205,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None: | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--hyperopt', 'DefaultHyperOpt', |         '--hyperopt', 'HyperoptTestSepFile', | ||||||
|         '--hyperopt-loss', 'SharpeHyperOptLossDaily', |         '--hyperopt-loss', 'SharpeHyperOptLossDaily', | ||||||
|         '--epochs', '5' |         '--epochs', '5' | ||||||
|     ] |     ] | ||||||
| @@ -229,7 +229,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None: | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--hyperopt', 'DefaultHyperOpt', |         '--hyperopt', 'HyperoptTestSepFile', | ||||||
|         '--hyperopt-loss', 'SharpeHyperOptLossDaily', |         '--hyperopt-loss', 'SharpeHyperOptLossDaily', | ||||||
|         '--epochs', '5' |         '--epochs', '5' | ||||||
|     ] |     ] | ||||||
| @@ -247,7 +247,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None: | |||||||
|     args = [ |     args = [ | ||||||
|         'hyperopt', |         'hyperopt', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--hyperopt', 'DefaultHyperOpt', |         '--hyperopt', 'HyperoptTestSepFile', | ||||||
|         '--hyperopt-loss', 'SharpeHyperOptLossDaily', |         '--hyperopt-loss', 'SharpeHyperOptLossDaily', | ||||||
|         '--epochs', '5' |         '--epochs', '5' | ||||||
|     ] |     ] | ||||||
|   | |||||||
| @@ -167,9 +167,9 @@ def test__pprint_dict(): | |||||||
|  |  | ||||||
| def test_get_strategy_filename(default_conf): | def test_get_strategy_filename(default_conf): | ||||||
|  |  | ||||||
|     x = HyperoptTools.get_strategy_filename(default_conf, 'DefaultStrategy') |     x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2') | ||||||
|     assert isinstance(x, Path) |     assert isinstance(x, Path) | ||||||
|     assert x == Path(__file__).parents[1] / 'strategy/strats/default_strategy.py' |     assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py' | ||||||
|  |  | ||||||
|     x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy') |     x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy') | ||||||
|     assert x is None |     assert x is None | ||||||
| @@ -177,7 +177,7 @@ def test_get_strategy_filename(default_conf): | |||||||
|  |  | ||||||
| def test_export_params(tmpdir): | def test_export_params(tmpdir): | ||||||
|  |  | ||||||
|     filename = Path(tmpdir) / "DefaultStrategy.json" |     filename = Path(tmpdir) / "StrategyTestV2.json" | ||||||
|     assert not filename.is_file() |     assert not filename.is_file() | ||||||
|     params = { |     params = { | ||||||
|         "params_details": { |         "params_details": { | ||||||
| @@ -205,12 +205,12 @@ def test_export_params(tmpdir): | |||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
|     HyperoptTools.export_params(params, "DefaultStrategy", filename) |     HyperoptTools.export_params(params, "StrategyTestV2", filename) | ||||||
|  |  | ||||||
|     assert filename.is_file() |     assert filename.is_file() | ||||||
|  |  | ||||||
|     content = rapidjson.load(filename.open('r')) |     content = rapidjson.load(filename.open('r')) | ||||||
|     assert content['strategy_name'] == 'DefaultStrategy' |     assert content['strategy_name'] == 'StrategyTestV2' | ||||||
|     assert 'params' in content |     assert 'params' in content | ||||||
|     assert "buy" in content["params"] |     assert "buy" in content["params"] | ||||||
|     assert "sell" in content["params"] |     assert "sell" in content["params"] | ||||||
| @@ -223,7 +223,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): | |||||||
|     default_conf['disableparamexport'] = False |     default_conf['disableparamexport'] = False | ||||||
|     export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params") |     export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params") | ||||||
|  |  | ||||||
|     filename = Path(tmpdir) / "DefaultStrategy.json" |     filename = Path(tmpdir) / "StrategyTestV2.json" | ||||||
|     assert not filename.is_file() |     assert not filename.is_file() | ||||||
|     params = { |     params = { | ||||||
|         "params_details": { |         "params_details": { | ||||||
| @@ -252,17 +252,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): | |||||||
|         FTHYPT_FILEVERSION: 2, |         FTHYPT_FILEVERSION: 2, | ||||||
|  |  | ||||||
|     } |     } | ||||||
|     HyperoptTools.try_export_params(default_conf, "DefaultStrategy22", params) |     HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params) | ||||||
|  |  | ||||||
|     assert log_has("Strategy not found, not exporting parameter file.", caplog) |     assert log_has("Strategy not found, not exporting parameter file.", caplog) | ||||||
|     assert export_mock.call_count == 0 |     assert export_mock.call_count == 0 | ||||||
|     caplog.clear() |     caplog.clear() | ||||||
|  |  | ||||||
|     HyperoptTools.try_export_params(default_conf, "DefaultStrategy", params) |     HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params) | ||||||
|  |  | ||||||
|     assert export_mock.call_count == 1 |     assert export_mock.call_count == 1 | ||||||
|     assert export_mock.call_args_list[0][0][1] == 'DefaultStrategy' |     assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2' | ||||||
|     assert export_mock.call_args_list[0][0][2].name == 'default_strategy.json' |     assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json' | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_params_print(capsys): | def test_params_print(capsys): | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ from unittest.mock import MagicMock | |||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from freqtrade.exceptions import OperationalException | from freqtrade.exceptions import OperationalException | ||||||
| from freqtrade.optimize.default_hyperopt_loss import ShortTradeDurHyperOptLoss | from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss | ||||||
| from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver | from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ def test_text_table_bt_results(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): | def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     StrategyResolver.load_strategy(default_conf) |     StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     results = {'DefStrat': { |     results = {'DefStrat': { | ||||||
|   | |||||||
| @@ -879,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): | |||||||
|         'open_trade_value': 15.1668225, |         'open_trade_value': 15.1668225, | ||||||
|         'sell_reason': None, |         'sell_reason': None, | ||||||
|         'sell_order_status': None, |         'sell_order_status': None, | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'buy_tag': None, |         'buy_tag': None, | ||||||
|         'timeframe': 5, |         'timeframe': 5, | ||||||
|         'exchange': 'binance', |         'exchange': 'binance', | ||||||
| @@ -984,7 +984,7 @@ def test_api_forcebuy(botclient, mocker, fee): | |||||||
|         close_rate=0.265441, |         close_rate=0.265441, | ||||||
|         id=22, |         id=22, | ||||||
|         timeframe=5, |         timeframe=5, | ||||||
|         strategy="DefaultStrategy" |         strategy="StrategyTestV2" | ||||||
|     )) |     )) | ||||||
|     mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) |     mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) | ||||||
|  |  | ||||||
| @@ -1034,7 +1034,7 @@ def test_api_forcebuy(botclient, mocker, fee): | |||||||
|         'open_trade_value': 0.24605460, |         'open_trade_value': 0.24605460, | ||||||
|         'sell_reason': None, |         'sell_reason': None, | ||||||
|         'sell_order_status': None, |         'sell_order_status': None, | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'buy_tag': None, |         'buy_tag': None, | ||||||
|         'timeframe': 5, |         'timeframe': 5, | ||||||
|         'exchange': 'binance', |         'exchange': 'binance', | ||||||
| @@ -1101,7 +1101,7 @@ def test_api_pair_candles(botclient, ohlcv_history): | |||||||
|                     f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") |                     f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") | ||||||
|     assert_response(rc) |     assert_response(rc) | ||||||
|     assert 'strategy' in rc.json() |     assert 'strategy' in rc.json() | ||||||
|     assert rc.json()['strategy'] == 'DefaultStrategy' |     assert rc.json()['strategy'] == 'StrategyTestV2' | ||||||
|     assert 'columns' in rc.json() |     assert 'columns' in rc.json() | ||||||
|     assert 'data_start_ts' in rc.json() |     assert 'data_start_ts' in rc.json() | ||||||
|     assert 'data_start' in rc.json() |     assert 'data_start' in rc.json() | ||||||
| @@ -1139,19 +1139,19 @@ def test_api_pair_history(botclient, ohlcv_history): | |||||||
|     # No pair |     # No pair | ||||||
|     rc = client_get(client, |     rc = client_get(client, | ||||||
|                     f"{BASE_URI}/pair_history?timeframe={timeframe}" |                     f"{BASE_URI}/pair_history?timeframe={timeframe}" | ||||||
|                     "&timerange=20180111-20180112&strategy=DefaultStrategy") |                     "&timerange=20180111-20180112&strategy=StrategyTestV2") | ||||||
|     assert_response(rc, 422) |     assert_response(rc, 422) | ||||||
|  |  | ||||||
|     # No Timeframe |     # No Timeframe | ||||||
|     rc = client_get(client, |     rc = client_get(client, | ||||||
|                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" |                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" | ||||||
|                     "&timerange=20180111-20180112&strategy=DefaultStrategy") |                     "&timerange=20180111-20180112&strategy=StrategyTestV2") | ||||||
|     assert_response(rc, 422) |     assert_response(rc, 422) | ||||||
|  |  | ||||||
|     # No timerange |     # No timerange | ||||||
|     rc = client_get(client, |     rc = client_get(client, | ||||||
|                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" |                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" | ||||||
|                     "&strategy=DefaultStrategy") |                     "&strategy=StrategyTestV2") | ||||||
|     assert_response(rc, 422) |     assert_response(rc, 422) | ||||||
|  |  | ||||||
|     # No strategy |     # No strategy | ||||||
| @@ -1163,14 +1163,14 @@ def test_api_pair_history(botclient, ohlcv_history): | |||||||
|     # Working |     # Working | ||||||
|     rc = client_get(client, |     rc = client_get(client, | ||||||
|                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" |                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" | ||||||
|                     "&timerange=20180111-20180112&strategy=DefaultStrategy") |                     "&timerange=20180111-20180112&strategy=StrategyTestV2") | ||||||
|     assert_response(rc, 200) |     assert_response(rc, 200) | ||||||
|     assert rc.json()['length'] == 289 |     assert rc.json()['length'] == 289 | ||||||
|     assert len(rc.json()['data']) == rc.json()['length'] |     assert len(rc.json()['data']) == rc.json()['length'] | ||||||
|     assert 'columns' in rc.json() |     assert 'columns' in rc.json() | ||||||
|     assert 'data' in rc.json() |     assert 'data' in rc.json() | ||||||
|     assert rc.json()['pair'] == 'UNITTEST/BTC' |     assert rc.json()['pair'] == 'UNITTEST/BTC' | ||||||
|     assert rc.json()['strategy'] == 'DefaultStrategy' |     assert rc.json()['strategy'] == 'StrategyTestV2' | ||||||
|     assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00' |     assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00' | ||||||
|     assert rc.json()['data_start_ts'] == 1515628800000 |     assert rc.json()['data_start_ts'] == 1515628800000 | ||||||
|     assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00' |     assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00' | ||||||
| @@ -1179,7 +1179,7 @@ def test_api_pair_history(botclient, ohlcv_history): | |||||||
|     # No data found |     # No data found | ||||||
|     rc = client_get(client, |     rc = client_get(client, | ||||||
|                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" |                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" | ||||||
|                     "&timerange=20200111-20200112&strategy=DefaultStrategy") |                     "&timerange=20200111-20200112&strategy=StrategyTestV2") | ||||||
|     assert_response(rc, 502) |     assert_response(rc, 502) | ||||||
|     assert rc.json()['error'] == ("Error querying /api/v1/pair_history: " |     assert rc.json()['error'] == ("Error querying /api/v1/pair_history: " | ||||||
|                                   "No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") |                                   "No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") | ||||||
| @@ -1217,21 +1217,21 @@ def test_api_strategies(botclient): | |||||||
|  |  | ||||||
|     assert_response(rc) |     assert_response(rc) | ||||||
|     assert rc.json() == {'strategies': [ |     assert rc.json() == {'strategies': [ | ||||||
|         'DefaultStrategy', |  | ||||||
|         'HyperoptableStrategy', |         'HyperoptableStrategy', | ||||||
|         'TestStrategyLegacy' |         'StrategyTestV2', | ||||||
|  |         'TestStrategyLegacyV1' | ||||||
|     ]} |     ]} | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_api_strategy(botclient): | def test_api_strategy(botclient): | ||||||
|     ftbot, client = botclient |     ftbot, client = botclient | ||||||
|  |  | ||||||
|     rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy") |     rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2") | ||||||
|  |  | ||||||
|     assert_response(rc) |     assert_response(rc) | ||||||
|     assert rc.json()['strategy'] == 'DefaultStrategy' |     assert rc.json()['strategy'] == 'StrategyTestV2' | ||||||
|  |  | ||||||
|     data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text() |     data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text() | ||||||
|     assert rc.json()['code'] == data |     assert rc.json()['code'] == data | ||||||
|  |  | ||||||
|     rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") |     rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") | ||||||
| @@ -1288,7 +1288,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog): | |||||||
|  |  | ||||||
|     # start backtesting |     # start backtesting | ||||||
|     data = { |     data = { | ||||||
|         "strategy": "DefaultStrategy", |         "strategy": "StrategyTestV2", | ||||||
|         "timeframe": "5m", |         "timeframe": "5m", | ||||||
|         "timerange": "20180110-20180111", |         "timerange": "20180110-20180111", | ||||||
|         "max_open_trades": 3, |         "max_open_trades": 3, | ||||||
|   | |||||||
| @@ -1236,7 +1236,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None: | |||||||
|     assert msg_mock.call_count == 1 |     assert msg_mock.call_count == 1 | ||||||
|     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] |     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] | ||||||
|     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] |     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] | ||||||
|     assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0] |     assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0] | ||||||
|     assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] |     assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] | ||||||
|  |  | ||||||
|     msg_mock.reset_mock() |     msg_mock.reset_mock() | ||||||
| @@ -1245,7 +1245,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None: | |||||||
|     assert msg_mock.call_count == 1 |     assert msg_mock.call_count == 1 | ||||||
|     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] |     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] | ||||||
|     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] |     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] | ||||||
|     assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0] |     assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0] | ||||||
|     assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] |     assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,5 +5,5 @@ import nonexiting_module  # noqa | |||||||
| from freqtrade.strategy.interface import IStrategy | from freqtrade.strategy.interface import IStrategy | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestStrategyLegacy(IStrategy): | class TestStrategyLegacyV1(IStrategy): | ||||||
|     pass |     pass | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ from freqtrade.strategy.interface import IStrategy | |||||||
| # -------------------------------- | # -------------------------------- | ||||||
| 
 | 
 | ||||||
| # This class is a sample. Feel free to customize it. | # This class is a sample. Feel free to customize it. | ||||||
| class TestStrategyLegacy(IStrategy): | class TestStrategyLegacyV1(IStrategy): | ||||||
|     """ |     """ | ||||||
|     This is a test strategy using the legacy function headers, which will be |     This is a test strategy using the legacy function headers, which will be | ||||||
|     removed in a future update. |     removed in a future update. | ||||||
| @@ -7,9 +7,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib | |||||||
| from freqtrade.strategy.interface import IStrategy | from freqtrade.strategy.interface import IStrategy | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DefaultStrategy(IStrategy): | class StrategyTestV2(IStrategy): | ||||||
|     """ |     """ | ||||||
|     Default Strategy provided by freqtrade bot. |     Strategy used by tests freqtrade bot. | ||||||
|     Please do not modify this strategy, it's  intended for internal use only. |     Please do not modify this strategy, it's  intended for internal use only. | ||||||
|     Please look at the SampleStrategy in the user_data/strategy directory |     Please look at the SampleStrategy in the user_data/strategy directory | ||||||
|     or strategy repository https://github.com/freqtrade/freqtrade-strategies |     or strategy repository https://github.com/freqtrade/freqtrade-strategies | ||||||
| @@ -4,20 +4,20 @@ from pandas import DataFrame | |||||||
|  |  | ||||||
| from freqtrade.persistence.models import Trade | from freqtrade.persistence.models import Trade | ||||||
|  |  | ||||||
| from .strats.default_strategy import DefaultStrategy | from .strats.strategy_test_v2 import StrategyTestV2 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_default_strategy_structure(): | def test_strategy_test_v2_structure(): | ||||||
|     assert hasattr(DefaultStrategy, 'minimal_roi') |     assert hasattr(StrategyTestV2, 'minimal_roi') | ||||||
|     assert hasattr(DefaultStrategy, 'stoploss') |     assert hasattr(StrategyTestV2, 'stoploss') | ||||||
|     assert hasattr(DefaultStrategy, 'timeframe') |     assert hasattr(StrategyTestV2, 'timeframe') | ||||||
|     assert hasattr(DefaultStrategy, 'populate_indicators') |     assert hasattr(StrategyTestV2, 'populate_indicators') | ||||||
|     assert hasattr(DefaultStrategy, 'populate_buy_trend') |     assert hasattr(StrategyTestV2, 'populate_buy_trend') | ||||||
|     assert hasattr(DefaultStrategy, 'populate_sell_trend') |     assert hasattr(StrategyTestV2, 'populate_sell_trend') | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_default_strategy(result, fee): | def test_strategy_test_v2(result, fee): | ||||||
|     strategy = DefaultStrategy({}) |     strategy = StrategyTestV2({}) | ||||||
|  |  | ||||||
|     metadata = {'pair': 'ETH/BTC'} |     metadata = {'pair': 'ETH/BTC'} | ||||||
|     assert type(strategy.minimal_roi) is dict |     assert type(strategy.minimal_roi) is dict | ||||||
|   | |||||||
| @@ -22,11 +22,11 @@ from freqtrade.strategy.interface import SellCheckTuple | |||||||
| from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper | from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper | ||||||
| from tests.conftest import log_has, log_has_re | from tests.conftest import log_has, log_has_re | ||||||
|  |  | ||||||
| from .strats.default_strategy import DefaultStrategy | from .strats.strategy_test_v2 import StrategyTestV2 | ||||||
|  |  | ||||||
|  |  | ||||||
| # Avoid to reinit the same object again and again | # Avoid to reinit the same object again and again | ||||||
| _STRATEGY = DefaultStrategy(config={}) | _STRATEGY = StrategyTestV2(config={}) | ||||||
| _STRATEGY.dp = DataProvider({}, None, None) | _STRATEGY.dp = DataProvider({}, None, None) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -148,7 +148,7 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_ignore_expired_candle(default_conf): | def test_ignore_expired_candle(default_conf): | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     strategy.ignore_buying_expired_candle_after = 60 |     strategy.ignore_buying_expired_candle_after = 60 | ||||||
|  |  | ||||||
| @@ -233,7 +233,7 @@ def test_assert_df(ohlcv_history, caplog): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_advise_all_indicators(default_conf, testdatadir) -> None: | def test_advise_all_indicators(default_conf, testdatadir) -> None: | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     timerange = TimeRange.parse_timerange('1510694220-1510700340') |     timerange = TimeRange.parse_timerange('1510694220-1510700340') | ||||||
| @@ -244,7 +244,7 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None: | def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None: | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators') |     aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators') | ||||||
|     timerange = TimeRange.parse_timerange('1510694220-1510700340') |     timerange = TimeRange.parse_timerange('1510694220-1510700340') | ||||||
| @@ -262,7 +262,7 @@ def test_min_roi_reached(default_conf, fee) -> None: | |||||||
|     min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1}, |     min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1}, | ||||||
|                     {0: 0.1, 20: 0.05, 55: 0.01}] |                     {0: 0.1, 20: 0.05, 55: 0.01}] | ||||||
|     for roi in min_roi_list: |     for roi in min_roi_list: | ||||||
|         default_conf.update({'strategy': 'DefaultStrategy'}) |         default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|         strategy = StrategyResolver.load_strategy(default_conf) |         strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|         strategy.minimal_roi = roi |         strategy.minimal_roi = roi | ||||||
|         trade = Trade( |         trade = Trade( | ||||||
| @@ -301,7 +301,7 @@ def test_min_roi_reached2(default_conf, fee) -> None: | |||||||
|                      }, |                      }, | ||||||
|                     ] |                     ] | ||||||
|     for roi in min_roi_list: |     for roi in min_roi_list: | ||||||
|         default_conf.update({'strategy': 'DefaultStrategy'}) |         default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|         strategy = StrategyResolver.load_strategy(default_conf) |         strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|         strategy.minimal_roi = roi |         strategy.minimal_roi = roi | ||||||
|         trade = Trade( |         trade = Trade( | ||||||
| @@ -336,7 +336,7 @@ def test_min_roi_reached3(default_conf, fee) -> None: | |||||||
|                30: 0.05, |                30: 0.05, | ||||||
|                55: 0.30, |                55: 0.30, | ||||||
|                } |                } | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     strategy.minimal_roi = min_roi |     strategy.minimal_roi = min_roi | ||||||
|     trade = Trade( |     trade = Trade( | ||||||
| @@ -389,7 +389,7 @@ def test_min_roi_reached3(default_conf, fee) -> None: | |||||||
| def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom, | def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom, | ||||||
|                            profit2, adjusted2, expected2, custom_stop) -> None: |                            profit2, adjusted2, expected2, custom_stop) -> None: | ||||||
|  |  | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|  |  | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     trade = Trade( |     trade = Trade( | ||||||
| @@ -437,7 +437,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili | |||||||
|  |  | ||||||
| def test_custom_sell(default_conf, fee, caplog) -> None: | def test_custom_sell(default_conf, fee, caplog) -> None: | ||||||
|  |  | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|  |  | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     trade = Trade( |     trade = Trade( | ||||||
| @@ -491,7 +491,7 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: | |||||||
|         advise_sell=sell_mock, |         advise_sell=sell_mock, | ||||||
|  |  | ||||||
|     ) |     ) | ||||||
|     strategy = DefaultStrategy({}) |     strategy = StrategyTestV2({}) | ||||||
|     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) |     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) | ||||||
|     assert ind_mock.call_count == 1 |     assert ind_mock.call_count == 1 | ||||||
|     assert buy_mock.call_count == 1 |     assert buy_mock.call_count == 1 | ||||||
| @@ -522,7 +522,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> | |||||||
|         advise_sell=sell_mock, |         advise_sell=sell_mock, | ||||||
|  |  | ||||||
|     ) |     ) | ||||||
|     strategy = DefaultStrategy({}) |     strategy = StrategyTestV2({}) | ||||||
|     strategy.dp = DataProvider({}, None, None) |     strategy.dp = DataProvider({}, None, None) | ||||||
|     strategy.process_only_new_candles = True |     strategy.process_only_new_candles = True | ||||||
|  |  | ||||||
| @@ -554,7 +554,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> | |||||||
|  |  | ||||||
| @pytest.mark.usefixtures("init_persistence") | @pytest.mark.usefixtures("init_persistence") | ||||||
| def test_is_pair_locked(default_conf): | def test_is_pair_locked(default_conf): | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     PairLocks.timeframe = default_conf['timeframe'] |     PairLocks.timeframe = default_conf['timeframe'] | ||||||
|     PairLocks.use_db = True |     PairLocks.use_db = True | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -607,7 +607,7 @@ def test_is_pair_locked(default_conf): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_is_informative_pairs_callback(default_conf): | def test_is_informative_pairs_callback(default_conf): | ||||||
|     default_conf.update({'strategy': 'TestStrategyLegacy'}) |     default_conf.update({'strategy': 'TestStrategyLegacyV1'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     # Should return empty |     # Should return empty | ||||||
|     # Uses fallback to base implementation |     # Uses fallback to base implementation | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ def test_search_strategy(): | |||||||
|  |  | ||||||
|     s, _ = StrategyResolver._search_object( |     s, _ = StrategyResolver._search_object( | ||||||
|         directory=default_location, |         directory=default_location, | ||||||
|         object_name='DefaultStrategy', |         object_name='StrategyTestV2', | ||||||
|         add_source=True, |         add_source=True, | ||||||
|     ) |     ) | ||||||
|     assert issubclass(s, IStrategy) |     assert issubclass(s, IStrategy) | ||||||
| @@ -74,10 +74,10 @@ def test_load_strategy_base64(result, caplog, default_conf): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_strategy_invalid_directory(result, caplog, default_conf): | def test_load_strategy_invalid_directory(result, caplog, default_conf): | ||||||
|     default_conf['strategy'] = 'DefaultStrategy' |     default_conf['strategy'] = 'StrategyTestV2' | ||||||
|     extra_dir = Path.cwd() / 'some/path' |     extra_dir = Path.cwd() / 'some/path' | ||||||
|     with pytest.raises(OperationalException): |     with pytest.raises(OperationalException): | ||||||
|         StrategyResolver._load_strategy('DefaultStrategy', config=default_conf, |         StrategyResolver._load_strategy('StrategyTestV2', config=default_conf, | ||||||
|                                         extra_dir=extra_dir) |                                         extra_dir=extra_dir) | ||||||
|  |  | ||||||
|     assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog) |     assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog) | ||||||
| @@ -100,7 +100,7 @@ def test_load_strategy_noname(default_conf): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_strategy(result, default_conf): | def test_strategy(result, default_conf): | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|  |  | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     metadata = {'pair': 'ETH/BTC'} |     metadata = {'pair': 'ETH/BTC'} | ||||||
| @@ -127,7 +127,7 @@ def test_strategy(result, default_conf): | |||||||
| def test_strategy_override_minimal_roi(caplog, default_conf): | def test_strategy_override_minimal_roi(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'minimal_roi': { |         'minimal_roi': { | ||||||
|             "20": 0.1, |             "20": 0.1, | ||||||
|             "0": 0.5 |             "0": 0.5 | ||||||
| @@ -144,7 +144,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf): | |||||||
| def test_strategy_override_stoploss(caplog, default_conf): | def test_strategy_override_stoploss(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'stoploss': -0.5 |         'stoploss': -0.5 | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -156,7 +156,7 @@ def test_strategy_override_stoploss(caplog, default_conf): | |||||||
| def test_strategy_override_trailing_stop(caplog, default_conf): | def test_strategy_override_trailing_stop(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'trailing_stop': True |         'trailing_stop': True | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -169,7 +169,7 @@ def test_strategy_override_trailing_stop(caplog, default_conf): | |||||||
| def test_strategy_override_trailing_stop_positive(caplog, default_conf): | def test_strategy_override_trailing_stop_positive(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'trailing_stop_positive': -0.1, |         'trailing_stop_positive': -0.1, | ||||||
|         'trailing_stop_positive_offset': -0.2 |         'trailing_stop_positive_offset': -0.2 | ||||||
|  |  | ||||||
| @@ -189,7 +189,7 @@ def test_strategy_override_timeframe(caplog, default_conf): | |||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|  |  | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'timeframe': 60, |         'timeframe': 60, | ||||||
|         'stake_currency': 'ETH' |         'stake_currency': 'ETH' | ||||||
|     }) |     }) | ||||||
| @@ -205,7 +205,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf): | |||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|  |  | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'process_only_new_candles': True |         'process_only_new_candles': True | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -225,7 +225,7 @@ def test_strategy_override_order_types(caplog, default_conf): | |||||||
|         'stoploss_on_exchange': True, |         'stoploss_on_exchange': True, | ||||||
|     } |     } | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'order_types': order_types |         'order_types': order_types | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -239,12 +239,12 @@ def test_strategy_override_order_types(caplog, default_conf): | |||||||
|                    " 'stoploss_on_exchange': True}.", caplog) |                    " 'stoploss_on_exchange': True}.", caplog) | ||||||
|  |  | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'order_types': {'buy': 'market'} |         'order_types': {'buy': 'market'} | ||||||
|     }) |     }) | ||||||
|     # Raise error for invalid configuration |     # Raise error for invalid configuration | ||||||
|     with pytest.raises(ImportError, |     with pytest.raises(ImportError, | ||||||
|                        match=r"Impossible to load Strategy 'DefaultStrategy'. " |                        match=r"Impossible to load Strategy 'StrategyTestV2'. " | ||||||
|                              r"Order-types mapping is incomplete."): |                              r"Order-types mapping is incomplete."): | ||||||
|         StrategyResolver.load_strategy(default_conf) |         StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
| @@ -258,7 +258,7 @@ def test_strategy_override_order_tif(caplog, default_conf): | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'order_time_in_force': order_time_in_force |         'order_time_in_force': order_time_in_force | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -271,12 +271,12 @@ def test_strategy_override_order_tif(caplog, default_conf): | |||||||
|                    " {'buy': 'fok', 'sell': 'gtc'}.", caplog) |                    " {'buy': 'fok', 'sell': 'gtc'}.", caplog) | ||||||
|  |  | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'order_time_in_force': {'buy': 'fok'} |         'order_time_in_force': {'buy': 'fok'} | ||||||
|     }) |     }) | ||||||
|     # Raise error for invalid configuration |     # Raise error for invalid configuration | ||||||
|     with pytest.raises(ImportError, |     with pytest.raises(ImportError, | ||||||
|                        match=r"Impossible to load Strategy 'DefaultStrategy'. " |                        match=r"Impossible to load Strategy 'StrategyTestV2'. " | ||||||
|                              r"Order-time-in-force mapping is incomplete."): |                              r"Order-time-in-force mapping is incomplete."): | ||||||
|         StrategyResolver.load_strategy(default_conf) |         StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
| @@ -284,7 +284,7 @@ def test_strategy_override_order_tif(caplog, default_conf): | |||||||
| def test_strategy_override_use_sell_signal(caplog, default_conf): | def test_strategy_override_use_sell_signal(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     assert strategy.use_sell_signal |     assert strategy.use_sell_signal | ||||||
| @@ -294,7 +294,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf): | |||||||
|     assert default_conf['use_sell_signal'] |     assert default_conf['use_sell_signal'] | ||||||
|  |  | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'use_sell_signal': False, |         'use_sell_signal': False, | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -307,7 +307,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf): | |||||||
| def test_strategy_override_use_sell_profit_only(caplog, default_conf): | def test_strategy_override_use_sell_profit_only(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     assert not strategy.sell_profit_only |     assert not strategy.sell_profit_only | ||||||
| @@ -317,7 +317,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf): | |||||||
|     assert not default_conf['sell_profit_only'] |     assert not default_conf['sell_profit_only'] | ||||||
|  |  | ||||||
|     default_conf.update({ |     default_conf.update({ | ||||||
|         'strategy': 'DefaultStrategy', |         'strategy': 'StrategyTestV2', | ||||||
|         'sell_profit_only': True, |         'sell_profit_only': True, | ||||||
|     }) |     }) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
| @@ -330,7 +330,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf): | |||||||
| @pytest.mark.filterwarnings("ignore:deprecated") | @pytest.mark.filterwarnings("ignore:deprecated") | ||||||
| def test_deprecate_populate_indicators(result, default_conf): | def test_deprecate_populate_indicators(result, default_conf): | ||||||
|     default_location = Path(__file__).parent / "strats" |     default_location = Path(__file__).parent / "strats" | ||||||
|     default_conf.update({'strategy': 'TestStrategyLegacy', |     default_conf.update({'strategy': 'TestStrategyLegacyV1', | ||||||
|                          'strategy_path': default_location}) |                          'strategy_path': default_location}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     with warnings.catch_warnings(record=True) as w: |     with warnings.catch_warnings(record=True) as w: | ||||||
| @@ -365,7 +365,7 @@ def test_deprecate_populate_indicators(result, default_conf): | |||||||
| def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): | def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): | ||||||
|     default_location = Path(__file__).parent / "strats" |     default_location = Path(__file__).parent / "strats" | ||||||
|     del default_conf['timeframe'] |     del default_conf['timeframe'] | ||||||
|     default_conf.update({'strategy': 'TestStrategyLegacy', |     default_conf.update({'strategy': 'TestStrategyLegacyV1', | ||||||
|                          'strategy_path': default_location}) |                          'strategy_path': default_location}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     metadata = {'pair': 'ETH/BTC'} |     metadata = {'pair': 'ETH/BTC'} | ||||||
| @@ -395,7 +395,7 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_strategy_interface_versioning(result, monkeypatch, default_conf): | def test_strategy_interface_versioning(result, monkeypatch, default_conf): | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|     metadata = {'pair': 'ETH/BTC'} |     metadata = {'pair': 'ETH/BTC'} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -123,7 +123,7 @@ def test_parse_args_backtesting_custom() -> None: | |||||||
|         '-c', 'test_conf.json', |         '-c', 'test_conf.json', | ||||||
|         '--ticker-interval', '1m', |         '--ticker-interval', '1m', | ||||||
|         '--strategy-list', |         '--strategy-list', | ||||||
|         'DefaultStrategy', |         'StrategyTestV2', | ||||||
|         'SampleStrategy' |         'SampleStrategy' | ||||||
|     ] |     ] | ||||||
|     call_args = Arguments(args).get_parsed_arg() |     call_args = Arguments(args).get_parsed_arg() | ||||||
|   | |||||||
| @@ -404,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> | |||||||
|     arglist = [ |     arglist = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     args = Arguments(arglist).get_parsed_arg() |     args = Arguments(arglist).get_parsed_arg() | ||||||
| @@ -441,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non | |||||||
|     arglist = [ |     arglist = [ | ||||||
|         'backtesting', |         'backtesting', | ||||||
|         '--config', 'config.json', |         '--config', 'config.json', | ||||||
|         '--strategy', 'DefaultStrategy', |         '--strategy', 'StrategyTestV2', | ||||||
|         '--datadir', '/foo/bar', |         '--datadir', '/foo/bar', | ||||||
|         '--userdir', "/tmp/freqtrade", |         '--userdir', "/tmp/freqtrade", | ||||||
|         '--ticker-interval', '1m', |         '--ticker-interval', '1m', | ||||||
| @@ -498,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non | |||||||
|         '--ticker-interval', '1m', |         '--ticker-interval', '1m', | ||||||
|         '--export', 'trades', |         '--export', 'trades', | ||||||
|         '--strategy-list', |         '--strategy-list', | ||||||
|         'DefaultStrategy', |         'StrategyTestV2', | ||||||
|         'TestStrategy' |         'TestStrategy' | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -185,7 +185,7 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b | |||||||
|             limit_buy_order_open['id'] = str(i) |             limit_buy_order_open['id'] = str(i) | ||||||
|             result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC') |             result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC') | ||||||
|             assert pytest.approx(result) == expected[i] |             assert pytest.approx(result) == expected[i] | ||||||
|             freqtrade.execute_buy('ETH/BTC', result) |             freqtrade.execute_entry('ETH/BTC', result) | ||||||
|         else: |         else: | ||||||
|             with pytest.raises(DependencyException): |             with pytest.raises(DependencyException): | ||||||
|                 freqtrade.wallets.get_trade_stake_amount('ETH/BTC') |                 freqtrade.wallets.get_trade_stake_amount('ETH/BTC') | ||||||
| @@ -584,8 +584,8 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde | |||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|  |  | ||||||
|     # Create 2 existing trades |     # Create 2 existing trades | ||||||
|     freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount']) |     freqtrade.execute_entry('ETH/BTC', default_conf['stake_amount']) | ||||||
|     freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount']) |     freqtrade.execute_entry('NEO/BTC', default_conf['stake_amount']) | ||||||
|  |  | ||||||
|     assert len(Trade.get_open_trades()) == 2 |     assert len(Trade.get_open_trades()) == 2 | ||||||
|     # Change order_id for new orders |     # Change order_id for new orders | ||||||
| @@ -776,7 +776,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: | |||||||
|     assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0] |     assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: | def test_execute_entry(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: | ||||||
|     patch_RPCManager(mocker) |     patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
| @@ -799,7 +799,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|     ) |     ) | ||||||
|     pair = 'ETH/BTC' |     pair = 'ETH/BTC' | ||||||
|  |  | ||||||
|     assert not freqtrade.execute_buy(pair, stake_amount) |     assert not freqtrade.execute_entry(pair, stake_amount) | ||||||
|     assert buy_rate_mock.call_count == 1 |     assert buy_rate_mock.call_count == 1 | ||||||
|     assert buy_mm.call_count == 0 |     assert buy_mm.call_count == 0 | ||||||
|     assert freqtrade.strategy.confirm_trade_entry.call_count == 1 |     assert freqtrade.strategy.confirm_trade_entry.call_count == 1 | ||||||
| @@ -807,7 +807,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|  |  | ||||||
|     limit_buy_order_open['id'] = '22' |     limit_buy_order_open['id'] = '22' | ||||||
|     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) |     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     assert buy_rate_mock.call_count == 1 |     assert buy_rate_mock.call_count == 1 | ||||||
|     assert buy_mm.call_count == 1 |     assert buy_mm.call_count == 1 | ||||||
|     call_args = buy_mm.call_args_list[0][1] |     call_args = buy_mm.call_args_list[0][1] | ||||||
| @@ -826,7 +826,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|     # Test calling with price |     # Test calling with price | ||||||
|     limit_buy_order_open['id'] = '33' |     limit_buy_order_open['id'] = '33' | ||||||
|     fix_price = 0.06 |     fix_price = 0.06 | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount, fix_price) |     assert freqtrade.execute_entry(pair, stake_amount, fix_price) | ||||||
|     # Make sure get_rate wasn't called again |     # Make sure get_rate wasn't called again | ||||||
|     assert buy_rate_mock.call_count == 0 |     assert buy_rate_mock.call_count == 0 | ||||||
|  |  | ||||||
| @@ -844,7 +844,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|  |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.create_order', |     mocker.patch('freqtrade.exchange.Exchange.create_order', | ||||||
|                  MagicMock(return_value=limit_buy_order)) |                  MagicMock(return_value=limit_buy_order)) | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     trade = Trade.query.all()[2] |     trade = Trade.query.all()[2] | ||||||
|     assert trade |     assert trade | ||||||
|     assert trade.open_order_id is None |     assert trade.open_order_id is None | ||||||
| @@ -861,7 +861,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|     limit_buy_order['id'] = '555' |     limit_buy_order['id'] = '555' | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.create_order', |     mocker.patch('freqtrade.exchange.Exchange.create_order', | ||||||
|                  MagicMock(return_value=limit_buy_order)) |                  MagicMock(return_value=limit_buy_order)) | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     trade = Trade.query.all()[3] |     trade = Trade.query.all()[3] | ||||||
|     assert trade |     assert trade | ||||||
|     assert trade.open_order_id == '555' |     assert trade.open_order_id == '555' | ||||||
| @@ -873,7 +873,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|     limit_buy_order['id'] = '556' |     limit_buy_order['id'] = '556' | ||||||
|  |  | ||||||
|     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0 |     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0 | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     trade = Trade.query.all()[4] |     trade = Trade.query.all()[4] | ||||||
|     assert trade |     assert trade | ||||||
|     assert trade.stake_amount == 150 |     assert trade.stake_amount == 150 | ||||||
| @@ -881,7 +881,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|     # Exception case |     # Exception case | ||||||
|     limit_buy_order['id'] = '557' |     limit_buy_order['id'] = '557' | ||||||
|     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 |     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     trade = Trade.query.all()[5] |     trade = Trade.query.all()[5] | ||||||
|     assert trade |     assert trade | ||||||
|     assert trade.stake_amount == 2.0 |     assert trade.stake_amount == 2.0 | ||||||
| @@ -896,20 +896,20 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|     limit_buy_order['id'] = '66' |     limit_buy_order['id'] = '66' | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.create_order', |     mocker.patch('freqtrade.exchange.Exchange.create_order', | ||||||
|                  MagicMock(return_value=limit_buy_order)) |                  MagicMock(return_value=limit_buy_order)) | ||||||
|     assert not freqtrade.execute_buy(pair, stake_amount) |     assert not freqtrade.execute_entry(pair, stake_amount) | ||||||
|  |  | ||||||
|     # Fail to get price... |     # Fail to get price... | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) |     mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) | ||||||
|  |  | ||||||
|     with pytest.raises(PricingError, match="Could not determine buy price."): |     with pytest.raises(PricingError, match="Could not determine buy price."): | ||||||
|         freqtrade.execute_buy(pair, stake_amount) |         freqtrade.execute_entry(pair, stake_amount) | ||||||
|  |  | ||||||
|     # In case of custom entry price |     # In case of custom entry price | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50) |     mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50) | ||||||
|     limit_buy_order['status'] = 'open' |     limit_buy_order['status'] = 'open' | ||||||
|     limit_buy_order['id'] = '5566' |     limit_buy_order['id'] = '5566' | ||||||
|     freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508 |     freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508 | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     trade = Trade.query.all()[6] |     trade = Trade.query.all()[6] | ||||||
|     assert trade |     assert trade | ||||||
|     assert trade.open_rate_requested == 0.508 |     assert trade.open_rate_requested == 0.508 | ||||||
| @@ -924,7 +924,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|         get_rate=MagicMock(return_value=10), |         get_rate=MagicMock(return_value=10), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     trade = Trade.query.all()[7] |     trade = Trade.query.all()[7] | ||||||
|     assert trade |     assert trade | ||||||
|     assert trade.open_rate_requested == 10 |     assert trade.open_rate_requested == 10 | ||||||
| @@ -933,13 +933,13 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order | |||||||
|     limit_buy_order['status'] = 'open' |     limit_buy_order['status'] = 'open' | ||||||
|     limit_buy_order['id'] = '5568' |     limit_buy_order['id'] = '5568' | ||||||
|     freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" |     freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|     trade = Trade.query.all()[8] |     trade = Trade.query.all()[8] | ||||||
|     assert trade |     assert trade | ||||||
|     assert trade.open_rate_requested == 10 |     assert trade.open_rate_requested == 10 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: | def test_execute_entry_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: | ||||||
|     freqtrade = get_patched_freqtradebot(mocker, default_conf) |     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
| @@ -957,18 +957,18 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) - | |||||||
|     pair = 'ETH/BTC' |     pair = 'ETH/BTC' | ||||||
|  |  | ||||||
|     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError) |     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError) | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|  |  | ||||||
|     limit_buy_order['id'] = '222' |     limit_buy_order['id'] = '222' | ||||||
|     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception) |     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception) | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|  |  | ||||||
|     limit_buy_order['id'] = '2223' |     limit_buy_order['id'] = '2223' | ||||||
|     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) |     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) | ||||||
|     assert freqtrade.execute_buy(pair, stake_amount) |     assert freqtrade.execute_entry(pair, stake_amount) | ||||||
|  |  | ||||||
|     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) |     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) | ||||||
|     assert not freqtrade.execute_buy(pair, stake_amount) |     assert not freqtrade.execute_entry(pair, stake_amount) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None: | def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None: | ||||||
| @@ -2007,7 +2007,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, | |||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
|     trade.is_open = True |     trade.is_open = True | ||||||
|  |  | ||||||
|     # FIX: sniffing logs, suggest handle_trade should not execute_sell |     # FIX: sniffing logs, suggest handle_trade should not execute_trade_exit | ||||||
|     #      instead that responsibility should be moved out of handle_trade(), |     #      instead that responsibility should be moved out of handle_trade(), | ||||||
|     #      we might just want to check if we are in a sell condition without |     #      we might just want to check if we are in a sell condition without | ||||||
|     #      executing |     #      executing | ||||||
| @@ -2634,7 +2634,7 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None: | |||||||
|     assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order' |     assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order' | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: | def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: | ||||||
|     rpc_mock = patch_RPCManager(mocker) |     rpc_mock = patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -2662,16 +2662,16 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N | |||||||
|         fetch_ticker=ticker_sell_up |         fetch_ticker=ticker_sell_up | ||||||
|     ) |     ) | ||||||
|     # Prevented sell ... |     # Prevented sell ... | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.ROI)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||||
|     assert rpc_mock.call_count == 0 |     assert rpc_mock.call_count == 0 | ||||||
|     assert freqtrade.strategy.confirm_trade_exit.call_count == 1 |     assert freqtrade.strategy.confirm_trade_exit.call_count == 1 | ||||||
|  |  | ||||||
|     # Repatch with true |     # Repatch with true | ||||||
|     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) |     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) | ||||||
|  |  | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.ROI)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||||
|     assert freqtrade.strategy.confirm_trade_exit.call_count == 1 |     assert freqtrade.strategy.confirm_trade_exit.call_count == 1 | ||||||
|  |  | ||||||
|     assert rpc_mock.call_count == 1 |     assert rpc_mock.call_count == 1 | ||||||
| @@ -2698,7 +2698,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N | |||||||
|     } == last_msg |     } == last_msg | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: | def test_execute_trade_exit_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: | ||||||
|     rpc_mock = patch_RPCManager(mocker) |     rpc_mock = patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -2723,8 +2723,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) | |||||||
|         fetch_ticker=ticker_sell_down |         fetch_ticker=ticker_sell_down | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||||
|  |  | ||||||
|     assert rpc_mock.call_count == 2 |     assert rpc_mock.call_count == 2 | ||||||
|     last_msg = rpc_mock.call_args_list[-1][0][0] |     last_msg = rpc_mock.call_args_list[-1][0][0] | ||||||
| @@ -2750,7 +2750,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) | |||||||
|     } == last_msg |     } == last_msg | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: | def test_execute_trade_exit_custom_exit_price(default_conf, ticker, fee, ticker_sell_up, | ||||||
|  |                                               mocker) -> None: | ||||||
|     rpc_mock = patch_RPCManager(mocker) |     rpc_mock = patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -2783,8 +2784,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u | |||||||
|     # Set a custom exit price |     # Set a custom exit price | ||||||
|     freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05 |     freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05 | ||||||
|  |  | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)) | ||||||
|  |  | ||||||
|     # Sell price must be different to default bid price |     # Sell price must be different to default bid price | ||||||
|  |  | ||||||
| @@ -2814,8 +2815,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u | |||||||
|     } == last_msg |     } == last_msg | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, | def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, | ||||||
|                                                         ticker_sell_down, mocker) -> None: |                                                               ticker_sell_down, mocker) -> None: | ||||||
|     rpc_mock = patch_RPCManager(mocker) |     rpc_mock = patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -2845,8 +2846,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe | |||||||
|     # Setting trade stoploss to 0.01 |     # Setting trade stoploss to 0.01 | ||||||
|  |  | ||||||
|     trade.stop_loss = 0.00001099 * 0.99 |     trade.stop_loss = 0.00001099 * 0.99 | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||||
|  |  | ||||||
|     assert rpc_mock.call_count == 2 |     assert rpc_mock.call_count == 2 | ||||||
|     last_msg = rpc_mock.call_args_list[-1][0][0] |     last_msg = rpc_mock.call_args_list[-1][0][0] | ||||||
| @@ -2873,7 +2874,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe | |||||||
|     } == last_msg |     } == last_msg | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None: | def test_execute_trade_exit_sloe_cancel_exception( | ||||||
|  |         mocker, default_conf, ticker, fee, caplog) -> None: | ||||||
|     freqtrade = get_patched_freqtradebot(mocker, default_conf) |     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', |     mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', | ||||||
|                  side_effect=InvalidOrderException()) |                  side_effect=InvalidOrderException()) | ||||||
| @@ -2900,14 +2902,14 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c | |||||||
|     freqtrade.config['dry_run'] = False |     freqtrade.config['dry_run'] = False | ||||||
|     trade.stoploss_order_id = "abcd" |     trade.stoploss_order_id = "abcd" | ||||||
|  |  | ||||||
|     freqtrade.execute_sell(trade=trade, limit=1234, |     freqtrade.execute_trade_exit(trade=trade, limit=1234, | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||||
|     assert create_order_mock.call_count == 2 |     assert create_order_mock.call_count == 2 | ||||||
|     assert log_has('Could not cancel stoploss order abcd', caplog) |     assert log_has('Could not cancel stoploss order abcd', caplog) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up, | def test_execute_trade_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up, | ||||||
|                                                 mocker) -> None: |                                                       mocker) -> None: | ||||||
|  |  | ||||||
|     default_conf['exchange']['name'] = 'binance' |     default_conf['exchange']['name'] = 'binance' | ||||||
|     rpc_mock = patch_RPCManager(mocker) |     rpc_mock = patch_RPCManager(mocker) | ||||||
| @@ -2951,8 +2953,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke | |||||||
|         fetch_ticker=ticker_sell_up |         fetch_ticker=ticker_sell_up | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||||
|  |  | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
|     assert trade |     assert trade | ||||||
| @@ -2960,8 +2962,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke | |||||||
|     assert rpc_mock.call_count == 3 |     assert rpc_mock.call_count == 3 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee, | def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee, | ||||||
|                                                          mocker) -> None: |                                                                mocker) -> None: | ||||||
|     default_conf['exchange']['name'] = 'binance' |     default_conf['exchange']['name'] = 'binance' | ||||||
|     rpc_mock = patch_RPCManager(mocker) |     rpc_mock = patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
| @@ -3032,8 +3034,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f | |||||||
|     assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL |     assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_market_order(default_conf, ticker, fee, | def test_execute_trade_exit_market_order(default_conf, ticker, fee, | ||||||
|                                    ticker_sell_up, mocker) -> None: |                                          ticker_sell_up, mocker) -> None: | ||||||
|     rpc_mock = patch_RPCManager(mocker) |     rpc_mock = patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -3059,8 +3061,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee, | |||||||
|     ) |     ) | ||||||
|     freqtrade.config['order_types']['sell'] = 'market' |     freqtrade.config['order_types']['sell'] = 'market' | ||||||
|  |  | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.ROI)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||||
|  |  | ||||||
|     assert not trade.is_open |     assert not trade.is_open | ||||||
|     assert trade.close_profit == 0.0620716 |     assert trade.close_profit == 0.0620716 | ||||||
| @@ -3090,8 +3092,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee, | |||||||
|     } == last_msg |     } == last_msg | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, | def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee, | ||||||
|                                                ticker_sell_up, mocker) -> None: |                                                      ticker_sell_up, mocker) -> None: | ||||||
|     freqtrade = get_patched_freqtradebot(mocker, default_conf) |     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||||
|     mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') |     mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -3118,8 +3120,8 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     sell_reason = SellCheckTuple(sell_type=SellType.ROI) |     sell_reason = SellCheckTuple(sell_type=SellType.ROI) | ||||||
|     assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], |     assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||||
|                                       sell_reason=sell_reason) |                                             sell_reason=sell_reason) | ||||||
|     assert mock_insuf.call_count == 1 |     assert mock_insuf.call_count == 1 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -3375,8 +3377,8 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo | |||||||
|         fetch_ticker=ticker_sell_down |         fetch_ticker=ticker_sell_down | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], |     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], | ||||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) |                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||||
|     trade.close(ticker_sell_down()['bid']) |     trade.close(ticker_sell_down()['bid']) | ||||||
|     assert freqtrade.strategy.is_pair_locked(trade.pair) |     assert freqtrade.strategy.is_pair_locked(trade.pair) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ from freqtrade.strategy.interface import SellCheckTuple | |||||||
| from tests.conftest import get_patched_freqtradebot, patch_get_signal | from tests.conftest import get_patched_freqtradebot, patch_get_signal | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, | def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, | ||||||
|                                                      limit_buy_order, mocker) -> None: |                                                      limit_buy_order, mocker) -> None: | ||||||
|     """ |     """ | ||||||
|     Tests workflow of selling stoploss_on_exchange. |     Tests workflow of selling stoploss_on_exchange. | ||||||
|   | |||||||
| @@ -70,7 +70,6 @@ def test_add_indicators(default_conf, testdatadir, caplog): | |||||||
|     indicators1 = {"ema10": {}} |     indicators1 = {"ema10": {}} | ||||||
|     indicators2 = {"macd": {"color": "red"}} |     indicators2 = {"macd": {"color": "red"}} | ||||||
|  |  | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |  | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     # Generate buy/sell signals and indicators |     # Generate buy/sell signals and indicators | ||||||
| @@ -112,7 +111,6 @@ def test_add_areas(default_conf, testdatadir, caplog): | |||||||
|                              "fill_to": "macdhist"}} |                              "fill_to": "macdhist"}} | ||||||
|  |  | ||||||
|     ind_plain = {"macd": {"fill_to": "macdhist"}} |     ind_plain = {"macd": {"fill_to": "macdhist"}} | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |  | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     # Generate buy/sell signals and indicators |     # Generate buy/sell signals and indicators | ||||||
| @@ -239,7 +237,6 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir) | |||||||
|     data = history.load_pair_history(pair=pair, timeframe='1m', |     data = history.load_pair_history(pair=pair, timeframe='1m', | ||||||
|                                      datadir=testdatadir, timerange=timerange) |                                      datadir=testdatadir, timerange=timerange) | ||||||
|  |  | ||||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) |  | ||||||
|     strategy = StrategyResolver.load_strategy(default_conf) |     strategy = StrategyResolver.load_strategy(default_conf) | ||||||
|  |  | ||||||
|     # Generate buy/sell signals and indicators |     # Generate buy/sell signals and indicators | ||||||
|   | |||||||
| @@ -157,13 +157,13 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r | |||||||
|     assert result == result1 |     assert result == result1 | ||||||
|  |  | ||||||
|     # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' |     # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' | ||||||
|     freqtrade.execute_buy('ETH/USDT', result) |     freqtrade.execute_entry('ETH/USDT', result) | ||||||
|  |  | ||||||
|     result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT') |     result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT') | ||||||
|     assert result == result1 |     assert result == result1 | ||||||
|  |  | ||||||
|     # create 2 trades, order amount should be None |     # create 2 trades, order amount should be None | ||||||
|     freqtrade.execute_buy('LTC/BTC', result) |     freqtrade.execute_entry('LTC/BTC', result) | ||||||
|  |  | ||||||
|     result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT') |     result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT') | ||||||
|     assert result == 0 |     assert result == 0 | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								tests/testdata/backtest-result_new.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								tests/testdata/backtest-result_new.json
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user