Merge branch 'develop' into main_refactoring
This commit is contained in:
		| @@ -68,9 +68,9 @@ For any other type of installation please refer to [Installation doc](https://ww | |||||||
| ### Bot commands | ### Bot commands | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] | usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH] | ||||||
|                  [--strategy-path PATH] [--dynamic-whitelist [INT]] |                  [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]] | ||||||
|                  [--db-url PATH] |                  [--db-url PATH] [--sd-notify] | ||||||
|                  {backtesting,edge,hyperopt} ... |                  {backtesting,edge,hyperopt} ... | ||||||
|  |  | ||||||
| Free, open source crypto trading bot | Free, open source crypto trading bot | ||||||
| @@ -84,6 +84,7 @@ positional arguments: | |||||||
| optional arguments: | optional arguments: | ||||||
|   -h, --help            show this help message and exit |   -h, --help            show this help message and exit | ||||||
|   -v, --verbose         Verbose mode (-vv for more, -vvv to get all messages). |   -v, --verbose         Verbose mode (-vv for more, -vvv to get all messages). | ||||||
|  |   --logfile FILE        Log to the file specified | ||||||
|   --version             show program's version number and exit |   --version             show program's version number and exit | ||||||
|   -c PATH, --config PATH |   -c PATH, --config PATH | ||||||
|                         Specify configuration file (default: None). Multiple |                         Specify configuration file (default: None). Multiple | ||||||
| @@ -100,6 +101,7 @@ optional arguments: | |||||||
|   --db-url PATH         Override trades database URL, this is useful if |   --db-url PATH         Override trades database URL, this is useful if | ||||||
|                         dry_run is enabled or in custom deployments (default: |                         dry_run is enabled or in custom deployments (default: | ||||||
|                         None). |                         None). | ||||||
|  |   --sd-notify           Notify systemd service manager. | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### Telegram RPC commands | ### Telegram RPC commands | ||||||
|   | |||||||
| @@ -39,12 +39,12 @@ | |||||||
|         "buy": "limit", |         "buy": "limit", | ||||||
|         "sell": "limit", |         "sell": "limit", | ||||||
|         "stoploss": "market", |         "stoploss": "market", | ||||||
|         "stoploss_on_exchange": "false", |         "stoploss_on_exchange": false, | ||||||
|         "stoploss_on_exchange_interval": 60 |         "stoploss_on_exchange_interval": 60 | ||||||
|     }, |     }, | ||||||
|     "order_time_in_force": { |     "order_time_in_force": { | ||||||
|         "buy": "gtc", |         "buy": "gtc", | ||||||
|         "sell": "gtc", |         "sell": "gtc" | ||||||
|     }, |     }, | ||||||
|     "pairlist": { |     "pairlist": { | ||||||
|         "method": "VolumePairList", |         "method": "VolumePairList", | ||||||
|   | |||||||
| @@ -6,9 +6,9 @@ This page explains the different parameters of the bot and how to run it. | |||||||
| ## Bot commands | ## Bot commands | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] | usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH] | ||||||
|                  [--strategy-path PATH] [--dynamic-whitelist [INT]] |                  [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]] | ||||||
|                  [--db-url PATH] |                  [--db-url PATH] [--sd-notify] | ||||||
|                  {backtesting,edge,hyperopt} ... |                  {backtesting,edge,hyperopt} ... | ||||||
|  |  | ||||||
| Free, open source crypto trading bot | Free, open source crypto trading bot | ||||||
| @@ -22,6 +22,7 @@ positional arguments: | |||||||
| optional arguments: | optional arguments: | ||||||
|   -h, --help            show this help message and exit |   -h, --help            show this help message and exit | ||||||
|   -v, --verbose         Verbose mode (-vv for more, -vvv to get all messages). |   -v, --verbose         Verbose mode (-vv for more, -vvv to get all messages). | ||||||
|  |   --logfile FILE        Log to the file specified | ||||||
|   --version             show program's version number and exit |   --version             show program's version number and exit | ||||||
|   -c PATH, --config PATH |   -c PATH, --config PATH | ||||||
|                         Specify configuration file (default: None). Multiple |                         Specify configuration file (default: None). Multiple | ||||||
| @@ -78,7 +79,7 @@ prevent unintended disclosure of sensitive private data when you publish example | |||||||
| of your configuration in the project issues or in the Internet. | of your configuration in the project issues or in the Internet. | ||||||
|  |  | ||||||
| See more details on this technique with examples in the documentation page on | See more details on this technique with examples in the documentation page on | ||||||
| [configuration](bot-configuration.md). | [configuration](configuration.md). | ||||||
|  |  | ||||||
| ### How to use **--strategy**? | ### How to use **--strategy**? | ||||||
|  |  | ||||||
|   | |||||||
| @@ -70,6 +70,7 @@ Mandatory Parameters are marked as **Required**. | |||||||
| | `strategy` | DefaultStrategy | Defines Strategy class to use. | | `strategy` | DefaultStrategy | Defines Strategy class to use. | ||||||
| | `strategy_path` | null | Adds an additional strategy lookup path (must be a folder). | | `strategy_path` | null | Adds an additional strategy lookup path (must be a folder). | ||||||
| | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. | | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. | ||||||
|  | | `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. | ||||||
| | `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. | | `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. | ||||||
|  |  | ||||||
| ### Parameters in the strategy | ### Parameters in the strategy | ||||||
|   | |||||||
| @@ -65,16 +65,14 @@ Once all positions are sold, run `/stop` to completely stop the bot. | |||||||
|  |  | ||||||
| For each open trade, the bot will send you the following message. | For each open trade, the bot will send you the following message. | ||||||
|  |  | ||||||
| > **Trade ID:** `123` | > **Trade ID:** `123` `(since 1 days ago)`   | ||||||
| > **Current Pair:** CVC/BTC   | > **Current Pair:** CVC/BTC   | ||||||
| > **Open Since:** `1 days ago`   | > **Open Since:** `1 days ago`   | ||||||
| > **Amount:** `26.64180098`   | > **Amount:** `26.64180098`   | ||||||
| > **Open Rate:** `0.00007489`   | > **Open Rate:** `0.00007489`   | ||||||
| > **Close Rate:** `None` |  | ||||||
| > **Current Rate:** `0.00007489`   | > **Current Rate:** `0.00007489`   | ||||||
| > **Close Profit:** `None` |  | ||||||
| > **Current Profit:** `12.95%`   | > **Current Profit:** `12.95%`   | ||||||
| > **Open Order:** `None` | > **Stoploss:** `0.00007389 (-0.02%)`   | ||||||
|  |  | ||||||
| ### /status table | ### /status table | ||||||
|  |  | ||||||
|   | |||||||
| @@ -71,6 +71,13 @@ class Arguments(object): | |||||||
|             dest='loglevel', |             dest='loglevel', | ||||||
|             default=0, |             default=0, | ||||||
|         ) |         ) | ||||||
|  |         self.parser.add_argument( | ||||||
|  |             '--logfile', | ||||||
|  |             help='Log to the file specified', | ||||||
|  |             dest='logfile', | ||||||
|  |             type=str, | ||||||
|  |             metavar='FILE' | ||||||
|  |         ) | ||||||
|         self.parser.add_argument( |         self.parser.add_argument( | ||||||
|             '--version', |             '--version', | ||||||
|             action='version', |             action='version', | ||||||
|   | |||||||
| @@ -4,16 +4,18 @@ This module contains the configuration class | |||||||
| import json | import json | ||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
|  | import sys | ||||||
| from argparse import Namespace | from argparse import Namespace | ||||||
| from typing import Any, Dict, Optional | from logging.handlers import RotatingFileHandler | ||||||
|  | from typing import Any, Dict, List, Optional | ||||||
|  |  | ||||||
| import ccxt | import ccxt | ||||||
| from jsonschema import Draft4Validator, validate | from jsonschema import Draft4Validator, validate | ||||||
| from jsonschema.exceptions import ValidationError, best_match | from jsonschema.exceptions import ValidationError, best_match | ||||||
|  |  | ||||||
| from freqtrade import OperationalException, constants | from freqtrade import OperationalException, constants | ||||||
| from freqtrade.state import RunMode |  | ||||||
| from freqtrade.misc import deep_merge_dicts | from freqtrade.misc import deep_merge_dicts | ||||||
|  | from freqtrade.state import RunMode | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -116,9 +118,22 @@ class Configuration(object): | |||||||
|             config.update({'verbosity': self.args.loglevel}) |             config.update({'verbosity': self.args.loglevel}) | ||||||
|         else: |         else: | ||||||
|             config.update({'verbosity': 0}) |             config.update({'verbosity': 0}) | ||||||
|  |  | ||||||
|  |         # Log to stdout, not stderr | ||||||
|  |         log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)] | ||||||
|  |         if 'logfile' in self.args and self.args.logfile: | ||||||
|  |             config.update({'logfile': self.args.logfile}) | ||||||
|  |  | ||||||
|  |         # Allow setting this as either configuration or argument | ||||||
|  |         if 'logfile' in config: | ||||||
|  |             log_handlers.append(RotatingFileHandler(config['logfile'], | ||||||
|  |                                                     maxBytes=1024 * 1024,  # 1Mb | ||||||
|  |                                                     backupCount=10)) | ||||||
|  |  | ||||||
|         logging.basicConfig( |         logging.basicConfig( | ||||||
|             level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG, |             level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG, | ||||||
|             format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |             format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | ||||||
|  |             handlers=log_handlers | ||||||
|         ) |         ) | ||||||
|         set_loggers(config['verbosity']) |         set_loggers(config['verbosity']) | ||||||
|         logger.info('Verbosity set to %s', config['verbosity']) |         logger.info('Verbosity set to %s', config['verbosity']) | ||||||
|   | |||||||
| @@ -367,7 +367,6 @@ class FreqtradeBot(object): | |||||||
|             stake_amount = order['cost'] |             stake_amount = order['cost'] | ||||||
|             amount = order['amount'] |             amount = order['amount'] | ||||||
|             buy_limit_filled_price = order['price'] |             buy_limit_filled_price = order['price'] | ||||||
|             order_id = None |  | ||||||
|  |  | ||||||
|         self.rpc.send_msg({ |         self.rpc.send_msg({ | ||||||
|             'type': RPCMessageType.BUY_NOTIFICATION, |             'type': RPCMessageType.BUY_NOTIFICATION, | ||||||
| @@ -396,6 +395,10 @@ class FreqtradeBot(object): | |||||||
|             ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] |             ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |         # Update fees if order is closed | ||||||
|  |         if order_status == 'closed': | ||||||
|  |             self.update_trade_state(trade, order) | ||||||
|  |  | ||||||
|         Trade.session.add(trade) |         Trade.session.add(trade) | ||||||
|         Trade.session.flush() |         Trade.session.flush() | ||||||
|  |  | ||||||
| @@ -426,24 +429,7 @@ class FreqtradeBot(object): | |||||||
|         :return: True if executed |         :return: True if executed | ||||||
|         """ |         """ | ||||||
|         try: |         try: | ||||||
|             # Get order details for actual price per unit |             self.update_trade_state(trade) | ||||||
|             if trade.open_order_id: |  | ||||||
|                 # Update trade with order values |  | ||||||
|                 logger.info('Found open order for %s', trade) |  | ||||||
|                 order = self.exchange.get_order(trade.open_order_id, trade.pair) |  | ||||||
|                 # Try update amount (binance-fix) |  | ||||||
|                 try: |  | ||||||
|                     new_amount = self.get_real_amount(trade, order) |  | ||||||
|                     if order['amount'] != new_amount: |  | ||||||
|                         order['amount'] = new_amount |  | ||||||
|                         # Fee was applied, so set to 0 |  | ||||||
|                         trade.fee_open = 0 |  | ||||||
|  |  | ||||||
|                 except OperationalException as exception: |  | ||||||
|                     logger.warning("Could not update trade amount: %s", exception) |  | ||||||
|  |  | ||||||
|                 # This handles both buy and sell orders! |  | ||||||
|                 trade.update(order) |  | ||||||
|  |  | ||||||
|             if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: |             if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: | ||||||
|                 result = self.handle_stoploss_on_exchange(trade) |                 result = self.handle_stoploss_on_exchange(trade) | ||||||
| @@ -508,6 +494,28 @@ class FreqtradeBot(object): | |||||||
|                         f"(from {order_amount} to {real_amount}) from Trades") |                         f"(from {order_amount} to {real_amount}) from Trades") | ||||||
|         return real_amount |         return real_amount | ||||||
|  |  | ||||||
|  |     def update_trade_state(self, trade, action_order: dict = None): | ||||||
|  |         """ | ||||||
|  |         Checks trades with open orders and updates the amount if necessary | ||||||
|  |         """ | ||||||
|  |         # Get order details for actual price per unit | ||||||
|  |         if trade.open_order_id: | ||||||
|  |             # Update trade with order values | ||||||
|  |             logger.info('Found open order for %s', trade) | ||||||
|  |             order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair) | ||||||
|  |             # Try update amount (binance-fix) | ||||||
|  |             try: | ||||||
|  |                 new_amount = self.get_real_amount(trade, order) | ||||||
|  |                 if order['amount'] != new_amount: | ||||||
|  |                     order['amount'] = new_amount | ||||||
|  |                     # Fee was applied, so set to 0 | ||||||
|  |                     trade.fee_open = 0 | ||||||
|  |  | ||||||
|  |             except OperationalException as exception: | ||||||
|  |                 logger.warning("Could not update trade amount: %s", exception) | ||||||
|  |  | ||||||
|  |             trade.update(order) | ||||||
|  |  | ||||||
|     def get_sell_rate(self, pair: str, refresh: bool) -> float: |     def get_sell_rate(self, pair: str, refresh: bool) -> float: | ||||||
|         """ |         """ | ||||||
|         Get sell rate - either using get-ticker bid or first bid based on orderbook |         Get sell rate - either using get-ticker bid or first bid based on orderbook | ||||||
| @@ -578,7 +586,7 @@ class FreqtradeBot(object): | |||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         result = False |         result = False | ||||||
|  |         try: | ||||||
|             # If trade is open and the buy order is fulfilled but there is no stoploss, |             # If trade is open and the buy order is fulfilled but there is no stoploss, | ||||||
|             # then we add a stoploss on exchange |             # then we add a stoploss on exchange | ||||||
|             if not trade.open_order_id and not trade.stoploss_order_id: |             if not trade.open_order_id and not trade.stoploss_order_id: | ||||||
| @@ -614,7 +622,8 @@ class FreqtradeBot(object): | |||||||
|                     # in which case we cancel stoploss order and put another one with new |                     # in which case we cancel stoploss order and put another one with new | ||||||
|                     # value immediately |                     # value immediately | ||||||
|                     self.handle_trailing_stoploss_on_exchange(trade, order) |                     self.handle_trailing_stoploss_on_exchange(trade, order) | ||||||
|  |         except DependencyException as exception: | ||||||
|  |             logger.warning('Unable to create stoploss order: %s', exception) | ||||||
|         return result |         return result | ||||||
|  |  | ||||||
|     def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): |     def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ def check_migrate(engine) -> None: | |||||||
|         logger.debug(f'trying {table_back_name}') |         logger.debug(f'trying {table_back_name}') | ||||||
|  |  | ||||||
|     # Check for latest column |     # Check for latest column | ||||||
|     if not has_column(cols, 'stoploss_last_update'): |     if not has_column(cols, 'stop_loss_pct'): | ||||||
|         logger.info(f'Running database migration - backup available as {table_back_name}') |         logger.info(f'Running database migration - backup available as {table_back_name}') | ||||||
|  |  | ||||||
|         fee_open = get_column_def(cols, 'fee_open', 'fee') |         fee_open = get_column_def(cols, 'fee_open', 'fee') | ||||||
| @@ -91,10 +91,13 @@ def check_migrate(engine) -> None: | |||||||
|         open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null') |         open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null') | ||||||
|         close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null') |         close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null') | ||||||
|         stop_loss = get_column_def(cols, 'stop_loss', '0.0') |         stop_loss = get_column_def(cols, 'stop_loss', '0.0') | ||||||
|  |         stop_loss_pct = get_column_def(cols, 'stop_loss_pct', 'null') | ||||||
|         initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') |         initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') | ||||||
|  |         initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null') | ||||||
|         stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') |         stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') | ||||||
|         stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') |         stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') | ||||||
|         max_rate = get_column_def(cols, 'max_rate', '0.0') |         max_rate = get_column_def(cols, 'max_rate', '0.0') | ||||||
|  |         min_rate = get_column_def(cols, 'min_rate', 'null') | ||||||
|         sell_reason = get_column_def(cols, 'sell_reason', 'null') |         sell_reason = get_column_def(cols, 'sell_reason', 'null') | ||||||
|         strategy = get_column_def(cols, 'strategy', 'null') |         strategy = get_column_def(cols, 'strategy', 'null') | ||||||
|         ticker_interval = get_column_def(cols, 'ticker_interval', 'null') |         ticker_interval = get_column_def(cols, 'ticker_interval', 'null') | ||||||
| @@ -112,8 +115,9 @@ def check_migrate(engine) -> None: | |||||||
|                 (id, exchange, pair, is_open, fee_open, fee_close, open_rate, |                 (id, exchange, pair, is_open, fee_open, fee_close, open_rate, | ||||||
|                 open_rate_requested, close_rate, close_rate_requested, close_profit, |                 open_rate_requested, close_rate, close_rate_requested, close_profit, | ||||||
|                 stake_amount, amount, open_date, close_date, open_order_id, |                 stake_amount, amount, open_date, close_date, open_order_id, | ||||||
|                 stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update, |                 stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, | ||||||
|                 max_rate, sell_reason, strategy, |                 stoploss_order_id, stoploss_last_update, | ||||||
|  |                 max_rate, min_rate, sell_reason, strategy, | ||||||
|                 ticker_interval |                 ticker_interval | ||||||
|                 ) |                 ) | ||||||
|             select id, lower(exchange), |             select id, lower(exchange), | ||||||
| @@ -128,9 +132,11 @@ def check_migrate(engine) -> None: | |||||||
|                 open_rate, {open_rate_requested} open_rate_requested, close_rate, |                 open_rate, {open_rate_requested} open_rate_requested, close_rate, | ||||||
|                 {close_rate_requested} close_rate_requested, close_profit, |                 {close_rate_requested} close_rate_requested, close_profit, | ||||||
|                 stake_amount, amount, open_date, close_date, open_order_id, |                 stake_amount, amount, open_date, close_date, open_order_id, | ||||||
|                 {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, |                 {stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct, | ||||||
|  |                 {initial_stop_loss} initial_stop_loss, | ||||||
|  |                 {initial_stop_loss_pct} initial_stop_loss_pct, | ||||||
|                 {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, |                 {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, | ||||||
|                 {max_rate} max_rate, {sell_reason} sell_reason, |                 {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, | ||||||
|                 {strategy} strategy, {ticker_interval} ticker_interval |                 {strategy} strategy, {ticker_interval} ticker_interval | ||||||
|                 from {table_back_name} |                 from {table_back_name} | ||||||
|              """) |              """) | ||||||
| @@ -183,14 +189,20 @@ class Trade(_DECL_BASE): | |||||||
|     open_order_id = Column(String) |     open_order_id = Column(String) | ||||||
|     # absolute value of the stop loss |     # absolute value of the stop loss | ||||||
|     stop_loss = Column(Float, nullable=True, default=0.0) |     stop_loss = Column(Float, nullable=True, default=0.0) | ||||||
|  |     # percentage value of the stop loss | ||||||
|  |     stop_loss_pct = Column(Float, nullable=True) | ||||||
|     # absolute value of the initial stop loss |     # absolute value of the initial stop loss | ||||||
|     initial_stop_loss = Column(Float, nullable=True, default=0.0) |     initial_stop_loss = Column(Float, nullable=True, default=0.0) | ||||||
|  |     # percentage value of the initial stop loss | ||||||
|  |     initial_stop_loss_pct = Column(Float, nullable=True) | ||||||
|     # stoploss order id which is on exchange |     # stoploss order id which is on exchange | ||||||
|     stoploss_order_id = Column(String, nullable=True, index=True) |     stoploss_order_id = Column(String, nullable=True, index=True) | ||||||
|     # last update time of the stoploss order on exchange |     # last update time of the stoploss order on exchange | ||||||
|     stoploss_last_update = Column(DateTime, nullable=True) |     stoploss_last_update = Column(DateTime, nullable=True) | ||||||
|     # absolute value of the highest reached price |     # absolute value of the highest reached price | ||||||
|     max_rate = Column(Float, nullable=True, default=0.0) |     max_rate = Column(Float, nullable=True, default=0.0) | ||||||
|  |     # Lowest price reached | ||||||
|  |     min_rate = Column(Float, nullable=True) | ||||||
|     sell_reason = Column(String, nullable=True) |     sell_reason = Column(String, nullable=True) | ||||||
|     strategy = Column(String, nullable=True) |     strategy = Column(String, nullable=True) | ||||||
|     ticker_interval = Column(Integer, nullable=True) |     ticker_interval = Column(Integer, nullable=True) | ||||||
| @@ -201,8 +213,22 @@ class Trade(_DECL_BASE): | |||||||
|         return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' |         return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' | ||||||
|                 f'open_rate={self.open_rate:.8f}, open_since={open_since})') |                 f'open_rate={self.open_rate:.8f}, open_since={open_since})') | ||||||
|  |  | ||||||
|  |     def adjust_min_max_rates(self, current_price: float): | ||||||
|  |         """ | ||||||
|  |         Adjust the max_rate and min_rate. | ||||||
|  |         """ | ||||||
|  |         logger.debug("Adjusting min/max rates") | ||||||
|  |         self.max_rate = max(current_price, self.max_rate or self.open_rate) | ||||||
|  |         self.min_rate = min(current_price, self.min_rate or self.open_rate) | ||||||
|  |  | ||||||
|     def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): |     def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): | ||||||
|         """this adjusts the stop loss to it's most recently observed setting""" |         """ | ||||||
|  |         This adjusts the stop loss to it's most recently observed setting | ||||||
|  |         :param current_price: Current rate the asset is traded | ||||||
|  |         :param stoploss: Stoploss as factor (sample -0.05 -> -5% below current price). | ||||||
|  |         :param initial: Called to initiate stop_loss. | ||||||
|  |             Skips everything if self.stop_loss is already set. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|         if initial and not (self.stop_loss is None or self.stop_loss == 0): |         if initial and not (self.stop_loss is None or self.stop_loss == 0): | ||||||
|             # Don't modify if called with initial and nothing to do |             # Don't modify if called with initial and nothing to do | ||||||
| @@ -210,24 +236,20 @@ class Trade(_DECL_BASE): | |||||||
|  |  | ||||||
|         new_loss = float(current_price * (1 - abs(stoploss))) |         new_loss = float(current_price * (1 - abs(stoploss))) | ||||||
|  |  | ||||||
|         # keeping track of the highest observed rate for this trade |  | ||||||
|         if self.max_rate is None: |  | ||||||
|             self.max_rate = current_price |  | ||||||
|         else: |  | ||||||
|             if current_price > self.max_rate: |  | ||||||
|                 self.max_rate = current_price |  | ||||||
|  |  | ||||||
|         # no stop loss assigned yet |         # no stop loss assigned yet | ||||||
|         if not self.stop_loss: |         if not self.stop_loss: | ||||||
|             logger.debug("assigning new stop loss") |             logger.debug("assigning new stop loss") | ||||||
|             self.stop_loss = new_loss |             self.stop_loss = new_loss | ||||||
|  |             self.stop_loss_pct = -1 * abs(stoploss) | ||||||
|             self.initial_stop_loss = new_loss |             self.initial_stop_loss = new_loss | ||||||
|  |             self.initial_stop_loss_pct = -1 * abs(stoploss) | ||||||
|             self.stoploss_last_update = datetime.utcnow() |             self.stoploss_last_update = datetime.utcnow() | ||||||
|  |  | ||||||
|         # evaluate if the stop loss needs to be updated |         # evaluate if the stop loss needs to be updated | ||||||
|         else: |         else: | ||||||
|             if new_loss > self.stop_loss:  # stop losses only walk up, never down! |             if new_loss > self.stop_loss:  # stop losses only walk up, never down! | ||||||
|                 self.stop_loss = new_loss |                 self.stop_loss = new_loss | ||||||
|  |                 self.stop_loss_pct = -1 * abs(stoploss) | ||||||
|                 self.stoploss_last_update = datetime.utcnow() |                 self.stoploss_last_update = datetime.utcnow() | ||||||
|                 logger.debug("adjusted stop loss") |                 logger.debug("adjusted stop loss") | ||||||
|             else: |             else: | ||||||
|   | |||||||
| @@ -110,6 +110,10 @@ class RPC(object): | |||||||
|                     amount=round(trade.amount, 8), |                     amount=round(trade.amount, 8), | ||||||
|                     close_profit=fmt_close_profit, |                     close_profit=fmt_close_profit, | ||||||
|                     current_profit=round(current_profit * 100, 2), |                     current_profit=round(current_profit * 100, 2), | ||||||
|  |                     stop_loss=trade.stop_loss, | ||||||
|  |                     stop_loss_pct=trade.stop_loss_pct, | ||||||
|  |                     initial_stop_loss=trade.initial_stop_loss, | ||||||
|  |                     initial_stop_loss_pct=trade.initial_stop_loss_pct, | ||||||
|                     open_order='({} {} rem={:.8f})'.format( |                     open_order='({} {} rem={:.8f})'.format( | ||||||
|                       order['type'], order['side'], order['remaining'] |                       order['type'], order['side'], order['remaining'] | ||||||
|                     ) if order else None, |                     ) if order else None, | ||||||
|   | |||||||
| @@ -194,21 +194,34 @@ class Telegram(RPC): | |||||||
|             for result in results: |             for result in results: | ||||||
|                 result['date'] = result['date'].humanize() |                 result['date'] = result['date'].humanize() | ||||||
|  |  | ||||||
|             messages = [ |             messages = [] | ||||||
|                 "*Trade ID:* `{trade_id}`\n" |             for r in results: | ||||||
|                 "*Current Pair:* {pair}\n" |                 lines = [ | ||||||
|                 "*Open Since:* `{date}`\n" |                     "*Trade ID:* `{trade_id}` `(since {date})`", | ||||||
|                 "*Amount:* `{amount}`\n" |                     "*Current Pair:* {pair}", | ||||||
|                 "*Open Rate:* `{open_rate:.8f}`\n" |                     "*Amount:* `{amount}`", | ||||||
|                 "*Close Rate:* `{close_rate}`\n" |                     "*Open Rate:* `{open_rate:.8f}`", | ||||||
|                 "*Current Rate:* `{current_rate:.8f}`\n" |                     "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", | ||||||
|                 "*Close Profit:* `{close_profit}`\n" |                     "*Current Rate:* `{current_rate:.8f}`", | ||||||
|                 "*Current Profit:* `{current_profit:.2f}%`\n" |                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", | ||||||
|                 "*Open Order:* `{open_order}`".format(**result) |                     "*Current Profit:* `{current_profit:.2f}%`", | ||||||
|                 for result in results |  | ||||||
|  |                     # Adding initial stoploss only if it is different from stoploss | ||||||
|  |                     "*Initial Stoploss:* `{initial_stop_loss:.8f}` " + | ||||||
|  |                     ("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "") | ||||||
|  |                     if r['stop_loss'] != r['initial_stop_loss'] else "", | ||||||
|  |  | ||||||
|  |                     # Adding stoploss and stoploss percentage only if it is not None | ||||||
|  |                     "*Stoploss:* `{stop_loss:.8f}` " + | ||||||
|  |                     ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""), | ||||||
|  |  | ||||||
|  |                     "*Open Order:* `{open_order}`" if r['open_order'] else "" | ||||||
|                 ] |                 ] | ||||||
|  |                 messages.append("\n".join(filter(None, lines)).format(**r)) | ||||||
|  |  | ||||||
|             for msg in messages: |             for msg in messages: | ||||||
|                 self._send_msg(msg, bot=bot) |                 self._send_msg(msg, bot=bot) | ||||||
|  |  | ||||||
|         except RPCException as e: |         except RPCException as e: | ||||||
|             self._send_msg(str(e), bot=bot) |             self._send_msg(str(e), bot=bot) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -247,6 +247,9 @@ class IStrategy(ABC): | |||||||
|         """ |         """ | ||||||
|         This function evaluate if on the condition required to trigger a sell has been reached |         This function evaluate if on the condition required to trigger a sell has been reached | ||||||
|         if the threshold is reached and updates the trade record. |         if the threshold is reached and updates the trade record. | ||||||
|  |         :param low: Only used during backtesting to simulate stoploss | ||||||
|  |         :param high: Only used during backtesting, to simulate ROI | ||||||
|  |         :param force_stoploss: Externally provided stoploss | ||||||
|         :return: True if trade should be sold, False otherwise |         :return: True if trade should be sold, False otherwise | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
| @@ -254,14 +257,16 @@ class IStrategy(ABC): | |||||||
|         current_rate = low or rate |         current_rate = low or rate | ||||||
|         current_profit = trade.calc_profit_percent(current_rate) |         current_profit = trade.calc_profit_percent(current_rate) | ||||||
|  |  | ||||||
|  |         trade.adjust_min_max_rates(high or current_rate) | ||||||
|  |  | ||||||
|         stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, |         stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, | ||||||
|                                               current_time=date, current_profit=current_profit, |                                               current_time=date, current_profit=current_profit, | ||||||
|                                               force_stoploss=force_stoploss) |                                               force_stoploss=force_stoploss, high=high) | ||||||
|  |  | ||||||
|         if stoplossflag.sell_flag: |         if stoplossflag.sell_flag: | ||||||
|             return stoplossflag |             return stoplossflag | ||||||
|  |  | ||||||
|         # Set current rate to low for backtesting sell |         # Set current rate to high for backtesting sell | ||||||
|         current_rate = high or rate |         current_rate = high or rate | ||||||
|         current_profit = trade.calc_profit_percent(current_rate) |         current_profit = trade.calc_profit_percent(current_rate) | ||||||
|         experimental = self.config.get('experimental', {}) |         experimental = self.config.get('experimental', {}) | ||||||
| @@ -285,8 +290,9 @@ class IStrategy(ABC): | |||||||
|  |  | ||||||
|         return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) |         return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) | ||||||
|  |  | ||||||
|     def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, |     def stop_loss_reached(self, current_rate: float, trade: Trade, | ||||||
|                           current_profit: float, force_stoploss: float) -> SellCheckTuple: |                           current_time: datetime, current_profit: float, | ||||||
|  |                           force_stoploss: float, high: float = None) -> SellCheckTuple: | ||||||
|         """ |         """ | ||||||
|         Based on current profit of the trade and configured (trailing) stoploss, |         Based on current profit of the trade and configured (trailing) stoploss, | ||||||
|         decides to sell or not |         decides to sell or not | ||||||
| @@ -294,13 +300,33 @@ class IStrategy(ABC): | |||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         trailing_stop = self.config.get('trailing_stop', False) |         trailing_stop = self.config.get('trailing_stop', False) | ||||||
|         trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss |         stop_loss_value = force_stoploss if force_stoploss else self.stoploss | ||||||
|                                else self.stoploss, initial=True) |  | ||||||
|  |         # Initiate stoploss with open_rate. Does nothing if stoploss is already set. | ||||||
|  |         trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) | ||||||
|  |  | ||||||
|  |         if trailing_stop: | ||||||
|  |             # trailing stoploss handling | ||||||
|  |  | ||||||
|  |             sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 | ||||||
|  |             tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) | ||||||
|  |  | ||||||
|  |             # Don't update stoploss if trailing_only_offset_is_reached is true. | ||||||
|  |             if not (tsl_only_offset and current_profit < sl_offset): | ||||||
|  |                 # Specific handling for trailing_stop_positive | ||||||
|  |                 if 'trailing_stop_positive' in self.config and current_profit > sl_offset: | ||||||
|  |                     # Ignore mypy error check in configuration that this is a float | ||||||
|  |                     stop_loss_value = self.config.get('trailing_stop_positive')  # type: ignore | ||||||
|  |                     logger.debug(f"using positive stop loss: {stop_loss_value} " | ||||||
|  |                                  f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") | ||||||
|  |  | ||||||
|  |                 trade.adjust_stop_loss(high or current_rate, stop_loss_value) | ||||||
|  |  | ||||||
|         # evaluate if the stoploss was hit if stoploss is not on exchange |         # evaluate if the stoploss was hit if stoploss is not on exchange | ||||||
|         if ((self.stoploss is not None) and |         if ((self.stoploss is not None) and | ||||||
|             (trade.stop_loss >= current_rate) and |             (trade.stop_loss >= current_rate) and | ||||||
|                 (not self.order_types.get('stoploss_on_exchange'))): |                 (not self.order_types.get('stoploss_on_exchange'))): | ||||||
|  |  | ||||||
|             selltype = SellType.STOP_LOSS |             selltype = SellType.STOP_LOSS | ||||||
|             # If Trailing stop (and max-rate did move above open rate) |             # If Trailing stop (and max-rate did move above open rate) | ||||||
|             if trailing_stop and trade.open_rate != trade.max_rate: |             if trailing_stop and trade.open_rate != trade.max_rate: | ||||||
| @@ -315,29 +341,6 @@ class IStrategy(ABC): | |||||||
|             logger.debug('Stop loss hit.') |             logger.debug('Stop loss hit.') | ||||||
|             return SellCheckTuple(sell_flag=True, sell_type=selltype) |             return SellCheckTuple(sell_flag=True, sell_type=selltype) | ||||||
|  |  | ||||||
|         # update the stop loss afterwards, after all by definition it's supposed to be hanging |  | ||||||
|         if trailing_stop: |  | ||||||
|  |  | ||||||
|             # check if we have a special stop loss for positive condition |  | ||||||
|             # and if profit is positive |  | ||||||
|             stop_loss_value = force_stoploss if force_stoploss else self.stoploss |  | ||||||
|  |  | ||||||
|             sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 |  | ||||||
|  |  | ||||||
|             if 'trailing_stop_positive' in self.config and current_profit > sl_offset: |  | ||||||
|  |  | ||||||
|                 # Ignore mypy error check in configuration that this is a float |  | ||||||
|                 stop_loss_value = self.config.get('trailing_stop_positive')  # type: ignore |  | ||||||
|                 logger.debug(f"using positive stop loss mode: {stop_loss_value} " |  | ||||||
|                              f"with offset {sl_offset:.4g} " |  | ||||||
|                              f"since we have profit {current_profit:.4f}%") |  | ||||||
|  |  | ||||||
|             # if trailing_only_offset_is_reached is true, |  | ||||||
|             # we update trailing stoploss only if offset is reached. |  | ||||||
|             tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) |  | ||||||
|             if not (tsl_only_offset and current_profit < sl_offset): |  | ||||||
|                 trade.adjust_stop_loss(current_rate, stop_loss_value) |  | ||||||
|  |  | ||||||
|         return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) |         return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) | ||||||
|  |  | ||||||
|     def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: |     def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import logging | |||||||
| import re | import re | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from functools import reduce | from functools import reduce | ||||||
| from typing import Dict, Optional |  | ||||||
| from unittest.mock import MagicMock, PropertyMock | from unittest.mock import MagicMock, PropertyMock | ||||||
|  |  | ||||||
| import arrow | import arrow | ||||||
| @@ -13,11 +12,11 @@ from telegram import Chat, Message, Update | |||||||
|  |  | ||||||
| from freqtrade import constants | from freqtrade import constants | ||||||
| from freqtrade.data.converter import parse_ticker_dataframe | from freqtrade.data.converter import parse_ticker_dataframe | ||||||
| from freqtrade.exchange import Exchange |  | ||||||
| from freqtrade.edge import Edge, PairInfo | from freqtrade.edge import Edge, PairInfo | ||||||
| from freqtrade.worker import Worker | from freqtrade.exchange import Exchange | ||||||
| from freqtrade.freqtradebot import FreqtradeBot | from freqtrade.freqtradebot import FreqtradeBot | ||||||
| from freqtrade.resolvers import ExchangeResolver | from freqtrade.resolvers import ExchangeResolver | ||||||
|  | from freqtrade.worker import Worker | ||||||
|  |  | ||||||
| logging.getLogger('').setLevel(logging.INFO) | logging.getLogger('').setLevel(logging.INFO) | ||||||
|  |  | ||||||
| @@ -96,7 +95,6 @@ def patch_freqtradebot(mocker, config) -> None: | |||||||
|     :param config: Config to pass to the bot |     :param config: Config to pass to the bot | ||||||
|     :return: None |     :return: None | ||||||
|     """ |     """ | ||||||
|     patch_coinmarketcap(mocker, {'price_usd': 12345.0}) |  | ||||||
|     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) |     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) | ||||||
|     mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) |     mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) | ||||||
|     patch_exchange(mocker, None) |     patch_exchange(mocker, None) | ||||||
| @@ -114,7 +112,8 @@ def get_patched_worker(mocker, config) -> Worker: | |||||||
|     return Worker(args=None, config=config) |     return Worker(args=None, config=config) | ||||||
|  |  | ||||||
|  |  | ||||||
| def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None: | @pytest.fixture(autouse=True) | ||||||
|  | def patch_coinmarketcap(mocker) -> None: | ||||||
|     """ |     """ | ||||||
|     Mocker to coinmarketcap to speed up tests |     Mocker to coinmarketcap to speed up tests | ||||||
|     :param mocker: mocker to patch coinmarketcap class |     :param mocker: mocker to patch coinmarketcap class | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ class BTContainer(NamedTuple): | |||||||
|     roi: float |     roi: float | ||||||
|     trades: List[BTrade] |     trades: List[BTrade] | ||||||
|     profit_perc: float |     profit_perc: float | ||||||
|  |     trailing_stop: bool = False | ||||||
|  |  | ||||||
|  |  | ||||||
| def _get_frame_time_from_offset(offset): | def _get_frame_time_from_offset(offset): | ||||||
|   | |||||||
| @@ -14,10 +14,10 @@ from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataf | |||||||
| from freqtrade.tests.conftest import patch_exchange | from freqtrade.tests.conftest import patch_exchange | ||||||
|  |  | ||||||
|  |  | ||||||
| # Test 0 Minus 8% Close | # Test 1 Minus 8% Close | ||||||
| # Test with Stop-loss at 1% | # Test with Stop-loss at 1% | ||||||
| # TC1: Stop-Loss Triggered 1% loss | # TC1: Stop-Loss Triggered 1% loss | ||||||
| tc0 = BTContainer(data=[ | tc1 = BTContainer(data=[ | ||||||
|     # D  O     H     L     C     V    B  S |     # D  O     H     L     C     V    B  S | ||||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], |     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||||
|     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) |     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) | ||||||
| @@ -30,10 +30,10 @@ tc0 = BTContainer(data=[ | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| # Test 1 Minus 4% Low, minus 1% close | # Test 2 Minus 4% Low, minus 1% close | ||||||
| # Test with Stop-Loss at 3% | # Test with Stop-Loss at 3% | ||||||
| # TC2: Stop-Loss Triggered 3% Loss | # TC2: Stop-Loss Triggered 3% Loss | ||||||
| tc1 = BTContainer(data=[ | tc2 = BTContainer(data=[ | ||||||
|     # D  O     H     L     C     V    B  S |     # D  O     H     L     C     V    B  S | ||||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], |     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||||
|     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) |     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) | ||||||
| @@ -49,11 +49,10 @@ tc1 = BTContainer(data=[ | |||||||
| # Test 3 Candle drops 4%, Recovers 1%. | # Test 3 Candle drops 4%, Recovers 1%. | ||||||
| #               Entry Criteria Met | #               Entry Criteria Met | ||||||
| # 	            Candle drops 20% | # 	            Candle drops 20% | ||||||
| # Candle Data for test 3 |  | ||||||
| # Test with Stop-Loss at 2% | # Test with Stop-Loss at 2% | ||||||
| # TC3: Trade-A: Stop-Loss Triggered 2% Loss | # TC3: Trade-A: Stop-Loss Triggered 2% Loss | ||||||
| #          Trade-B: Stop-Loss Triggered 2% Loss | #          Trade-B: Stop-Loss Triggered 2% Loss | ||||||
| tc2 = BTContainer(data=[ | tc3 = BTContainer(data=[ | ||||||
|     # D  O     H     L     C     V    B  S |     # D  O     H     L     C     V    B  S | ||||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], |     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||||
|     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) |     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) | ||||||
| @@ -71,7 +70,7 @@ tc2 = BTContainer(data=[ | |||||||
| # Candle Data for test 3 – Candle drops 3% Closed 15% up | # Candle Data for test 3 – Candle drops 3% Closed 15% up | ||||||
| # Test with Stop-loss at 2% ROI 6% | # Test with Stop-loss at 2% ROI 6% | ||||||
| # TC4: Stop-Loss Triggered 2% Loss | # TC4: Stop-Loss Triggered 2% Loss | ||||||
| tc3 = BTContainer(data=[ | tc4 = BTContainer(data=[ | ||||||
|     # D  O     H     L     C     V    B  S |     # D  O     H     L     C     V    B  S | ||||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], |     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||||
|     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) |     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) | ||||||
| @@ -83,10 +82,10 @@ tc3 = BTContainer(data=[ | |||||||
|     trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] |     trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] | ||||||
| ) | ) | ||||||
|  |  | ||||||
| # Test 4 / Drops 0.5% Closes +20% | # Test 5 / Drops 0.5% Closes +20% | ||||||
| # Set stop-loss at 1% ROI 3% | # Set stop-loss at 1% ROI 3% | ||||||
| # TC5: ROI triggers 3% Gain | # TC5: ROI triggers 3% Gain | ||||||
| tc4 = BTContainer(data=[ | tc5 = BTContainer(data=[ | ||||||
|     # D  O     H     L     C     V    B  S |     # D  O     H     L     C     V    B  S | ||||||
|     [0, 5000, 5025, 4980, 4987, 6172, 1, 0], |     [0, 5000, 5025, 4980, 4987, 6172, 1, 0], | ||||||
|     [1, 5000, 5025, 4980, 4987, 6172, 0, 0],  # enter trade (signal on last candle) |     [1, 5000, 5025, 4980, 4987, 6172, 0, 0],  # enter trade (signal on last candle) | ||||||
| @@ -99,10 +98,9 @@ tc4 = BTContainer(data=[ | |||||||
| ) | ) | ||||||
|  |  | ||||||
| # Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve | # Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve | ||||||
| # Candle Data for test 6 |  | ||||||
| # Set stop-loss at 2% ROI at 5% | # Set stop-loss at 2% ROI at 5% | ||||||
| # TC6: Stop-Loss triggers 2% Loss | # TC6: Stop-Loss triggers 2% Loss | ||||||
| tc5 = BTContainer(data=[ | tc6 = BTContainer(data=[ | ||||||
|     # D  O     H     L     C     V    B  S |     # D  O     H     L     C     V    B  S | ||||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], |     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||||
|     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) |     [1, 5000, 5025, 4975, 4987, 6172, 0, 0],  # enter trade (signal on last candle) | ||||||
| @@ -115,10 +113,9 @@ tc5 = BTContainer(data=[ | |||||||
| ) | ) | ||||||
|  |  | ||||||
| # Test 7 - 6% Positive / 1% Negative / Close 1% Positve | # Test 7 - 6% Positive / 1% Negative / Close 1% Positve | ||||||
| # Candle Data for test 7 |  | ||||||
| # Set stop-loss at 2% ROI at 3% | # Set stop-loss at 2% ROI at 3% | ||||||
| # TC7: ROI Triggers 3% Gain | # TC7: ROI Triggers 3% Gain | ||||||
| tc6 = BTContainer(data=[ | tc7 = BTContainer(data=[ | ||||||
|     # D  O     H     L     C     V    B  S |     # D  O     H     L     C     V    B  S | ||||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], |     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||||
|     [1, 5000, 5025, 4975, 4987, 6172, 0, 0], |     [1, 5000, 5025, 4975, 4987, 6172, 0, 0], | ||||||
| @@ -130,14 +127,47 @@ tc6 = BTContainer(data=[ | |||||||
|     trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] |     trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Test 8 - trailing_stop should raise so candle 3 causes a stoploss. | ||||||
|  | # Set stop-loss at 10%, ROI at 10% (should not apply) | ||||||
|  | # TC8: Trailing stoploss - stoploss should be adjusted candle 2 | ||||||
|  | tc8 = BTContainer(data=[ | ||||||
|  |     # D   O     H     L    C     V    B  S | ||||||
|  |     [0, 5000, 5050, 4950, 5000, 6172, 1, 0], | ||||||
|  |     [1, 5000, 5050, 4950, 5000, 6172, 0, 0], | ||||||
|  |     [2, 5000, 5250, 4750, 4850, 6172, 0, 0], | ||||||
|  |     [3, 4850, 5050, 4650, 4750, 6172, 0, 0], | ||||||
|  |     [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], | ||||||
|  |     stop_loss=-0.10, roi=0.10, profit_perc=-0.055, trailing_stop=True, | ||||||
|  |     trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Test 9 - trailing_stop should raise - high and low in same candle. | ||||||
|  | # Candle Data for test 9 | ||||||
|  | # Set stop-loss at 10%, ROI at 10% (should not apply) | ||||||
|  | # TC9: Trailing stoploss - stoploss should be adjusted candle 2 | ||||||
|  | tc9 = BTContainer(data=[ | ||||||
|  |     # D   O     H     L     C    V    B  S | ||||||
|  |     [0, 5000, 5050, 4950, 5000, 6172, 1, 0], | ||||||
|  |     [1, 5000, 5050, 4950, 5000, 6172, 0, 0], | ||||||
|  |     [2, 5000, 5050, 4950, 5000, 6172, 0, 0], | ||||||
|  |     [3, 5000, 5200, 4550, 4850, 6172, 0, 0], | ||||||
|  |     [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], | ||||||
|  |     stop_loss=-0.10, roi=0.10, profit_perc=-0.064, trailing_stop=True, | ||||||
|  |     trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] | ||||||
|  | ) | ||||||
|  |  | ||||||
| TESTS = [ | TESTS = [ | ||||||
|     tc0, |  | ||||||
|     tc1, |     tc1, | ||||||
|     tc2, |     tc2, | ||||||
|     tc3, |     tc3, | ||||||
|     tc4, |     tc4, | ||||||
|     tc5, |     tc5, | ||||||
|     tc6, |     tc6, | ||||||
|  |     tc7, | ||||||
|  |     tc8, | ||||||
|  |     tc9, | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -148,8 +178,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: | |||||||
|     """ |     """ | ||||||
|     default_conf["stoploss"] = data.stop_loss |     default_conf["stoploss"] = data.stop_loss | ||||||
|     default_conf["minimal_roi"] = {"0": data.roi} |     default_conf["minimal_roi"] = {"0": data.roi} | ||||||
|     default_conf['ticker_interval'] = tests_ticker_interval |     default_conf["ticker_interval"] = tests_ticker_interval | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0)) |     default_conf["trailing_stop"] = data.trailing_stop | ||||||
|  |     mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0)) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     frame = _build_backtest_dataframe(data.data) |     frame = _build_backtest_dataframe(data.data) | ||||||
|     backtesting = Backtesting(default_conf) |     backtesting = Backtesting(default_conf) | ||||||
| @@ -157,7 +188,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: | |||||||
|     backtesting.advise_sell = lambda a, m: frame |     backtesting.advise_sell = lambda a, m: frame | ||||||
|     caplog.set_level(logging.DEBUG) |     caplog.set_level(logging.DEBUG) | ||||||
|  |  | ||||||
|     pair = 'UNITTEST/BTC' |     pair = "UNITTEST/BTC" | ||||||
|     # Dummy data as we mock the analyze functions |     # Dummy data as we mock the analyze functions | ||||||
|     data_processed = {pair: DataFrame()} |     data_processed = {pair: DataFrame()} | ||||||
|     min_date, max_date = get_timeframe({pair: frame}) |     min_date, max_date = get_timeframe({pair: frame}) | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ import pytest | |||||||
| from requests.exceptions import RequestException | from requests.exceptions import RequestException | ||||||
|  |  | ||||||
| from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter | from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter | ||||||
| from freqtrade.tests.conftest import log_has, patch_coinmarketcap | from freqtrade.tests.conftest import log_has | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_pair_convertion_object(): | def test_pair_convertion_object(): | ||||||
| @@ -40,7 +40,6 @@ def test_pair_convertion_object(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_fiat_convert_is_supported(mocker): | def test_fiat_convert_is_supported(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|     assert fiat_convert._is_supported_fiat(fiat='USD') is True |     assert fiat_convert._is_supported_fiat(fiat='USD') is True | ||||||
|     assert fiat_convert._is_supported_fiat(fiat='usd') is True |     assert fiat_convert._is_supported_fiat(fiat='usd') is True | ||||||
| @@ -49,7 +48,6 @@ def test_fiat_convert_is_supported(mocker): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_fiat_convert_add_pair(mocker): | def test_fiat_convert_add_pair(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|  |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|  |  | ||||||
| @@ -72,8 +70,6 @@ def test_fiat_convert_add_pair(mocker): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_fiat_convert_find_price(mocker): | def test_fiat_convert_find_price(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|  |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|  |  | ||||||
|     with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): |     with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): | ||||||
| @@ -93,15 +89,12 @@ def test_fiat_convert_find_price(mocker): | |||||||
|  |  | ||||||
| def test_fiat_convert_unsupported_crypto(mocker, caplog): | def test_fiat_convert_unsupported_crypto(mocker, caplog): | ||||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) |     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|     assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 |     assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 | ||||||
|     assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples) |     assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_fiat_convert_get_price(mocker): | def test_fiat_convert_get_price(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|  |  | ||||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', |     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||||
|                  return_value=28000.0) |                  return_value=28000.0) | ||||||
|  |  | ||||||
| @@ -134,21 +127,18 @@ def test_fiat_convert_get_price(mocker): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_fiat_convert_same_currencies(mocker): | def test_fiat_convert_same_currencies(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|  |  | ||||||
|     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0 |     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_fiat_convert_two_FIAT(mocker): | def test_fiat_convert_two_FIAT(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|  |  | ||||||
|     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0 |     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_loadcryptomap(mocker): | def test_loadcryptomap(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|  |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|     assert len(fiat_convert._cryptomap) == 2 |     assert len(fiat_convert._cryptomap) == 2 | ||||||
| @@ -174,7 +164,6 @@ def test_fiat_init_network_exception(mocker): | |||||||
|  |  | ||||||
| def test_fiat_convert_without_network(mocker): | def test_fiat_convert_without_network(mocker): | ||||||
|     # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap |     # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|  |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|  |  | ||||||
| @@ -205,7 +194,6 @@ def test_fiat_invalid_response(mocker, caplog): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_convert_amount(mocker): | def test_convert_amount(mocker): | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0) |     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0) | ||||||
|  |  | ||||||
|     fiat_convert = CryptoToFiatConverter() |     fiat_convert = CryptoToFiatConverter() | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ from freqtrade.persistence import Trade | |||||||
| from freqtrade.rpc import RPC, RPCException | from freqtrade.rpc import RPC, RPCException | ||||||
| from freqtrade.rpc.fiat_convert import CryptoToFiatConverter | from freqtrade.rpc.fiat_convert import CryptoToFiatConverter | ||||||
| from freqtrade.state import State | from freqtrade.state import State | ||||||
| from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange | from freqtrade.tests.conftest import patch_exchange | ||||||
| from freqtrade.tests.test_freqtradebot import patch_get_signal | from freqtrade.tests.test_freqtradebot import patch_get_signal | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -28,7 +28,6 @@ def prec_satoshi(a, b) -> float: | |||||||
|  |  | ||||||
| # Unit tests | # Unit tests | ||||||
| def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: | def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
| @@ -60,6 +59,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: | |||||||
|         'amount': 90.99181074, |         'amount': 90.99181074, | ||||||
|         'close_profit': None, |         'close_profit': None, | ||||||
|         'current_profit': -0.59, |         'current_profit': -0.59, | ||||||
|  |         'stop_loss': 0.0, | ||||||
|  |         'initial_stop_loss': 0.0, | ||||||
|  |         'initial_stop_loss_pct': None, | ||||||
|  |         'stop_loss_pct': None, | ||||||
|         'open_order': '(limit buy rem=0.00000000)' |         'open_order': '(limit buy rem=0.00000000)' | ||||||
|     } == results[0] |     } == results[0] | ||||||
|  |  | ||||||
| @@ -80,12 +83,15 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: | |||||||
|         'amount': 90.99181074, |         'amount': 90.99181074, | ||||||
|         'close_profit': None, |         'close_profit': None, | ||||||
|         'current_profit': ANY, |         'current_profit': ANY, | ||||||
|  |         'stop_loss': 0.0, | ||||||
|  |         'initial_stop_loss': 0.0, | ||||||
|  |         'initial_stop_loss_pct': None, | ||||||
|  |         'stop_loss_pct': None, | ||||||
|         'open_order': '(limit buy rem=0.00000000)' |         'open_order': '(limit buy rem=0.00000000)' | ||||||
|     } == results[0] |     } == results[0] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: | def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -122,7 +128,6 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: | |||||||
|  |  | ||||||
| def test_rpc_daily_profit(default_conf, update, ticker, fee, | def test_rpc_daily_profit(default_conf, update, ticker, fee, | ||||||
|                           limit_buy_order, limit_sell_order, markets, mocker) -> None: |                           limit_buy_order, limit_sell_order, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -176,7 +181,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, | |||||||
|         'freqtrade.rpc.fiat_convert.Market', |         'freqtrade.rpc.fiat_convert.Market', | ||||||
|         ticker=MagicMock(return_value={'price_usd': 15000.0}), |         ticker=MagicMock(return_value={'price_usd': 15000.0}), | ||||||
|     ) |     ) | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) |     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
| @@ -336,7 +340,6 @@ def test_rpc_balance_handle(default_conf, mocker): | |||||||
|         'freqtrade.rpc.fiat_convert.Market', |         'freqtrade.rpc.fiat_convert.Market', | ||||||
|         ticker=MagicMock(return_value={'price_usd': 15000.0}), |         ticker=MagicMock(return_value={'price_usd': 15000.0}), | ||||||
|     ) |     ) | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) |     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
| @@ -367,7 +370,6 @@ def test_rpc_balance_handle(default_conf, mocker): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_start(mocker, default_conf) -> None: | def test_rpc_start(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -391,7 +393,6 @@ def test_rpc_start(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_stop(mocker, default_conf) -> None: | def test_rpc_stop(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -416,7 +417,6 @@ def test_rpc_stop(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_stopbuy(mocker, default_conf) -> None: | def test_rpc_stopbuy(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -437,7 +437,6 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: | def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|  |  | ||||||
| @@ -539,7 +538,6 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: | |||||||
|  |  | ||||||
| def test_performance_handle(default_conf, ticker, limit_buy_order, fee, | def test_performance_handle(default_conf, ticker, limit_buy_order, fee, | ||||||
|                             limit_sell_order, markets, mocker) -> None: |                             limit_sell_order, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -576,7 +574,6 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: | def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -605,7 +602,6 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: | |||||||
|  |  | ||||||
| def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None: | def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None: | ||||||
|     default_conf['forcebuy_enable'] = True |     default_conf['forcebuy_enable'] = True | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     buy_mm = MagicMock(return_value={'id': limit_buy_order['id']}) |     buy_mm = MagicMock(return_value={'id': limit_buy_order['id']}) | ||||||
| @@ -657,7 +653,6 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order | |||||||
| def test_rpcforcebuy_stopped(mocker, default_conf) -> None: | def test_rpcforcebuy_stopped(mocker, default_conf) -> None: | ||||||
|     default_conf['forcebuy_enable'] = True |     default_conf['forcebuy_enable'] = True | ||||||
|     default_conf['initial_state'] = 'stopped' |     default_conf['initial_state'] = 'stopped' | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|  |  | ||||||
| @@ -671,7 +666,6 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpcforcebuy_disabled(mocker, default_conf) -> None: | def test_rpcforcebuy_disabled(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|  |  | ||||||
| @@ -685,7 +679,6 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_whitelist(mocker, default_conf) -> None: | def test_rpc_whitelist(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|  |  | ||||||
| @@ -698,7 +691,6 @@ def test_rpc_whitelist(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: | def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     default_conf['pairlist'] = {'method': 'VolumePairList', |     default_conf['pairlist'] = {'method': 'VolumePairList', | ||||||
|                                 'config': {'number_assets': 4} |                                 'config': {'number_assets': 4} | ||||||
| @@ -716,7 +708,6 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_blacklist(mocker, default_conf) -> None: | def test_rpc_blacklist(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|  |  | ||||||
| @@ -736,7 +727,6 @@ def test_rpc_blacklist(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_edge_disabled(mocker, default_conf) -> None: | def test_rpc_edge_disabled(mocker, default_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     freqtradebot = FreqtradeBot(default_conf) |     freqtradebot = FreqtradeBot(default_conf) | ||||||
| @@ -746,7 +736,6 @@ def test_rpc_edge_disabled(mocker, default_conf) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_rpc_edge_enabled(mocker, edge_conf) -> None: | def test_rpc_edge_enabled(mocker, edge_conf) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||||
|     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( |     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( | ||||||
|   | |||||||
| @@ -20,8 +20,7 @@ from freqtrade.rpc import RPCMessageType | |||||||
| from freqtrade.rpc.telegram import Telegram, authorized_only | from freqtrade.rpc.telegram import Telegram, authorized_only | ||||||
| from freqtrade.state import State | from freqtrade.state import State | ||||||
| from freqtrade.strategy.interface import SellType | from freqtrade.strategy.interface import SellType | ||||||
| from freqtrade.tests.conftest import (get_patched_freqtradebot, | from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange) | ||||||
|                                       log_has, patch_coinmarketcap, patch_exchange) |  | ||||||
| from freqtrade.tests.test_freqtradebot import patch_get_signal | from freqtrade.tests.test_freqtradebot import patch_get_signal | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -90,7 +89,6 @@ def test_cleanup(default_conf, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_authorized_only(default_conf, mocker, caplog) -> None: | def test_authorized_only(default_conf, mocker, caplog) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker, None) |     patch_exchange(mocker, None) | ||||||
|  |  | ||||||
|     chat = Chat(0, 0) |     chat = Chat(0, 0) | ||||||
| @@ -120,7 +118,6 @@ def test_authorized_only(default_conf, mocker, caplog) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: | def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker, None) |     patch_exchange(mocker, None) | ||||||
|     chat = Chat(0xdeadbeef, 0) |     chat = Chat(0xdeadbeef, 0) | ||||||
|     update = Update(randint(1, 100)) |     update = Update(randint(1, 100)) | ||||||
| @@ -149,7 +146,6 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_authorized_only_exception(default_conf, mocker, caplog) -> None: | def test_authorized_only_exception(default_conf, mocker, caplog) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|  |  | ||||||
|     update = Update(randint(1, 100)) |     update = Update(randint(1, 100)) | ||||||
| @@ -183,7 +179,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: | |||||||
|     default_conf['telegram']['enabled'] = False |     default_conf['telegram']['enabled'] = False | ||||||
|     default_conf['telegram']['chat_id'] = 123 |     default_conf['telegram']['chat_id'] = 123 | ||||||
|  |  | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
| @@ -206,6 +201,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: | |||||||
|             'amount': 90.99181074, |             'amount': 90.99181074, | ||||||
|             'close_profit': None, |             'close_profit': None, | ||||||
|             'current_profit': -0.59, |             'current_profit': -0.59, | ||||||
|  |             'initial_stop_loss': 1.098e-05, | ||||||
|  |             'stop_loss': 1.099e-05, | ||||||
|  |             'initial_stop_loss_pct': -0.05, | ||||||
|  |             'stop_loss_pct': -0.01, | ||||||
|             'open_order': '(limit buy rem=0.00000000)' |             'open_order': '(limit buy rem=0.00000000)' | ||||||
|         }]), |         }]), | ||||||
|         _status_table=status_table, |         _status_table=status_table, | ||||||
| @@ -232,7 +231,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
| @@ -274,12 +272,18 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No | |||||||
|     # Trigger status while we have a fulfilled order for the open trade |     # Trigger status while we have a fulfilled order for the open trade | ||||||
|     telegram._status(bot=MagicMock(), update=update) |     telegram._status(bot=MagicMock(), update=update) | ||||||
|  |  | ||||||
|  |     # close_rate should not be included in the message as the trade is not closed | ||||||
|  |     # and no line should be empty | ||||||
|  |     lines = msg_mock.call_args_list[0][0][0].split('\n') | ||||||
|  |     assert '' not in lines | ||||||
|  |     assert 'Close Rate' not in ''.join(lines) | ||||||
|  |     assert 'Close Profit' not in ''.join(lines) | ||||||
|  |  | ||||||
|     assert msg_mock.call_count == 1 |     assert msg_mock.call_count == 1 | ||||||
|     assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] |     assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
| @@ -333,7 +337,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) | |||||||
|  |  | ||||||
| def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, | def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, | ||||||
|                       limit_sell_order, markets, mocker) -> None: |                       limit_sell_order, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch( |     mocker.patch( | ||||||
|         'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', |         'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', | ||||||
| @@ -405,7 +408,6 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: | def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
| @@ -442,7 +444,6 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: | |||||||
|  |  | ||||||
| def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, | def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, | ||||||
|                        limit_buy_order, limit_sell_order, markets, mocker) -> None: |                        limit_buy_order, limit_sell_order, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) |     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -548,7 +549,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: | |||||||
|             'last': 0.1, |             'last': 0.1, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance) |     mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) |     mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) | ||||||
|  |  | ||||||
| @@ -635,7 +635,6 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_stop_handle(default_conf, update, mocker) -> None: | def test_stop_handle(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -655,7 +654,6 @@ def test_stop_handle(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: | def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -675,7 +673,6 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_stopbuy_handle(default_conf, update, mocker) -> None: | def test_stopbuy_handle(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -695,7 +692,6 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_reload_conf_handle(default_conf, update, mocker) -> None: | def test_reload_conf_handle(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -716,7 +712,6 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
| def test_forcesell_handle(default_conf, update, ticker, fee, | def test_forcesell_handle(default_conf, update, ticker, fee, | ||||||
|                           ticker_sell_up, markets, mocker) -> None: |                           ticker_sell_up, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) |     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||||
|     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) |     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||||
| @@ -767,7 +762,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee, | |||||||
|  |  | ||||||
| def test_forcesell_down_handle(default_conf, update, ticker, fee, | def test_forcesell_down_handle(default_conf, update, ticker, fee, | ||||||
|                                ticker_sell_down, markets, mocker) -> None: |                                ticker_sell_down, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', |     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||||
|                  return_value=15000.0) |                  return_value=15000.0) | ||||||
|     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) |     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) | ||||||
| @@ -822,7 +816,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', |     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||||
|                  return_value=15000.0) |                  return_value=15000.0) | ||||||
| @@ -869,7 +862,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: | def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', |     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||||
|                  return_value=15000.0) |                  return_value=15000.0) | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
| @@ -910,7 +902,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_forcebuy_handle(default_conf, update, markets, mocker) -> None: | def test_forcebuy_handle(default_conf, update, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) |     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock()) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||||
| @@ -948,7 +939,6 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> None: | def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) |  | ||||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) |     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||||
|     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock()) |     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock()) | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||||
| @@ -973,7 +963,6 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non | |||||||
|  |  | ||||||
| def test_performance_handle(default_conf, update, ticker, fee, | def test_performance_handle(default_conf, update, ticker, fee, | ||||||
|                             limit_buy_order, limit_sell_order, markets, mocker) -> None: |                             limit_buy_order, limit_sell_order, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -1015,7 +1004,6 @@ def test_performance_handle(default_conf, update, ticker, fee, | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
| @@ -1058,7 +1046,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_whitelist_static(default_conf, update, mocker) -> None: | def test_whitelist_static(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -1076,7 +1063,6 @@ def test_whitelist_static(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_whitelist_dynamic(default_conf, update, mocker) -> None: | def test_whitelist_dynamic(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -1098,7 +1084,6 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_blacklist_static(default_conf, update, mocker) -> None: | def test_blacklist_static(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -1123,7 +1108,6 @@ def test_blacklist_static(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_edge_disabled(default_conf, update, mocker) -> None: | def test_edge_disabled(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -1141,7 +1125,6 @@ def test_edge_disabled(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_edge_enabled(edge_conf, update, mocker) -> None: | def test_edge_enabled(edge_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( |     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( | ||||||
|         return_value={ |         return_value={ | ||||||
| @@ -1165,7 +1148,6 @@ def test_edge_enabled(edge_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_help_handle(default_conf, update, mocker) -> None: | def test_help_handle(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -1182,7 +1164,6 @@ def test_help_handle(default_conf, update, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_handle(default_conf, update, mocker) -> None: | def test_version_handle(default_conf, update, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     msg_mock = MagicMock() |     msg_mock = MagicMock() | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.rpc.telegram.Telegram', |         'freqtrade.rpc.telegram.Telegram', | ||||||
| @@ -1409,7 +1390,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test__send_msg(default_conf, mocker) -> None: | def test__send_msg(default_conf, mocker) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||||
|     bot = MagicMock() |     bot = MagicMock() | ||||||
|     freqtradebot = get_patched_freqtradebot(mocker, default_conf) |     freqtradebot = get_patched_freqtradebot(mocker, default_conf) | ||||||
| @@ -1421,7 +1401,6 @@ def test__send_msg(default_conf, mocker) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| def test__send_msg_network_error(default_conf, mocker, caplog) -> None: | def test__send_msg_network_error(default_conf, mocker, caplog) -> None: | ||||||
|     patch_coinmarketcap(mocker) |  | ||||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) |     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||||
|     bot = MagicMock() |     bot = MagicMock() | ||||||
|     bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) |     bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import logging | |||||||
| from argparse import Namespace | from argparse import Namespace | ||||||
| from copy import deepcopy | from copy import deepcopy | ||||||
| from unittest.mock import MagicMock | from unittest.mock import MagicMock | ||||||
|  | from pathlib import Path | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| from jsonschema import Draft4Validator, ValidationError, validate | from jsonschema import Draft4Validator, ValidationError, validate | ||||||
| @@ -547,6 +548,23 @@ def test_set_loggers() -> None: | |||||||
|     assert logging.getLogger('telegram').level is logging.INFO |     assert logging.getLogger('telegram').level is logging.INFO | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_set_logfile(default_conf, mocker): | ||||||
|  |     mocker.patch('freqtrade.configuration.open', | ||||||
|  |                  mocker.mock_open(read_data=json.dumps(default_conf))) | ||||||
|  |  | ||||||
|  |     arglist = [ | ||||||
|  |         '--logfile', 'test_file.log', | ||||||
|  |     ] | ||||||
|  |     args = Arguments(arglist, '').get_parsed_arg() | ||||||
|  |     configuration = Configuration(args) | ||||||
|  |     validated_conf = configuration.load_config() | ||||||
|  |  | ||||||
|  |     assert validated_conf['logfile'] == "test_file.log" | ||||||
|  |     f = Path("test_file.log") | ||||||
|  |     assert f.is_file() | ||||||
|  |     f.unlink() | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None: | def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None: | ||||||
|     default_conf['forcebuy_enable'] = True |     default_conf['forcebuy_enable'] = True | ||||||
|     mocker.patch('freqtrade.configuration.open', mocker.mock_open( |     mocker.patch('freqtrade.configuration.open', mocker.mock_open( | ||||||
|   | |||||||
| @@ -1062,6 +1062,13 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, | |||||||
|     assert trade.stoploss_order_id is None |     assert trade.stoploss_order_id is None | ||||||
|     assert trade.is_open is False |     assert trade.is_open is False | ||||||
|  |  | ||||||
|  |     mocker.patch( | ||||||
|  |         'freqtrade.exchange.Exchange.stoploss_limit', | ||||||
|  |         side_effect=DependencyException() | ||||||
|  |     ) | ||||||
|  |     freqtrade.handle_stoploss_on_exchange(trade) | ||||||
|  |     assert log_has('Unable to create stoploss order: ', caplog.record_tuples) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, | def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, | ||||||
|                                               markets, limit_buy_order, limit_sell_order) -> None: |                                               markets, limit_buy_order, limit_sell_order) -> None: | ||||||
| @@ -1312,16 +1319,83 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo | |||||||
|     # test amount modified by fee-logic |     # test amount modified by fee-logic | ||||||
|     assert not freqtrade.process_maybe_execute_sell(trade) |     assert not freqtrade.process_maybe_execute_sell(trade) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_process_maybe_execute_sell_exception(mocker, default_conf, | ||||||
|  |                                               limit_buy_order, caplog) -> None: | ||||||
|  |     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) | ||||||
|  |  | ||||||
|  |     trade = MagicMock() | ||||||
|  |     trade.open_order_id = '123' | ||||||
|  |     trade.open_fee = 0.001 | ||||||
|  |  | ||||||
|  |     # Test raise of DependencyException exception | ||||||
|  |     mocker.patch( | ||||||
|  |         'freqtrade.freqtradebot.FreqtradeBot.update_trade_state', | ||||||
|  |         side_effect=DependencyException() | ||||||
|  |     ) | ||||||
|  |     freqtrade.process_maybe_execute_sell(trade) | ||||||
|  |     assert log_has('Unable to sell trade: ', caplog.record_tuples) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None: | ||||||
|  |     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||||
|  |  | ||||||
|  |     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_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['amount']) | ||||||
|  |  | ||||||
|  |     trade = Trade() | ||||||
|  |     # Mock session away | ||||||
|  |     Trade.session = MagicMock() | ||||||
|  |     trade.open_order_id = '123' | ||||||
|  |     trade.open_fee = 0.001 | ||||||
|  |     freqtrade.update_trade_state(trade) | ||||||
|  |     # Test amount not modified by fee-logic | ||||||
|  |     assert not log_has_re(r'Applying fee to .*', caplog.record_tuples) | ||||||
|  |     assert trade.open_order_id is None | ||||||
|  |     assert trade.amount == limit_buy_order['amount'] | ||||||
|  |  | ||||||
|  |     trade.open_order_id = '123' | ||||||
|  |     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81) | ||||||
|  |     assert trade.amount != 90.81 | ||||||
|  |     # test amount modified by fee-logic | ||||||
|  |     freqtrade.update_trade_state(trade) | ||||||
|  |     assert trade.amount == 90.81 | ||||||
|  |     assert trade.open_order_id is None | ||||||
|  |  | ||||||
|     trade.is_open = True |     trade.is_open = True | ||||||
|     trade.open_order_id = None |     trade.open_order_id = None | ||||||
|     # Assert we call handle_trade() if trade is feasible for execution |     # Assert we call handle_trade() if trade is feasible for execution | ||||||
|     assert freqtrade.process_maybe_execute_sell(trade) |     freqtrade.update_trade_state(trade) | ||||||
|  |  | ||||||
|     regexp = re.compile('Found open order for.*') |     regexp = re.compile('Found open order for.*') | ||||||
|     assert filter(regexp.match, caplog.record_tuples) |     assert filter(regexp.match, caplog.record_tuples) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_process_maybe_execute_sell_exception(mocker, default_conf, | def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker): | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) | ||||||
|  |     # get_order should not be called!! | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError)) | ||||||
|  |     patch_exchange(mocker) | ||||||
|  |     Trade.session = MagicMock() | ||||||
|  |     amount = sum(x['amount'] for x in trades_for_order) | ||||||
|  |     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||||
|  |     trade = Trade( | ||||||
|  |         pair='LTC/ETH', | ||||||
|  |         amount=amount, | ||||||
|  |         exchange='binance', | ||||||
|  |         open_rate=0.245441, | ||||||
|  |         open_order_id="123456" | ||||||
|  |     ) | ||||||
|  |     freqtrade.update_trade_state(trade, limit_buy_order) | ||||||
|  |     assert trade.amount != amount | ||||||
|  |     assert trade.amount == limit_buy_order['amount'] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_update_trade_state_exception(mocker, default_conf, | ||||||
|                                       limit_buy_order, caplog) -> None: |                                       limit_buy_order, caplog) -> None: | ||||||
|     freqtrade = get_patched_freqtradebot(mocker, default_conf) |     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) |     mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) | ||||||
| @@ -1335,17 +1409,9 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf, | |||||||
|         'freqtrade.freqtradebot.FreqtradeBot.get_real_amount', |         'freqtrade.freqtradebot.FreqtradeBot.get_real_amount', | ||||||
|         side_effect=OperationalException() |         side_effect=OperationalException() | ||||||
|     ) |     ) | ||||||
|     freqtrade.process_maybe_execute_sell(trade) |     freqtrade.update_trade_state(trade) | ||||||
|     assert log_has('Could not update trade amount: ', caplog.record_tuples) |     assert log_has('Could not update trade amount: ', caplog.record_tuples) | ||||||
|  |  | ||||||
|     # Test raise of DependencyException exception |  | ||||||
|     mocker.patch( |  | ||||||
|         'freqtrade.freqtradebot.FreqtradeBot.get_real_amount', |  | ||||||
|         side_effect=DependencyException() |  | ||||||
|     ) |  | ||||||
|     freqtrade.process_maybe_execute_sell(trade) |  | ||||||
|     assert log_has('Unable to sell trade: ', caplog.record_tuples) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, | def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, | ||||||
|                       fee, markets, mocker) -> None: |                       fee, markets, mocker) -> None: | ||||||
| @@ -2296,9 +2362,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market | |||||||
|     } |     } | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.stop_loss_reached = \ |     freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( | ||||||
|         lambda current_rate, trade, current_time, force_stoploss, current_profit: SellCheckTuple( |             sell_flag=False, sell_type=SellType.NONE)) | ||||||
|             sell_flag=False, sell_type=SellType.NONE) |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
| @@ -2444,8 +2509,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets | |||||||
|                  })) |                  })) | ||||||
|     # stop-loss not reached, adjusted stoploss |     # stop-loss not reached, adjusted stoploss | ||||||
|     assert freqtrade.handle_trade(trade) is False |     assert freqtrade.handle_trade(trade) is False | ||||||
|     assert log_has(f'using positive stop loss mode: 0.01 with offset 0 ' |     assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%', | ||||||
|                    f'since we have profit 0.2666%', |  | ||||||
|                    caplog.record_tuples) |                    caplog.record_tuples) | ||||||
|     assert log_has(f'adjusted stop loss', caplog.record_tuples) |     assert log_has(f'adjusted stop loss', caplog.record_tuples) | ||||||
|     assert trade.stop_loss == 0.0000138501 |     assert trade.stop_loss == 0.0000138501 | ||||||
| @@ -2504,8 +2568,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, | |||||||
|                  })) |                  })) | ||||||
|     # stop-loss not reached, adjusted stoploss |     # stop-loss not reached, adjusted stoploss | ||||||
|     assert freqtrade.handle_trade(trade) is False |     assert freqtrade.handle_trade(trade) is False | ||||||
|     assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 ' |     assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%', | ||||||
|                    f'since we have profit 0.2666%', |  | ||||||
|                    caplog.record_tuples) |                    caplog.record_tuples) | ||||||
|     assert log_has(f'adjusted stop loss', caplog.record_tuples) |     assert log_has(f'adjusted stop loss', caplog.record_tuples) | ||||||
|     assert trade.stop_loss == 0.0000138501 |     assert trade.stop_loss == 0.0000138501 | ||||||
| @@ -2584,8 +2647,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, | |||||||
|                  })) |                  })) | ||||||
|  |  | ||||||
|     assert freqtrade.handle_trade(trade) is False |     assert freqtrade.handle_trade(trade) is False | ||||||
|     assert log_has(f'using positive stop loss mode: 0.05 with offset 0.055 ' |     assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%', | ||||||
|                    f'since we have profit 0.1218%', |  | ||||||
|                    caplog.record_tuples) |                    caplog.record_tuples) | ||||||
|     assert log_has(f'adjusted stop loss', caplog.record_tuples) |     assert log_has(f'adjusted stop loss', caplog.record_tuples) | ||||||
|     assert trade.stop_loss == 0.0000117705 |     assert trade.stop_loss == 0.0000117705 | ||||||
|   | |||||||
| @@ -510,6 +510,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): | |||||||
|     assert trade.pair == "ETC/BTC" |     assert trade.pair == "ETC/BTC" | ||||||
|     assert trade.exchange == "binance" |     assert trade.exchange == "binance" | ||||||
|     assert trade.max_rate == 0.0 |     assert trade.max_rate == 0.0 | ||||||
|  |     assert trade.min_rate is None | ||||||
|     assert trade.stop_loss == 0.0 |     assert trade.stop_loss == 0.0 | ||||||
|     assert trade.initial_stop_loss == 0.0 |     assert trade.initial_stop_loss == 0.0 | ||||||
|     assert trade.sell_reason is None |     assert trade.sell_reason is None | ||||||
| @@ -585,7 +586,58 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): | |||||||
|                    caplog.record_tuples) |                    caplog.record_tuples) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): | def test_adjust_stop_loss(fee): | ||||||
|  |     trade = Trade( | ||||||
|  |         pair='ETH/BTC', | ||||||
|  |         stake_amount=0.001, | ||||||
|  |         fee_open=fee.return_value, | ||||||
|  |         fee_close=fee.return_value, | ||||||
|  |         exchange='bittrex', | ||||||
|  |         open_rate=1, | ||||||
|  |         max_rate=1, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     trade.adjust_stop_loss(trade.open_rate, 0.05, True) | ||||||
|  |     assert trade.stop_loss == 0.95 | ||||||
|  |     assert trade.stop_loss_pct == -0.05 | ||||||
|  |     assert trade.initial_stop_loss == 0.95 | ||||||
|  |     assert trade.initial_stop_loss_pct == -0.05 | ||||||
|  |  | ||||||
|  |     # Get percent of profit with a lower rate | ||||||
|  |     trade.adjust_stop_loss(0.96, 0.05) | ||||||
|  |     assert trade.stop_loss == 0.95 | ||||||
|  |     assert trade.stop_loss_pct == -0.05 | ||||||
|  |     assert trade.initial_stop_loss == 0.95 | ||||||
|  |     assert trade.initial_stop_loss_pct == -0.05 | ||||||
|  |  | ||||||
|  |     # Get percent of profit with a custom rate (Higher than open rate) | ||||||
|  |     trade.adjust_stop_loss(1.3, -0.1) | ||||||
|  |     assert round(trade.stop_loss, 8) == 1.17 | ||||||
|  |     assert trade.stop_loss_pct == -0.1 | ||||||
|  |     assert trade.initial_stop_loss == 0.95 | ||||||
|  |     assert trade.initial_stop_loss_pct == -0.05 | ||||||
|  |  | ||||||
|  |     # current rate lower again ... should not change | ||||||
|  |     trade.adjust_stop_loss(1.2, 0.1) | ||||||
|  |     assert round(trade.stop_loss, 8) == 1.17 | ||||||
|  |     assert trade.initial_stop_loss == 0.95 | ||||||
|  |     assert trade.initial_stop_loss_pct == -0.05 | ||||||
|  |  | ||||||
|  |     # current rate higher... should raise stoploss | ||||||
|  |     trade.adjust_stop_loss(1.4, 0.1) | ||||||
|  |     assert round(trade.stop_loss, 8) == 1.26 | ||||||
|  |     assert trade.initial_stop_loss == 0.95 | ||||||
|  |     assert trade.initial_stop_loss_pct == -0.05 | ||||||
|  |  | ||||||
|  |     #  Initial is true but stop_loss set - so doesn't do anything | ||||||
|  |     trade.adjust_stop_loss(1.7, 0.1, True) | ||||||
|  |     assert round(trade.stop_loss, 8) == 1.26 | ||||||
|  |     assert trade.initial_stop_loss == 0.95 | ||||||
|  |     assert trade.initial_stop_loss_pct == -0.05 | ||||||
|  |     assert trade.stop_loss_pct == -0.1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_adjust_min_max_rates(fee): | ||||||
|     trade = Trade( |     trade = Trade( | ||||||
|         pair='ETH/BTC', |         pair='ETH/BTC', | ||||||
|         stake_amount=0.001, |         stake_amount=0.001, | ||||||
| @@ -595,40 +647,24 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): | |||||||
|         open_rate=1, |         open_rate=1, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     trade.adjust_stop_loss(trade.open_rate, 0.05, True) |     trade.adjust_min_max_rates(trade.open_rate) | ||||||
|     assert trade.stop_loss == 0.95 |  | ||||||
|     assert trade.max_rate == 1 |     assert trade.max_rate == 1 | ||||||
|     assert trade.initial_stop_loss == 0.95 |     assert trade.min_rate == 1 | ||||||
|  |  | ||||||
|     # Get percent of profit with a lowre rate |     # check min adjusted, max remained | ||||||
|     trade.adjust_stop_loss(0.96, 0.05) |     trade.adjust_min_max_rates(0.96) | ||||||
|     assert trade.stop_loss == 0.95 |  | ||||||
|     assert trade.max_rate == 1 |     assert trade.max_rate == 1 | ||||||
|     assert trade.initial_stop_loss == 0.95 |     assert trade.min_rate == 0.96 | ||||||
|  |  | ||||||
|     # Get percent of profit with a custom rate (Higher than open rate) |     # check max adjusted, min remains | ||||||
|     trade.adjust_stop_loss(1.3, -0.1) |     trade.adjust_min_max_rates(1.05) | ||||||
|     assert round(trade.stop_loss, 8) == 1.17 |     assert trade.max_rate == 1.05 | ||||||
|     assert trade.max_rate == 1.3 |     assert trade.min_rate == 0.96 | ||||||
|     assert trade.initial_stop_loss == 0.95 |  | ||||||
|  |  | ||||||
|     # current rate lower again ... should not change |     # current rate "in the middle" - no adjustment | ||||||
|     trade.adjust_stop_loss(1.2, 0.1) |     trade.adjust_min_max_rates(1.03) | ||||||
|     assert round(trade.stop_loss, 8) == 1.17 |     assert trade.max_rate == 1.05 | ||||||
|     assert trade.max_rate == 1.3 |     assert trade.min_rate == 0.96 | ||||||
|     assert trade.initial_stop_loss == 0.95 |  | ||||||
|  |  | ||||||
|     # current rate higher... should raise stoploss |  | ||||||
|     trade.adjust_stop_loss(1.4, 0.1) |  | ||||||
|     assert round(trade.stop_loss, 8) == 1.26 |  | ||||||
|     assert trade.max_rate == 1.4 |  | ||||||
|     assert trade.initial_stop_loss == 0.95 |  | ||||||
|  |  | ||||||
|     #  Initial is true but stop_loss set - so doesn't do anything |  | ||||||
|     trade.adjust_stop_loss(1.7, 0.1, True) |  | ||||||
|     assert round(trade.stop_loss, 8) == 1.26 |  | ||||||
|     assert trade.max_rate == 1.4 |  | ||||||
|     assert trade.initial_stop_loss == 0.95 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_open(default_conf, fee): | def test_get_open(default_conf, fee): | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ | |||||||
| flake8==3.7.7 | flake8==3.7.7 | ||||||
| flake8-type-annotations==0.1.0 | flake8-type-annotations==0.1.0 | ||||||
| flake8-tidy-imports==2.0.0 | flake8-tidy-imports==2.0.0 | ||||||
| pytest==4.3.1 | pytest==4.4.0 | ||||||
| pytest-mock==1.10.2 | pytest-mock==1.10.3 | ||||||
| pytest-asyncio==0.10.0 | pytest-asyncio==0.10.0 | ||||||
| pytest-cov==2.6.1 | pytest-cov==2.6.1 | ||||||
| coveralls==1.7.0 | coveralls==1.7.0 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| ccxt==1.18.406 | ccxt==1.18.425 | ||||||
| SQLAlchemy==1.3.1 | SQLAlchemy==1.3.1 | ||||||
| python-telegram-bot==11.1.0 | python-telegram-bot==11.1.0 | ||||||
| arrow==0.13.1 | arrow==0.13.1 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user