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 | ||||
| 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 | ||||
|  | ||||
| # 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 | ||||
|     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 | ||||
|  | ||||
| # 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 | ||||
|     echo "failed running backtest" | ||||
|   | ||||
| @@ -174,7 +174,7 @@ | ||||
|         "heartbeat_interval": 60 | ||||
|     }, | ||||
|     "disable_dataframe_checks": false, | ||||
|     "strategy": "DefaultStrategy", | ||||
|     "strategy": "SampleStrategy", | ||||
|     "strategy_path": "user_data/strategies/", | ||||
|     "dataformat_ohlcv": "json", | ||||
|     "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. | ||||
|  | ||||
| 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. | ||||
| 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] | ||||
|                              [--enable-protections] | ||||
|                              [--dry-run-wallet DRY_RUN_WALLET] | ||||
|                              [--timeframe-detail TIMEFRAME_DETAIL] | ||||
|                              [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] | ||||
|                              [--export {none,trades}] [--export-filename PATH] | ||||
|  | ||||
| @@ -55,6 +56,9 @@ optional arguments: | ||||
|   --dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET | ||||
|                         Starting balance, used for backtesting / hyperopt and | ||||
|                         dry-runs. | ||||
|   --timeframe-detail TIMEFRAME_DETAIL | ||||
|                         Specify detail timeframe for backtesting (`1m`, `5m`, | ||||
|                         `30m`, `1h`, `1d`). | ||||
|   --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] | ||||
|                         Provide a space-separated list of strategies to | ||||
|                         backtest. Please note that ticker-interval needs to be | ||||
| @@ -62,7 +66,7 @@ optional arguments: | ||||
|                         this together with `--export trades`, the strategy- | ||||
|                         name is injected into the filename (so `backtest- | ||||
|                         data.json` becomes `backtest-data- | ||||
|                         DefaultStrategy.json` | ||||
|                         SampleStrategy.json` | ||||
|   --export {none,trades} | ||||
|                         Export backtest results (default: trades). | ||||
|   --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). | ||||
| - `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: | ||||
|  | ||||
| @@ -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. | ||||
|  | ||||
| ### Further backtest-result analysis | ||||
| ### Improved backtest accuracy | ||||
|  | ||||
| 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. | ||||
| One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?). | ||||
| 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 | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
| * **Trade**: Open position. | ||||
| * **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"`, ...). | ||||
| * **Indicators**: Technical indicators (SMA, EMA, RSI, ...). | ||||
| * **Limit order**: Limit orders which execute at the defined limit price or better. | ||||
|   | ||||
| @@ -456,7 +456,7 @@ class MyAwesomeStrategy(IStrategy): | ||||
|                 "only_per_pair": False | ||||
|             }) | ||||
|  | ||||
|         return protection | ||||
|         return prot | ||||
|  | ||||
|     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. | ||||
|  | ||||
| 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. | ||||
| 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-material==7.2.4 | ||||
| mkdocs-material==7.2.5 | ||||
| mdx_truly_sane_lists==1.2 | ||||
| pymdown-extensions==8.2 | ||||
|   | ||||
| @@ -22,7 +22,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", | ||||
|                         "max_open_trades", "stake_amount", "fee", "pairs"] | ||||
|  | ||||
| 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"] | ||||
|  | ||||
| ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", | ||||
|   | ||||
| @@ -66,16 +66,22 @@ def ask_user_config() -> Dict[str, Any]: | ||||
|         { | ||||
|             "type": "text", | ||||
|             "name": "stake_amount", | ||||
|             "message": "Please insert your stake amount:", | ||||
|             "message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):", | ||||
|             "default": "0.01", | ||||
|             "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", | ||||
|             "name": "max_open_trades", | ||||
|             "message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):", | ||||
|             "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", | ||||
|   | ||||
| @@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = { | ||||
|         help='Override the value of the `stake_amount` configuration setting.', | ||||
|     ), | ||||
|     # Backtesting | ||||
|     "timeframe_detail": Arg( | ||||
|         '--timeframe-detail', | ||||
|         help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).', | ||||
|     ), | ||||
|     "position_stacking": Arg( | ||||
|         '--eps', '--enable-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 ' | ||||
|         'or via command line. When using this together with `--export trades`, ' | ||||
|         'the strategy-name is injected into the filename ' | ||||
|         '(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`', | ||||
|         '(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`', | ||||
|         nargs='+', | ||||
|     ), | ||||
|     "export": Arg( | ||||
|   | ||||
| @@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None: | ||||
|     config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) | ||||
|  | ||||
|     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') | ||||
|  | ||||
| @@ -128,8 +126,6 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: | ||||
|     config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) | ||||
|  | ||||
|     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') | ||||
|  | ||||
|   | ||||
| @@ -242,6 +242,9 @@ class Configuration: | ||||
|             except ValueError: | ||||
|                 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', | ||||
|                              logstring='Parameter --stake-amount detected, ' | ||||
|                              'overriding stake_amount to: {} ...') | ||||
|   | ||||
| @@ -49,6 +49,8 @@ USERPATH_NOTEBOOKS = 'notebooks' | ||||
| TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent'] | ||||
| ENV_VAR_PREFIX = 'FREQTRADE__' | ||||
|  | ||||
| NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired') | ||||
|  | ||||
|  | ||||
| # Define decimals per coin 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) | ||||
| 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.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, | ||||
|                                   InvalidOrderException, OperationalException, PricingError, | ||||
| @@ -351,9 +352,16 @@ class Exchange: | ||||
|     def validate_stakecurrency(self, stake_currency: str) -> None: | ||||
|         """ | ||||
|         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 | ||||
|         :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() | ||||
|         if stake_currency not in quote_currencies: | ||||
|             raise OperationalException( | ||||
| @@ -804,7 +812,7 @@ class Exchange: | ||||
|         :param order: Order dict as returned from fetch_order() | ||||
|         :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) | ||||
|  | ||||
|     @retrier | ||||
|   | ||||
| @@ -433,11 +433,11 @@ class FreqtradeBot(LoggingMixin): | ||||
|             if ((bid_check_dom.get('enabled', False)) and | ||||
|                     (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): | ||||
|                 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: | ||||
|                     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: | ||||
|             return False | ||||
|  | ||||
| @@ -465,8 +465,8 @@ class FreqtradeBot(LoggingMixin): | ||||
|             logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") | ||||
|             return False | ||||
|  | ||||
|     def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, | ||||
|                     forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: | ||||
|     def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, | ||||
|                       forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: | ||||
|         """ | ||||
|         Executes a limit buy for the given pair | ||||
|         :param pair: pair for which we want to create a LIMIT_BUY | ||||
| @@ -745,7 +745,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|             trade.stoploss_order_id = None | ||||
|             logger.error(f'Unable to place a stoploss order on exchange. {e}') | ||||
|             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)) | ||||
|  | ||||
|         except ExchangeError: | ||||
| @@ -863,7 +863,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|  | ||||
|         if should_sell.sell_flag: | ||||
|             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 False | ||||
|  | ||||
| @@ -945,7 +945,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|         was_trade_fully_canceled = False | ||||
|  | ||||
|         # 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_stake = filled_val * trade.open_rate | ||||
|             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. | ||||
|             # Simply bailing here is the only safe way - as this order will then be | ||||
|             # 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.") | ||||
|                 return False | ||||
|         else: | ||||
| @@ -1064,9 +1064,9 @@ class FreqtradeBot(LoggingMixin): | ||||
|             raise DependencyException( | ||||
|                 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 limit: limit rate for the sell order | ||||
|         :param sell_reason: Reason the sell was triggered | ||||
| @@ -1142,7 +1142,7 @@ class FreqtradeBot(LoggingMixin): | ||||
|         trade.close_rate_requested = limit | ||||
|         trade.sell_reason = sell_reason.sell_reason | ||||
|         # 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) | ||||
|         Trade.commit() | ||||
|  | ||||
|   | ||||
| @@ -86,6 +86,17 @@ class Backtesting: | ||||
|                                        "configuration or as cli argument `--timeframe 5m`") | ||||
|         self.timeframe = str(self.config.get('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) | ||||
|         if 'VolumePairList' in self.pairlists.name_list: | ||||
| @@ -188,6 +199,23 @@ class Backtesting: | ||||
|         self.progress.set_new_value(1) | ||||
|         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): | ||||
|         """ | ||||
|         Backtesting setup method - called once for every call to "backtest()". | ||||
| @@ -320,7 +348,8 @@ class Backtesting: | ||||
|         else: | ||||
|             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 = self.strategy.should_sell(trade, sell_row[OPEN_IDX],  # type: ignore | ||||
|                                          sell_candle_time, sell_row[BUY_IDX], | ||||
| @@ -348,6 +377,32 @@ class Backtesting: | ||||
|  | ||||
|         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]: | ||||
|         try: | ||||
|             stake_amount = self.wallets.get_trade_stake_amount(pair, None) | ||||
| @@ -594,6 +649,7 @@ class Backtesting: | ||||
|         data: Dict[str, Any] = {} | ||||
|  | ||||
|         data, timerange = self.load_bt_data() | ||||
|         self.load_bt_data_detail() | ||||
|         logger.info("Dataload complete. Calculating indicators") | ||||
|  | ||||
|         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'] | ||||
|                                     if config['max_open_trades'] != float('inf') else -1), | ||||
|         'timeframe': config['timeframe'], | ||||
|         'timeframe_detail': config.get('timeframe_detail', ''), | ||||
|         'timerange': config.get('timerange', ''), | ||||
|         'enable_protections': config.get('enable_protections', False), | ||||
|         'strategy_name': strategy, | ||||
|   | ||||
| @@ -13,7 +13,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session | ||||
| from sqlalchemy.pool import StaticPool | ||||
| 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.exceptions import DependencyException, OperationalException | ||||
| 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.ft_is_open = True | ||||
|         if self.status in ('closed', 'canceled', 'cancelled'): | ||||
|         if self.status in NON_OPEN_EXCHANGE_STATES: | ||||
|             self.ft_is_open = False | ||||
|             if (order.get('filled', 0.0) or 0.0) > 0: | ||||
|                 self.order_filled_date = datetime.now(timezone.utc) | ||||
|   | ||||
| @@ -46,11 +46,14 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac | ||||
|             if ( | ||||
|                 not ApiServer._bt | ||||
|                 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('timerange') != btconfig['timerange'] | ||||
|             ): | ||||
|                 from freqtrade.optimize.backtesting import Backtesting | ||||
|                 ApiServer._bt = Backtesting(btconfig) | ||||
|                 if ApiServer._bt.timeframe_detail: | ||||
|                     ApiServer._bt.load_bt_data_detail() | ||||
|  | ||||
|             # Only reload data if timeframe changed. | ||||
|             if ( | ||||
|   | ||||
| @@ -324,6 +324,7 @@ class PairHistory(BaseModel): | ||||
| class BacktestRequest(BaseModel): | ||||
|     strategy: str | ||||
|     timeframe: Optional[str] | ||||
|     timeframe_detail: Optional[str] | ||||
|     timerange: Optional[str] | ||||
|     max_open_trades: Optional[int] | ||||
|     stake_amount: Optional[Union[float, str]] | ||||
|   | ||||
| @@ -557,7 +557,7 @@ class RPC: | ||||
|                 current_rate = self._freqtrade.exchange.get_rate( | ||||
|                     trade.pair, refresh=False, side="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 ---- | ||||
|  | ||||
|         if self._freqtrade.state != State.RUNNING: | ||||
| @@ -613,7 +613,7 @@ class RPC: | ||||
|         stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) | ||||
|  | ||||
|         # 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 = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() | ||||
|             return trade | ||||
|   | ||||
| @@ -120,6 +120,8 @@ class IStrategy(ABC, HyperStrategyMixin): | ||||
|     # and wallets - access to the current balance. | ||||
|     dp: Optional[DataProvider] = None | ||||
|     wallets: Optional[Wallets] = None | ||||
|     # Filled from configuration | ||||
|     stake_currency: str | ||||
|     # container variable for strategy source code | ||||
|     __source__: str = '' | ||||
|  | ||||
|   | ||||
| @@ -36,6 +36,6 @@ | ||||
|         "BNB/TUSD", | ||||
|         "BNB/USDC", | ||||
|         "BNB/USDS", | ||||
|         "BNB/USDT", | ||||
|         "BNB/USDT" | ||||
|     ] | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| # Include all requirements to run the bot. | ||||
| -r requirements.txt | ||||
|  | ||||
| plotly==5.2.1 | ||||
| plotly==5.3.0 | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| numpy==1.21.2 | ||||
| pandas==1.3.2 | ||||
|  | ||||
| ccxt==1.55.28 | ||||
| ccxt==1.55.56 | ||||
| # Pin cryptography for now due to rust build errors with piwheels | ||||
| cryptography==3.4.7 | ||||
| cryptography==3.4.8 | ||||
| aiohttp==3.7.4.post0 | ||||
| SQLAlchemy==1.4.23 | ||||
| python-telegram-bot==13.7 | ||||
| @@ -31,7 +31,7 @@ python-rapidjson==1.4 | ||||
| sdnotify==0.3.2 | ||||
|  | ||||
| # API Server | ||||
| fastapi==0.68.0 | ||||
| fastapi==0.68.1 | ||||
| uvicorn==0.15.0 | ||||
| pyjwt==2.1.0 | ||||
| aiofiles==0.7.0 | ||||
|   | ||||
							
								
								
									
										2
									
								
								setup.sh
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.sh
									
									
									
									
									
								
							| @@ -119,6 +119,7 @@ function install_mac_newer_python_dependencies() { | ||||
|         echo "-------------------------" | ||||
|         brew install hdf5 | ||||
|     fi | ||||
|     export HDF5_DIR=$(brew --prefix) | ||||
|  | ||||
|     if [ ! $(brew --prefix --installed c-blosc 2>/dev/null) ] | ||||
|     then | ||||
| @@ -127,6 +128,7 @@ function install_mac_newer_python_dependencies() { | ||||
|         echo "-------------------------" | ||||
|         brew install c-blosc | ||||
|     fi     | ||||
|     export CBLOSC_DIR=$(brew --prefix) | ||||
| } | ||||
|  | ||||
| # Install bot MacOS | ||||
|   | ||||
| @@ -510,17 +510,6 @@ def test_start_new_strategy(mocker, caplog): | ||||
|         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): | ||||
|     args = [ | ||||
|         "new-strategy", | ||||
| @@ -552,17 +541,6 @@ def test_start_new_hyperopt(mocker, caplog): | ||||
|         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): | ||||
|     args = [ | ||||
|         "new-hyperopt", | ||||
| @@ -827,9 +805,9 @@ def test_start_list_strategies(mocker, caplog, capsys): | ||||
|     # pargs['config'] = None | ||||
|     start_list_strategies(pargs) | ||||
|     captured = capsys.readouterr() | ||||
|     assert "TestStrategyLegacy" in captured.out | ||||
|     assert "legacy_strategy.py" not in captured.out | ||||
|     assert "DefaultStrategy" in captured.out | ||||
|     assert "TestStrategyLegacyV1" in captured.out | ||||
|     assert "legacy_strategy_v1.py" not in captured.out | ||||
|     assert "StrategyTestV2" in captured.out | ||||
|  | ||||
|     # Test regular output | ||||
|     args = [ | ||||
| @@ -842,9 +820,9 @@ def test_start_list_strategies(mocker, caplog, capsys): | ||||
|     # pargs['config'] = None | ||||
|     start_list_strategies(pargs) | ||||
|     captured = capsys.readouterr() | ||||
|     assert "TestStrategyLegacy" in captured.out | ||||
|     assert "legacy_strategy.py" in captured.out | ||||
|     assert "DefaultStrategy" in captured.out | ||||
|     assert "TestStrategyLegacyV1" in captured.out | ||||
|     assert "legacy_strategy_v1.py" in captured.out | ||||
|     assert "StrategyTestV2" in captured.out | ||||
|  | ||||
|  | ||||
| def test_start_list_hyperopts(mocker, caplog, capsys): | ||||
| @@ -861,7 +839,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys): | ||||
|     captured = capsys.readouterr() | ||||
|     assert "TestHyperoptLegacy" 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 | ||||
|  | ||||
|     # Test regular output | ||||
| @@ -876,7 +854,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys): | ||||
|     captured = capsys.readouterr() | ||||
|     assert "TestHyperoptLegacy" 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): | ||||
|   | ||||
| @@ -360,7 +360,7 @@ def get_default_conf(testdatadir): | ||||
|         "user_data_dir": Path("user_data"), | ||||
|         "verbosity": 3, | ||||
|         "strategy_path": str(Path(__file__).parent / "strategy" / "strats"), | ||||
|         "strategy": "DefaultStrategy", | ||||
|         "strategy": "StrategyTestV2", | ||||
|         "disableparamexport": True, | ||||
|         "internals": {}, | ||||
|         "export": "none", | ||||
|   | ||||
| @@ -33,7 +33,7 @@ def mock_trade_1(fee): | ||||
|         open_rate=0.123, | ||||
|         exchange='binance', | ||||
|         open_order_id='dry_run_buy_12345', | ||||
|         strategy='DefaultStrategy', | ||||
|         strategy='StrategyTestV2', | ||||
|         timeframe=5, | ||||
|     ) | ||||
|     o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy') | ||||
| @@ -87,7 +87,7 @@ def mock_trade_2(fee): | ||||
|         exchange='binance', | ||||
|         is_open=False, | ||||
|         open_order_id='dry_run_sell_12345', | ||||
|         strategy='DefaultStrategy', | ||||
|         strategy='StrategyTestV2', | ||||
|         timeframe=5, | ||||
|         sell_reason='sell_signal', | ||||
|         open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), | ||||
| @@ -146,7 +146,7 @@ def mock_trade_3(fee): | ||||
|         close_profit_abs=0.000155, | ||||
|         exchange='binance', | ||||
|         is_open=False, | ||||
|         strategy='DefaultStrategy', | ||||
|         strategy='StrategyTestV2', | ||||
|         timeframe=5, | ||||
|         sell_reason='roi', | ||||
|         open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), | ||||
| @@ -189,7 +189,7 @@ def mock_trade_4(fee): | ||||
|         open_rate=0.123, | ||||
|         exchange='binance', | ||||
|         open_order_id='prod_buy_12345', | ||||
|         strategy='DefaultStrategy', | ||||
|         strategy='StrategyTestV2', | ||||
|         timeframe=5, | ||||
|     ) | ||||
|     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): | ||||
|  | ||||
|     filename = testdatadir / "backtest-result_multistrat.json" | ||||
|     for strategy in ('DefaultStrategy', 'TestStrategy'): | ||||
|     for strategy in ('StrategyTestV2', 'TestStrategy'): | ||||
|         bt_data = load_backtest_data(filename, strategy=strategy) | ||||
|         assert isinstance(bt_data, DataFrame) | ||||
|         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: | ||||
|         if col not in ['index', 'open_at_end']: | ||||
|             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 | ||||
|     trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy') | ||||
|     assert len(trades) == 0 | ||||
| @@ -186,7 +186,7 @@ def test_load_trades(default_conf, mocker): | ||||
|                 db_url=default_conf.get('db_url'), | ||||
|                 exportfilename=default_conf.get('exportfilename'), | ||||
|                 no_trades=False, | ||||
|                 strategy="DefaultStrategy", | ||||
|                 strategy="StrategyTestV2", | ||||
|                 ) | ||||
|  | ||||
|     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: | ||||
|     patch_exchange(mocker) | ||||
|  | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     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: | ||||
|     patch_exchange(mocker) | ||||
|  | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     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: | ||||
|     patch_exchange(mocker) | ||||
|  | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     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']) | ||||
| 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 | ||||
|     api_mock = MagicMock() | ||||
|     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) | ||||
|  | ||||
|  | ||||
| def test_validate_stake_currency_error(default_conf, mocker, caplog): | ||||
| def test_validate_stakecurrency_error(default_conf, mocker, caplog): | ||||
|     default_conf['stake_currency'] = 'XRP' | ||||
|     api_mock = MagicMock() | ||||
|     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'): | ||||
|         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): | ||||
|     ex = get_patched_exchange(mocker, default_conf) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ def hyperopt_conf(default_conf): | ||||
|     hyperconf.update({ | ||||
|         'datadir': Path(default_conf['datadir']), | ||||
|         'runmode': RunMode.HYPEROPT, | ||||
|         'hyperopt': 'DefaultHyperOpt', | ||||
|         'hyperopt': 'HyperoptTestSepFile', | ||||
|         'hyperopt_loss': 'ShortTradeDurHyperOptLoss', | ||||
|                          'hyperopt_path': str(Path(__file__).parent / 'hyperopts'), | ||||
|                          'epochs': 1, | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib | ||||
| from freqtrade.optimize.hyperopt_interface import IHyperOpt | ||||
| 
 | ||||
| 
 | ||||
| class DefaultHyperOpt(IHyperOpt): | ||||
| class HyperoptTestSepFile(IHyperOpt): | ||||
|     """ | ||||
|     Default hyperopt provided by the Freqtrade bot. | ||||
|     You can override it with your own Hyperopt | ||||
| @@ -1,7 +1,7 @@ | ||||
| # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument | ||||
|  | ||||
| import random | ||||
| from datetime import timedelta | ||||
| from datetime import datetime, timedelta, timezone | ||||
| from pathlib import Path | ||||
| from unittest.mock import MagicMock, PropertyMock | ||||
|  | ||||
| @@ -155,7 +155,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca | ||||
|     args = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--export', 'none' | ||||
|     ] | ||||
|  | ||||
| @@ -190,7 +190,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> | ||||
|     args = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--datadir', '/foo/bar', | ||||
|         '--timeframe', '1m', | ||||
|         '--enable-position-stacking', | ||||
| @@ -240,7 +240,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) | ||||
|     args = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--stake-amount', '1', | ||||
|         '--starting-balance', '2' | ||||
|     ] | ||||
| @@ -251,7 +251,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) | ||||
|     args = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--stake-amount', '1', | ||||
|         '--starting-balance', '0.5' | ||||
|     ] | ||||
| @@ -269,7 +269,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None: | ||||
|     args = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|     ] | ||||
|     pargs = get_args(args) | ||||
|     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: | ||||
|     patch_exchange(mocker) | ||||
|     del default_conf['timeframe'] | ||||
|     default_conf['strategy_list'] = ['DefaultStrategy', | ||||
|     default_conf['strategy_list'] = ['StrategyTestV2', | ||||
|                                      'SampleStrategy'] | ||||
|  | ||||
|     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 | ||||
|  | ||||
|     # 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) | ||||
|  | ||||
|     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.'): | ||||
|         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: | ||||
|     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) | ||||
|  | ||||
|     # Multiple strategies | ||||
|     default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy'] | ||||
|     default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1'] | ||||
|     with pytest.raises(OperationalException, | ||||
|                        match='PrecisionFilter not allowed for backtesting multiple strategies.'): | ||||
|         Backtesting(default_conf) | ||||
| @@ -491,7 +500,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: | ||||
|     pair = 'UNITTEST/BTC' | ||||
|     row = [ | ||||
|         pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0), | ||||
|         1,  # Sell | ||||
|         1,  # Buy | ||||
|         0.001,  # Open | ||||
|         0.0011,  # Close | ||||
|         0,  # Sell | ||||
| @@ -539,6 +548,88 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: | ||||
|     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: | ||||
|     default_conf['use_sell_signal'] = False | ||||
|     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): | ||||
|     # 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): | ||||
|         buy_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): | ||||
|     # 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): | ||||
|         buy_value = 0 | ||||
|         sell_value = 1 | ||||
| @@ -837,7 +928,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): | ||||
|     args = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--datadir', str(testdatadir), | ||||
|         '--timeframe', '1m', | ||||
|         '--timerange', '1510694220-1510700340', | ||||
| @@ -908,8 +999,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): | ||||
|         '--enable-position-stacking', | ||||
|         '--disable-max-market-positions', | ||||
|         '--strategy-list', | ||||
|         'DefaultStrategy', | ||||
|         'TestStrategyLegacy', | ||||
|         'StrategyTestV2', | ||||
|         'TestStrategyLegacyV1', | ||||
|     ] | ||||
|     args = get_args(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 ' | ||||
|         'up to 2017-11-14 22:58:00 (0 days).', | ||||
|         'Parameter --enable-position-stacking detected ...', | ||||
|         'Running backtesting for Strategy DefaultStrategy', | ||||
|         'Running backtesting for Strategy TestStrategyLegacy', | ||||
|         'Running backtesting for Strategy StrategyTestV2', | ||||
|         'Running backtesting for Strategy TestStrategyLegacyV1', | ||||
|     ] | ||||
|  | ||||
|     for line in exists: | ||||
| @@ -1012,8 +1103,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat | ||||
|         '--enable-position-stacking', | ||||
|         '--disable-max-market-positions', | ||||
|         '--strategy-list', | ||||
|         'DefaultStrategy', | ||||
|         'TestStrategyLegacy', | ||||
|         'StrategyTestV2', | ||||
|         'TestStrategyLegacyV1', | ||||
|     ] | ||||
|     args = get_args(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 ' | ||||
|         'up to 2017-11-14 22:58:00 (0 days).', | ||||
|         'Parameter --enable-position-stacking detected ...', | ||||
|         'Running backtesting for Strategy DefaultStrategy', | ||||
|         'Running backtesting for Strategy TestStrategyLegacy', | ||||
|         'Running backtesting for Strategy StrategyTestV2', | ||||
|         'Running backtesting for Strategy TestStrategyLegacyV1', | ||||
|     ] | ||||
|  | ||||
|     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 '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 | ||||
|  | ||||
|  | ||||
| @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 = [ | ||||
|         'edge', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|     ] | ||||
|  | ||||
|     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 = [ | ||||
|         'edge', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--datadir', '/foo/bar', | ||||
|         '--timeframe', '1m', | ||||
|         '--timerange', ':100', | ||||
| @@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None: | ||||
|     args = [ | ||||
|         'edge', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|     ] | ||||
|     pargs = get_args(args) | ||||
|     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, | ||||
|                             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: | ||||
| @@ -31,7 +31,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca | ||||
|     args = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--hyperopt', 'DefaultHyperOpt', | ||||
|         '--hyperopt', 'HyperoptTestSepFile', | ||||
|     ] | ||||
|  | ||||
|     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 = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--hyperopt', 'DefaultHyperOpt', | ||||
|         '--hyperopt', 'HyperoptTestSepFile', | ||||
|         '--datadir', '/foo/bar', | ||||
|         '--timeframe', '1m', | ||||
|         '--timerange', ':100', | ||||
| @@ -115,7 +115,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None | ||||
|     args = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--hyperopt', 'DefaultHyperOpt', | ||||
|         '--hyperopt', 'HyperoptTestSepFile', | ||||
|         '--stake-amount', '1', | ||||
|         '--starting-balance', '2' | ||||
|     ] | ||||
| @@ -125,7 +125,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None | ||||
|     args = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--stake-amount', '1', | ||||
|         '--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: | ||||
|     patched_configuration_load_config_file(mocker, default_conf) | ||||
|  | ||||
|     hyperopt = DefaultHyperOpt | ||||
|     hyperopt = HyperoptTestSepFile | ||||
|     delattr(hyperopt, 'populate_indicators') | ||||
|     delattr(hyperopt, 'populate_buy_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', | ||||
|         MagicMock(return_value=hyperopt(default_conf)) | ||||
|     ) | ||||
|     default_conf.update({'hyperopt': 'DefaultHyperOpt'}) | ||||
|     default_conf.update({'hyperopt': 'HyperoptTestSepFile'}) | ||||
|     x = HyperOptResolver.load_hyperopt(default_conf) | ||||
|     assert not hasattr(x, 'populate_indicators') | ||||
|     assert not hasattr(x, 'populate_buy_trend') | ||||
| @@ -184,7 +184,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None: | ||||
|     args = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--hyperopt', 'DefaultHyperOpt', | ||||
|         '--hyperopt', 'HyperoptTestSepFile', | ||||
|         '--hyperopt-path', | ||||
|         str(Path(__file__).parent / "hyperopts"), | ||||
|         '--epochs', '5', | ||||
| @@ -205,7 +205,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None: | ||||
|     args = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--hyperopt', 'DefaultHyperOpt', | ||||
|         '--hyperopt', 'HyperoptTestSepFile', | ||||
|         '--hyperopt-loss', 'SharpeHyperOptLossDaily', | ||||
|         '--epochs', '5' | ||||
|     ] | ||||
| @@ -229,7 +229,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None: | ||||
|     args = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--hyperopt', 'DefaultHyperOpt', | ||||
|         '--hyperopt', 'HyperoptTestSepFile', | ||||
|         '--hyperopt-loss', 'SharpeHyperOptLossDaily', | ||||
|         '--epochs', '5' | ||||
|     ] | ||||
| @@ -247,7 +247,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None: | ||||
|     args = [ | ||||
|         'hyperopt', | ||||
|         '--config', 'config.json', | ||||
|         '--hyperopt', 'DefaultHyperOpt', | ||||
|         '--hyperopt', 'HyperoptTestSepFile', | ||||
|         '--hyperopt-loss', 'SharpeHyperOptLossDaily', | ||||
|         '--epochs', '5' | ||||
|     ] | ||||
|   | ||||
| @@ -167,9 +167,9 @@ def test__pprint_dict(): | ||||
|  | ||||
| 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 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') | ||||
|     assert x is None | ||||
| @@ -177,7 +177,7 @@ def test_get_strategy_filename(default_conf): | ||||
|  | ||||
| def test_export_params(tmpdir): | ||||
|  | ||||
|     filename = Path(tmpdir) / "DefaultStrategy.json" | ||||
|     filename = Path(tmpdir) / "StrategyTestV2.json" | ||||
|     assert not filename.is_file() | ||||
|     params = { | ||||
|         "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() | ||||
|  | ||||
|     content = rapidjson.load(filename.open('r')) | ||||
|     assert content['strategy_name'] == 'DefaultStrategy' | ||||
|     assert content['strategy_name'] == 'StrategyTestV2' | ||||
|     assert 'params' in content | ||||
|     assert "buy" 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 | ||||
|     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() | ||||
|     params = { | ||||
|         "params_details": { | ||||
| @@ -252,17 +252,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): | ||||
|         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 export_mock.call_count == 0 | ||||
|     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_args_list[0][0][1] == 'DefaultStrategy' | ||||
|     assert export_mock.call_args_list[0][0][2].name == 'default_strategy.json' | ||||
|     assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2' | ||||
|     assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json' | ||||
|  | ||||
|  | ||||
| def test_params_print(capsys): | ||||
|   | ||||
| @@ -4,7 +4,7 @@ from unittest.mock import MagicMock | ||||
| import pytest | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -52,7 +52,7 @@ def test_text_table_bt_results(): | ||||
|  | ||||
|  | ||||
| def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     results = {'DefStrat': { | ||||
|   | ||||
| @@ -879,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): | ||||
|         'open_trade_value': 15.1668225, | ||||
|         'sell_reason': None, | ||||
|         'sell_order_status': None, | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'buy_tag': None, | ||||
|         'timeframe': 5, | ||||
|         'exchange': 'binance', | ||||
| @@ -984,7 +984,7 @@ def test_api_forcebuy(botclient, mocker, fee): | ||||
|         close_rate=0.265441, | ||||
|         id=22, | ||||
|         timeframe=5, | ||||
|         strategy="DefaultStrategy" | ||||
|         strategy="StrategyTestV2" | ||||
|     )) | ||||
|     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, | ||||
|         'sell_reason': None, | ||||
|         'sell_order_status': None, | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'buy_tag': None, | ||||
|         'timeframe': 5, | ||||
|         '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}") | ||||
|     assert_response(rc) | ||||
|     assert 'strategy' in rc.json() | ||||
|     assert rc.json()['strategy'] == 'DefaultStrategy' | ||||
|     assert rc.json()['strategy'] == 'StrategyTestV2' | ||||
|     assert 'columns' in rc.json() | ||||
|     assert 'data_start_ts' in rc.json() | ||||
|     assert 'data_start' in rc.json() | ||||
| @@ -1139,19 +1139,19 @@ def test_api_pair_history(botclient, ohlcv_history): | ||||
|     # No pair | ||||
|     rc = client_get(client, | ||||
|                     f"{BASE_URI}/pair_history?timeframe={timeframe}" | ||||
|                     "&timerange=20180111-20180112&strategy=DefaultStrategy") | ||||
|                     "&timerange=20180111-20180112&strategy=StrategyTestV2") | ||||
|     assert_response(rc, 422) | ||||
|  | ||||
|     # No Timeframe | ||||
|     rc = client_get(client, | ||||
|                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" | ||||
|                     "&timerange=20180111-20180112&strategy=DefaultStrategy") | ||||
|                     "&timerange=20180111-20180112&strategy=StrategyTestV2") | ||||
|     assert_response(rc, 422) | ||||
|  | ||||
|     # No timerange | ||||
|     rc = client_get(client, | ||||
|                     f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" | ||||
|                     "&strategy=DefaultStrategy") | ||||
|                     "&strategy=StrategyTestV2") | ||||
|     assert_response(rc, 422) | ||||
|  | ||||
|     # No strategy | ||||
| @@ -1163,14 +1163,14 @@ def test_api_pair_history(botclient, ohlcv_history): | ||||
|     # Working | ||||
|     rc = client_get(client, | ||||
|                     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 rc.json()['length'] == 289 | ||||
|     assert len(rc.json()['data']) == rc.json()['length'] | ||||
|     assert 'columns' in rc.json() | ||||
|     assert 'data' in rc.json() | ||||
|     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_ts'] == 1515628800000 | ||||
|     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 | ||||
|     rc = client_get(client, | ||||
|                     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 rc.json()['error'] == ("Error querying /api/v1/pair_history: " | ||||
|                                   "No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") | ||||
| @@ -1217,21 +1217,21 @@ def test_api_strategies(botclient): | ||||
|  | ||||
|     assert_response(rc) | ||||
|     assert rc.json() == {'strategies': [ | ||||
|         'DefaultStrategy', | ||||
|         'HyperoptableStrategy', | ||||
|         'TestStrategyLegacy' | ||||
|         'StrategyTestV2', | ||||
|         'TestStrategyLegacyV1' | ||||
|     ]} | ||||
|  | ||||
|  | ||||
| def test_api_strategy(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 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 | ||||
|  | ||||
|     rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") | ||||
| @@ -1288,7 +1288,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog): | ||||
|  | ||||
|     # start backtesting | ||||
|     data = { | ||||
|         "strategy": "DefaultStrategy", | ||||
|         "strategy": "StrategyTestV2", | ||||
|         "timeframe": "5m", | ||||
|         "timerange": "20180110-20180111", | ||||
|         "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 '*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 '*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] | ||||
|  | ||||
|     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 '*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 '*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] | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -5,5 +5,5 @@ import nonexiting_module  # noqa | ||||
| from freqtrade.strategy.interface import IStrategy | ||||
|  | ||||
|  | ||||
| class TestStrategyLegacy(IStrategy): | ||||
| class TestStrategyLegacyV1(IStrategy): | ||||
|     pass | ||||
|   | ||||
| @@ -10,7 +10,7 @@ from freqtrade.strategy.interface import IStrategy | ||||
| # -------------------------------- | ||||
| 
 | ||||
| # 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 | ||||
|     removed in a future update. | ||||
| @@ -7,9 +7,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib | ||||
| 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 look at the SampleStrategy in the user_data/strategy directory | ||||
|     or strategy repository https://github.com/freqtrade/freqtrade-strategies | ||||
| @@ -4,20 +4,20 @@ from pandas import DataFrame | ||||
|  | ||||
| from freqtrade.persistence.models import Trade | ||||
|  | ||||
| from .strats.default_strategy import DefaultStrategy | ||||
| from .strats.strategy_test_v2 import StrategyTestV2 | ||||
|  | ||||
|  | ||||
| def test_default_strategy_structure(): | ||||
|     assert hasattr(DefaultStrategy, 'minimal_roi') | ||||
|     assert hasattr(DefaultStrategy, 'stoploss') | ||||
|     assert hasattr(DefaultStrategy, 'timeframe') | ||||
|     assert hasattr(DefaultStrategy, 'populate_indicators') | ||||
|     assert hasattr(DefaultStrategy, 'populate_buy_trend') | ||||
|     assert hasattr(DefaultStrategy, 'populate_sell_trend') | ||||
| def test_strategy_test_v2_structure(): | ||||
|     assert hasattr(StrategyTestV2, 'minimal_roi') | ||||
|     assert hasattr(StrategyTestV2, 'stoploss') | ||||
|     assert hasattr(StrategyTestV2, 'timeframe') | ||||
|     assert hasattr(StrategyTestV2, 'populate_indicators') | ||||
|     assert hasattr(StrategyTestV2, 'populate_buy_trend') | ||||
|     assert hasattr(StrategyTestV2, 'populate_sell_trend') | ||||
|  | ||||
|  | ||||
| def test_default_strategy(result, fee): | ||||
|     strategy = DefaultStrategy({}) | ||||
| def test_strategy_test_v2(result, fee): | ||||
|     strategy = StrategyTestV2({}) | ||||
|  | ||||
|     metadata = {'pair': 'ETH/BTC'} | ||||
|     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 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 | ||||
| _STRATEGY = DefaultStrategy(config={}) | ||||
| _STRATEGY = StrategyTestV2(config={}) | ||||
| _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): | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     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: | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     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: | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators') | ||||
|     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}, | ||||
|                     {0: 0.1, 20: 0.05, 55: 0.01}] | ||||
|     for roi in min_roi_list: | ||||
|         default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|         default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|         strategy = StrategyResolver.load_strategy(default_conf) | ||||
|         strategy.minimal_roi = roi | ||||
|         trade = Trade( | ||||
| @@ -301,7 +301,7 @@ def test_min_roi_reached2(default_conf, fee) -> None: | ||||
|                      }, | ||||
|                     ] | ||||
|     for roi in min_roi_list: | ||||
|         default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|         default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|         strategy = StrategyResolver.load_strategy(default_conf) | ||||
|         strategy.minimal_roi = roi | ||||
|         trade = Trade( | ||||
| @@ -336,7 +336,7 @@ def test_min_roi_reached3(default_conf, fee) -> None: | ||||
|                30: 0.05, | ||||
|                55: 0.30, | ||||
|                } | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     strategy.minimal_roi = min_roi | ||||
|     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, | ||||
|                            profit2, adjusted2, expected2, custom_stop) -> None: | ||||
|  | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|  | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     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: | ||||
|  | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|  | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     trade = Trade( | ||||
| @@ -491,7 +491,7 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: | ||||
|         advise_sell=sell_mock, | ||||
|  | ||||
|     ) | ||||
|     strategy = DefaultStrategy({}) | ||||
|     strategy = StrategyTestV2({}) | ||||
|     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) | ||||
|     assert ind_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, | ||||
|  | ||||
|     ) | ||||
|     strategy = DefaultStrategy({}) | ||||
|     strategy = StrategyTestV2({}) | ||||
|     strategy.dp = DataProvider({}, None, None) | ||||
|     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") | ||||
| def test_is_pair_locked(default_conf): | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     PairLocks.timeframe = default_conf['timeframe'] | ||||
|     PairLocks.use_db = True | ||||
|     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): | ||||
|     default_conf.update({'strategy': 'TestStrategyLegacy'}) | ||||
|     default_conf.update({'strategy': 'TestStrategyLegacyV1'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     # Should return empty | ||||
|     # Uses fallback to base implementation | ||||
|   | ||||
| @@ -18,7 +18,7 @@ def test_search_strategy(): | ||||
|  | ||||
|     s, _ = StrategyResolver._search_object( | ||||
|         directory=default_location, | ||||
|         object_name='DefaultStrategy', | ||||
|         object_name='StrategyTestV2', | ||||
|         add_source=True, | ||||
|     ) | ||||
|     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): | ||||
|     default_conf['strategy'] = 'DefaultStrategy' | ||||
|     default_conf['strategy'] = 'StrategyTestV2' | ||||
|     extra_dir = Path.cwd() / 'some/path' | ||||
|     with pytest.raises(OperationalException): | ||||
|         StrategyResolver._load_strategy('DefaultStrategy', config=default_conf, | ||||
|         StrategyResolver._load_strategy('StrategyTestV2', config=default_conf, | ||||
|                                         extra_dir=extra_dir) | ||||
|  | ||||
|     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): | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|  | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     metadata = {'pair': 'ETH/BTC'} | ||||
| @@ -127,7 +127,7 @@ def test_strategy(result, default_conf): | ||||
| def test_strategy_override_minimal_roi(caplog, default_conf): | ||||
|     caplog.set_level(logging.INFO) | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'minimal_roi': { | ||||
|             "20": 0.1, | ||||
|             "0": 0.5 | ||||
| @@ -144,7 +144,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf): | ||||
| def test_strategy_override_stoploss(caplog, default_conf): | ||||
|     caplog.set_level(logging.INFO) | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'stoploss': -0.5 | ||||
|     }) | ||||
|     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): | ||||
|     caplog.set_level(logging.INFO) | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'trailing_stop': True | ||||
|     }) | ||||
|     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): | ||||
|     caplog.set_level(logging.INFO) | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'trailing_stop_positive': -0.1, | ||||
|         'trailing_stop_positive_offset': -0.2 | ||||
|  | ||||
| @@ -189,7 +189,7 @@ def test_strategy_override_timeframe(caplog, default_conf): | ||||
|     caplog.set_level(logging.INFO) | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'timeframe': 60, | ||||
|         'stake_currency': 'ETH' | ||||
|     }) | ||||
| @@ -205,7 +205,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf): | ||||
|     caplog.set_level(logging.INFO) | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'process_only_new_candles': True | ||||
|     }) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
| @@ -225,7 +225,7 @@ def test_strategy_override_order_types(caplog, default_conf): | ||||
|         'stoploss_on_exchange': True, | ||||
|     } | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'order_types': order_types | ||||
|     }) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
| @@ -239,12 +239,12 @@ def test_strategy_override_order_types(caplog, default_conf): | ||||
|                    " 'stoploss_on_exchange': True}.", caplog) | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'order_types': {'buy': 'market'} | ||||
|     }) | ||||
|     # Raise error for invalid configuration | ||||
|     with pytest.raises(ImportError, | ||||
|                        match=r"Impossible to load Strategy 'DefaultStrategy'. " | ||||
|                        match=r"Impossible to load Strategy 'StrategyTestV2'. " | ||||
|                              r"Order-types mapping is incomplete."): | ||||
|         StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
| @@ -258,7 +258,7 @@ def test_strategy_override_order_tif(caplog, default_conf): | ||||
|     } | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'order_time_in_force': order_time_in_force | ||||
|     }) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
| @@ -271,12 +271,12 @@ def test_strategy_override_order_tif(caplog, default_conf): | ||||
|                    " {'buy': 'fok', 'sell': 'gtc'}.", caplog) | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'order_time_in_force': {'buy': 'fok'} | ||||
|     }) | ||||
|     # Raise error for invalid configuration | ||||
|     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."): | ||||
|         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): | ||||
|     caplog.set_level(logging.INFO) | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|     }) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     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'] | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'use_sell_signal': False, | ||||
|     }) | ||||
|     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): | ||||
|     caplog.set_level(logging.INFO) | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|     }) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     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'] | ||||
|  | ||||
|     default_conf.update({ | ||||
|         'strategy': 'DefaultStrategy', | ||||
|         'strategy': 'StrategyTestV2', | ||||
|         'sell_profit_only': True, | ||||
|     }) | ||||
|     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") | ||||
| def test_deprecate_populate_indicators(result, default_conf): | ||||
|     default_location = Path(__file__).parent / "strats" | ||||
|     default_conf.update({'strategy': 'TestStrategyLegacy', | ||||
|     default_conf.update({'strategy': 'TestStrategyLegacyV1', | ||||
|                          'strategy_path': default_location}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     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): | ||||
|     default_location = Path(__file__).parent / "strats" | ||||
|     del default_conf['timeframe'] | ||||
|     default_conf.update({'strategy': 'TestStrategyLegacy', | ||||
|     default_conf.update({'strategy': 'TestStrategyLegacyV1', | ||||
|                          'strategy_path': default_location}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     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): | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     default_conf.update({'strategy': 'StrategyTestV2'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|     metadata = {'pair': 'ETH/BTC'} | ||||
|  | ||||
|   | ||||
| @@ -123,7 +123,7 @@ def test_parse_args_backtesting_custom() -> None: | ||||
|         '-c', 'test_conf.json', | ||||
|         '--ticker-interval', '1m', | ||||
|         '--strategy-list', | ||||
|         'DefaultStrategy', | ||||
|         'StrategyTestV2', | ||||
|         'SampleStrategy' | ||||
|     ] | ||||
|     call_args = Arguments(args).get_parsed_arg() | ||||
|   | ||||
| @@ -404,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> | ||||
|     arglist = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|     ] | ||||
|  | ||||
|     args = Arguments(arglist).get_parsed_arg() | ||||
| @@ -441,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non | ||||
|     arglist = [ | ||||
|         'backtesting', | ||||
|         '--config', 'config.json', | ||||
|         '--strategy', 'DefaultStrategy', | ||||
|         '--strategy', 'StrategyTestV2', | ||||
|         '--datadir', '/foo/bar', | ||||
|         '--userdir', "/tmp/freqtrade", | ||||
|         '--ticker-interval', '1m', | ||||
| @@ -498,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non | ||||
|         '--ticker-interval', '1m', | ||||
|         '--export', 'trades', | ||||
|         '--strategy-list', | ||||
|         'DefaultStrategy', | ||||
|         'StrategyTestV2', | ||||
|         '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) | ||||
|             result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC') | ||||
|             assert pytest.approx(result) == expected[i] | ||||
|             freqtrade.execute_buy('ETH/BTC', result) | ||||
|             freqtrade.execute_entry('ETH/BTC', result) | ||||
|         else: | ||||
|             with pytest.raises(DependencyException): | ||||
|                 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) | ||||
|  | ||||
|     # Create 2 existing trades | ||||
|     freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount']) | ||||
|     freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount']) | ||||
|     freqtrade.execute_entry('ETH/BTC', default_conf['stake_amount']) | ||||
|     freqtrade.execute_entry('NEO/BTC', default_conf['stake_amount']) | ||||
|  | ||||
|     assert len(Trade.get_open_trades()) == 2 | ||||
|     # 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] | ||||
|  | ||||
|  | ||||
| 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_exchange(mocker) | ||||
|     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' | ||||
|  | ||||
|     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_mm.call_count == 0 | ||||
|     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' | ||||
|     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_mm.call_count == 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 | ||||
|     limit_buy_order_open['id'] = '33' | ||||
|     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 | ||||
|     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', | ||||
|                  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] | ||||
|     assert trade | ||||
|     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' | ||||
|     mocker.patch('freqtrade.exchange.Exchange.create_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] | ||||
|     assert trade | ||||
|     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' | ||||
|  | ||||
|     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] | ||||
|     assert trade | ||||
|     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 | ||||
|     limit_buy_order['id'] = '557' | ||||
|     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] | ||||
|     assert trade | ||||
|     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' | ||||
|     mocker.patch('freqtrade.exchange.Exchange.create_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... | ||||
|     mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) | ||||
|  | ||||
|     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 | ||||
|     mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50) | ||||
|     limit_buy_order['status'] = 'open' | ||||
|     limit_buy_order['id'] = '5566' | ||||
|     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] | ||||
|     assert trade | ||||
|     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), | ||||
|     ) | ||||
|  | ||||
|     assert freqtrade.execute_buy(pair, stake_amount) | ||||
|     assert freqtrade.execute_entry(pair, stake_amount) | ||||
|     trade = Trade.query.all()[7] | ||||
|     assert trade | ||||
|     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['id'] = '5568' | ||||
|     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] | ||||
|     assert trade | ||||
|     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) | ||||
|     mocker.patch.multiple( | ||||
|         'freqtrade.exchange.Exchange', | ||||
| @@ -957,18 +957,18 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) - | ||||
|     pair = 'ETH/BTC' | ||||
|  | ||||
|     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' | ||||
|     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' | ||||
|     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) | ||||
|     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: | ||||
| @@ -2007,7 +2007,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, | ||||
|     trade = Trade.query.first() | ||||
|     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(), | ||||
|     #      we might just want to check if we are in a sell condition without | ||||
|     #      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' | ||||
|  | ||||
|  | ||||
| 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) | ||||
|     patch_exchange(mocker) | ||||
|     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 | ||||
|     ) | ||||
|     # Prevented sell ... | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||
|     assert rpc_mock.call_count == 0 | ||||
|     assert freqtrade.strategy.confirm_trade_exit.call_count == 1 | ||||
|  | ||||
|     # Repatch with true | ||||
|     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) | ||||
|  | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||
|     assert freqtrade.strategy.confirm_trade_exit.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 | ||||
|  | ||||
|  | ||||
| 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) | ||||
|     patch_exchange(mocker) | ||||
|     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 | ||||
|     ) | ||||
|  | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|  | ||||
|     assert rpc_mock.call_count == 2 | ||||
|     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 | ||||
|  | ||||
|  | ||||
| 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) | ||||
|     patch_exchange(mocker) | ||||
|     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 | ||||
|     freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05 | ||||
|  | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)) | ||||
|  | ||||
|     # 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 | ||||
|  | ||||
|  | ||||
| def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, | ||||
|                                                         ticker_sell_down, mocker) -> None: | ||||
| def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, | ||||
|                                                               ticker_sell_down, mocker) -> None: | ||||
|     rpc_mock = patch_RPCManager(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     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 | ||||
|  | ||||
|     trade.stop_loss = 0.00001099 * 0.99 | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|  | ||||
|     assert rpc_mock.call_count == 2 | ||||
|     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 | ||||
|  | ||||
|  | ||||
| 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) | ||||
|     mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', | ||||
|                  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 | ||||
|     trade.stoploss_order_id = "abcd" | ||||
|  | ||||
|     freqtrade.execute_sell(trade=trade, limit=1234, | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=1234, | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|     assert create_order_mock.call_count == 2 | ||||
|     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, | ||||
|                                                 mocker) -> None: | ||||
| def test_execute_trade_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up, | ||||
|                                                       mocker) -> None: | ||||
|  | ||||
|     default_conf['exchange']['name'] = 'binance' | ||||
|     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 | ||||
|     ) | ||||
|  | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|  | ||||
|     trade = Trade.query.first() | ||||
|     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 | ||||
|  | ||||
|  | ||||
| def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee, | ||||
|                                                          mocker) -> None: | ||||
| def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee, | ||||
|                                                                mocker) -> None: | ||||
|     default_conf['exchange']['name'] = 'binance' | ||||
|     rpc_mock = patch_RPCManager(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 | ||||
|  | ||||
|  | ||||
| def test_execute_sell_market_order(default_conf, ticker, fee, | ||||
|                                    ticker_sell_up, mocker) -> None: | ||||
| def test_execute_trade_exit_market_order(default_conf, ticker, fee, | ||||
|                                          ticker_sell_up, mocker) -> None: | ||||
|     rpc_mock = patch_RPCManager(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch.multiple( | ||||
| @@ -3059,8 +3061,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee, | ||||
|     ) | ||||
|     freqtrade.config['order_types']['sell'] = 'market' | ||||
|  | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.ROI)) | ||||
|  | ||||
|     assert not trade.is_open | ||||
|     assert trade.close_profit == 0.0620716 | ||||
| @@ -3090,8 +3092,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee, | ||||
|     } == last_msg | ||||
|  | ||||
|  | ||||
| def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, | ||||
|                                                ticker_sell_up, mocker) -> None: | ||||
| def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee, | ||||
|                                                      ticker_sell_up, mocker) -> None: | ||||
|     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||
|     mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') | ||||
|     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) | ||||
|     assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                                       sell_reason=sell_reason) | ||||
|     assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], | ||||
|                                             sell_reason=sell_reason) | ||||
|     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 | ||||
|     ) | ||||
|  | ||||
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], | ||||
|                            sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|     freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], | ||||
|                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) | ||||
|     trade.close(ticker_sell_down()['bid']) | ||||
|     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 | ||||
|  | ||||
|  | ||||
| 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: | ||||
|     """ | ||||
|     Tests workflow of selling stoploss_on_exchange. | ||||
|   | ||||
| @@ -70,7 +70,6 @@ def test_add_indicators(default_conf, testdatadir, caplog): | ||||
|     indicators1 = {"ema10": {}} | ||||
|     indicators2 = {"macd": {"color": "red"}} | ||||
|  | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     # Generate buy/sell signals and indicators | ||||
| @@ -112,7 +111,6 @@ def test_add_areas(default_conf, testdatadir, caplog): | ||||
|                              "fill_to": "macdhist"}} | ||||
|  | ||||
|     ind_plain = {"macd": {"fill_to": "macdhist"}} | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     # 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', | ||||
|                                      datadir=testdatadir, timerange=timerange) | ||||
|  | ||||
|     default_conf.update({'strategy': 'DefaultStrategy'}) | ||||
|     strategy = StrategyResolver.load_strategy(default_conf) | ||||
|  | ||||
|     # 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 | ||||
|  | ||||
|     # 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') | ||||
|     assert result == result1 | ||||
|  | ||||
|     # 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') | ||||
|     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