diff --git a/build_helpers/install_ta-lib.sh b/build_helpers/install_ta-lib.sh index d12b16364..00c4417ae 100755 --- a/build_helpers/install_ta-lib.sh +++ b/build_helpers/install_ta-lib.sh @@ -11,8 +11,13 @@ if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then && curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess \ && curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub \ && ./configure --prefix=${INSTALL_LOC}/ \ - && make -j$(nproc) \ - && which sudo && sudo make install || make install + && make + if [ $? -ne 0 ]; then + echo "Failed building ta-lib." + cd .. && rm -rf ./ta-lib/ + exit 1 + fi + which sudo && sudo make install || make install if [ -x "$(command -v apt-get)" ]; then echo "Updating library path using ldconfig" sudo ldconfig diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index f03bc10c0..02b0307e5 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -52,6 +52,71 @@ freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md). +### Multiple instances using docker + +To run multiple instances of freqtrade using docker you will need to edit the docker-compose.yml file and add all the instances you want as separate services. Remember, you can separate your configuration into multiple files, so it's a good idea to think about making them modular, then if you need to edit something common to all bots, you can do that in a single config file. +``` yml +--- +version: '3' +services: + freqtrade1: + image: freqtradeorg/freqtrade:stable + # image: freqtradeorg/freqtrade:develop + # Use plotting image + # image: freqtradeorg/freqtrade:develop_plot + # Build step - only needed when additional dependencies are needed + # build: + # context: . + # dockerfile: "./docker/Dockerfile.custom" + restart: always + container_name: freqtrade1 + volumes: + - "./user_data:/freqtrade/user_data" + # Expose api on port 8080 (localhost only) + # Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation + # before enabling this. + ports: + - "127.0.0.1:8080:8080" + # Default command used when running `docker compose up` + command: > + trade + --logfile /freqtrade/user_data/logs/freqtrade1.log + --db-url sqlite:////freqtrade/user_data/tradesv3_freqtrade1.sqlite + --config /freqtrade/user_data/config.json + --config /freqtrade/user_data/config.freqtrade1.json + --strategy SampleStrategy + + freqtrade2: + image: freqtradeorg/freqtrade:stable + # image: freqtradeorg/freqtrade:develop + # Use plotting image + # image: freqtradeorg/freqtrade:develop_plot + # Build step - only needed when additional dependencies are needed + # build: + # context: . + # dockerfile: "./docker/Dockerfile.custom" + restart: always + container_name: freqtrade2 + volumes: + - "./user_data:/freqtrade/user_data" + # Expose api on port 8080 (localhost only) + # Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation + # before enabling this. + ports: + - "127.0.0.1:8081:8080" + # Default command used when running `docker compose up` + command: > + trade + --logfile /freqtrade/user_data/logs/freqtrade2.log + --db-url sqlite:////freqtrade/user_data/tradesv3_freqtrade2.sqlite + --config /freqtrade/user_data/config.json + --config /freqtrade/user_data/config.freqtrade2.json + --strategy SampleStrategy + +``` +You can use whatever naming convention you want, freqtrade1 and 2 are arbitrary. Note, that you will need to use different database files, port mappings and telegram configurations for each instance, as mentioned above. + + ## Configure the bot running as a systemd service Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup. diff --git a/docs/data-download.md b/docs/data-download.md index 5f605c404..6c7d5312d 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -22,6 +22,7 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-p PAIRS [PAIRS ...]] [--pairs-file FILE] [--days INT] [--new-pairs-days INT] + [--include-inactive-pairs] [--timerange TIMERANGE] [--dl-trades] [--exchange EXCHANGE] [-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]] @@ -38,6 +39,8 @@ optional arguments: --days INT Download data for given number of days. --new-pairs-days INT Download data of new pairs for given number of days. Default: `None`. + --include-inactive-pairs + Also download data from inactive pairs. --timerange TIMERANGE Specify what timerange of data to use. --dl-trades Download trades instead of OHLCV data. The bot will @@ -52,10 +55,10 @@ optional arguments: exchange/pairs/timeframes. --data-format-ohlcv {json,jsongz,hdf5} Storage format for downloaded candle (OHLCV) data. - (default: `None`). + (default: `json`). --data-format-trades {json,jsongz,hdf5} Storage format for downloaded trades data. (default: - `None`). + `jsongz`). Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -80,6 +83,82 @@ Common arguments: For that reason, `download-data` does not care about the "startup-period" defined in a strategy. It's up to the user to download additional days if the backtest should start at a specific point in time (while respecting startup period). +### Pairs file + +In alternative to the whitelist from `config.json`, a `pairs.json` file can be used. +If you are using Binance for example: + +- create a directory `user_data/data/binance` and copy or create the `pairs.json` file in that directory. +- update the `pairs.json` file to contain the currency pairs you are interested in. + +```bash +mkdir -p user_data/data/binance +touch user_data/data/binance/pairs.json +``` + +The format of the `pairs.json` file is a simple json list. +Mixing different stake-currencies is allowed for this file, since it's only used for downloading. + +``` json +[ + "ETH/BTC", + "ETH/USDT", + "BTC/USDT", + "XRP/ETH" +] +``` + +!!! Tip "Downloading all data for one quote currency" + Often, you'll want to download data for all pairs of a specific quote-currency. In such cases, you can use the following shorthand: + `freqtrade download-data --exchange binance --pairs .*/USDT <...>`. The provided "pairs" string will be expanded to contain all active pairs on the exchange. + To also download data for inactive (delisted) pairs, add `--include-inactive-pairs` to the command. + +??? Note "Permission denied errors" + If your configuration directory `user_data` was made by docker, you may get the following error: + + ``` + cp: cannot create regular file 'user_data/data/binance/pairs.json': Permission denied + ``` + + You can fix the permissions of your user-data directory as follows: + + ``` + sudo chown -R $UID:$GID user_data + ``` + +### Start download + +Then run: + +```bash +freqtrade download-data --exchange binance +``` + +This will download historical candle (OHLCV) data for all the currency pairs you defined in `pairs.json`. + +Alternatively, specify the pairs directly + +```bash +freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT +``` + +or as regex (to download all active USDT pairs) + +```bash +freqtrade download-data --exchange binance --pairs .*/USDT +``` + +### Other Notes + +- To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`. +- To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust rate limits etc.) +- To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`. +- To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days). +- To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020. Eventually set end dates are ignored. +- Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data. +- To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options. + + ### Data format Freqtrade currently supports 3 data-formats for both OHLCV and trades data: @@ -312,64 +391,6 @@ ETH/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h ``` -### Pairs file - -In alternative to the whitelist from `config.json`, a `pairs.json` file can be used. - -If you are using Binance for example: - -- create a directory `user_data/data/binance` and copy or create the `pairs.json` file in that directory. -- update the `pairs.json` file to contain the currency pairs you are interested in. - -```bash -mkdir -p user_data/data/binance -cp tests/testdata/pairs.json user_data/data/binance -``` - -If your configuration directory `user_data` was made by docker, you may get the following error: - -``` -cp: cannot create regular file 'user_data/data/binance/pairs.json': Permission denied -``` - -You can fix the permissions of your user-data directory as follows: - -``` -sudo chown -R $UID:$GID user_data -``` - -The format of the `pairs.json` file is a simple json list. -Mixing different stake-currencies is allowed for this file, since it's only used for downloading. - -``` json -[ - "ETH/BTC", - "ETH/USDT", - "BTC/USDT", - "XRP/ETH" -] -``` - -### Start download - -Then run: - -```bash -freqtrade download-data --exchange binance -``` - -This will download historical candle (OHLCV) data for all the currency pairs you defined in `pairs.json`. - -### Other Notes - -- To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`. -- To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust rate limits etc.) -- To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`. -- To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days). -- To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020. Eventually set end dates are ignored. -- Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data. -- To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options. - ### Trades (tick) data By default, `download-data` sub-command downloads Candles (OHLCV) data. Some exchanges also provide historic trade-data via their API. diff --git a/docs/developer.md b/docs/developer.md index bd138212b..a6c9ec322 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -8,7 +8,7 @@ All contributions, bug reports, bug fixes, documentation improvements, enhanceme Documentation is available at [https://freqtrade.io](https://www.freqtrade.io/) and needs to be provided with every new feature PR. -Special fields for the documentation (like Note boxes, ...) can be found [here](https://squidfunk.github.io/mkdocs-material/extensions/admonition/). +Special fields for the documentation (like Note boxes, ...) can be found [here](https://squidfunk.github.io/mkdocs-material/reference/admonitions/). To test the documentation locally use the following commands. diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 45e0d444d..49b4cdda6 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -51,6 +51,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--print-all] [--no-color] [--print-json] [-j JOBS] [--random-state INT] [--min-trades INT] [--hyperopt-loss NAME] [--disable-param-export] + [--ignore-missing-spaces] optional arguments: -h, --help show this help message and exit @@ -118,6 +119,9 @@ optional arguments: MaxDrawDownHyperOptLoss --disable-param-export Disable automatic hyperopt parameter export. + --ignore-missing-spaces, --ignore-unparameterized-spaces + Suppress errors for any requested Hyperopt spaces that + do not contain any parameters. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index b612a4ddf..589bc23b2 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -52,6 +52,8 @@ To skip pair validation against active markets, set `"allow_inactive": true` wit This can be useful for backtesting expired pairs (like quarterly spot-markets). This option must be configured along with `exchange.skip_pair_validation` in the exchange configuration. +When used in a "follow-up" position (e.g. after VolumePairlist), all pairs in `'pair_whitelist'` will be added to the end of the pairlist. + #### Volume Pair List `VolumePairList` employs sorting/filtering of pairs by their trading volume. It selects `number_assets` top pairs with sorting based on the `sort_key` (which can only be `quoteVolume`). @@ -194,17 +196,22 @@ Trade count is used as a tie breaker. You can use the `minutes` parameter to only consider performance of the past X minutes (rolling window). Not defining this parameter (or setting it to 0) will use all-time performance. +The optional `min_profit` parameter defines the minimum profit a pair must have to be considered. +Pairs below this level will be filtered out. +Using this parameter without `minutes` is highly discouraged, as it can lead to an empty pairlist without without a way to recover. + ```json "pairlists": [ // ... { "method": "PerformanceFilter", - "minutes": 1440 // rolling 24h + "minutes": 1440, // rolling 24h + "min_profit": 0.01 } ], ``` -!!! Note +!!! Warning "Backtesting" `PerformanceFilter` does not support backtesting mode. #### PrecisionFilter diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 9a733d8f7..72d1d0494 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ -mkdocs==1.2.2 -mkdocs-material==7.3.2 +mkdocs==1.2.3 +mkdocs-material==7.3.4 mdx_truly_sane_lists==1.2 pymdown-extensions==9.0 diff --git a/docs/utils.md b/docs/utils.md index d8fbcacb7..e915528ec 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -281,7 +281,7 @@ bitmax True missing opt: fetchMyTrades bitmex False Various reasons. bitpanda True bitso False missing: fetchOHLCV -bitstamp False Does not provide history. Details in https://github.com/freqtrade/freqtrade/issues/1983 +bitstamp True missing opt: fetchTickers bitstamp1 False missing: fetchOrder, fetchOHLCV bittrex True bitvavo True diff --git a/environment.yml b/environment.yml index fa71b5fe9..5f67ccb16 100644 --- a/environment.yml +++ b/environment.yml @@ -16,7 +16,6 @@ dependencies: - cachetools - requests - urllib3 - - wrapt - jsonschema - TA-Lib - tabulate @@ -64,7 +63,6 @@ dependencies: - py_find_1st - tables - pytest-random-order - - flake8-type-annotations - ccxt - flake8-tidy-imports - -e . diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index a02faa736..86d7a1923 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -31,7 +31,8 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "epochs", "spaces", "print_all", "print_colorized", "print_json", "hyperopt_jobs", "hyperopt_random_state", "hyperopt_min_trades", - "hyperopt_loss", "disableparamexport"] + "hyperopt_loss", "disableparamexport", + "hyperopt_ignore_missing_space"] ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] @@ -62,9 +63,9 @@ ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "d ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs"] -ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "timerange", - "download_trades", "exchange", "timeframes", "erase", "dataformat_ohlcv", - "dataformat_trades"] +ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "include_inactive", + "timerange", "download_trades", "exchange", "timeframes", + "erase", "dataformat_ohlcv", "dataformat_trades"] ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url", "trade_source", "export", "exportfilename", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 30a9b0137..b60692c67 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -355,6 +355,11 @@ AVAILABLE_CLI_OPTIONS = { type=check_int_positive, metavar='INT', ), + "include_inactive": Arg( + '--include-inactive-pairs', + help='Also download data from inactive pairs.', + action='store_true', + ), "new_pairs_days": Arg( '--new-pairs-days', help='Download data of new pairs for given number of days. Default: `%(default)s`.', @@ -558,4 +563,10 @@ AVAILABLE_CLI_OPTIONS = { help='Do not print epoch details header.', action='store_true', ), + "hyperopt_ignore_missing_space": Arg( + "--ignore-missing-spaces", "--ignore-unparameterized-spaces", + help=("Suppress errors for any requested Hyperopt spaces " + "that do not contain any parameters."), + action="store_true", + ), } diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index ee05e6c69..5dc5fe7ea 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -11,6 +11,7 @@ from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_oh from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes +from freqtrade.exchange.exchange import market_is_active from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.resolvers import ExchangeResolver @@ -47,11 +48,13 @@ def start_download_data(args: Dict[str, Any]) -> None: # Init exchange exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) + markets = [p for p, m in exchange.markets.items() if market_is_active(m) + or config.get('include_inactive')] + expanded_pairs = expand_pairlist(config['pairs'], markets) + # Manual validations of relevant settings if not config['exchange'].get('skip_pair_validation', False): - exchange.validate_pairs(config['pairs']) - expanded_pairs = expand_pairlist(config['pairs'], list(exchange.markets)) - + exchange.validate_pairs(expanded_pairs) logger.info(f"About to download pairs: {expanded_pairs}, " f"intervals: {config['timeframes']} to {config['datadir']}") diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 94b108f2b..5db3379d2 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -369,6 +369,9 @@ class Configuration: self._args_to_config(config, argname='hyperopt_show_no_header', logstring='Parameter --no-header detected: {}') + self._args_to_config(config, argname="hyperopt_ignore_missing_space", + logstring="Paramter --ignore-missing-space detected: {}") + def _process_plot_options(self, config: Dict[str, Any]) -> None: self._args_to_config(config, argname='pairs', @@ -404,6 +407,9 @@ class Configuration: self._args_to_config(config, argname='days', logstring='Detected --days: {}') + self._args_to_config(config, argname='include_inactive', + logstring='Detected --include-inactive-pairs: {}') + self._args_to_config(config, argname='download_trades', logstring='Detected --dl-trades: {}') diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c6b8f0e62..ee104325b 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -39,6 +39,8 @@ DEFAULT_DATAFRAME_COLUMNS = ['date', 'open', 'high', 'low', 'close', 'volume'] # Don't modify sequence of DEFAULT_TRADES_COLUMNS # it has wide consequences for stored trades files DEFAULT_TRADES_COLUMNS = ['timestamp', 'id', 'type', 'side', 'price', 'amount', 'cost'] +TRADING_MODES = ['spot', 'margin', 'futures'] +COLLATERAL_TYPES = ['cross', 'isolated'] LAST_BT_RESULT_FN = '.last_result.json' FTHYPT_FILEVERSION = 'fthypt_fileversion' @@ -146,6 +148,8 @@ CONF_SCHEMA = { 'sell_profit_offset': {'type': 'number'}, 'ignore_roi_if_buy_signal': {'type': 'boolean'}, 'ignore_buying_expired_candle_after': {'type': 'number'}, + 'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, + 'collateral_type': {'type': 'string', 'enum': COLLATERAL_TYPES}, 'bot_name': {'type': 'string'}, 'unfilledtimeout': { 'type': 'object', @@ -193,7 +197,7 @@ CONF_SCHEMA = { 'required': ['price_side'] }, 'custom_price_max_distance_ratio': { - 'type': 'number', 'minimum': 0.0 + 'type': 'number', 'minimum': 0.0 }, 'order_types': { 'type': 'object', diff --git a/freqtrade/enums/rpcmessagetype.py b/freqtrade/enums/rpcmessagetype.py index 4e3f693e5..663b37b83 100644 --- a/freqtrade/enums/rpcmessagetype.py +++ b/freqtrade/enums/rpcmessagetype.py @@ -5,15 +5,21 @@ class RPCMessageType(Enum): STATUS = 'status' WARNING = 'warning' STARTUP = 'startup' + BUY = 'buy' BUY_FILL = 'buy_fill' BUY_CANCEL = 'buy_cancel' + SELL = 'sell' SELL_FILL = 'sell_fill' SELL_CANCEL = 'sell_cancel' PROTECTION_TRIGGER = 'protection_trigger' PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global' + SHORT = 'short' + SHORT_FILL = 'short_fill' + SHORT_CANCEL = 'short_cancel' + def __repr__(self): return self.value diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 7b89adf06..644a13e93 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -16,8 +16,6 @@ API_FETCH_ORDER_RETRY_COUNT = 5 BAD_EXCHANGES = { "bitmex": "Various reasons.", - "bitstamp": "Does not provide history. " - "Details in https://github.com/freqtrade/freqtrade/issues/1983", "phemex": "Does not provide history. ", "poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.", } diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2b203ab09..f3395c09c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -805,8 +805,14 @@ class Exchange: rate_for_order = self.price_to_precision(pair, rate) if needs_price else None self._lev_prep(pair, leverage) - order = self._api.create_order(pair, ordertype, side, - amount, rate_for_order, params) + order = self._api.create_order( + pair, + ordertype, + side, + amount, + rate_for_order, + params + ) self._log_exchange_response('create_order', order) return order diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ddb4b148f..bb7e06e8a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -7,7 +7,7 @@ import traceback from datetime import datetime, time, timezone from math import isclose from threading import Lock -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple import arrow from schedule import Scheduler @@ -17,7 +17,8 @@ from freqtrade.configuration import validate_config_consistency from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge -from freqtrade.enums import RPCMessageType, SellType, State, TradingMode +from freqtrade.enums import (Collateral, RPCMessageType, SellType, SignalDirection, State, + TradingMode) from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds @@ -101,14 +102,19 @@ class FreqtradeBot(LoggingMixin): initial_state = self.config.get('initial_state') self.state = State[initial_state.upper()] if initial_state else State.STOPPED - # Protect sell-logic from forcesell and vice versa + # Protect exit-logic from forcesell and vice versa self._exit_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) + self.trading_mode: TradingMode = TradingMode.SPOT + self.collateral_type: Optional[Collateral] = None + if 'trading_mode' in self.config: self.trading_mode = TradingMode(self.config['trading_mode']) - else: - self.trading_mode = TradingMode.SPOT + + if 'collateral_type' in self.config: + self.collateral_type = Collateral(self.config['collateral_type']) + self._schedule = Scheduler() if self.trading_mode == TradingMode.FUTURES: @@ -194,7 +200,7 @@ class FreqtradeBot(LoggingMixin): # Protect from collisions with forceexit. # Without this, freqtrade my try to recreate stoploss_on_exchange orders - # while selling is in process, since telegram messages arrive in an different thread. + # while exiting is in process, since telegram messages arrive in an different thread. with self._exit_lock: trades = Trade.get_open_trades() # First process current opened trades (positions) @@ -305,21 +311,26 @@ class FreqtradeBot(LoggingMixin): trades: List[Trade] = Trade.get_closed_trades_without_assigned_fees() for trade in trades: - - if not trade.is_open and not trade.fee_updated('sell'): + if not trade.is_open and not trade.fee_updated(trade.exit_side): # Get sell fee - order = trade.select_order('sell', False) + order = trade.select_order(trade.exit_side, False) if order: - logger.info(f"Updating sell-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.exit_side}-fee on trade {trade}" + f"for order {order.order_id}." + ) self.update_trade_state(trade, order.order_id, stoploss_order=order.ft_order_side == 'stoploss') trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() for trade in trades: - if trade.is_open and not trade.fee_updated('buy'): - order = trade.select_order('buy', False) + if trade.is_open and not trade.fee_updated(trade.enter_side): + order = trade.select_order(trade.enter_side, False) if order: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.enter_side}-fee on trade {trade}" + f"for order {order.order_id}." + ) self.update_trade_state(trade, order.order_id) def handle_insufficient_funds(self, trade: Trade): @@ -327,8 +338,8 @@ class FreqtradeBot(LoggingMixin): Determine if we ever opened a exiting order for this trade. If not, try update entering fees - otherwise "refind" the open order we obviously lost. """ - sell_order = trade.select_order('sell', None) - if sell_order: + exit_order = trade.select_order(trade.exit_side, None) + if exit_order: self.refind_lost_order(trade) else: self.reupdate_enter_order_fees(trade) @@ -338,10 +349,11 @@ class FreqtradeBot(LoggingMixin): Get buy order from database, and try to reupdate. Handles trades where the initial fee-update did not work. """ - logger.info(f"Trying to reupdate buy fees for {trade}") - order = trade.select_order('buy', False) + logger.info(f"Trying to reupdate {trade.enter_side} fees for {trade}") + order = trade.select_order(trade.enter_side, False) if order: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.enter_side}-fee on trade {trade} for order {order.order_id}.") self.update_trade_state(trade, order.order_id) def refind_lost_order(self, trade): @@ -357,7 +369,7 @@ class FreqtradeBot(LoggingMixin): if not order.ft_is_open: logger.debug(f"Order {order} is no longer open.") continue - if order.ft_order_side == 'buy': + if order.ft_order_side == trade.enter_side: # Skip buy side - this is handled by reupdate_enter_order_fees continue try: @@ -367,7 +379,7 @@ class FreqtradeBot(LoggingMixin): if fo and fo['status'] == 'open': # Assume this as the open stoploss order trade.stoploss_order_id = order.order_id - elif order.ft_order_side == 'sell': + elif order.ft_order_side == trade.exit_side: if fo and fo['status'] == 'open': # Assume this as the open order trade.open_order_id = order.order_id @@ -456,7 +468,9 @@ class FreqtradeBot(LoggingMixin): # running get_signal on historical data fetched (signal, enter_tag) = self.strategy.get_entry_signal( - pair, self.strategy.timeframe, analyzed_df + pair, + self.strategy.timeframe, + analyzed_df ) if signal: @@ -465,19 +479,31 @@ class FreqtradeBot(LoggingMixin): bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) if ((bid_check_dom.get('enabled', False)) and (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): - # TODO-lev: Does the below need to be adjusted for shorts? - if self._check_depth_of_market_buy(pair, bid_check_dom): - # TODO-lev: pass in "enter" as side. - - return self.execute_entry(pair, stake_amount, enter_tag=enter_tag) + if self._check_depth_of_market(pair, bid_check_dom, side=signal): + return self.execute_entry( + pair, + stake_amount, + enter_tag=enter_tag, + is_short=(signal == SignalDirection.SHORT) + ) else: return False - return self.execute_entry(pair, stake_amount, enter_tag=enter_tag) + return self.execute_entry( + pair, + stake_amount, + enter_tag=enter_tag, + is_short=(signal == SignalDirection.SHORT) + ) else: return False - def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: + def _check_depth_of_market( + self, + pair: str, + conf: Dict, + side: SignalDirection + ) -> bool: """ Checks depth of market before executing a buy """ @@ -487,9 +513,17 @@ class FreqtradeBot(LoggingMixin): order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks']) order_book_bids = order_book_data_frame['b_size'].sum() order_book_asks = order_book_data_frame['a_size'].sum() - bids_ask_delta = order_book_bids / order_book_asks + + enter_side = order_book_bids if side == SignalDirection.LONG else order_book_asks + exit_side = order_book_asks if side == SignalDirection.LONG else order_book_bids + bids_ask_delta = enter_side / exit_side + + bids = f"Bids: {order_book_bids}" + asks = f"Asks: {order_book_asks}" + delta = f"Delta: {bids_ask_delta}" + logger.info( - f"Bids: {order_book_bids}, Asks: {order_book_asks}, Delta: {bids_ask_delta}, " + f"{bids}, {asks}, {delta}, Direction: {side.value}" f"Bid Price: {order_book['bids'][0][0]}, Ask Price: {order_book['asks'][0][0]}, " f"Immediate Bid Quantity: {order_book['bids'][0][1]}, " f"Immediate Ask Quantity: {order_book['asks'][0][1]}." @@ -501,21 +535,65 @@ class FreqtradeBot(LoggingMixin): logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") return False - def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False, enter_tag: Optional[str] = None) -> bool: + def leverage_prep( + self, + pair: str, + open_rate: float, + amount: float, + leverage: float, + is_short: bool + ) -> Tuple[float, Optional[float]]: + + interest_rate = 0.0 + isolated_liq = None + + # TODO-lev: Uncomment once liq and interest merged in + # if TradingMode == TradingMode.MARGIN: + # interest_rate = self.exchange.get_interest_rate( + # pair=pair, + # open_rate=open_rate, + # is_short=is_short + # ) + + # if self.collateral_type == Collateral.ISOLATED: + + # isolated_liq = liquidation_price( + # exchange_name=self.exchange.name, + # trading_mode=self.trading_mode, + # open_rate=open_rate, + # amount=amount, + # leverage=leverage, + # is_short=is_short + # ) + + return interest_rate, isolated_liq + + def execute_entry( + self, + pair: str, + stake_amount: float, + price: Optional[float] = None, + forcebuy: bool = False, + leverage: float = 1.0, + is_short: bool = False, + enter_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 :param stake_amount: amount of stake-currency for the pair + :param leverage: amount of leverage applied to this trade :return: True if a buy order is created, false if it fails. """ time_in_force = self.strategy.order_time_in_force['buy'] + [side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long'] + if price: enter_limit_requested = price else: # Calculate price - proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy") + proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side=side) custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=proposed_enter_rate)( pair=pair, current_time=datetime.now(timezone.utc), @@ -524,10 +602,14 @@ class FreqtradeBot(LoggingMixin): enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate) if not enter_limit_requested: - raise PricingError('Could not determine buy price.') + raise PricingError(f'Could not determine {side} price.') - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested, - self.strategy.stoploss) + min_stake_amount = self.exchange.get_min_pair_stake_amount( + pair, + enter_limit_requested, + self.strategy.stoploss, + leverage=leverage + ) if not self.edge: max_stake_amount = self.wallets.get_available_stake_amount() @@ -543,10 +625,12 @@ class FreqtradeBot(LoggingMixin): if not stake_amount: return False - logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: " - f"{stake_amount} ...") + logger.info( + f"{name} signal found: about create a new trade for {pair} with stake_amount: " + f"{stake_amount} ..." + ) - amount = stake_amount / enter_limit_requested + amount = (stake_amount / enter_limit_requested) * leverage order_type = self.strategy.order_types['buy'] if forcebuy: # Forcebuy can define a different ordertype @@ -558,15 +642,21 @@ class FreqtradeBot(LoggingMixin): if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, time_in_force=time_in_force, current_time=datetime.now(timezone.utc), - side='long' + side='short' if is_short else 'long' ): logger.info(f"User requested abortion of buying {pair}") return False amount = self.exchange.amount_to_precision(pair, amount) - order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy", - amount=amount, rate=enter_limit_requested, - time_in_force=time_in_force) - order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') + order = self.exchange.create_order( + pair=pair, + ordertype=order_type, + side=side, + amount=amount, + rate=enter_limit_requested, + time_in_force=time_in_force, + leverage=leverage + ) + order_obj = Order.parse_from_ccxt_object(order, pair, side) order_id = order['id'] order_status = order.get('status', None) @@ -579,17 +669,17 @@ class FreqtradeBot(LoggingMixin): # return false if the order is not filled if float(order['filled']) == 0: - logger.warning('Buy %s order with time in force %s for %s is %s by %s.' + logger.warning('%s %s order with time in force %s for %s is %s by %s.' ' zero amount is fulfilled.', - order_tif, order_type, pair, order_status, self.exchange.name) + name, order_tif, order_type, pair, order_status, self.exchange.name) return False else: # the order is partially fulfilled # in case of IOC orders we can check immediately # if the order is fulfilled fully or partially - logger.warning('Buy %s order with time in force %s for %s is %s by %s.' + logger.warning('%s %s order with time in force %s for %s is %s by %s.' ' %s amount fulfilled out of %s (%s remaining which is canceled).', - order_tif, order_type, pair, order_status, self.exchange.name, + name, order_tif, order_type, pair, order_status, self.exchange.name, order['filled'], order['amount'], order['remaining'] ) stake_amount = order['cost'] @@ -602,6 +692,14 @@ class FreqtradeBot(LoggingMixin): amount = safe_value_fallback(order, 'filled', 'amount') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') + interest_rate, isolated_liq = self.leverage_prep( + leverage=leverage, + pair=pair, + amount=amount, + open_rate=enter_limit_filled_price, + is_short=is_short + ) + # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') open_date = datetime.now(timezone.utc) @@ -627,6 +725,10 @@ class FreqtradeBot(LoggingMixin): # TODO-lev: compatibility layer for buy_tag (!) buy_tag=enter_tag, timeframe=timeframe_to_minutes(self.config['timeframe']), + leverage=leverage, + is_short=is_short, + interest_rate=interest_rate, + isolated_liq=isolated_liq, trading_mode=self.trading_mode, funding_fees=funding_fees ) @@ -652,7 +754,7 @@ class FreqtradeBot(LoggingMixin): """ msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY, + 'type': RPCMessageType.SHORT if trade.is_short else RPCMessageType.BUY, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, @@ -673,11 +775,11 @@ class FreqtradeBot(LoggingMixin): """ Sends rpc notification when a entry order cancel occurred. """ - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") - + current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side) + msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY_CANCEL, + 'type': msg_type, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, @@ -696,9 +798,10 @@ class FreqtradeBot(LoggingMixin): self.rpc.send_msg(msg) def _notify_enter_fill(self, trade: Trade) -> None: + msg_type = RPCMessageType.SHORT_FILL if trade.is_short else RPCMessageType.BUY_FILL msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY_FILL, + 'type': msg_type, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, @@ -752,6 +855,7 @@ class FreqtradeBot(LoggingMixin): logger.debug('Handling %s ...', trade) (enter, exit_) = (False, False) + exit_signal_type = "exit_short" if trade.is_short else "exit_long" # TODO-lev: change to use_exit_signal, ignore_roi_if_enter_signal if (self.config.get('use_sell_signal', True) or @@ -762,15 +866,16 @@ class FreqtradeBot(LoggingMixin): (enter, exit_) = self.strategy.get_exit_signal( trade.pair, self.strategy.timeframe, - analyzed_df, is_short=trade.is_short + analyzed_df, + is_short=trade.is_short ) - # TODO-lev: side should depend on trade side. - exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") + logger.debug('checking exit') + exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.exit_side) if self._check_and_execute_exit(trade, exit_rate, enter, exit_): return True - logger.debug('Found no sell signal for %s.', trade) + logger.debug(f'Found no {exit_signal_type} signal for %s.', trade) return False def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool: @@ -855,7 +960,10 @@ class FreqtradeBot(LoggingMixin): # If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange if not stoploss_order: stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss - stop_price = trade.open_rate * (1 + stoploss) + if trade.is_short: + stop_price = trade.open_rate * (1 - stoploss) + else: + stop_price = trade.open_rate * (1 + stoploss) if self.create_stoploss_order(trade=trade, stop_price=stop_price): trade.stoploss_last_update = datetime.utcnow() @@ -880,11 +988,11 @@ class FreqtradeBot(LoggingMixin): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - self.handle_trailing_stoploss_on_exchange(trade, stoploss_order, side=trade.exit_side) + self.handle_trailing_stoploss_on_exchange(trade, stoploss_order) return False - def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict, side: str) -> None: + def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None: """ Check to see if stoploss on exchange should be updated in case of trailing stoploss on exchange @@ -892,7 +1000,7 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - if self.exchange.stoploss_adjust(trade.stop_loss, order, side): + if self.exchange.stoploss_adjust(trade.stop_loss, order, side=trade.exit_side): # we check if the update is necessary update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: @@ -918,7 +1026,11 @@ class FreqtradeBot(LoggingMixin): Check and execute trade exit """ should_exit: SellCheckTuple = self.strategy.should_exit( - trade, exit_rate, datetime.now(timezone.utc), enter=enter, exit_=exit_, + trade, + exit_rate, + datetime.now(timezone.utc), + enter=enter, + exit_=exit_, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) @@ -959,24 +1071,23 @@ class FreqtradeBot(LoggingMixin): continue fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) + is_entering = order['side'] == trade.enter_side + not_closed = order['status'] == 'open' or fully_cancelled + side = trade.enter_side if is_entering else trade.exit_side + timed_out = self._check_timed_out(side, order) + time_method = 'check_sell_timeout' if order['side'] == 'sell' else 'check_buy_timeout' - if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and ( - fully_cancelled - or self._check_timed_out('buy', order) - or strategy_safe_wrapper(self.strategy.check_buy_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): - self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) - - elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( - fully_cancelled - or self._check_timed_out('sell', order) - or strategy_safe_wrapper(self.strategy.check_sell_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): - self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) + if not_closed and (fully_cancelled or timed_out or ( + strategy_safe_wrapper(getattr(self.strategy, time_method), default_retval=False)( + pair=trade.pair, + trade=trade, + order=order + ) + )): + if is_entering: + self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) + else: + self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) def cancel_all_open_orders(self) -> None: """ @@ -991,10 +1102,10 @@ class FreqtradeBot(LoggingMixin): logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc()) continue - if order['side'] == 'buy': + if order['side'] == trade.enter_side: self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) - elif order['side'] == 'sell': + elif order['side'] == trade.exit_side: self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) Trade.commit() @@ -1016,7 +1127,7 @@ class FreqtradeBot(LoggingMixin): if filled_val > 0 and filled_stake < minstake: logger.warning( f"Order {trade.open_order_id} for {trade.pair} not cancelled, " - f"as the filled amount of {filled_val} would result in an unsellable trade.") + f"as the filled amount of {filled_val} would result in an unexitable trade.") return False corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair, trade.amount) @@ -1031,12 +1142,16 @@ class FreqtradeBot(LoggingMixin): corder = order reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info('Buy order %s for %s.', reason, trade) + side = trade.enter_side.capitalize() + logger.info('%s order %s for %s.', side, reason, trade) # Using filled to determine the filled amount filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled') if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): - logger.info('Buy order fully cancelled. Removing %s from database.', trade) + logger.info( + '%s order fully cancelled. Removing %s from database.', + side, trade + ) # if trade is not partially completed, just delete the trade trade.delete() was_trade_fully_canceled = True @@ -1054,11 +1169,11 @@ class FreqtradeBot(LoggingMixin): self.update_trade_state(trade, trade.open_order_id, corder) trade.open_order_id = None - logger.info('Partial buy order timeout for %s.', trade) + logger.info('Partial %s order timeout for %s.', trade.enter_side, trade) reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}" self.wallets.update() - self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'], + self._notify_enter_cancel(trade, order_type=self.strategy.order_types[trade.enter_side], reason=reason) return was_trade_fully_canceled @@ -1076,12 +1191,13 @@ class FreqtradeBot(LoggingMixin): trade.amount) trade.update_order(co) except InvalidOrderException: - logger.exception(f"Could not cancel sell order {trade.open_order_id}") + logger.exception( + f"Could not cancel {trade.exit_side} order {trade.open_order_id}") return 'error cancelling order' - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) else: reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) trade.update_order(order) trade.close_rate = None @@ -1098,7 +1214,7 @@ class FreqtradeBot(LoggingMixin): self.wallets.update() self._notify_exit_cancel( trade, - order_type=self.strategy.order_types['sell'], + order_type=self.strategy.order_types[trade.exit_side], reason=reason ) return reason @@ -1129,7 +1245,12 @@ class FreqtradeBot(LoggingMixin): raise DependencyException( f"Not enough amount to exit trade. Trade-amount: {amount}, Wallet: {wallet_amount}") - def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: + def execute_trade_exit( + self, + trade: Trade, + limit: float, + sell_reason: SellCheckTuple, # TODO-lev update to exit_reason + ) -> bool: """ Executes a trade exit for the given trade and limit :param trade: Trade instance @@ -1137,13 +1258,13 @@ class FreqtradeBot(LoggingMixin): :param sell_reason: Reason the sell was triggered :return: True if it succeeds (supported) False (not supported) """ - sell_type = 'sell' # TODO-lev: Update to exit + exit_type = 'sell' # TODO-lev: Update to exit if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): - sell_type = 'stoploss' + exit_type = 'stoploss' # if stoploss is on exchange and we are on dry_run mode, # we consider the sell price stop price - if self.config['dry_run'] and sell_type == 'stoploss' \ + if self.config['dry_run'] and exit_type == 'stoploss' \ and self.strategy.order_types['stoploss_on_exchange']: limit = trade.stop_loss @@ -1167,7 +1288,7 @@ class FreqtradeBot(LoggingMixin): except InvalidOrderException: logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") - order_type = self.strategy.order_types[sell_type] + order_type = self.strategy.order_types[exit_type] if sell_reason.sell_type == SellType.EMERGENCY_SELL: # Emergency sells (default to market!) order_type = self.strategy.order_types.get("emergencysell", "market") @@ -1177,7 +1298,7 @@ class FreqtradeBot(LoggingMixin): order_type = self.strategy.order_types.get("forcesell", order_type) amount = self._safe_exit_amount(trade.pair, trade.amount) - time_in_force = self.strategy.order_time_in_force['sell'] + time_in_force = self.strategy.order_time_in_force['sell'] # TODO-lev update to exit if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, @@ -1191,7 +1312,7 @@ class FreqtradeBot(LoggingMixin): order = self.exchange.create_order( pair=trade.pair, ordertype=order_type, - side="sell", + side=trade.exit_side, amount=amount, rate=limit, time_in_force=time_in_force @@ -1202,7 +1323,7 @@ class FreqtradeBot(LoggingMixin): self.handle_insufficient_funds(trade) return False - order_obj = Order.parse_from_ccxt_object(order, trade.pair, 'sell') + order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side) trade.orders.append(order_obj) trade.open_order_id = order['id'] @@ -1230,7 +1351,7 @@ class FreqtradeBot(LoggingMixin): profit_trade = trade.calc_profit(rate=profit_rate) # Use cached rates here - it was updated seconds ago. current_rate = self.exchange.get_rate( - trade.pair, refresh=False, side="sell") if not fill else None + trade.pair, refresh=False, side=trade.exit_side) if not fill else None profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1275,7 +1396,7 @@ class FreqtradeBot(LoggingMixin): profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="sell") + current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.exit_side) profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1390,7 +1511,7 @@ class FreqtradeBot(LoggingMixin): self.wallets.update() if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount: # Eat into dust if we own more than base currency - # TODO-lev: won't be in "base"(quote) currency for shorts + # TODO-lev: won't be in base currency for shorts logger.info(f"Fee amount for {trade} was in base currency - " f"Eating Fee {fee_abs} into dust.") elif fee_abs != 0: diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 9549b4054..2c7cc0ea7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -45,7 +45,7 @@ progressbar.streams.wrap_stdout() logger = logging.getLogger(__name__) -INITIAL_POINTS = 5 +INITIAL_POINTS = 30 # Keep no more than SKOPT_MODEL_QUEUE_SIZE models # in the skopt model queue, to optimize memory consumption @@ -258,6 +258,7 @@ class Hyperopt: if HyperoptTools.has_space(self.config, 'trailing'): logger.debug("Hyperopt has 'trailing' space") self.trailing_space = self.custom_hyperopt.trailing_space() + self.dimensions = (self.buy_space + self.sell_space + self.protection_space + self.roi_space + self.stoploss_space + self.trailing_space) diff --git a/freqtrade/optimize/hyperopt_auto.py b/freqtrade/optimize/hyperopt_auto.py index c1c769c72..63b4b14e1 100644 --- a/freqtrade/optimize/hyperopt_auto.py +++ b/freqtrade/optimize/hyperopt_auto.py @@ -3,6 +3,7 @@ HyperOptAuto class. This module implements a convenience auto-hyperopt class, which can be used together with strategies that implement IHyperStrategy interface. """ +import logging from contextlib import suppress from typing import Callable, Dict, List @@ -15,12 +16,19 @@ with suppress(ImportError): from freqtrade.optimize.hyperopt_interface import EstimatorType, IHyperOpt -def _format_exception_message(space: str) -> str: - raise OperationalException( - f"The '{space}' space is included into the hyperoptimization " - f"but no parameter for this space was not found in your Strategy. " - f"Please make sure to have parameters for this space enabled for optimization " - f"or remove the '{space}' space from hyperoptimization.") +logger = logging.getLogger(__name__) + + +def _format_exception_message(space: str, ignore_missing_space: bool) -> None: + msg = (f"The '{space}' space is included into the hyperoptimization " + f"but no parameter for this space was not found in your Strategy. " + ) + if ignore_missing_space: + logger.warning(msg + "This space will be ignored.") + else: + raise OperationalException( + msg + f"Please make sure to have parameters for this space enabled for optimization " + f"or remove the '{space}' space from hyperoptimization.") class HyperOptAuto(IHyperOpt): @@ -48,13 +56,16 @@ class HyperOptAuto(IHyperOpt): if attr.optimize: yield attr.get_space(attr_name) - def _get_indicator_space(self, category): + def _get_indicator_space(self, category) -> List: # TODO: is this necessary, or can we call "generate_space" directly? indicator_space = list(self._generate_indicator_space(category)) if len(indicator_space) > 0: return indicator_space else: - _format_exception_message(category) + _format_exception_message( + category, + self.config.get("hyperopt_ignore_missing_space", False)) + return [] def buy_indicator_space(self) -> List['Dimension']: return self._get_indicator_space('buy') diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 452ddec32..623dd74d3 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -506,7 +506,6 @@ class LocalTrade(): lower_stop = new_loss < self.stop_loss # stop losses only walk up, never down!, - # TODO-lev # ? But adding more to a leveraged trade would create a lower liquidation price, # ? decreasing the minimum stoploss if (higher_stop and not self.is_short) or (lower_stop and self.is_short): diff --git a/freqtrade/plugins/pairlist/PerformanceFilter.py b/freqtrade/plugins/pairlist/PerformanceFilter.py index 301ee57ab..671b6362b 100644 --- a/freqtrade/plugins/pairlist/PerformanceFilter.py +++ b/freqtrade/plugins/pairlist/PerformanceFilter.py @@ -21,6 +21,7 @@ class PerformanceFilter(IPairList): super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) self._minutes = pairlistconfig.get('minutes', 0) + self._min_profit = pairlistconfig.get('min_profit', None) @property def needstickers(self) -> bool: @@ -68,6 +69,14 @@ class PerformanceFilter(IPairList): sorted_df = list_df.merge(performance, on='pair', how='left')\ .fillna(0).sort_values(by=['count', 'pair'], ascending=True)\ .sort_values(by=['profit'], ascending=False) + if self._min_profit is not None: + removed = sorted_df[sorted_df['profit'] < self._min_profit] + for _, row in removed.iterrows(): + self.log_once( + f"Removing pair {row['pair']} since {row['profit']} is " + f"below {self._min_profit}", logger.info) + sorted_df = sorted_df[sorted_df['profit'] >= self._min_profit] + pairlist = sorted_df['pair'].tolist() return pairlist diff --git a/freqtrade/plugins/pairlist/StaticPairList.py b/freqtrade/plugins/pairlist/StaticPairList.py index d8623e13d..30fa474e4 100644 --- a/freqtrade/plugins/pairlist/StaticPairList.py +++ b/freqtrade/plugins/pairlist/StaticPairList.py @@ -4,9 +4,9 @@ Static Pair List provider Provides pair white list as it configured in config """ import logging +from copy import deepcopy from typing import Any, Dict, List -from freqtrade.exceptions import OperationalException from freqtrade.plugins.pairlist.IPairList import IPairList @@ -20,10 +20,6 @@ class StaticPairList(IPairList): pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - if self._pairlist_pos != 0: - raise OperationalException(f"{self.name} can only be used in the first position " - "in the list of Pairlist Handlers.") - self._allow_inactive = self._pairlistconfig.get('allow_inactive', False) @property @@ -64,4 +60,8 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - return pairlist + pairlist_ = deepcopy(pairlist) + for pair in self._config['exchange']['pair_whitelist']: + if pair not in pairlist_: + pairlist_.append(pair) + return pairlist_ diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index d2459010f..16f9a78c0 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -1,5 +1,6 @@ -from typing import Any, Dict, Optional +from typing import Any, Dict, Iterator, Optional +from freqtrade.persistence import Trade from freqtrade.rpc.rpc import RPC, RPCException from .webserver import ApiServer @@ -11,10 +12,12 @@ def get_rpc_optional() -> Optional[RPC]: return None -def get_rpc() -> Optional[RPC]: +def get_rpc() -> Optional[Iterator[RPC]]: _rpc = get_rpc_optional() if _rpc: - return _rpc + Trade.query.session.rollback() + yield _rpc + Trade.query.session.rollback() else: raise RPCException('Bot is not in the correct state') diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 059ba9c41..846747f40 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -25,6 +25,7 @@ from freqtrade.constants import DUST_PER_COIN from freqtrade.enums import RPCMessageType from freqtrade.exceptions import OperationalException from freqtrade.misc import chunks, plural, round_coin_value +from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCException, RPCHandler @@ -59,7 +60,8 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: update.message.chat_id ) return wrapper - + # Rollback session to avoid getting data stored in a transaction. + Trade.query.session.rollback() logger.debug( 'Executing handler: %s for chat_id: %s', command_handler.__name__, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a22a0b6b8..94541218c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -840,28 +840,32 @@ class IStrategy(ABC, HyperStrategyMixin): else: logger.warning("CustomStoploss function did not return valid stoploss") - if self.trailing_stop and trade.stop_loss < (low or current_rate): + sl_lower_long = (trade.stop_loss < (low or current_rate) and not trade.is_short) + sl_higher_short = (trade.stop_loss > (high or current_rate) and trade.is_short) + if self.trailing_stop and (sl_lower_long or sl_higher_short): # trailing stoploss handling sl_offset = self.trailing_stop_positive_offset # Make sure current_profit is calculated using high for backtesting. - # TODO-lev: Check this function - high / low usage must be inversed for short trades! - high_profit = current_profit if not high else trade.calc_profit_ratio(high) + bound = low if trade.is_short else high + bound_profit = current_profit if not bound else trade.calc_profit_ratio(bound) # Don't update stoploss if trailing_only_offset_is_reached is true. - if not (self.trailing_only_offset_is_reached and high_profit < sl_offset): + if not (self.trailing_only_offset_is_reached and bound_profit < sl_offset): # Specific handling for trailing_stop_positive - if self.trailing_stop_positive is not None and high_profit > sl_offset: + if self.trailing_stop_positive is not None and bound_profit > sl_offset: stop_loss_value = self.trailing_stop_positive logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} " f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") - trade.adjust_stop_loss(high or current_rate, stop_loss_value) + trade.adjust_stop_loss(bound or current_rate, stop_loss_value) + sl_higher_short = (trade.stop_loss >= (low or current_rate) and not trade.is_short) + sl_lower_long = ((trade.stop_loss <= (high or current_rate) and trade.is_short)) # evaluate if the stoploss was hit if stoploss is not on exchange # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to # regular stoploss handling. - if ((trade.stop_loss >= (low or current_rate)) and + if ((sl_higher_short or sl_lower_long) and (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])): sell_type = SellType.STOP_LOSS @@ -870,12 +874,18 @@ class IStrategy(ABC, HyperStrategyMixin): if trade.initial_stop_loss != trade.stop_loss: sell_type = SellType.TRAILING_STOP_LOSS logger.debug( - f"{trade.pair} - HIT STOP: current price at {(low or current_rate):.6f}, " + f"{trade.pair} - HIT STOP: current price at " + f"{((high if trade.is_short else low) or current_rate):.6f}, " f"stoploss is {trade.stop_loss:.6f}, " f"initial stoploss was at {trade.initial_stop_loss:.6f}, " f"trade opened at {trade.open_rate:.6f}") + new_stoploss = ( + trade.stop_loss + trade.initial_stop_loss + if trade.is_short else + trade.stop_loss - trade.initial_stop_loss + ) logger.debug(f"{trade.pair} - Trailing stop saved " - f"{trade.stop_loss - trade.initial_stop_loss:.6f}") + f"{new_stoploss:.6f}") return SellCheckTuple(sell_type=sell_type) diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 80fa7cdae..13df9c2a8 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -58,6 +58,8 @@ class SampleStrategy(IStrategy): # Hyperoptable parameters buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True) + short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True) + exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) # Optimal timeframe for the strategy. timeframe = '5m' @@ -354,6 +356,16 @@ class SampleStrategy(IStrategy): ), 'enter_long'] = 1 + dataframe.loc[ + ( + # Signal: RSI crosses above 70 + (qtpylib.crossed_above(dataframe['rsi'], self.short_rsi.value)) & + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'enter_short'] = 1 + return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -371,5 +383,18 @@ class SampleStrategy(IStrategy): (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling (dataframe['volume'] > 0) # Make sure Volume is not 0 ), + 'exit_long'] = 1 + + dataframe.loc[ + ( + # Signal: RSI crosses above 30 + (qtpylib.crossed_above(dataframe['rsi'], self.exit_short_rsi.value)) & + # Guard: tema below BB middle + (dataframe['tema'] <= dataframe['bb_middleband']) & + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'exit_short'] = 1 + return dataframe diff --git a/freqtrade/vendor/qtpylib/indicators.py b/freqtrade/vendor/qtpylib/indicators.py index 4c0fb5b5c..4f14ae13c 100644 --- a/freqtrade/vendor/qtpylib/indicators.py +++ b/freqtrade/vendor/qtpylib/indicators.py @@ -339,11 +339,13 @@ def vwap(bars): (input can be pandas series or numpy array) bars are usually mid [ (h+l)/2 ] or typical [ (h+l+c)/3 ] """ - typical = ((bars['high'] + bars['low'] + bars['close']) / 3).values - volume = bars['volume'].values + raise ValueError("using `qtpylib.vwap` facilitates lookahead bias. Please use " + "`qtpylib.rolling_vwap` instead, which calculates vwap in a rolling manner.") + # typical = ((bars['high'] + bars['low'] + bars['close']) / 3).values + # volume = bars['volume'].values - return pd.Series(index=bars.index, - data=np.cumsum(volume * typical) / np.cumsum(volume)) + # return pd.Series(index=bars.index, + # data=np.cumsum(volume * typical) / np.cumsum(volume)) # --------------------------------------------- diff --git a/mkdocs.yml b/mkdocs.yml index 45b8d2557..87a6cc464 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,8 +55,8 @@ theme: primary: "blue grey" accent: "tear" toggle: - icon: material/toggle-switch-off-outline - name: Switch to dark mode + icon: material/toggle-switch + name: Switch to light mode extra_css: - "stylesheets/ft.extra.css" extra_javascript: diff --git a/requirements-dev.txt b/requirements-dev.txt index 74ebee479..c2b62196c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,11 +4,11 @@ -r requirements-hyperopt.txt coveralls==3.2.0 -flake8==4.0.0 +flake8==4.0.1 flake8-tidy-imports==4.5.0 mypy==0.910 pytest==6.2.5 -pytest-asyncio==0.15.1 +pytest-asyncio==0.16.0 pytest-cov==3.0.0 pytest-mock==3.6.1 pytest-random-order==1.0.4 @@ -20,7 +20,7 @@ time-machine==2.4.0 nbconvert==6.2.0 # mypy types -types-cachetools==4.2.2 -types-filelock==3.2.0 -types-requests==2.25.9 -types-tabulate==0.8.2 +types-cachetools==4.2.4 +types-filelock==3.2.1 +types-requests==2.25.11 +types-tabulate==0.8.3 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index e97e78638..288d3efad 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -5,7 +5,7 @@ scipy==1.7.1 scikit-learn==1.0 scikit-optimize==0.9.0 -filelock==3.3.0 +filelock==3.3.1 joblib==1.1.0 psutil==5.8.0 -progressbar2==3.53.3 +progressbar2==3.55.0 diff --git a/requirements.txt b/requirements.txt index 7ee5eed33..315ef69e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ numpy==1.21.2 -pandas==1.3.3 +pandas==1.3.4 pandas-ta==0.3.14b -ccxt==1.57.94 +ccxt==1.58.47 # Pin cryptography for now due to rust build errors with piwheels cryptography==35.0.0 aiohttp==3.7.4.post0 @@ -12,7 +12,6 @@ arrow==1.2.0 cachetools==4.2.2 requests==2.26.0 urllib3==1.26.7 -wrapt==1.13.1 jsonschema==4.1.0 TA-Lib==0.4.21 technical==1.3.0 @@ -26,15 +25,15 @@ blosc==1.10.6 py_find_1st==1.1.5 # Load ticker files 30% faster -python-rapidjson==1.4 +python-rapidjson==1.5 # Notify systemd sdnotify==0.3.2 # API Server -fastapi==0.68.1 +fastapi==0.70.0 uvicorn==0.15.0 -pyjwt==2.2.0 +pyjwt==2.3.0 aiofiles==0.7.0 psutil==5.8.0 diff --git a/setup.py b/setup.py index c0696abdf..a2e50b2e5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,6 @@ hyperopt = [ develop = [ 'coveralls', 'flake8', - 'flake8-type-annotations', 'flake8-tidy-imports', 'mypy', 'pytest', @@ -51,7 +50,6 @@ setup( 'cachetools', 'requests', 'urllib3', - 'wrapt', 'jsonschema', 'TA-Lib', 'pandas-ta', diff --git a/setup.sh b/setup.sh index aee7c80b5..1173b59b9 100755 --- a/setup.sh +++ b/setup.sh @@ -30,7 +30,7 @@ function check_installed_python() { check_installed_pip return fi - done + done echo "No usable python found. Please make sure to have python3.7 or newer installed" exit 1 @@ -95,11 +95,19 @@ function install_talib() { return fi - cd build_helpers && ./install_ta-lib.sh && cd .. + cd build_helpers && ./install_ta-lib.sh + + if [ $? -ne 0 ]; then + echo "Quitting. Please fix the above error before continuing." + cd .. + exit 1 + fi; + + cd .. } -function install_mac_newer_python_dependencies() { - +function install_mac_newer_python_dependencies() { + if [ ! $(brew --prefix --installed hdf5 2>/dev/null) ] then echo "-------------------------" @@ -115,7 +123,7 @@ function install_mac_newer_python_dependencies() { echo "Installing c-blosc" echo "-------------------------" brew install c-blosc - fi + fi export CBLOSC_DIR=$(brew --prefix) } @@ -130,7 +138,7 @@ function install_macos() { fi #Gets number after decimal in python version version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g') - + if [[ $version -ge 9 ]]; then #Checks if python version >= 3.9 install_mac_newer_python_dependencies fi diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 4e07e299c..4bc12ed6b 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -754,6 +754,46 @@ def test_download_data_no_pairs(mocker, caplog): start_download_data(pargs) +def test_download_data_all_pairs(mocker, markets): + + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + + dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', + MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) + patch_exchange(mocker) + mocker.patch( + 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) + ) + args = [ + "download-data", + "--exchange", + "binance", + "--pairs", + ".*/USDT" + ] + pargs = get_args(args) + pargs['config'] = None + start_download_data(pargs) + expected = set(['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT']) + assert set(dl_mock.call_args_list[0][1]['pairs']) == expected + assert dl_mock.call_count == 1 + + dl_mock.reset_mock() + args = [ + "download-data", + "--exchange", + "binance", + "--pairs", + ".*/USDT", + "--include-inactive-pairs", + ] + pargs = get_args(args) + pargs['config'] = None + start_download_data(pargs) + expected = set(['ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT']) + assert set(dl_mock.call_args_list[0][1]['pairs']) == expected + + def test_download_data_trades(mocker, caplog): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_trades_data', MagicMock(return_value=[])) @@ -903,7 +943,7 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir): mocker.patch( 'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist', return_value=True - ) + ) def fake_iterator(*args, **kwargs): yield from [saved_hyperopt_results] @@ -1309,9 +1349,10 @@ def test_start_list_data(testdatadir, capsys): @pytest.mark.usefixtures("init_persistence") +# TODO-lev: Short trades? def test_show_trades(mocker, fee, capsys, caplog): mocker.patch("freqtrade.persistence.init_db") - create_mock_trades(fee) + create_mock_trades(fee, False) args = [ "show-trades", "--db-url", diff --git a/tests/conftest.py b/tests/conftest.py index aa6a6b05e..1cb4c186e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -209,8 +209,14 @@ def get_patched_worker(mocker, config) -> Worker: return Worker(args=None, config=config) -def patch_get_signal(freqtrade: FreqtradeBot, enter_long=True, exit_long=False, - enter_short=False, exit_short=False, enter_tag: Optional[str] = None) -> None: +def patch_get_signal( + freqtrade: FreqtradeBot, + enter_long=True, + exit_long=False, + enter_short=False, + exit_short=False, + enter_tag: Optional[str] = None +) -> None: """ :param mocker: mocker to patch IStrategy class :param value: which value IStrategy.get_signal() must return @@ -241,7 +247,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, enter_long=True, exit_long=False, freqtrade.exchange.refresh_latest_ohlcv = lambda p: None -def create_mock_trades(fee, use_db: bool = True): +def create_mock_trades(fee, is_short: bool, use_db: bool = True): """ Create some fake trades ... """ @@ -252,26 +258,26 @@ def create_mock_trades(fee, use_db: bool = True): LocalTrade.add_bt_trade(trade) # Simulate dry_run entries - trade = mock_trade_1(fee) + trade = mock_trade_1(fee, is_short) add_trade(trade) - trade = mock_trade_2(fee) + trade = mock_trade_2(fee, is_short) add_trade(trade) - trade = mock_trade_3(fee) + trade = mock_trade_3(fee, is_short) add_trade(trade) - trade = mock_trade_4(fee) + trade = mock_trade_4(fee, is_short) add_trade(trade) - trade = mock_trade_5(fee) + trade = mock_trade_5(fee, is_short) add_trade(trade) - trade = mock_trade_6(fee) + trade = mock_trade_6(fee, is_short) add_trade(trade) if use_db: - Trade.query.session.flush() + Trade.commit() def create_mock_trades_with_leverage(fee, use_db: bool = True): @@ -285,22 +291,22 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True): LocalTrade.add_bt_trade(trade) # Simulate dry_run entries - trade = mock_trade_1(fee) + trade = mock_trade_1(fee, False) add_trade(trade) - trade = mock_trade_2(fee) + trade = mock_trade_2(fee, False) add_trade(trade) - trade = mock_trade_3(fee) + trade = mock_trade_3(fee, False) add_trade(trade) - trade = mock_trade_4(fee) + trade = mock_trade_4(fee, False) add_trade(trade) - trade = mock_trade_5(fee) + trade = mock_trade_5(fee, False) add_trade(trade) - trade = mock_trade_6(fee) + trade = mock_trade_6(fee, False) add_trade(trade) trade = short_trade(fee) @@ -323,7 +329,7 @@ def create_mock_trades_usdt(fee, use_db: bool = True): else: LocalTrade.add_bt_trade(trade) - # Simulate dry_run entries + # Simulate dry_run entries trade = mock_trade_usdt_1(fee) add_trade(trade) @@ -343,7 +349,7 @@ def create_mock_trades_usdt(fee, use_db: bool = True): add_trade(trade) if use_db: - Trade.query.session.flush() + Trade.commit() def get_sides(is_short: bool) -> Tuple[str, str]: @@ -2295,6 +2301,7 @@ def limit_sell_order_usdt_open(): 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, 'amount': 30.0, + 'cost': 66.0, 'filled': 0.0, 'remaining': 30.0, 'status': 'open' @@ -2340,3 +2347,27 @@ def market_sell_order_usdt(): 'remaining': 0.0, 'status': 'closed' } + + +@pytest.fixture(scope='function') +def limit_order(limit_buy_order_usdt, limit_sell_order_usdt): + return { + 'buy': limit_buy_order_usdt, + 'sell': limit_sell_order_usdt + } + + +@pytest.fixture(scope='function') +def market_order(market_buy_order_usdt, market_sell_order_usdt): + return { + 'buy': market_buy_order_usdt, + 'sell': market_sell_order_usdt + } + + +@pytest.fixture(scope='function') +def limit_order_open(limit_buy_order_usdt_open, limit_sell_order_usdt_open): + return { + 'buy': limit_buy_order_usdt_open, + 'sell': limit_sell_order_usdt_open + } diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index cf3c970f6..fe88064a3 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -6,12 +6,24 @@ from freqtrade.persistence.models import Order, Trade MOCK_TRADE_COUNT = 6 -def mock_order_1(): +def enter_side(is_short: bool): + return "sell" if is_short else "buy" + + +def exit_side(is_short: bool): + return "buy" if is_short else "sell" + + +def direc(is_short: bool): + return "short" if is_short else "long" + + +def mock_order_1(is_short: bool): return { - 'id': '1234', + 'id': f'1234_{direc(is_short)}', 'symbol': 'ETH/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -20,7 +32,7 @@ def mock_order_1(): } -def mock_trade_1(fee): +def mock_trade_1(fee, is_short: bool): trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -32,21 +44,22 @@ def mock_trade_1(fee): open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17), open_rate=0.123, exchange='binance', - open_order_id='dry_run_buy_12345', + open_order_id=f'dry_run_buy_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_1(is_short), 'ETH/BTC', enter_side(is_short)) trade.orders.append(o) return trade -def mock_order_2(): +def mock_order_2(is_short: bool): return { - 'id': '1235', + 'id': f'1235_{direc(is_short)}', 'symbol': 'ETC/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -55,12 +68,12 @@ def mock_order_2(): } -def mock_order_2_sell(): +def mock_order_2_sell(is_short: bool): return { - 'id': '12366', + 'id': f'12366_{direc(is_short)}', 'symbol': 'ETC/BTC', 'status': 'closed', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'limit', 'price': 0.128, 'amount': 123.0, @@ -69,7 +82,7 @@ def mock_order_2_sell(): } -def mock_trade_2(fee): +def mock_trade_2(fee, is_short: bool): """ Closed trade... """ @@ -82,30 +95,31 @@ def mock_trade_2(fee): fee_close=fee.return_value, open_rate=0.123, close_rate=0.128, - close_profit=0.005, - close_profit_abs=0.000584127, + close_profit=-0.005 if is_short else 0.005, + close_profit_abs=-0.005584127 if is_short else 0.000584127, exchange='binance', is_open=False, - open_order_id='dry_run_sell_12345', + open_order_id=f'dry_run_sell_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, sell_reason='sell_signal', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_2(), 'ETC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_2(is_short), 'ETC/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_2_sell(), 'ETC/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_2_sell(is_short), 'ETC/BTC', exit_side(is_short)) trade.orders.append(o) return trade -def mock_order_3(): +def mock_order_3(is_short: bool): return { - 'id': '41231a12a', + 'id': f'41231a12a_{direc(is_short)}', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.05, 'amount': 123.0, @@ -114,12 +128,12 @@ def mock_order_3(): } -def mock_order_3_sell(): +def mock_order_3_sell(is_short: bool): return { - 'id': '41231a666a', + 'id': f'41231a666a_{direc(is_short)}', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'stop_loss_limit', 'price': 0.06, 'average': 0.06, @@ -129,7 +143,7 @@ def mock_order_3_sell(): } -def mock_trade_3(fee): +def mock_trade_3(fee, is_short: bool): """ Closed trade """ @@ -142,8 +156,8 @@ def mock_trade_3(fee): fee_close=fee.return_value, open_rate=0.05, close_rate=0.06, - close_profit=0.01, - close_profit_abs=0.000155, + close_profit=-0.01 if is_short else 0.01, + close_profit_abs=-0.001155 if is_short else 0.000155, exchange='binance', is_open=False, strategy='StrategyTestV3', @@ -151,20 +165,21 @@ def mock_trade_3(fee): sell_reason='roi', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), close_date=datetime.now(tz=timezone.utc), + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_3(), 'XRP/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_3(is_short), 'XRP/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_3_sell(), 'XRP/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_3_sell(is_short), 'XRP/BTC', exit_side(is_short)) trade.orders.append(o) return trade -def mock_order_4(): +def mock_order_4(is_short: bool): return { - 'id': 'prod_buy_12345', + 'id': f'prod_buy_{direc(is_short)}_12345', 'symbol': 'ETC/BTC', 'status': 'open', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -173,7 +188,7 @@ def mock_order_4(): } -def mock_trade_4(fee): +def mock_trade_4(fee, is_short: bool): """ Simulate prod entry """ @@ -188,21 +203,22 @@ def mock_trade_4(fee): is_open=True, open_rate=0.123, exchange='binance', - open_order_id='prod_buy_12345', + open_order_id=f'prod_buy_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_4(is_short), 'ETC/BTC', enter_side(is_short)) trade.orders.append(o) return trade -def mock_order_5(): +def mock_order_5(is_short: bool): return { - 'id': 'prod_buy_3455', + 'id': f'prod_buy_{direc(is_short)}_3455', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -211,12 +227,12 @@ def mock_order_5(): } -def mock_order_5_stoploss(): +def mock_order_5_stoploss(is_short: bool): return { - 'id': 'prod_stoploss_3455', + 'id': f'prod_stoploss_{direc(is_short)}_3455', 'symbol': 'XRP/BTC', 'status': 'open', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'stop_loss_limit', 'price': 0.123, 'amount': 123.0, @@ -225,7 +241,7 @@ def mock_order_5_stoploss(): } -def mock_trade_5(fee): +def mock_trade_5(fee, is_short: bool): """ Simulate prod entry with stoploss """ @@ -241,22 +257,23 @@ def mock_trade_5(fee): open_rate=0.123, exchange='binance', strategy='SampleStrategy', - stoploss_order_id='prod_stoploss_3455', + stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_5(), 'XRP/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_5(is_short), 'XRP/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_5_stoploss(), 'XRP/BTC', 'stoploss') + o = Order.parse_from_ccxt_object(mock_order_5_stoploss(is_short), 'XRP/BTC', 'stoploss') trade.orders.append(o) return trade -def mock_order_6(): +def mock_order_6(is_short: bool): return { - 'id': 'prod_buy_6', + 'id': f'prod_buy_{direc(is_short)}_6', 'symbol': 'LTC/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.15, 'amount': 2.0, @@ -265,23 +282,23 @@ def mock_order_6(): } -def mock_order_6_sell(): +def mock_order_6_sell(is_short: bool): return { - 'id': 'prod_sell_6', + 'id': f'prod_sell_{direc(is_short)}_6', 'symbol': 'LTC/BTC', 'status': 'open', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'limit', - 'price': 0.20, + 'price': 0.15 if is_short else 0.20, 'amount': 2.0, 'filled': 0.0, 'remaining': 2.0, } -def mock_trade_6(fee): +def mock_trade_6(fee, is_short: bool): """ - Simulate prod entry with open sell order + Simulate prod entry with open exit order """ trade = Trade( pair='LTC/BTC', @@ -295,12 +312,12 @@ def mock_trade_6(fee): open_rate=0.15, exchange='binance', strategy='SampleStrategy', - open_order_id="prod_sell_6", + open_order_id=f"prod_sell_{direc(is_short)}_6", timeframe=5, ) - o = Order.parse_from_ccxt_object(mock_order_6(), 'LTC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_6(is_short), 'LTC/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_6_sell(), 'LTC/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_6_sell(is_short), 'LTC/BTC', exit_side(is_short)) trade.orders.append(o) return trade diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index e7b8c5b2f..94cea62eb 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -111,9 +111,10 @@ def test_load_backtest_data_multi(testdatadir): @pytest.mark.usefixtures("init_persistence") -def test_load_trades_from_db(default_conf, fee, mocker): +@pytest.mark.parametrize('is_short', [False, True]) +def test_load_trades_from_db(default_conf, fee, is_short, mocker): - create_mock_trades(fee) + create_mock_trades(fee, is_short) # remove init so it does not init again init_mock = mocker.patch('freqtrade.data.btanalysis.init_db', MagicMock()) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b692f90f4..70f1d8d0b 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3248,6 +3248,33 @@ def test_validate_trading_mode_and_collateral( exchange.validate_trading_mode_and_collateral(trading_mode, collateral) +@pytest.mark.parametrize("exchange_name,trading_mode,ccxt_config", [ + ("binance", "spot", {}), + ("binance", "margin", {"options": {"defaultType": "margin"}}), + ("binance", "futures", {"options": {"defaultType": "future"}}), + ("kraken", "spot", {}), + ("kraken", "margin", {}), + ("kraken", "futures", {}), + ("ftx", "spot", {}), + ("ftx", "margin", {}), + ("ftx", "futures", {}), + ("bittrex", "spot", {}), + ("bittrex", "margin", {}), + ("bittrex", "futures", {}), +]) +def test__ccxt_config( + default_conf, + mocker, + exchange_name, + trading_mode, + ccxt_config +): + default_conf['trading_mode'] = trading_mode + default_conf['collateral'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + assert exchange._ccxt_config == ccxt_config + + def test_get_mark_price(): return diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index f0033b2d7..641d2f263 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -164,6 +164,8 @@ def test_get_balances_prod(default_conf, mocker): ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "get_balances", "fetch_balance") +# TODO-lev: All these stoploss tests with shorts + @pytest.mark.parametrize('ordertype', ['market', 'limit']) @pytest.mark.parametrize('side,adjustedprice', [ diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 57d10d048..1dbea2af1 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -705,7 +705,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non assert hasattr(hyperopt, "position_stacking") -def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None: +def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None: mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', @@ -727,7 +727,13 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None: hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) with pytest.raises(OperationalException, match=r"The 'protection' space is included into *"): - hyperopt.start() + hyperopt.init_spaces() + + hyperopt.config['hyperopt_ignore_missing_space'] = True + caplog.clear() + hyperopt.init_spaces() + assert log_has_re(r"The 'protection' space is included into *", caplog) + assert hyperopt.protection_space == [] def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index cf918e2a0..692f1fb51 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -415,10 +415,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # SpreadFilter only ([{"method": "SpreadFilter", "max_spread_ratio": 0.005}], "BTC", 'filter_at_the_beginning'), # OperationalException expected - # Static Pairlist after VolumePairList, on a non-first position - ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + # Static Pairlist after VolumePairList, on a non-first position (appends pairs) + ([{"method": "VolumePairList", "number_assets": 2, "sort_key": "quoteVolume"}, {"method": "StaticPairList"}], - "BTC", 'static_in_the_middle'), + "BTC", ['ETH/BTC', 'TKN/BTC', 'TRST/BTC', 'SWT/BTC', 'BCC/BTC', 'HOT/BTC']), ([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.02}], "USDT", ['ETH/USDT', 'NANO/USDT']), @@ -469,13 +469,6 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - if whitelist_result == 'static_in_the_middle': - with pytest.raises(OperationalException, - match=r"StaticPairList can only be used in the first position " - r"in the list of Pairlist Handlers."): - freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - return - freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers, @@ -665,11 +658,11 @@ def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None: @pytest.mark.usefixtures("init_persistence") -def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee) -> None: +def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee, caplog) -> None: whitelist_conf['exchange']['pair_whitelist'].append('XRP/BTC') whitelist_conf['pairlists'] = [ {"method": "StaticPairList"}, - {"method": "PerformanceFilter", "minutes": 60} + {"method": "PerformanceFilter", "minutes": 60, "min_profit": 0.01} ] mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) exchange = get_patched_exchange(mocker, whitelist_conf) @@ -679,9 +672,10 @@ def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee) -> None: assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC'] with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: - create_mock_trades(fee) + create_mock_trades(fee, False) pm.refresh_pairlist() - assert pm.whitelist == ['XRP/BTC', 'ETH/BTC', 'TKN/BTC'] + assert pm.whitelist == ['XRP/BTC'] + assert log_has_re(r'Removing pair .* since .* is below .*', caplog) # Move to "outside" of lookback window, so original sorting is restored. t.move_to("2021-09-01 07:00:00 +00:00") diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 2f8fb9b85..0d349abf3 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -289,7 +289,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency) -def test_rpc_trade_history(mocker, default_conf, markets, fee): +@pytest.mark.parametrize('is_short', [True, False]) +def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -297,7 +298,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - create_mock_trades(fee) + create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() trades = rpc._rpc_trade_history(2) @@ -314,7 +315,8 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee): assert trades['trades'][0]['pair'] == 'XRP/BTC' -def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog): +@pytest.mark.parametrize('is_short', [True, False]) +def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) stoploss_mock = MagicMock() cancel_mock = MagicMock() @@ -327,7 +329,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog): freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot.strategy.order_types['stoploss_on_exchange'] = True - create_mock_trades(fee) + create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) with pytest.raises(RPCException, match='invalid argument'): rpc._rpc_delete('200') diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 0528964cd..9691e5f3f 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -95,7 +95,7 @@ def test_api_not_found(botclient): assert rc.json() == {"detail": "Not Found"} -def test_api_ui_fallback(botclient): +def test_api_ui_fallback(botclient, mocker): ftbot, client = botclient rc = client_get(client, "/favicon.ico") @@ -109,9 +109,16 @@ def test_api_ui_fallback(botclient): rc = client_get(client, "/something") assert rc.status_code == 200 - # Test directory traversal + # Test directory traversal without mock rc = client_get(client, '%2F%2F%2Fetc/passwd') assert rc.status_code == 200 + # Allow both fallback or real UI + assert '`freqtrade install-ui`' in rc.text or '' in rc.text + + mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[True, False])) + rc = client_get(client, '%2F%2F%2Fetc/passwd') + assert rc.status_code == 200 + assert '`freqtrade install-ui`' in rc.text @@ -451,7 +458,8 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): assert 'starting_capital_ratio' in response -def test_api_count(botclient, mocker, ticker, fee, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_count(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -468,7 +476,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json()["max"] == 1 # Create some test data - create_mock_trades(fee) + create_mock_trades(fee, is_short) rc = client_get(client, f"{BASE_URI}/count") assert_response(rc) assert rc.json()["current"] == 4 @@ -549,7 +557,8 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): assert rc.json()['data'][0]['date'] == str(datetime.utcnow().date()) -def test_api_trades(botclient, mocker, fee, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_trades(botclient, mocker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -562,7 +571,7 @@ def test_api_trades(botclient, mocker, fee, markets): assert rc.json()['trades_count'] == 0 assert rc.json()['total_trades'] == 0 - create_mock_trades(fee) + create_mock_trades(fee, is_short) Trade.query.session.flush() rc = client_get(client, f"{BASE_URI}/trades") @@ -577,6 +586,7 @@ def test_api_trades(botclient, mocker, fee, markets): assert rc.json()['total_trades'] == 2 +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_api_trade_single(botclient, mocker, fee, ticker, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -589,7 +599,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): assert_response(rc, 404) assert rc.json()['detail'] == 'Trade not found.' - create_mock_trades(fee) + create_mock_trades(fee, False) Trade.query.session.flush() rc = client_get(client, f"{BASE_URI}/trade/3") @@ -597,6 +607,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): assert rc.json()['trade_id'] == 3 +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_api_delete_trade(botclient, mocker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -612,11 +623,12 @@ def test_api_delete_trade(botclient, mocker, fee, markets): # Error - trade won't exist yet. assert_response(rc, 502) - create_mock_trades(fee) - Trade.query.session.flush() + create_mock_trades(fee, False) + ftbot.strategy.order_types['stoploss_on_exchange'] = True trades = Trade.query.all() trades[1].stoploss_order_id = '1234' + Trade.commit() assert len(trades) > 2 rc = client_delete(client, f"{BASE_URI}/trades/1") @@ -686,7 +698,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): assert rc.json() == {"error": "Error querying /api/v1/edge: Edge is not enabled."} -@pytest.mark.usefixtures("init_persistence") +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_api_profit(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -702,7 +714,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): assert_response(rc, 200) assert rc.json()['trade_count'] == 0 - create_mock_trades(fee) + create_mock_trades(fee, False) # Simulate fulfilled LIMIT_BUY order for trade rc = client_get(client, f"{BASE_URI}/profit") @@ -737,7 +749,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): } -@pytest.mark.usefixtures("init_persistence") +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_api_stats(botclient, mocker, ticker, fee, markets,): ftbot, client = botclient patch_get_signal(ftbot) @@ -754,7 +766,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,): assert 'durations' in rc.json() assert 'sell_reasons' in rc.json() - create_mock_trades(fee) + create_mock_trades(fee, False) rc = client_get(client, f"{BASE_URI}/stats") assert_response(rc, 200) @@ -803,7 +815,7 @@ def test_api_performance(botclient, fee): trade.close_profit_abs = trade.calc_profit() Trade.query.session.add(trade) - Trade.query.session.flush() + Trade.commit() rc = client_get(client, f"{BASE_URI}/performance") assert_response(rc) @@ -812,6 +824,10 @@ def test_api_performance(botclient, fee): {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}] +# TODO-lev: @pytest.mark.parametrize('is_short,side', [ +# (True, "short"), +# (False, "long") +# ]) def test_api_status(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot) @@ -827,7 +843,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): rc = client_get(client, f"{BASE_URI}/status") assert_response(rc, 200) assert rc.json() == [] - create_mock_trades(fee) + create_mock_trades(fee, False) rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) @@ -880,7 +896,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'is_open': True, 'max_rate': ANY, 'min_rate': ANY, - 'open_order_id': 'dry_run_buy_12345', + 'open_order_id': 'dry_run_buy_long_12345', 'open_rate_requested': ANY, 'open_trade_value': 15.1668225, 'sell_reason': None, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 791797ceb..c81fda101 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -33,6 +33,7 @@ class DummyCls(Telegram): """ Dummy class for testing the Telegram @authorized_only decorator """ + def __init__(self, rpc: RPC, config) -> None: super().__init__(rpc, config) self.state = {'called': False} @@ -479,8 +480,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] +@pytest.mark.parametrize('is_short', [True, False]) def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, - limit_buy_order, limit_sell_order, mocker) -> None: + limit_buy_order, limit_sell_order, mocker, is_short) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -496,7 +498,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, msg_mock.reset_mock() # Create some test data - create_mock_trades(fee) + create_mock_trades(fee, is_short) telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -997,9 +999,9 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: msg = ('
  current    max    total stake\n---------  -----  -------------\n'
            '        1      {}          {}
').format( - default_conf['max_open_trades'], - default_conf['stake_amount'] - ) + default_conf['max_open_trades'], + default_conf['stake_amount'] + ) assert msg in msg_mock.call_args_list[0][0][0] @@ -1159,6 +1161,7 @@ def test_edge_enabled(edge_conf, update, mocker) -> None: assert 'Winrate' not in msg_mock.call_args_list[0][0][0] +# TODO-lev: @pytest.mark.parametrize('is_short', [True, False]) def test_telegram_trades(mocker, update, default_conf, fee): telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -1177,7 +1180,7 @@ def test_telegram_trades(mocker, update, default_conf, fee): assert "
" not in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, False)
 
     context = MagicMock()
     context.args = [5]
@@ -1191,6 +1194,7 @@ def test_telegram_trades(mocker, update, default_conf, fee):
                 msg_mock.call_args_list[0][0][0]))
 
 
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
 def test_telegram_delete_trade(mocker, update, default_conf, fee):
 
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1201,7 +1205,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
     assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
-    create_mock_trades(fee)
+    create_mock_trades(fee, False)
 
     context = MagicMock()
     context.args = [1]
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index a9334c616..e735ab181 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -47,8 +47,8 @@ def test_returns_latest_signal(ohlcv_history):
     mocked_history.loc[1, 'exit_long'] = 0
     mocked_history.loc[1, 'enter_long'] = 1
 
-    assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history
-                                      ) == (SignalDirection.LONG, None)
+    assert _STRATEGY.get_entry_signal(
+        'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, None)
     assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False)
     assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
     mocked_history.loc[1, 'exit_long'] = 0
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index d09fc18a2..6d784d9d1 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -11,7 +11,7 @@ import arrow
 import pytest
 
 from freqtrade.constants import CANCEL_REASON, MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT
-from freqtrade.enums import RPCMessageType, RunMode, SellType, State, TradingMode
+from freqtrade.enums import RPCMessageType, RunMode, SellType, SignalDirection, State
 from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
                                   InvalidOrderException, OperationalException, PricingError,
                                   TemporaryError)
@@ -23,9 +23,9 @@ from freqtrade.worker import Worker
 from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
                             log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
                             patch_wallet, patch_whitelist)
-from tests.conftest_trades import (MOCK_TRADE_COUNT, mock_order_1, mock_order_2, mock_order_2_sell,
-                                   mock_order_3, mock_order_3_sell, mock_order_4,
-                                   mock_order_5_stoploss, mock_order_6_sell)
+from tests.conftest_trades import (MOCK_TRADE_COUNT, enter_side, exit_side, mock_order_1,
+                                   mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell,
+                                   mock_order_4, mock_order_5_stoploss, mock_order_6_sell)
 
 
 def patch_RPCManager(mocker) -> MagicMock:
@@ -41,6 +41,7 @@ def patch_RPCManager(mocker) -> MagicMock:
 
 # Unit tests
 
+
 def test_freqtradebot_state(mocker, default_conf_usdt, markets) -> None:
     mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@@ -195,12 +196,10 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
 
 
 @pytest.mark.parametrize('buy_price_mult,ignore_strat_sl', [
-    # Override stoploss
-    (0.79, False),
-    # Override strategy stoploss
-    (0.85, True)
+    (0.79, False),   # Override stoploss
+    (0.85, True),    # Override strategy stoploss
 ])
-def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
+def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
                                  buy_price_mult, ignore_strat_sl, edge_conf) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -210,14 +209,14 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
     # Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
     # Thus, if price falls 21%, stoploss should be triggered
     #
-    # mocking the ticker_usdt: price is falling ...
-    buy_price = limit_buy_order_usdt['price']
+    # mocking the ticker: price is falling ...
+    enter_price = limit_order['buy']['price']
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
-            'bid': buy_price * buy_price_mult,
-            'ask': buy_price * buy_price_mult,
-            'last': buy_price * buy_price_mult,
+            'bid': enter_price * buy_price_mult,
+            'ask': enter_price * buy_price_mult,
+            'last': enter_price * buy_price_mult,
         }),
         get_fee=fee,
     )
@@ -230,7 +229,8 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     freqtrade.enter_positions()
     trade = Trade.query.first()
-    trade.update(limit_buy_order_usdt)
+    caplog.clear()
+    trade.update(limit_order['buy'])
     #############################################
 
     # stoploss shoud be hit
@@ -271,7 +271,12 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -
     assert Trade.total_open_trades_stakes() == 120.0
 
 
-def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short,open_rate", [
+    (False, 2.0),
+    (True, 2.2)
+])
+def test_create_trade(default_conf_usdt, ticker_usdt, limit_order,
+                      fee, mocker, is_short, open_rate) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -284,10 +289,11 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee,
     # Save state of current whitelist
     whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.create_trade('ETH/USDT')
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade is not None
     assert trade.stake_amount == 60.0
     assert trade.is_open
@@ -295,9 +301,9 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee,
     assert trade.exchange == 'binance'
 
     # Simulate fulfilled LIMIT_BUY order for trade
-    trade.update(limit_buy_order_usdt)
+    trade.update(limit_order[enter_side(is_short)])
 
-    assert trade.open_rate == 2.0
+    assert trade.open_rate == open_rate
     assert trade.amount == 30.0
 
     assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
@@ -319,6 +325,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
         freqtrade.create_trade('ETH/USDT')
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
     (5.0, True, True, 99),
     (0.00005, True, False, 99),
@@ -326,27 +333,27 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
     (UNLIMITED_STAKE_AMOUNT, False, True, 0),
 ])
 def test_create_trade_minimal_amount(
-    default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, fee, mocker,
-    stake_amount, create, amount_enough, max_open_trades, caplog
+    default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker,
+    stake_amount, create, amount_enough, max_open_trades, caplog, is_short
 ) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
-    buy_mock = MagicMock(return_value=limit_buy_order_usdt_open)
+    enter_mock = MagicMock(return_value=limit_order_open[enter_side(is_short)])
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=buy_mock,
+        create_order=enter_mock,
         get_fee=fee,
     )
     default_conf_usdt['max_open_trades'] = max_open_trades
     freqtrade = FreqtradeBot(default_conf_usdt)
     freqtrade.config['stake_amount'] = stake_amount
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     if create:
         assert freqtrade.create_trade('ETH/USDT')
         if amount_enough:
-            rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
+            rate, amount = enter_mock.call_args[1]['rate'], enter_mock.call_args[1]['amount']
             assert rate * amount <= default_conf_usdt['stake_amount']
         else:
             assert log_has_re(
@@ -416,7 +423,8 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b
     assert log_has_re(message, caplog)
 
 
-def test_handle_protections(mocker, default_conf_usdt, fee):
+@pytest.mark.parametrize('is_short', [False, True])
+def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
     default_conf_usdt['protections'] = [
         {"method": "CooldownPeriod", "stop_duration": 60},
         {
@@ -431,7 +439,7 @@ def test_handle_protections(mocker, default_conf_usdt, fee):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     freqtrade.protections._protection_handlers[1].global_stop = MagicMock(
         return_value=(True, arrow.utcnow().shift(hours=1).datetime, "asdf"))
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short)
     freqtrade.handle_protections('ETC/BTC')
     send_msg_mock = freqtrade.rpc.send_msg
     assert send_msg_mock.call_count == 2
@@ -450,7 +458,8 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None:
     )
     default_conf_usdt['stake_amount'] = 10
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade, enter_long=False)
+    # patch_get_signal(freqtrade, enter_long=False)
+    patch_get_signal(freqtrade, enter_long=False, exit_long=False)
 
     Trade.query = MagicMock()
     Trade.query.filter = MagicMock()
@@ -516,19 +525,24 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
     assert len(trades) == 4
 
 
-def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_buy_order_usdt,
-                                limit_buy_order_usdt_open, fee, mocker, caplog) -> None:
+@pytest.mark.parametrize('is_short, open_rate', [
+    (False, 2.0),
+    (True, 2.02)
+])
+def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, limit_order_open,
+                                is_short, open_rate, fee, mocker, caplog
+                                ) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=MagicMock(return_value=limit_buy_order_usdt_open),
-        fetch_order=MagicMock(return_value=limit_buy_order_usdt),
+        create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
+        fetch_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     trades = Trade.query.filter(Trade.is_open.is_(True)).all()
     assert not trades
@@ -543,11 +557,12 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_buy_order_
     assert trade.is_open
     assert trade.open_date is not None
     assert trade.exchange == 'binance'
-    assert trade.open_rate == 2.0
-    assert trade.amount == 30.0
+    assert trade.open_rate == open_rate  # TODO-lev: I think? That's what the ticker ask price is
+    assert isclose(trade.amount, 60 / open_rate)
 
     assert log_has(
-        'Buy signal found: about create a new trade for ETH/USDT with stake_amount: 60.0 ...',
+        f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
+        'with stake_amount: 60.0 ...',
         caplog
     )
 
@@ -695,148 +710,160 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
     assert ("ETH/USDT", default_conf_usdt["timeframe"]) in refresh_mock.call_args[0][0]
 
 
-def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
-                       limit_buy_order_usdt_open) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
+                       limit_order_open, is_short) -> None:
+
+    open_order = limit_order_open[enter_side(is_short)]
+    order = limit_order[enter_side(is_short)]
+
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     freqtrade = FreqtradeBot(default_conf_usdt)
     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
     stake_amount = 2
     bid = 0.11
-    buy_rate_mock = MagicMock(return_value=bid)
-    buy_mm = MagicMock(return_value=limit_buy_order_usdt_open)
+    enter_rate_mock = MagicMock(return_value=bid)
+    enter_mm = MagicMock(return_value=open_order)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        get_rate=buy_rate_mock,
+        get_rate=enter_rate_mock,
         fetch_ticker=MagicMock(return_value={
             'bid': 1.9,
             'ask': 2.2,
             'last': 1.9
         }),
-        create_order=buy_mm,
+        create_order=enter_mm,
         get_min_pair_stake_amount=MagicMock(return_value=1),
         get_fee=fee,
     )
     pair = 'ETH/USDT'
 
-    assert not freqtrade.execute_entry(pair, stake_amount)
-    assert buy_rate_mock.call_count == 1
-    assert buy_mm.call_count == 0
+    assert not freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
+    assert enter_rate_mock.call_count == 1
+    assert enter_mm.call_count == 0
     assert freqtrade.strategy.confirm_trade_entry.call_count == 1
-    buy_rate_mock.reset_mock()
+    enter_rate_mock.reset_mock()
 
-    limit_buy_order_usdt_open['id'] = '22'
+    open_order['id'] = '22'
     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
     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]
+    assert enter_rate_mock.call_count == 1
+    assert enter_mm.call_count == 1
+    call_args = enter_mm.call_args_list[0][1]
     assert call_args['pair'] == pair
     assert call_args['rate'] == bid
     assert call_args['amount'] == round(stake_amount / bid, 8)
-    buy_rate_mock.reset_mock()
+    enter_rate_mock.reset_mock()
 
     # Should create an open trade with an open order id
     # As the order is not fulfilled yet
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     assert trade.is_open is True
     assert trade.open_order_id == '22'
 
     # Test calling with price
-    limit_buy_order_usdt_open['id'] = '33'
+    open_order['id'] = '33'
     fix_price = 0.06
-    assert freqtrade.execute_entry(pair, stake_amount, fix_price)
+    assert freqtrade.execute_entry(pair, stake_amount, fix_price, is_short=is_short)
     # Make sure get_rate wasn't called again
-    assert buy_rate_mock.call_count == 0
+    assert enter_rate_mock.call_count == 0
 
-    assert buy_mm.call_count == 2
-    call_args = buy_mm.call_args_list[1][1]
+    assert enter_mm.call_count == 2
+    call_args = enter_mm.call_args_list[1][1]
     assert call_args['pair'] == pair
     assert call_args['rate'] == fix_price
     assert call_args['amount'] == round(stake_amount / fix_price, 8)
 
     # In case of closed order
-    limit_buy_order_usdt['status'] = 'closed'
-    limit_buy_order_usdt['price'] = 10
-    limit_buy_order_usdt['cost'] = 100
-    limit_buy_order_usdt['id'] = '444'
+    order['status'] = 'closed'
+    order['price'] = 10
+    order['cost'] = 100
+    order['id'] = '444'
 
     mocker.patch('freqtrade.exchange.Exchange.create_order',
-                 MagicMock(return_value=limit_buy_order_usdt))
-    assert freqtrade.execute_entry(pair, stake_amount)
+                 MagicMock(return_value=order))
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[2]
+    trade.is_short = is_short
     assert trade
     assert trade.open_order_id is None
     assert trade.open_rate == 10
     assert trade.stake_amount == 100
 
     # In case of rejected or expired order and partially filled
-    limit_buy_order_usdt['status'] = 'expired'
-    limit_buy_order_usdt['amount'] = 30.0
-    limit_buy_order_usdt['filled'] = 20.0
-    limit_buy_order_usdt['remaining'] = 10.00
-    limit_buy_order_usdt['price'] = 0.5
-    limit_buy_order_usdt['cost'] = 15.0
-    limit_buy_order_usdt['id'] = '555'
+    order['status'] = 'expired'
+    order['amount'] = 30.0
+    order['filled'] = 20.0
+    order['remaining'] = 10.00
+    order['price'] = 0.5
+    order['cost'] = 15.0
+    order['id'] = '555'
     mocker.patch('freqtrade.exchange.Exchange.create_order',
-                 MagicMock(return_value=limit_buy_order_usdt))
+                 MagicMock(return_value=order))
     assert freqtrade.execute_entry(pair, stake_amount)
     trade = Trade.query.all()[3]
+    trade.is_short = is_short
     assert trade
     assert trade.open_order_id == '555'
     assert trade.open_rate == 0.5
     assert trade.stake_amount == 15.0
 
     # Test with custom stake
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '556'
+    order['status'] = 'open'
+    order['id'] = '556'
 
     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[4]
+    trade.is_short = is_short
     assert trade
     assert trade.stake_amount == 150
 
     # Exception case
-    limit_buy_order_usdt['id'] = '557'
+    order['id'] = '557'
     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[5]
+    trade.is_short = is_short
     assert trade
     assert trade.stake_amount == 2.0
 
     # In case of the order is rejected and not filled at all
-    limit_buy_order_usdt['status'] = 'rejected'
-    limit_buy_order_usdt['amount'] = 30.0
-    limit_buy_order_usdt['filled'] = 0.0
-    limit_buy_order_usdt['remaining'] = 30.0
-    limit_buy_order_usdt['price'] = 0.5
-    limit_buy_order_usdt['cost'] = 0.0
-    limit_buy_order_usdt['id'] = '66'
+    order['status'] = 'rejected'
+    order['amount'] = 30.0
+    order['filled'] = 0.0
+    order['remaining'] = 30.0
+    order['price'] = 0.5
+    order['cost'] = 0.0
+    order['id'] = '66'
     mocker.patch('freqtrade.exchange.Exchange.create_order',
-                 MagicMock(return_value=limit_buy_order_usdt))
+                 MagicMock(return_value=order))
     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_entry(pair, stake_amount)
+    with pytest.raises(PricingError, match=f"Could not determine {enter_side(is_short)} price."):
+        freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
 
     # In case of custom entry price
     mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '5566'
+    order['status'] = 'open'
+    order['id'] = '5566'
     freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[6]
+    trade.is_short = is_short
     assert trade
     assert trade.open_rate_requested == 0.508
 
     # In case of custom entry price set to None
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '5567'
+
+    order['status'] = 'open'
+    order['id'] = '5567'
     freqtrade.strategy.custom_entry_price = lambda **kwargs: None
 
     mocker.patch.multiple(
@@ -844,22 +871,25 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
         get_rate=MagicMock(return_value=10),
     )
 
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[7]
+    trade.is_short = is_short
     assert trade
     assert trade.open_rate_requested == 10
 
     # In case of custom entry price not float type
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '5568'
+    order['status'] = 'open'
+    order['id'] = '5568'
     freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[8]
+    trade.is_short = is_short
     assert trade
     assert trade.open_rate_requested == 10
 
 
-def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -868,7 +898,7 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
             'ask': 2.2,
             'last': 1.9
         }),
-        create_order=MagicMock(return_value=limit_buy_order_usdt),
+        create_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
         get_rate=MagicMock(return_value=0.11),
         get_min_pair_stake_amount=MagicMock(return_value=1),
         get_fee=fee,
@@ -877,13 +907,14 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
     pair = 'ETH/USDT'
 
     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
+    # TODO-lev: KeyError happens on short, why?
     assert freqtrade.execute_entry(pair, stake_amount)
 
-    limit_buy_order_usdt['id'] = '222'
+    limit_order[enter_side(is_short)]['id'] = '222'
     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
     assert freqtrade.execute_entry(pair, stake_amount)
 
-    limit_buy_order_usdt['id'] = '2223'
+    limit_order[enter_side(is_short)]['id'] = '2223'
     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
     assert freqtrade.execute_entry(pair, stake_amount)
 
@@ -891,14 +922,16 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
     assert not freqtrade.execute_entry(pair, stake_amount)
 
 
-def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    order = limit_order[enter_side(is_short)]
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
-                 return_value=limit_buy_order_usdt['amount'])
+                 return_value=order['amount'])
 
     stoploss = MagicMock(return_value={'id': 13434334})
     mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
@@ -907,6 +940,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usd
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     trade = MagicMock()
+    trade.is_short = is_short
     trade.open_order_id = None
     trade.stoploss_order_id = None
     trade.is_open = True
@@ -918,9 +952,12 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usd
     assert trade.is_open is True
 
 
-def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
-                                     limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short,
+                                     limit_order) -> None:
     stoploss = MagicMock(return_value={'id': 13434334})
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -931,8 +968,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -941,15 +978,17 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
         stoploss=stoploss
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # First case: when stoploss is not yet set but the order is open
     # should get the stoploss order id immediately
     # and should return false as no trade actually happened
     trade = MagicMock()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = None
+    trade.is_short = is_short
 
     assert freqtrade.handle_stoploss_on_exchange(trade) is False
     assert stoploss.call_count == 1
@@ -988,6 +1027,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
     caplog.clear()
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = 100
@@ -999,7 +1039,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
         'type': 'stop_loss_limit',
         'price': 3,
         'average': 2,
-        'amount': limit_buy_order_usdt['amount'],
+        'amount': enter_order['amount'],
     })
     mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hit)
     assert freqtrade.handle_stoploss_on_exchange(trade) is True
@@ -1038,9 +1078,12 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
     assert stoploss.call_count == 0
 
 
-def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
-                                         limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
+                                         limit_order) -> None:
     # Sixth case: stoploss order was cancelled but couldn't create new one
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -1051,8 +1094,8 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1062,10 +1105,11 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
         stoploss=MagicMock(side_effect=ExchangeError()),
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = 100
@@ -1077,13 +1121,17 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
     assert trade.is_open is True
 
 
-def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog, fee,
-                                             limit_buy_order_usdt_open, limit_sell_order_usdt):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_create_stoploss_order_invalid_order(
+    mocker, default_conf_usdt, caplog, fee, is_short, limit_order, limit_order_open
+):
+    open_order = limit_order_open[enter_side(is_short)]
+    order = limit_order[exit_side(is_short)]
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     create_order_mock = MagicMock(side_effect=[
-        limit_buy_order_usdt_open,
-        {'id': limit_sell_order_usdt['id']}
+        open_order,
+        {'id': order['id']}
     ])
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -1101,11 +1149,12 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog,
         stoploss=MagicMock(side_effect=InvalidOrderException()),
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     caplog.clear()
     freqtrade.create_stoploss_order(trade, 200)
     assert trade.stoploss_order_id is None
@@ -1125,9 +1174,12 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog,
     assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
 
 
-def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, caplog, fee,
-                                                  limit_buy_order_usdt_open, limit_sell_order_usdt):
-    sell_mock = MagicMock(return_value={'id': limit_sell_order_usdt['id']})
+@pytest.mark.parametrize("is_short", [False, True])
+def test_create_stoploss_order_insufficient_funds(
+    mocker, default_conf_usdt, caplog, fee, limit_order_open,
+    limit_order, is_short
+):
+    exit_order = limit_order[exit_side(is_short)]['id']
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
 
     mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
@@ -1139,8 +1191,8 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
-            sell_mock,
+            limit_order[enter_side(is_short)],
+            exit_order,
         ]),
         get_fee=fee,
         fetch_order=MagicMock(return_value={'status': 'canceled'}),
@@ -1149,11 +1201,12 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
         'freqtrade.exchange.Binance',
         stoploss=MagicMock(side_effect=InsufficientFundsError()),
     )
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     caplog.clear()
     freqtrade.create_stoploss_order(trade, 200)
     # stoploss_orderid was empty before
@@ -1168,11 +1221,18 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
     assert mock_insuf.call_count == 1
 
 
+@pytest.mark.parametrize("is_short,bid,ask,stop_price,amt,hang_price", [
+    (False, [4.38, 4.16], [4.4, 4.17], ['2.0805', 4.4 * 0.95], 27.39726027, 3),
+    (True, [1.09, 1.21], [1.1, 1.22], ['2.321', 1.09 * 1.05], 27.27272727, 1.5),
+])
 @pytest.mark.usefixtures("init_persistence")
-def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
-                                              limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+def test_handle_stoploss_on_exchange_trailing(
+    mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, amt, hang_price
+) -> None:
     # TODO-lev: test for short
     # When trailing stoploss is set
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     stoploss = MagicMock(return_value={'id': 13434334})
     patch_RPCManager(mocker)
     mocker.patch.multiple(
@@ -1180,11 +1240,11 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
         fetch_ticker=MagicMock(return_value={
             'bid': 2.19,
             'ask': 2.2,
-            'last': 2.19
+            'last': 2.19,
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1206,15 +1266,16 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     # setting stoploss
-    freqtrade.strategy.stoploss = -0.05
+    freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
 
     # setting stoploss_on_exchange_interval to 60 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
 
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = 100
@@ -1223,10 +1284,10 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
         'id': 100,
         'status': 'open',
         'type': 'stop_loss_limit',
-        'price': 3,
+        'price': hang_price,
         'average': 2,
         'info': {
-            'stopPrice': '2.0805'
+            'stopPrice': stop_price[0]
         }
     })
 
@@ -1240,9 +1301,9 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': 4.38,
-            'ask': 4.4,
-            'last': 4.38
+            'bid': bid[0],
+            'ask': ask[0],
+            'last': bid[0],
         })
     )
 
@@ -1258,7 +1319,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
     stoploss_order_mock.assert_not_called()
 
     assert freqtrade.handle_trade(trade) is False
-    assert trade.stop_loss == 4.4 * 0.95
+    assert trade.stop_loss == stop_price[1]
 
     # setting stoploss_on_exchange_interval to 0 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
@@ -1267,11 +1328,11 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
 
     cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
     stoploss_order_mock.assert_called_once_with(
-        amount=27.39726027,
+        amount=amt,
         pair='ETH/USDT',
         order_types=freqtrade.strategy.order_types,
-        stop_price=4.4 * 0.95,
-        side="sell",
+        stop_price=stop_price[1],
+        side=exit_side(is_short),
         leverage=1.0
     )
 
@@ -1279,18 +1340,20 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': 4.16,
-            'ask': 4.17,
-            'last': 4.16
+            'bid': bid[1],
+            'ask': ask[1],
+            'last': bid[1],
         })
     )
     assert freqtrade.handle_trade(trade) is True
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 def test_handle_stoploss_on_exchange_trailing_error(
-        mocker, default_conf_usdt, fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt
+    mocker, default_conf_usdt, fee, caplog, limit_order, is_short
 ) -> None:
-    # TODO-lev: test for short
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     # When trailing stoploss is set
     stoploss = MagicMock(return_value={'id': 13434334})
     patch_exchange(mocker)
@@ -1303,8 +1366,8 @@ def test_handle_stoploss_on_exchange_trailing_error(
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1322,18 +1385,20 @@ def test_handle_stoploss_on_exchange_trailing_error(
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     # setting stoploss
-    freqtrade.strategy.stoploss = -0.05
+    freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
 
     # setting stoploss_on_exchange_interval to 60 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = "abcd"
     trade.stop_loss = 0.2
     trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime.replace(tzinfo=None)
+    trade.is_short = is_short
 
     stoploss_order_hanging = {
         'id': "abcd",
@@ -1349,7 +1414,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
                  side_effect=InvalidOrderException())
     mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
                  return_value=stoploss_order_hanging)
-    freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="sell")
+    freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
     assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog)
 
     # Still try to create order
@@ -1359,14 +1424,17 @@ def test_handle_stoploss_on_exchange_trailing_error(
     caplog.clear()
     cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
     mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError())
-    freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="sell")
+    freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
     assert cancel_mock.call_count == 1
     assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
 
 
-@pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize("is_short", [False, True])
 def test_handle_stoploss_on_exchange_custom_stop(
-        mocker, default_conf_usdt, fee, limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+    mocker, default_conf_usdt, fee, is_short, limit_order
+) -> None:
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     # When trailing stoploss is set
     # TODO-lev: test for short
     stoploss = MagicMock(return_value={'id': 13434334})
@@ -1379,8 +1447,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1407,10 +1475,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
     # setting stoploss_on_exchange_interval to 60 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
 
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = 100
@@ -1435,9 +1504,9 @@ def test_handle_stoploss_on_exchange_custom_stop(
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': 4.38,
-            'ask': 4.4,
-            'last': 4.38
+            'bid': 4.38 if not is_short else 1.9 / 2,
+            'ask': 4.4 if not is_short else 2.2 / 2,
+            'last': 4.38 if not is_short else 1.9 / 2,
         })
     )
 
@@ -1453,8 +1522,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
     stoploss_order_mock.assert_not_called()
 
     assert freqtrade.handle_trade(trade) is False
-    assert trade.stop_loss == 4.4 * 0.96
-    assert trade.stop_loss_pct == -0.04
+    assert trade.stop_loss == 4.4 * 0.96 if not is_short else 1.1
+    assert trade.stop_loss_pct == -0.04 if not is_short else 0.04
 
     # setting stoploss_on_exchange_interval to 0 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
@@ -1462,12 +1531,13 @@ def test_handle_stoploss_on_exchange_custom_stop(
     assert freqtrade.handle_stoploss_on_exchange(trade) is False
 
     cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
+    # Long uses modified ask - offset, short modified bid + offset
     stoploss_order_mock.assert_called_once_with(
-        amount=31.57894736,
+        amount=trade.amount,
         pair='ETH/USDT',
         order_types=freqtrade.strategy.order_types,
-        stop_price=4.4 * 0.96,
-        side="sell",
+        stop_price=4.4 * 0.96 if not is_short else 0.95 * 1.04,
+        side=exit_side(is_short),
         leverage=1.0
     )
 
@@ -1483,9 +1553,12 @@ def test_handle_stoploss_on_exchange_custom_stop(
     assert freqtrade.handle_trade(trade) is True
 
 
-def test_tsl_on_exchange_compatible_with_edge(
-        mocker, edge_conf, fee, limit_buy_order_usdt, limit_sell_order_usdt) -> None:
-    # TODO-lev: test for short
+def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
+                                              limit_order) -> None:
+
+    enter_order = limit_order['buy']
+    exit_order = limit_order['sell']
+
     # When trailing stoploss is set
     stoploss = MagicMock(return_value={'id': 13434334})
     patch_RPCManager(mocker)
@@ -1502,8 +1575,8 @@ def test_tsl_on_exchange_compatible_with_edge(
             'last': 2.19
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
         stoploss=stoploss,
@@ -1594,7 +1667,7 @@ def test_tsl_on_exchange_compatible_with_edge(
         pair='NEO/BTC',
         order_types=freqtrade.strategy.order_types,
         stop_price=4.4 * 0.99,
-        side="sell",
+        side='sell',
         leverage=1.0
     )
 
@@ -1622,11 +1695,47 @@ def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
     assert mock_ct.call_count == len(default_conf_usdt['exchange']['pair_whitelist'])
 
 
-def test_exit_positions_exception(mocker, default_conf_usdt, limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_exit_positions(
+    mocker, default_conf_usdt, limit_order, is_short, caplog
+) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order',
+                 return_value=limit_order[enter_side(is_short)])
+    mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
+                 return_value=limit_order[enter_side(is_short)]['amount'])
 
     trade = MagicMock()
+    trade.is_short = is_short
+    trade.open_order_id = '123'
+    trade.open_fee = 0.001
+    trades = [trade]
+    n = freqtrade.exit_positions(trades)
+    assert n == 0
+    # Test amount not modified by fee-logic
+    assert not log_has(
+        'Applying fee to amount for Trade {} from 30.0 to 90.81'.format(trade), caplog
+    )
+
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
+    # test amount modified by fee-logic
+    n = freqtrade.exit_positions(trades)
+    assert n == 0
+
+
+@pytest.mark.parametrize("is_short", [False, True])
+def test_exit_positions_exception(
+    mocker, default_conf_usdt, limit_order, caplog, is_short
+) -> None:
+    freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    order = limit_order[enter_side(is_short)]
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
+
+    trade = MagicMock()
+    trade.is_short = is_short
     trade.open_order_id = None
     trade.open_fee = 0.001
     trade.pair = 'ETH/USDT'
@@ -1637,19 +1746,24 @@ def test_exit_positions_exception(mocker, default_conf_usdt, limit_buy_order_usd
         'freqtrade.freqtradebot.FreqtradeBot.handle_trade',
         side_effect=DependencyException()
     )
+    caplog.clear()
     n = freqtrade.exit_positions(trades)
     assert n == 0
     assert log_has('Unable to exit trade ETH/USDT: ', caplog)
 
 
-def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state(
+    mocker, default_conf_usdt, limit_order, is_short, caplog
+) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    order = limit_order[enter_side(is_short)]
 
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
-                 return_value=limit_buy_order_usdt['amount'])
+                 return_value=order['amount'])
 
     trade = Trade(
         open_order_id=123,
@@ -1659,6 +1773,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
         open_date=arrow.utcnow().datetime,
         amount=11,
         exchange="binance",
+        is_short=is_short
     )
     assert not freqtrade.update_trade_state(trade, None)
     assert log_has_re(r'Orderid for trade .* is empty.', caplog)
@@ -1669,7 +1784,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
     assert not log_has_re(r'Applying fee to .*', caplog)
     caplog.clear()
     assert trade.open_order_id is None
-    assert trade.amount == limit_buy_order_usdt['amount']
+    assert trade.amount == order['amount']
 
     trade.open_order_id = '123'
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
@@ -1687,12 +1802,16 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
     assert log_has_re('Found open order for.*', caplog)
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.parametrize('initial_amount,has_rounding_fee', [
     (30.0 + 1e-14, True),
     (8.0, False)
 ])
-def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, limit_buy_order_usdt,
-                                          fee, mocker, initial_amount, has_rounding_fee, caplog):
+def test_update_trade_state_withorderdict(
+    default_conf_usdt, trades_for_order, limit_order, fee, mocker, initial_amount,
+    has_rounding_fee, is_short, caplog
+):
+    order = limit_order[enter_side(is_short)]
     trades_for_order[0]['amount'] = initial_amount
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     # fetch_order should not be called!!
@@ -1711,22 +1830,26 @@ def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, l
         fee_close=fee.return_value,
         open_order_id="123456",
         is_open=True,
+        is_short=is_short
     )
-    freqtrade.update_trade_state(trade, '123456', limit_buy_order_usdt)
+    freqtrade.update_trade_state(trade, '123456', order)
     assert trade.amount != amount
-    assert trade.amount == limit_buy_order_usdt['amount']
+    assert trade.amount == order['amount']
     if has_rounding_fee:
         assert log_has_re(r'Applying fee on amount for .*', caplog)
 
 
-def test_update_trade_state_exception(mocker, default_conf_usdt,
-                                      limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit_order,
+                                      caplog) -> None:
+    order = limit_order[enter_side(is_short)]
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
 
     trade = MagicMock()
     trade.open_order_id = '123'
     trade.open_fee = 0.001
+    trade.is_short = is_short
 
     # Test raise of OperationalException exception
     mocker.patch(
@@ -1753,8 +1876,12 @@ def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) ->
     assert log_has(f'Unable to fetch order {trade.open_order_id}: ', caplog)
 
 
-def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell_order_usdt_open,
-                                 limit_sell_order_usdt, mocker):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state_sell(
+    default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker,
+):
+    open_order = limit_order_open[exit_side(is_short)]
+    l_order = limit_order[exit_side(is_short)]
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     # fetch_order should not be called!!
     mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
@@ -1762,8 +1889,8 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
     mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
 
     patch_exchange(mocker)
-    amount = limit_sell_order_usdt["amount"]
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    amount = l_order["amount"]
     wallet_mock.reset_mock()
     trade = Trade(
         pair='LTC/ETH',
@@ -1775,12 +1902,14 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
         open_date=arrow.utcnow().datetime,
         open_order_id="123456",
         is_open=True,
+        interest_rate=0.0005,
+        is_short=is_short
     )
-    order = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'LTC/ETH', 'sell')
+    order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', (enter_side(is_short)))
     trade.orders.append(order)
     assert order.status == 'open'
-    freqtrade.update_trade_state(trade, trade.open_order_id, limit_sell_order_usdt)
-    assert trade.amount == limit_sell_order_usdt['amount']
+    freqtrade.update_trade_state(trade, trade.open_order_id, l_order)
+    assert trade.amount == l_order['amount']
     # Wallet needs to be updated after closing a limit-sell order to reenable buying
     assert wallet_mock.call_count == 1
     assert not trade.is_open
@@ -1788,78 +1917,96 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
     assert order.status == 'closed'
 
 
-def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_usdt_open,
-                      limit_sell_order_usdt, fee, mocker) -> None:
+@pytest.mark.parametrize('is_short,close_profit', [
+    (False, 0.09451372),
+    (True, 0.08635224),
+])
+def test_handle_trade(
+    default_conf_usdt, limit_order_open, limit_order, fee, mocker, is_short, close_profit
+) -> None:
+    open_order = limit_order_open[exit_side(is_short)]
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
-            'bid': 1.9,
+            'bid': 2.19,
             'ask': 2.2,
-            'last': 1.9
+            'last': 2.19
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt,
-            limit_sell_order_usdt_open,
+            enter_order,
+            open_order,
         ]),
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     time.sleep(0.01)  # Race condition fix
-    trade.update(limit_buy_order_usdt)
+    trade.update(enter_order)
     assert trade.is_open is True
     freqtrade.wallets.update()
 
-    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+    patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short)
     assert freqtrade.handle_trade(trade) is True
-    assert trade.open_order_id == limit_sell_order_usdt['id']
+    assert trade.open_order_id == exit_order['id']
 
-    # Simulate fulfilled LIMIT_SELL order for trade
-    trade.update(limit_sell_order_usdt)
+    # Simulate fulfilled LIMIT order for trade
+    trade.update(exit_order)
 
-    assert trade.close_rate == 2.2
-    assert trade.close_profit == 0.09451372
+    assert trade.close_rate == 2.0 if is_short else 2.2
+    assert trade.close_profit == close_profit
     assert trade.calc_profit() == 5.685
     assert trade.close_date is not None
 
 
-def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
-                                    fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_overlapping_signals(
+    default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short
+) -> None:
+    open_order = limit_order_open[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            open_order,
             {'id': 1234553382},
         ]),
         get_fee=fee,
     )
 
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=True, exit_long=True)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
 
     freqtrade.enter_positions()
 
     # Buy and Sell triggering, so doing nothing ...
     trades = Trade.query.all()
+
     nb_trades = len(trades)
     assert nb_trades == 0
 
     # Buy is triggering, so buying ...
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.enter_positions()
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     nb_trades = len(trades)
     assert nb_trades == 1
     assert trades[0].is_open is True
@@ -1868,26 +2015,42 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or
     patch_get_signal(freqtrade, enter_long=False)
     assert freqtrade.handle_trade(trades[0]) is False
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     nb_trades = len(trades)
     assert nb_trades == 1
     assert trades[0].is_open is True
 
     # Buy and Sell are triggering, so doing nothing ...
-    patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=True, exit_long=True)
     assert freqtrade.handle_trade(trades[0]) is False
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     nb_trades = len(trades)
     assert nb_trades == 1
     assert trades[0].is_open is True
 
     # Sell is triggering, guess what : we are Selling!
-    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     assert freqtrade.handle_trade(trades[0]) is True
 
 
-def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
-                          fee, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog,
+                          is_short) -> None:
+
+    open_order = limit_order_open[enter_side(is_short)]
+
     caplog.set_level(logging.DEBUG)
 
     patch_RPCManager(mocker)
@@ -1895,19 +2058,20 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            open_order,
             {'id': 1234553382},
         ]),
         get_fee=fee,
     )
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
 
     # FIX: sniffing logs, suggest handle_trade should not execute_trade_exit
@@ -1915,14 +2079,22 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o
     #      we might just want to check if we are in a sell condition without
     #      executing
     # if ROI is reached we must sell
+    # TODO-lev: Change the next line for shorts
+    caplog.clear()
     patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     assert freqtrade.handle_trade(trade)
     assert log_has("ETH/USDT - Required profit reached. sell_type=SellType.ROI",
                    caplog)
 
 
-def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
-                                      limit_sell_order_usdt_open, fee, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_trade_use_sell_signal(
+    default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
+) -> None:
+
+    enter_open_order = limit_order_open[exit_side(is_short)]
+    exit_open_order = limit_order_open[enter_side(is_short)]
+
     # use_sell_signal is True buy default
     caplog.set_level(logging.DEBUG)
     patch_RPCManager(mocker)
@@ -1930,50 +2102,61 @@ def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
-            limit_sell_order_usdt_open,
+            enter_open_order,
+            exit_open_order,
         ]),
         get_fee=fee,
     )
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
 
     patch_get_signal(freqtrade, enter_long=False, exit_long=False)
     assert not freqtrade.handle_trade(trade)
 
-    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     assert freqtrade.handle_trade(trade)
     assert log_has("ETH/USDT - Sell signal received. sell_type=SellType.SELL_SIGNAL",
                    caplog)
 
 
-def test_close_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt,
-                     limit_buy_order_usdt_open, limit_sell_order_usdt, fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_close_trade(
+    default_conf_usdt, ticker_usdt, limit_order_open,
+    limit_order, fee, mocker, is_short
+) -> None:
+    open_order = limit_order_open[exit_side(is_short)]
+    enter_order = limit_order[exit_side(is_short)]
+    exit_order = limit_order[enter_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=MagicMock(return_value=limit_buy_order_usdt_open),
+        create_order=MagicMock(return_value=open_order),
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create trade and sell it
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
-    trade.update(limit_buy_order_usdt)
-    trade.update(limit_sell_order_usdt)
+    trade.update(enter_order)
+    trade.update(exit_order)
     assert trade.is_open is False
 
     with pytest.raises(DependencyException, match=r'.*closed trade.*'):
@@ -1993,27 +2176,33 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
     assert ftbot.strategy.analyze.call_count == 1
 
 
-def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, limit_buy_order_old,
-                                              open_trade, fee, mocker) -> None:
-    default_conf_usdt["unfilledtimeout"] = {"buy": 1400, "sell": 30}
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_buy_usercustom(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+    limit_sell_order_old, fee, mocker, is_short
+) -> None:
+
+    old_order = limit_sell_order_old if is_short else limit_buy_order_old
+    default_conf_usdt["unfilledtimeout"] = {"buy": 30,
+                                            "sell": 1400} if is_short else {"buy": 1400, "sell": 30}
 
     rpc_mock = patch_RPCManager(mocker)
-    cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
-    cancel_buy_order = deepcopy(limit_buy_order_old)
-    cancel_buy_order['status'] = 'canceled'
-    cancel_order_wr_mock = MagicMock(return_value=cancel_buy_order)
+    cancel_order_mock = MagicMock(return_value=old_order)
+    cancel_enter_order = deepcopy(old_order)
+    cancel_enter_order['status'] = 'canceled'
+    cancel_order_wr_mock = MagicMock(return_value=cancel_enter_order)
 
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        fetch_order=MagicMock(return_value=limit_buy_order_old),
+        fetch_order=MagicMock(return_value=old_order),
         cancel_order_with_result=cancel_order_wr_mock,
         cancel_order=cancel_order_mock,
         get_fee=fee
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-
+    open_trade.is_short = is_short
     Trade.query.session.add(open_trade)
 
     # Ensure default is to return empty (so not mocked yet)
@@ -2021,24 +2210,34 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li
     assert cancel_order_mock.call_count == 0
 
     # Return false - trade remains open
-    freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
+    if is_short:
+        freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
+    else:
+        freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 1
-    assert freqtrade.strategy.check_buy_timeout.call_count == 1
+    if is_short:
+        assert freqtrade.strategy.check_sell_timeout.call_count == 1
+        # Raise Keyerror ... (no impact on trade)
+        freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
+    else:
+        assert freqtrade.strategy.check_buy_timeout.call_count == 1
+        freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
 
-    # Raise Keyerror ... (no impact on trade)
-    freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 1
-    assert freqtrade.strategy.check_buy_timeout.call_count == 1
-
-    freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
+    if is_short:
+        assert freqtrade.strategy.check_sell_timeout.call_count == 1
+        freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True)
+    else:
+        assert freqtrade.strategy.check_buy_timeout.call_count == 1
+        freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
     # Trade should be closed since the function returns true
     freqtrade.check_handle_timedout()
     assert cancel_order_wr_mock.call_count == 1
@@ -2046,28 +2245,39 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 0
-    assert freqtrade.strategy.check_buy_timeout.call_count == 1
+    if is_short:
+        assert freqtrade.strategy.check_sell_timeout.call_count == 1
+    else:
+        assert freqtrade.strategy.check_buy_timeout.call_count == 1
 
 
-def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
-                                   fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_buy(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+    limit_sell_order_old, fee, mocker, is_short
+) -> None:
+    old_order = limit_sell_order_old if is_short else limit_buy_order_old
     rpc_mock = patch_RPCManager(mocker)
-    limit_buy_cancel = deepcopy(limit_buy_order_old)
+    limit_buy_cancel = deepcopy(old_order)
     limit_buy_cancel['status'] = 'canceled'
     cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        fetch_order=MagicMock(return_value=limit_buy_order_old),
+        fetch_order=MagicMock(return_value=old_order),
         cancel_order_with_result=cancel_order_mock,
         get_fee=fee
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
+    open_trade.is_short = is_short
     Trade.query.session.add(open_trade)
 
-    freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
+    if is_short:
+        freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
+    else:
+        freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
     # check it does cancel buy orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
@@ -2076,25 +2286,32 @@ def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_ord
     nb_trades = len(trades)
     assert nb_trades == 0
     # Custom user buy-timeout is never called
-    assert freqtrade.strategy.check_buy_timeout.call_count == 0
+    if is_short:
+        assert freqtrade.strategy.check_sell_timeout.call_count == 0
+    else:
+        assert freqtrade.strategy.check_buy_timeout.call_count == 0
 
 
-def test_check_handle_cancelled_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
-                                    fee, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_cancelled_buy(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+    limit_sell_order_old, fee, mocker, caplog, is_short
+) -> None:
     """ Handle Buy order cancelled on exchange"""
+    old_order = limit_sell_order_old if is_short else limit_buy_order_old
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
-    limit_buy_order_old.update({"status": "canceled", 'filled': 0.0})
+    old_order.update({"status": "canceled", 'filled': 0.0})
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        fetch_order=MagicMock(return_value=limit_buy_order_old),
+        fetch_order=MagicMock(return_value=old_order),
         cancel_order=cancel_order_mock,
         get_fee=fee
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-
+    open_trade.is_short = is_short
     Trade.query.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
@@ -2104,11 +2321,15 @@ def test_check_handle_cancelled_buy(default_conf_usdt, ticker_usdt, limit_buy_or
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 0
-    assert log_has_re("Buy order cancelled on exchange for Trade.*", caplog)
+    assert log_has_re(
+        f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
 
 
-def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
-                                             open_trade, fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_buy_exception(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+    is_short, fee, mocker
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
@@ -2133,8 +2354,11 @@ def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
     assert nb_trades == 1
 
 
-def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, limit_sell_order_old,
-                                               mocker, open_trade) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_sell_usercustom(
+    default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
+    is_short, open_trade_usdt
+) -> None:
     default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440}
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
@@ -2147,12 +2371,12 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
-    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
-    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
-    open_trade.close_profit_abs = 0.001
-    open_trade.is_open = False
+    open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade_usdt.close_profit_abs = 0.001
+    open_trade_usdt.is_open = False
 
-    Trade.query.session.add(open_trade)
+    Trade.query.session.add(open_trade_usdt)
     # Ensure default is false
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
@@ -2162,7 +2386,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 0
-    assert open_trade.is_open is False
+    assert open_trade_usdt.is_open is False
     assert freqtrade.strategy.check_sell_timeout.call_count == 1
 
     freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
@@ -2170,7 +2394,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 0
-    assert open_trade.is_open is False
+    assert open_trade_usdt.is_open is False
     assert freqtrade.strategy.check_sell_timeout.call_count == 1
 
     # Return True - sells!
@@ -2178,12 +2402,15 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 1
-    assert open_trade.is_open is True
+    assert open_trade_usdt.is_open is True
     assert freqtrade.strategy.check_sell_timeout.call_count == 1
 
 
-def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
-                                    open_trade) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_sell(
+    default_conf_usdt, ticker_usdt, limit_sell_order_old,
+    mocker, is_short, open_trade_usdt
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
@@ -2195,25 +2422,28 @@ def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_o
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
-    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
-    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
-    open_trade.close_profit_abs = 0.001
-    open_trade.is_open = False
+    open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade_usdt.close_profit_abs = 0.001
+    open_trade_usdt.is_open = False
 
-    Trade.query.session.add(open_trade)
+    Trade.query.session.add(open_trade_usdt)
 
     freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
     # check it does cancel sell orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 1
-    assert open_trade.is_open is True
+    assert open_trade_usdt.is_open is True
     # Custom user sell-timeout is never called
     assert freqtrade.strategy.check_sell_timeout.call_count == 0
 
 
-def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_order_old,
-                                     open_trade, mocker, caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_cancelled_sell(
+    default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt,
+    is_short, mocker, caplog
+) -> None:
     """ Handle sell order cancelled on exchange"""
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
@@ -2227,22 +2457,25 @@ def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
-    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
-    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
-    open_trade.is_open = False
+    open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade_usdt.is_open = False
 
-    Trade.query.session.add(open_trade)
+    Trade.query.session.add(open_trade_usdt)
 
     # check it does cancel sell orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 1
-    assert open_trade.is_open is True
+    assert open_trade_usdt.is_open is True
     assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
 
 
-def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy_order_old_partial,
-                                       open_trade, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short,
+    open_trade, mocker
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
     limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
     limit_buy_canceled['status'] = 'canceled'
@@ -2270,9 +2503,12 @@ def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy
     assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount
 
 
-def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_trade, caplog, fee,
-                                           limit_buy_order_old_partial, trades_for_order,
-                                           limit_buy_order_old_partial_canceled, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial_fee(
+    default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
+    limit_buy_order_old_partial, trades_for_order,
+    limit_buy_order_old_partial_canceled, mocker
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
     mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
@@ -2309,9 +2545,12 @@ def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_
     assert pytest.approx(trades[0].fee_open) == 0.001
 
 
-def test_check_handle_timedout_partial_except(default_conf_usdt, ticker_usdt, open_trade, caplog,
-                                              fee, limit_buy_order_old_partial, trades_for_order,
-                                              limit_buy_order_old_partial_canceled, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial_except(
+    default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
+    limit_buy_order_old_partial, trades_for_order,
+    limit_buy_order_old_partial_canceled, mocker
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
     patch_exchange(mocker)
@@ -2370,6 +2609,7 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
 
     Trade.query.session.add(open_trade_usdt)
 
+    caplog.clear()
     freqtrade.check_handle_timedout()
     assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ADA/USDT, amount=30.00000000, "
                       r"is_short=False, leverage=1.0, "
@@ -2379,10 +2619,13 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
                       caplog)
 
 
-def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_usdt) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order,
+                             is_short) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
-    cancel_buy_order = deepcopy(limit_buy_order_usdt)
+    l_order = limit_order[enter_side(is_short)]
+    cancel_buy_order = deepcopy(limit_order[enter_side(is_short)])
     cancel_buy_order['status'] = 'canceled'
     del cancel_buy_order['filled']
 
@@ -2395,36 +2638,39 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_
     trade = MagicMock()
     trade.pair = 'LTC/USDT'
     trade.open_rate = 200
-    limit_buy_order_usdt['filled'] = 0.0
-    limit_buy_order_usdt['status'] = 'open'
+    trade.is_short = False
+    trade.enter_side = "buy"
+    l_order['filled'] = 0.0
+    l_order['status'] = 'open'
     reason = CANCEL_REASON['TIMEOUT']
-    assert freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    assert freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
     cancel_order_mock.reset_mock()
     caplog.clear()
-    limit_buy_order_usdt['filled'] = 0.01
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    l_order['filled'] = 0.01
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 0
-    assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
+    assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unexitable.*", caplog)
 
     caplog.clear()
     cancel_order_mock.reset_mock()
-    limit_buy_order_usdt['filled'] = 2
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    l_order['filled'] = 2
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
     # Order remained open for some reason (cancel failed)
     cancel_buy_order['status'] = 'open'
     cancel_order_mock = MagicMock(return_value=cancel_buy_order)
     mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert log_has_re(r"Order .* for .* not cancelled.", caplog)
 
 
-@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
-                         indirect=['limit_buy_order_canceled_empty'])
-def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt,
+@ pytest.mark.parametrize("is_short", [False, True])
+@ pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
+                          indirect=['limit_buy_order_canceled_empty'])
+def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short,
                                        limit_buy_order_canceled_empty) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -2437,22 +2683,25 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt,
     reason = CANCEL_REASON['TIMEOUT']
     trade = MagicMock()
     trade.pair = 'LTC/ETH'
+    trade.enter_side = "buy"
     assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
     assert cancel_order_mock.call_count == 0
     assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
     assert nofiy_mock.call_count == 1
 
 
-@pytest.mark.parametrize('cancelorder', [
+@ pytest.mark.parametrize("is_short", [False, True])
+@ pytest.mark.parametrize('cancelorder', [
     {},
     {'remaining': None},
     'String Return value',
     123
 ])
-def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_buy_order_usdt,
+def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order, is_short,
                                           cancelorder) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    l_order = limit_order[enter_side(is_short)]
     cancel_order_mock = MagicMock(return_value=cancelorder)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2464,16 +2713,18 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_buy_o
 
     trade = MagicMock()
     trade.pair = 'LTC/USDT'
+    trade.enter_side = "buy"
     trade.open_rate = 200
-    limit_buy_order_usdt['filled'] = 0.0
-    limit_buy_order_usdt['status'] = 'open'
+    trade.enter_side = "buy"
+    l_order['filled'] = 0.0
+    l_order['status'] = 'open'
     reason = CANCEL_REASON['TIMEOUT']
-    assert freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    assert freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
     cancel_order_mock.reset_mock()
-    limit_buy_order_usdt['filled'] = 1.0
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    l_order['filled'] = 1.0
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
 
@@ -2538,8 +2789,12 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
     assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
 
 
-def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker
-                               ) -> None:
+@ pytest.mark.parametrize("is_short, open_rate, amt", [
+    (False, 2.0, 30.0),
+    (True, 2.02, 29.70297029),
+])
+def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker,
+                               ticker_usdt_sell_down, is_short, open_rate, amt) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2550,7 +2805,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
 
     # Create some test data
@@ -2558,25 +2813,31 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
     rpc_mock.reset_mock()
 
     trade = Trade.query.first()
+    assert trade.is_short == is_short
     assert trade
     assert freqtrade.strategy.confirm_trade_exit.call_count == 0
 
     # Increase the price and sell it
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        fetch_ticker=ticker_usdt_sell_up
+        fetch_ticker=ticker_usdt_sell_down if is_short else ticker_usdt_sell_up
     )
     # Prevented sell ...
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_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_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_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
@@ -2587,13 +2848,13 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
         'gain': 'profit',
-        'limit': 2.2,
-        'amount': 30.0,
+        'limit': 2.0 if is_short else 2.2,
+        'amount': amt,
         'order_type': 'limit',
-        'open_rate': 2.0,
-        'current_rate': 2.3,
-        'profit_amount': 5.685,
-        'profit_ratio': 0.09451372,
+        'open_rate': open_rate,
+        'current_rate': 2.01 if is_short else 2.3,
+        'profit_amount': 0.29554455 if is_short else 5.685,
+        'profit_ratio': 0.00493809 if is_short else 0.09451372,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
         'sell_reason': SellType.ROI.value,
@@ -2603,8 +2864,9 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
     } == last_msg
 
 
+@ pytest.mark.parametrize("is_short", [False, True])
 def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down,
-                                 mocker) -> None:
+                                 ticker_usdt_sell_up, mocker, is_short) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2615,22 +2877,23 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Decrease the price and sell it
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        fetch_ticker=ticker_usdt_sell_down
+        fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
     )
-
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+    freqtrade.execute_trade_exit(
+        trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_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]
@@ -2640,13 +2903,13 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
         'gain': 'loss',
-        'limit': 2.01,
-        'amount': 30.0,
+        'limit': 2.2 if is_short else 2.01,
+        'amount': 29.70297029 if is_short else 30.0,
         'order_type': 'limit',
-        'open_rate': 2.0,
-        'current_rate': 2.0,
-        'profit_amount': -0.00075,
-        'profit_ratio': -1.247e-05,
+        'open_rate': 2.02 if is_short else 2.0,
+        'current_rate': 2.2 if is_short else 2.0,
+        'profit_amount': -5.65990099 if is_short else -0.00075,
+        'profit_ratio': -0.0945681 if is_short else -1.247e-05,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
         'sell_reason': SellType.STOP_LOSS.value,
@@ -2656,8 +2919,14 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
     } == last_msg
 
 
-def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fee,
-                                              ticker_usdt_sell_up, mocker) -> None:
+@pytest.mark.parametrize(
+    "is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
+        (False, 30, 2.0, 2.3, 2.25, 7.18125, 0.11938903, 'profit'),
+        (True, 29.70297029, 2.02, 2.2, 2.25, -7.14876237, -0.11944465, 'loss'),  # TODO-lev
+    ])
+def test_execute_trade_exit_custom_exit_price(
+        default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, amount, open_rate,
+        current_rate, limit, profit_amount, profit_ratio, profit_or_loss, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2670,7 +2939,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
     config['custom_price_max_distance_ratio'] = 0.1
     patch_whitelist(mocker, config)
     freqtrade = FreqtradeBot(config)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
 
     # Create some test data
@@ -2678,6 +2947,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
     rpc_mock.reset_mock()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     assert freqtrade.strategy.confirm_trade_exit.call_count == 0
 
@@ -2691,7 +2961,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
 
     # Set a custom exit price
     freqtrade.strategy.custom_exit_price = lambda **kwargs: 2.25
-
+    # TODO-lev: side="buy"
     freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
                                  sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
 
@@ -2706,14 +2976,14 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
         'type': RPCMessageType.SELL,
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
-        'gain': 'profit',
-        'limit': 2.25,
-        'amount': 30.0,
+        'gain': profit_or_loss,
+        'limit': limit,
+        'amount': amount,
         'order_type': 'limit',
-        'open_rate': 2.0,
-        'current_rate': 2.3,
-        'profit_amount': 7.18125,
-        'profit_ratio': 0.11938903,
+        'open_rate': open_rate,
+        'current_rate': current_rate,
+        'profit_amount': profit_amount,
+        'profit_ratio': profit_ratio,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
         'sell_reason': SellType.SELL_SIGNAL.value,
@@ -2723,8 +2993,10 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
     } == last_msg
 
 
+@ pytest.mark.parametrize("is_short", [False, True])
 def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
-        default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker) -> None:
+        default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down,
+        ticker_usdt_sell_up, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2735,27 +3007,29 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    assert trade.is_short == is_short
     assert trade
 
     # Decrease the price and sell it
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        fetch_ticker=ticker_usdt_sell_down
+        fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
     )
 
     default_conf_usdt['dry_run'] = True
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
     # Setting trade stoploss to 0.01
 
-    trade.stop_loss = 2.0 * 0.99
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+    trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
+    freqtrade.execute_trade_exit(
+        trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_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]
@@ -2766,13 +3040,13 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
         'gain': 'loss',
-        'limit': 1.98,
-        'amount': 30.0,
+        'limit': 2.02 if is_short else 1.98,
+        'amount': 29.70297029 if is_short else 30.0,
         'order_type': 'limit',
-        'open_rate': 2.0,
-        'current_rate': 2.0,
-        'profit_amount': -0.8985,
-        'profit_ratio': -0.01493766,
+        'open_rate': 2.02 if is_short else 2.0,
+        'current_rate': 2.2 if is_short else 2.0,
+        'profit_amount': -0.3 if is_short else -0.8985,
+        'profit_ratio': -0.00501253 if is_short else -0.01493766,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
         'sell_reason': SellType.STOP_LOSS.value,
@@ -2810,14 +3084,16 @@ def test_execute_trade_exit_sloe_cancel_exception(
     freqtrade.config['dry_run'] = False
     trade.stoploss_order_id = "abcd"
 
+    # TODO-lev: side="buy"
     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_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_usdt, fee,
-                                                      ticker_usdt_sell_up, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_execute_trade_exit_with_stoploss_on_exchange(
+        default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None:
 
     default_conf_usdt['exchange']['name'] = 'binance'
     rpc_mock = patch_RPCManager(mocker)
@@ -2843,12 +3119,13 @@ def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_
 
     freqtrade = FreqtradeBot(default_conf_usdt)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     trades = [trade]
 
@@ -2861,10 +3138,12 @@ def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_
         fetch_ticker=ticker_usdt_sell_up
     )
 
+    # TODO-lev: side="buy"
     freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     assert cancel_order.call_count == 1
     assert rpc_mock.call_count == 3
@@ -2942,8 +3221,33 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt
     assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
 
 
-def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
-                                         ticker_usdt_sell_up, mocker) -> None:
+@pytest.mark.parametrize(
+    "is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
+        (False, 30, 2.0, 2.3, 2.2, 5.685, 0.09451372, 'profit'),
+        # TODO-lev: Should the current rate be 2.2 for shorts?
+        (True, 29.70297029, 2.02, 2.2, 2.3, -8.63762376, -0.1443212, 'loss'),
+    ])
+def test_execute_trade_exit_market_order(
+    default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount, open_rate,
+    limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker
+) -> None:
+    """
+    amount
+        long: 60 / 2.0 = 30
+        short: 60 / 2.02 = 29.70297029
+    open_value
+        long: (30 * 2.0) + (30 * 2.0 * 0.0025) = 60.15
+        short: (29.702970297029704 * 2.02) - (29.702970297029704 * 2.02 * 0.0025) = 59.85
+    close_value
+        long: (30 * 2.2) - (30 * 2.2 * 0.0025) = 65.835
+        short: (29.702970297029704 * 2.3) + (29.702970297029704 * 2.3 * 0.0025) = 68.48762376237624
+    profit
+        long: 65.835 - 60.15 = 5.684999999999995
+        short: 59.85 - 68.48762376237624 = -8.637623762376244
+    profit_ratio
+        long: (65.835/60.15) - 1 = 0.0945137157107232
+        short: 1 - (68.48762376237624/59.85) = -0.1443211990371971
+    """
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2954,12 +3258,13 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Increase the price and sell it
@@ -2969,11 +3274,15 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
     )
     freqtrade.config['order_types']['sell'] = 'market'
 
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+    # TODO-lev: side="buy"
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
+        sell_reason=SellCheckTuple(sell_type=SellType.ROI)
+    )
 
     assert not trade.is_open
-    assert trade.close_profit == 0.09451372
+    assert trade.close_profit == profit_ratio
 
     assert rpc_mock.call_count == 3
     last_msg = rpc_mock.call_args_list[-1][0][0]
@@ -2982,14 +3291,14 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
-        'gain': 'profit',
-        'limit': 2.2,
-        'amount': 30.0,
+        'gain': profit_or_loss,
+        'limit': limit,
+        'amount': round(amount, 9),
         'order_type': 'market',
-        'open_rate': 2.0,
-        'current_rate': 2.3,
-        'profit_amount': 5.685,
-        'profit_ratio': 0.09451372,
+        'open_rate': open_rate,
+        'current_rate': current_rate,
+        'profit_amount': profit_amount,
+        'profit_ratio': profit_ratio,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
         'sell_reason': SellType.ROI.value,
@@ -3000,7 +3309,8 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
     } == last_msg
 
 
-def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee,
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee, is_short,
                                                      ticker_usdt_sell_up, mocker) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
@@ -3013,12 +3323,13 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
             InsufficientFundsError(),
         ]),
     )
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Increase the price and sell it
@@ -3028,24 +3339,29 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
     )
 
     sell_reason = SellCheckTuple(sell_type=SellType.ROI)
+    # TODO-lev: side="buy"
     assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
                                             sell_reason=sell_reason)
     assert mock_insuf.call_count == 1
 
 
-@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type', [
+@ pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [
     # Enable profit
-    (True, 1.9, 2.2, False, True, SellType.SELL_SIGNAL.value),
-    # Disable profit
-    (False, 2.9, 3.2, True,  False, SellType.SELL_SIGNAL.value),
-    # Enable loss
-    # * Shouldn't this be SellType.STOP_LOSS.value
-    (True, 0.19, 0.22, False, False, None),
+    (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, False),
+    (True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, True),
+    # # Disable profit
+    (False, 3.19, 3.2, True,  False, SellType.SELL_SIGNAL.value, False),
+    (False, 3.19, 3.2, True,  False, SellType.SELL_SIGNAL.value, True),
+    # # Enable loss
+    # # * Shouldn't this be SellType.STOP_LOSS.value
+    (True, 0.21, 0.22, False, False, None, False),
+    (True, 2.41, 2.42, False, False, None, True),
     # Disable loss
-    (False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value),
+    (False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value, False),
+    (False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value, True),
 ])
 def test_sell_profit_only(
-        default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
+        default_conf_usdt, limit_order, limit_order_open, is_short,
         fee, mocker, profit_only, bid, ask, handle_first, handle_second, sell_type) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -3057,7 +3373,7 @@ def test_sell_profit_only(
             'last': bid
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[enter_side(is_short)],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3068,7 +3384,7 @@ def test_sell_profit_only(
         'sell_profit_offset': 0.1,
     })
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     if sell_type == SellType.SELL_SIGNAL.value:
         freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     else:
@@ -3077,9 +3393,10 @@ def test_sell_profit_only(
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    trade.update(limit_buy_order_usdt)
+    trade.is_short = is_short
+    trade.update(limit_order[enter_side(is_short)])
     freqtrade.wallets.update()
-    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+    patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short)
     assert freqtrade.handle_trade(trade) is handle_first
 
     if handle_second:
@@ -3087,7 +3404,7 @@ def test_sell_profit_only(
         assert freqtrade.handle_trade(trade) is True
 
 
-def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
+def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_open,
                                  fee, mocker, caplog) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -3099,7 +3416,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
             'last': 0.00002172
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open['buy'],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3113,7 +3430,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
 
     trade = Trade.query.first()
     amnt = trade.amount
-    trade.update(limit_buy_order_usdt)
+    trade.update(limit_order['buy'])
     patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
 
@@ -3122,7 +3439,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
     assert trade.amount != amnt
 
 
-@pytest.mark.parametrize('amount_wallet,has_err', [
+@ pytest.mark.parametrize('amount_wallet,has_err', [
     (95.29, False),
     (91.29, True)
 ])
@@ -3159,8 +3476,9 @@ def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet
         assert wallet_update.call_count == 1
 
 
-def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker,
-                      caplog) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
+                      ticker_usdt_sell_down, mocker, caplog, is_short) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -3169,12 +3487,13 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Decrease the price and sell it
@@ -3183,6 +3502,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
         fetch_ticker=ticker_usdt_sell_down
     )
 
+    # TODO-lev: side="buy"
     freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
                                  sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
     trade.close(ticker_usdt_sell_down()['bid'])
@@ -3195,8 +3515,9 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
     assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog)
 
 
-def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
-                                  limit_buy_order_usdt_open, fee, mocker) -> None:
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short,
+                                  fee, mocker) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -3207,7 +3528,7 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
             'last': 2.19
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[enter_side(is_short)],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3215,25 +3536,37 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
     default_conf_usdt['ignore_roi_if_buy_signal'] = True
 
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    trade.update(limit_buy_order_usdt)
+    trade.is_short = is_short
+    trade.update(limit_order[enter_side(is_short)])
     freqtrade.wallets.update()
-    patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+
     assert freqtrade.handle_trade(trade) is False
 
     # Test if buy-signal is absent (should sell due to roi = true)
-    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     assert freqtrade.handle_trade(trade) is True
     assert trade.sell_reason == SellType.ROI.value
 
 
-def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
-                            fee, caplog, mocker) -> None:
+@ pytest.mark.parametrize("is_short,val1,val2", [
+    (False, 1.5, 1.1),
+    (True, 0.5, 0.9)
+])
+def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
+                            is_short, val1, val2, fee, caplog, mocker) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -3244,7 +3577,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
             'last': 2.0
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[enter_side(is_short)],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3252,19 +3585,20 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
     default_conf_usdt['trailing_stop'] = True
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    assert trade.is_short == is_short
     assert freqtrade.handle_trade(trade) is False
 
-    # Raise ticker_usdt above buy price
+    # Raise praise into profits
     mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
                  MagicMock(return_value={
-                     'bid': 2.0 * 1.5,
-                     'ask': 2.0 * 1.5,
-                     'last': 2.0 * 1.5
+                     'bid': 2.0 * val1,
+                     'ask': 2.0 * val1,
+                     'last': 2.0 * val1
                  }))
 
     # Stoploss should be adjusted
@@ -3273,40 +3607,46 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
     # Price fell
     mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
                  MagicMock(return_value={
-                     'bid': 2.0 * 1.1,
-                     'ask': 2.0 * 1.1,
-                     'last': 2.0 * 1.1
+                     'bid': 2.0 * val2,
+                     'ask': 2.0 * val2,
+                     'last': 2.0 * val2
                  }))
 
     caplog.set_level(logging.DEBUG)
     # Sell as trailing-stop is reached
     assert freqtrade.handle_trade(trade) is True
-    assert log_has("ETH/USDT - HIT STOP: current price at 2.200000, stoploss is 2.700000, "
-                   "initial stoploss was at 1.800000, trade opened at 2.000000", caplog)
+    stop_multi = 1.1 if is_short else 0.9
+    assert log_has(f"ETH/USDT - HIT STOP: current price at {(2.0 * val2):6f}, "
+                   f"stoploss is {(2.0 * val1 * stop_multi):6f}, "
+                   f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
+                   caplog)
     assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
 
 
-@pytest.mark.parametrize('offset,trail_if_reached,second_sl', [
-    (0, False, 2.0394),
-    (0.011, False, 2.0394),
-    (0.055, True, 1.8),
+@ pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [
+    (0, False, 2.0394, False),
+    (0.011, False, 2.0394, False),
+    (0.055, True, 1.8, False),
+    (0, False, 2.1614, True),
+    (0.011, False, 2.1614, True),
+    (0.055, True, 2.42, True),
 ])
 def test_trailing_stop_loss_positive(
-    default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
-    offset, fee, caplog, mocker, trail_if_reached, second_sl
+    default_conf_usdt, limit_order, limit_order_open,
+    offset, fee, caplog, mocker, trail_if_reached, second_sl, is_short
 ) -> None:
-    buy_price = limit_buy_order_usdt['price']
+    enter_price = limit_order[enter_side(is_short)]['price']
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
-            'bid': buy_price - 0.01,
-            'ask': buy_price - 0.01,
-            'last': buy_price - 0.01
+            'bid': enter_price - (-0.01 if is_short else 0.01),
+            'ask': enter_price - (-0.01 if is_short else 0.01),
+            'last': enter_price - (-0.01 if is_short else 0.01),
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[enter_side(is_short)],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3319,12 +3659,13 @@ def test_trailing_stop_loss_positive(
     patch_whitelist(mocker, default_conf_usdt)
 
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    trade.update(limit_buy_order_usdt)
+    trade.is_short = is_short
+    trade.update(limit_order[enter_side(is_short)])
     caplog.set_level(logging.DEBUG)
     # stop-loss not reached
     assert freqtrade.handle_trade(trade) is False
@@ -3333,34 +3674,36 @@ def test_trailing_stop_loss_positive(
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': buy_price + 0.06,
-            'ask': buy_price + 0.06,
-            'last': buy_price + 0.06
+            'bid': enter_price + (-0.06 if is_short else 0.06),
+            'ask': enter_price + (-0.06 if is_short else 0.06),
+            'last': enter_price + (-0.06 if is_short else 0.06),
         })
     )
     # stop-loss not reached, adjusted stoploss
     assert freqtrade.handle_trade(trade) is False
-    caplog_text = f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: 0.0249%"
+    caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
+                   f"{'0.0249' if not is_short else '0.0224'}%")
     if trail_if_reached:
         assert not log_has(caplog_text, caplog)
         assert not log_has("ETH/USDT - Adjusting stoploss...", caplog)
     else:
         assert log_has(caplog_text, caplog)
         assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
-    assert trade.stop_loss == second_sl
+    assert pytest.approx(trade.stop_loss) == second_sl
     caplog.clear()
 
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': buy_price + 0.125,
-            'ask': buy_price + 0.125,
-            'last': buy_price + 0.125,
+            'bid': enter_price + (-0.135 if is_short else 0.125),
+            'ask': enter_price + (-0.135 if is_short else 0.125),
+            'last': enter_price + (-0.135 if is_short else 0.125),
         })
     )
     assert freqtrade.handle_trade(trade) is False
     assert log_has(
-        f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: 0.0572%",
+        f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
+        f"{'0.0572' if not is_short else '0.0567'}%",
         caplog
     )
     assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
@@ -3368,33 +3711,36 @@ def test_trailing_stop_loss_positive(
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': buy_price + 0.02,
-            'ask': buy_price + 0.02,
-            'last': buy_price + 0.02
+            'bid': enter_price + (-0.02 if is_short else 0.02),
+            'ask': enter_price + (-0.02 if is_short else 0.02),
+            'last': enter_price + (-0.02 if is_short else 0.02),
         })
     )
     # Lower price again (but still positive)
     assert freqtrade.handle_trade(trade) is True
     assert log_has(
-        f"ETH/USDT - HIT STOP: current price at {buy_price + 0.02:.6f}, "
+        f"ETH/USDT - HIT STOP: current price at {enter_price + (-0.02 if is_short else 0.02):.6f}, "
         f"stoploss is {trade.stop_loss:.6f}, "
-        f"initial stoploss was at 1.800000, trade opened at 2.000000", caplog)
+        f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, "
+        f"trade opened at {2.2 if is_short else 2.0}00000",
+        caplog)
     assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
 
 
-def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
-                                          limit_buy_order_usdt_open, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open,
+                                          is_short, fee, mocker) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
-            'bid': 0.00000172,
-            'ask': 0.00000173,
-            'last': 0.00000172
+            'bid': 2.0,
+            'ask': 2.0,
+            'last': 2.0
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[enter_side(is_short)],
             {'id': 1234553382},
             {'id': 1234553383}
         ]),
@@ -3405,21 +3751,23 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usd
         'ignore_roi_if_buy_signal': False
     }
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    trade.update(limit_buy_order_usdt)
+    trade.is_short = is_short
+    trade.update(limit_order[enter_side(is_short)])
     # Sell due to min_roi_reached
-    patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+    patch_get_signal(freqtrade, enter_long=not is_short, exit_long=not is_short,
+                     enter_short=is_short, exit_short=is_short)
     assert freqtrade.handle_trade(trade) is True
 
     # Test if buy-signal is absent
-    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+    patch_get_signal(freqtrade, enter_long=False, exit_long=not is_short, exit_short=is_short)
     assert freqtrade.handle_trade(trade) is True
-    assert trade.sell_reason == SellType.SELL_SIGNAL.value
+    assert trade.sell_reason == SellType.ROI.value
 
 
 def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog,
@@ -3497,7 +3845,7 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
     )
 
 
-@pytest.mark.parametrize(
+@ pytest.mark.parametrize(
     'fee_par,fee_reduction_amount,use_ticker_usdt_rate,expected_log', [
         # basic, amount does not change
         ({'cost': 0.008, 'currency': 'ETH'}, 0, False, None),
@@ -3550,7 +3898,7 @@ def test_get_real_amount(
         assert log_has(expected_log, caplog)
 
 
-@pytest.mark.parametrize(
+@ pytest.mark.parametrize(
     'fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount', [
         # basic, amount is reduced by fee
         (None, None, 0.001, 0.001, 7.992),
@@ -3681,7 +4029,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
     )
 
 
-def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker):
+def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
     amount = 12345
     trade = Trade(
         pair='LTC/ETH',
@@ -3702,7 +4050,7 @@ def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker):
     assert freqtrade.get_real_amount(trade, order) == amount
 
 
-@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
+@ pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
     (8.0, 0.0, 10, 8),
     (8.0, 0.0, 0, 8),
     (8.0, 0.1, 0, 7.9),
@@ -3731,13 +4079,17 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
     assert walletmock.call_count == 1
 
 
-@pytest.mark.parametrize("delta, is_high_delta", [
+@ pytest.mark.parametrize("delta, is_high_delta", [
     (0.1, False),
     (100, True),
 ])
+@ pytest.mark.parametrize('is_short, open_rate', [
+    (False, 2.0),
+    (True, 2.02),
+])
 def test_order_book_depth_of_market(
-    default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt,
-    fee, mocker, order_book_l2, delta, is_high_delta
+    default_conf_usdt, ticker_usdt, limit_order, limit_order_open,
+    fee, mocker, order_book_l2, delta, is_high_delta, is_short, open_rate
 ):
     default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
     default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = delta
@@ -3747,20 +4099,21 @@ def test_order_book_depth_of_market(
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=MagicMock(return_value=limit_buy_order_usdt_open),
+        create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
         get_fee=fee,
     )
 
     # Save state of current whitelist
     whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
     if is_high_delta:
         assert trade is None
     else:
+        trade.is_short = is_short
         assert trade is not None
         assert trade.stake_amount == 60.0
         assert trade.is_open
@@ -3770,13 +4123,13 @@ def test_order_book_depth_of_market(
         assert len(Trade.query.all()) == 1
 
         # Simulate fulfilled LIMIT_BUY order for trade
-        trade.update(limit_buy_order_usdt)
+        trade.update(limit_order_open[enter_side(is_short)])
 
-        assert trade.open_rate == 2.0
+        assert trade.open_rate == open_rate  # TODO-lev: double check
         assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
 
 
-@pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [
+@ pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [
     (False, 0.045, 0.046, 2, None),
     (True,  0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
 ])
@@ -3809,7 +4162,7 @@ def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exce
         assert ticker_usdt_mock.call_count == 0
 
 
-def test_check_depth_of_market_buy(default_conf_usdt, mocker, order_book_l2) -> None:
+def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None:
     """
     test check depth of market
     """
@@ -3826,11 +4179,12 @@ def test_check_depth_of_market_buy(default_conf_usdt, mocker, order_book_l2) ->
     freqtrade = FreqtradeBot(default_conf_usdt)
 
     conf = default_conf_usdt['bid_strategy']['check_depth_of_market']
-    assert freqtrade._check_depth_of_market_buy('ETH/USDT', conf) is False
+    assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False
 
 
+@ pytest.mark.parametrize('is_short', [False, True])
 def test_order_book_ask_strategy(
-        default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee,
+        default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short,
         limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None:
     """
     test order book ask strategy
@@ -3868,7 +4222,10 @@ def test_order_book_ask_strategy(
     freqtrade.wallets.update()
     assert trade.is_open is True
 
-    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False,  exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     assert freqtrade.handle_trade(trade) is True
     assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]
 
@@ -3906,7 +4263,7 @@ def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker):
     assert reinit_mock.call_count == 0
 
 
-@pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.usefixtures("init_persistence")
 def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_buy_order_usdt_open,
                              caplog):
     default_conf_usdt['dry_run'] = True
@@ -3939,38 +4296,46 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_
                       caplog)
 
 
-@pytest.mark.usefixtures("init_persistence")
-def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
-                                limit_sell_order_usdt):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short,buy_calls,sell_calls", [
+    (False, 1, 2),
+    (True, 2, 1),
+])
+def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, limit_order_open,
+                                is_short, buy_calls, sell_calls):
     default_conf_usdt['cancel_open_orders_on_exit'] = True
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order',
-                 side_effect=[
-                     ExchangeError(),
-                     limit_sell_order_usdt,
-                     limit_buy_order_usdt,
-                     limit_sell_order_usdt
-                 ])
+    mocker.patch(
+        'freqtrade.exchange.Exchange.fetch_order',
+        side_effect=[
+            ExchangeError(),
+            limit_order[exit_side(is_short)],
+            limit_order_open[enter_side(is_short)],
+            limit_order_open[exit_side(is_short)],
+        ]
+    )
     buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
     sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
     trades = Trade.query.all()
     assert len(trades) == MOCK_TRADE_COUNT
     freqtrade.cancel_all_open_orders()
-    assert buy_mock.call_count == 1
-    assert sell_mock.call_count == 2
+    assert buy_mock.call_count == buy_calls
+    assert sell_mock.call_count == sell_calls
 
 
-@pytest.mark.usefixtures("init_persistence")
-def test_check_for_open_trades(mocker, default_conf_usdt, fee):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
 
     freqtrade.check_for_open_trades()
     assert freqtrade.rpc.send_msg.call_count == 0
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short)
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
 
     freqtrade.check_for_open_trades()
@@ -3978,10 +4343,11 @@ def test_check_for_open_trades(mocker, default_conf_usdt, fee):
     assert 'Handle these trades manually' in freqtrade.rpc.send_msg.call_args[0][0]['status']
 
 
-@pytest.mark.usefixtures("init_persistence")
-def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
+@ pytest.mark.parametrize("is_short", [False, True])
+@ pytest.mark.usefixtures("init_persistence")
+def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
 
     freqtrade.startup_update_open_orders()
     assert not log_has_re(r"Error updating Order .*", caplog)
@@ -3994,7 +4360,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
     caplog.clear()
 
     assert len(Order.get_open_orders()) == 3
-    matching_buy_order = mock_order_4()
+    matching_buy_order = mock_order_4(is_short=is_short)
     matching_buy_order.update({
         'status': 'closed',
     })
@@ -4004,8 +4370,9 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
     assert len(Order.get_open_orders()) == 2
 
 
-@pytest.mark.usefixtures("init_persistence")
-def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
 
     def patch_with_fee(order):
@@ -4015,19 +4382,20 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
 
     mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
                  side_effect=[
-                     patch_with_fee(mock_order_2_sell()),
-                     patch_with_fee(mock_order_3_sell()),
-                     patch_with_fee(mock_order_1()),
-                     patch_with_fee(mock_order_2()),
-                     patch_with_fee(mock_order_3()),
-                     patch_with_fee(mock_order_4()),
+                     patch_with_fee(mock_order_2_sell(is_short=is_short)),
+                     patch_with_fee(mock_order_3_sell(is_short=is_short)),
+                     patch_with_fee(mock_order_1(is_short=is_short)),
+                     patch_with_fee(mock_order_2(is_short=is_short)),
+                     patch_with_fee(mock_order_3(is_short=is_short)),
+                     patch_with_fee(mock_order_4(is_short=is_short)),
                  ]
                  )
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
     trades = Trade.get_trades().all()
     assert len(trades) == MOCK_TRADE_COUNT
     for trade in trades:
+        trade.is_short = is_short
         assert trade.fee_open_cost is None
         assert trade.fee_open_currency is None
         assert trade.fee_close_cost is None
@@ -4054,7 +4422,7 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
     for trade in trades:
         if trade.is_open:
             # Exclude Trade 4 - as the order is still open.
-            if trade.select_order('buy', False):
+            if trade.select_order(enter_side(is_short), False):
                 assert trade.fee_open_cost is not None
                 assert trade.fee_open_currency is not None
             else:
@@ -4066,20 +4434,29 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
             assert trade.fee_close_currency is not None
 
 
-@pytest.mark.usefixtures("init_persistence")
-def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
     trades = Trade.get_trades().all()
 
     freqtrade.reupdate_enter_order_fees(trades[0])
-    assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
+    assert log_has_re(
+        f"Trying to reupdate {enter_side(is_short)} "
+        r"fees for .*",
+        caplog
+    )
     assert mock_uts.call_count == 1
     assert mock_uts.call_args_list[0][0][0] == trades[0]
-    assert mock_uts.call_args_list[0][0][1] == mock_order_1()['id']
-    assert log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
+    assert mock_uts.call_args_list[0][0][1] == mock_order_1(is_short=is_short)['id']
+    assert log_has_re(
+        f"Updating {enter_side(is_short)}-fee on trade "
+        r".* for order .*\.",
+        caplog
+    )
     mock_uts.reset_mock()
     caplog.clear()
 
@@ -4094,21 +4471,24 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog):
         amount=30,
         open_rate=2.0,
         exchange='binance',
+        is_short=is_short
     )
     Trade.query.session.add(trade)
 
     freqtrade.reupdate_enter_order_fees(trade)
-    assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
+    assert log_has_re(f"Trying to reupdate {enter_side(is_short)} fees for "
+                      r".*", caplog)
     assert mock_uts.call_count == 0
-    assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
+    assert not log_has_re(f"Updating {enter_side(is_short)}-fee on trade "
+                          r".* for order .*\.", caplog)
 
 
-@pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.usefixtures("init_persistence")
 def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
     mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=False)
     trades = Trade.get_trades().all()
 
     # Trade 0 has only a open buy order, no closed order
@@ -4141,8 +4521,9 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
     assert mock_bof.call_count == 1
 
 
-@pytest.mark.usefixtures("init_persistence")
-def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
+@ pytest.mark.usefixtures("init_persistence")
+@ pytest.mark.parametrize("is_short", [False, True])
+def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
     caplog.set_level(logging.DEBUG)
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
@@ -4153,8 +4534,9 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
     def reset_open_orders(trade):
         trade.open_order_id = None
         trade.stoploss_order_id = None
+        trade.is_short = is_short
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
     trades = Trade.get_trades().all()
 
     caplog.clear()
@@ -4166,7 +4548,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.refind_lost_order(trade)
-    order = mock_order_1()
+    order = mock_order_1(is_short=is_short)
     assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog)
     assert mock_fo.call_count == 0
     assert mock_uts.call_count == 0
@@ -4184,7 +4566,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.refind_lost_order(trade)
-    order = mock_order_4()
+    order = mock_order_4(is_short=is_short)
     assert log_has_re(r"Trying to refind Order\(.*", caplog)
     assert mock_fo.call_count == 0
     assert mock_uts.call_count == 0
@@ -4202,7 +4584,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.refind_lost_order(trade)
-    order = mock_order_5_stoploss()
+    order = mock_order_5_stoploss(is_short=is_short)
     assert log_has_re(r"Trying to refind Order\(.*", caplog)
     assert mock_fo.call_count == 1
     assert mock_uts.call_count == 1
@@ -4221,7 +4603,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.refind_lost_order(trade)
-    order = mock_order_6_sell()
+    order = mock_order_6_sell(is_short=is_short)
     assert log_has_re(r"Trying to refind Order\(.*", caplog)
     assert mock_fo.call_count == 1
     assert mock_uts.call_count == 1
@@ -4234,7 +4616,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog):
     # Test error case
     mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
                            side_effect=ExchangeError())
-    order = mock_order_5_stoploss()
+    order = mock_order_5_stoploss(is_short=is_short)
 
     freqtrade.refind_lost_order(trades[4])
     assert log_has(f"Error updating {order['id']}.", caplog)
@@ -4280,20 +4662,25 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
     assert valid_price_at_min_alwd < proposed_price
 
 
+def test_leverage_prep():
+    # TODO-lev
+    return
+
+
 @pytest.mark.parametrize('trading_mode,calls,t1,t2', [
-    (TradingMode.SPOT, 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
-    (TradingMode.MARGIN, 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
-    (TradingMode.FUTURES, 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
-    (TradingMode.FUTURES, 32, "2021-09-01 00:00:00", "2021-09-01 08:00:01"),
-    (TradingMode.FUTURES, 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
-    (TradingMode.FUTURES, 33, "2021-09-01 00:00:00", "2021-09-01 08:00:02"),
-    (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
-    (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
-    (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
-    (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
-    (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
-    (TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
-    (TradingMode.FUTURES, 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
+    ('spot', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
+    ('margin', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
+    ('futures', 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
+    ('futures', 32, "2021-09-01 00:00:00", "2021-09-01 08:00:01"),
+    ('futures', 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
+    ('futures', 33, "2021-09-01 00:00:00", "2021-09-01 08:00:02"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
+    ('futures', 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
 ])
 def test_update_funding_fees(mocker, default_conf, trading_mode, calls, time_machine,
                              t1, t2):
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
index 7128fcd89..6f9bd6555 100644
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -1514,11 +1514,12 @@ def test_adjust_min_max_rates(fee):
 
 @pytest.mark.usefixtures("init_persistence")
 @pytest.mark.parametrize('use_db', [True, False])
-def test_get_open(fee, use_db):
+@pytest.mark.parametrize('is_short', [True, False])
+def test_get_open(fee, is_short, use_db):
     Trade.use_db = use_db
     Trade.reset_trades()
 
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, is_short, use_db)
     assert len(Trade.get_open_trades()) == 4
 
     Trade.use_db = True
@@ -1874,14 +1875,15 @@ def test_fee_updated(fee):
 
 
 @pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize('is_short', [True, False])
 @pytest.mark.parametrize('use_db', [True, False])
-def test_total_open_trades_stakes(fee, use_db):
+def test_total_open_trades_stakes(fee, is_short, use_db):
 
     Trade.use_db = use_db
     Trade.reset_trades()
     res = Trade.total_open_trades_stakes()
     assert res == 0
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, is_short, use_db)
     res = Trade.total_open_trades_stakes()
     assert res == 0.004
 
@@ -1889,6 +1891,7 @@ def test_total_open_trades_stakes(fee, use_db):
 
 
 @pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
 @pytest.mark.parametrize('use_db', [True, False])
 def test_get_total_closed_profit(fee, use_db):
 
@@ -1896,7 +1899,7 @@ def test_get_total_closed_profit(fee, use_db):
     Trade.reset_trades()
     res = Trade.get_total_closed_profit()
     assert res == 0
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, False, use_db)
     res = Trade.get_total_closed_profit()
     assert res == 0.000739127
 
@@ -1904,11 +1907,12 @@ def test_get_total_closed_profit(fee, use_db):
 
 
 @pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
 @pytest.mark.parametrize('use_db', [True, False])
 def test_get_trades_proxy(fee, use_db):
     Trade.use_db = use_db
     Trade.reset_trades()
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, False, use_db)
     trades = Trade.get_trades_proxy()
     assert len(trades) == 6
 
@@ -1937,9 +1941,10 @@ def test_get_trades_backtest():
 
 
 @pytest.mark.usefixtures("init_persistence")
+# @pytest.mark.parametrize('is_short', [True, False])
 def test_get_overall_performance(fee):
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, False)
     res = Trade.get_overall_performance()
 
     assert len(res) == 2
@@ -1949,12 +1954,13 @@ def test_get_overall_performance(fee):
 
 
 @pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
 def test_get_best_pair(fee):
 
     res = Trade.get_best_pair()
     assert res is None
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, False)
     res = Trade.get_best_pair()
     assert len(res) == 2
     assert res[0] == 'XRP/BTC'
@@ -2036,8 +2042,9 @@ def test_update_order_from_ccxt(caplog):
 
 
 @pytest.mark.usefixtures("init_persistence")
+# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
 def test_select_order(fee):
-    create_mock_trades(fee)
+    create_mock_trades(fee, False)
 
     trades = Trade.get_trades().all()