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 | ||||
|  | ||||
| ``` | ||||
| usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] | ||||
|                  [--strategy-path PATH] [--dynamic-whitelist [INT]] | ||||
|                  [--db-url PATH] | ||||
| usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH] | ||||
|                  [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]] | ||||
|                  [--db-url PATH] [--sd-notify] | ||||
|                  {backtesting,edge,hyperopt} ... | ||||
|  | ||||
| Free, open source crypto trading bot | ||||
| @@ -84,6 +84,7 @@ positional arguments: | ||||
| optional arguments: | ||||
|   -h, --help            show this help message and exit | ||||
|   -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 | ||||
|   -c PATH, --config PATH | ||||
|                         Specify configuration file (default: None). Multiple | ||||
| @@ -100,6 +101,7 @@ optional arguments: | ||||
|   --db-url PATH         Override trades database URL, this is useful if | ||||
|                         dry_run is enabled or in custom deployments (default: | ||||
|                         None). | ||||
|   --sd-notify           Notify systemd service manager. | ||||
| ``` | ||||
|  | ||||
| ### Telegram RPC commands | ||||
|   | ||||
| @@ -39,12 +39,12 @@ | ||||
|         "buy": "limit", | ||||
|         "sell": "limit", | ||||
|         "stoploss": "market", | ||||
|         "stoploss_on_exchange": "false", | ||||
|         "stoploss_on_exchange": false, | ||||
|         "stoploss_on_exchange_interval": 60 | ||||
|     }, | ||||
|     "order_time_in_force": { | ||||
|         "buy": "gtc", | ||||
|         "sell": "gtc", | ||||
|         "sell": "gtc" | ||||
|     }, | ||||
|     "pairlist": { | ||||
|         "method": "VolumePairList", | ||||
|   | ||||
| @@ -6,9 +6,9 @@ This page explains the different parameters of the bot and how to run it. | ||||
| ## Bot commands | ||||
|  | ||||
| ``` | ||||
| usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] | ||||
|                  [--strategy-path PATH] [--dynamic-whitelist [INT]] | ||||
|                  [--db-url PATH] | ||||
| usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH] | ||||
|                  [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]] | ||||
|                  [--db-url PATH] [--sd-notify] | ||||
|                  {backtesting,edge,hyperopt} ... | ||||
|  | ||||
| Free, open source crypto trading bot | ||||
| @@ -22,6 +22,7 @@ positional arguments: | ||||
| optional arguments: | ||||
|   -h, --help            show this help message and exit | ||||
|   -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 | ||||
|   -c PATH, --config PATH | ||||
|                         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. | ||||
|  | ||||
| See more details on this technique with examples in the documentation page on | ||||
| [configuration](bot-configuration.md). | ||||
| [configuration](configuration.md). | ||||
|  | ||||
| ### How to use **--strategy**? | ||||
|  | ||||
|   | ||||
| @@ -70,6 +70,7 @@ Mandatory Parameters are marked as **Required**. | ||||
| | `strategy` | DefaultStrategy | Defines Strategy class to use. | ||||
| | `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. | ||||
| | `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. | ||||
|  | ||||
| ### 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. | ||||
|  | ||||
| > **Trade ID:** `123` | ||||
| > **Trade ID:** `123` `(since 1 days ago)`   | ||||
| > **Current Pair:** CVC/BTC   | ||||
| > **Open Since:** `1 days ago`   | ||||
| > **Amount:** `26.64180098`   | ||||
| > **Open Rate:** `0.00007489`   | ||||
| > **Close Rate:** `None` | ||||
| > **Current Rate:** `0.00007489`   | ||||
| > **Close Profit:** `None` | ||||
| > **Current Profit:** `12.95%`   | ||||
| > **Open Order:** `None` | ||||
| > **Stoploss:** `0.00007389 (-0.02%)`   | ||||
|  | ||||
| ### /status table | ||||
|  | ||||
|   | ||||
| @@ -71,6 +71,13 @@ class Arguments(object): | ||||
|             dest='loglevel', | ||||
|             default=0, | ||||
|         ) | ||||
|         self.parser.add_argument( | ||||
|             '--logfile', | ||||
|             help='Log to the file specified', | ||||
|             dest='logfile', | ||||
|             type=str, | ||||
|             metavar='FILE' | ||||
|         ) | ||||
|         self.parser.add_argument( | ||||
|             '--version', | ||||
|             action='version', | ||||
|   | ||||
| @@ -4,16 +4,18 @@ This module contains the configuration class | ||||
| import json | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| from argparse import Namespace | ||||
| from typing import Any, Dict, Optional | ||||
| from logging.handlers import RotatingFileHandler | ||||
| from typing import Any, Dict, List, Optional | ||||
|  | ||||
| import ccxt | ||||
| from jsonschema import Draft4Validator, validate | ||||
| from jsonschema.exceptions import ValidationError, best_match | ||||
|  | ||||
| from freqtrade import OperationalException, constants | ||||
| from freqtrade.state import RunMode | ||||
| from freqtrade.misc import deep_merge_dicts | ||||
| from freqtrade.state import RunMode | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -116,9 +118,22 @@ class Configuration(object): | ||||
|             config.update({'verbosity': self.args.loglevel}) | ||||
|         else: | ||||
|             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( | ||||
|             level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG, | ||||
|             format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | ||||
|             handlers=log_handlers | ||||
|         ) | ||||
|         set_loggers(config['verbosity']) | ||||
|         logger.info('Verbosity set to %s', config['verbosity']) | ||||
|   | ||||
| @@ -367,7 +367,6 @@ class FreqtradeBot(object): | ||||
|             stake_amount = order['cost'] | ||||
|             amount = order['amount'] | ||||
|             buy_limit_filled_price = order['price'] | ||||
|             order_id = None | ||||
|  | ||||
|         self.rpc.send_msg({ | ||||
|             'type': RPCMessageType.BUY_NOTIFICATION, | ||||
| @@ -396,6 +395,10 @@ class FreqtradeBot(object): | ||||
|             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.flush() | ||||
|  | ||||
| @@ -426,24 +429,7 @@ class FreqtradeBot(object): | ||||
|         :return: True if executed | ||||
|         """ | ||||
|         try: | ||||
|             # 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 = 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) | ||||
|             self.update_trade_state(trade) | ||||
|  | ||||
|             if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: | ||||
|                 result = self.handle_stoploss_on_exchange(trade) | ||||
| @@ -508,6 +494,28 @@ class FreqtradeBot(object): | ||||
|                         f"(from {order_amount} to {real_amount}) from Trades") | ||||
|         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: | ||||
|         """ | ||||
|         Get sell rate - either using get-ticker bid or first bid based on orderbook | ||||
| @@ -578,7 +586,7 @@ class FreqtradeBot(object): | ||||
|         """ | ||||
|  | ||||
|         result = False | ||||
|  | ||||
|         try: | ||||
|             # If trade is open and the buy order is fulfilled but there is no stoploss, | ||||
|             # then we add a stoploss on exchange | ||||
|             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 | ||||
|                     # value immediately | ||||
|                     self.handle_trailing_stoploss_on_exchange(trade, order) | ||||
|  | ||||
|         except DependencyException as exception: | ||||
|             logger.warning('Unable to create stoploss order: %s', exception) | ||||
|         return result | ||||
|  | ||||
|     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}') | ||||
|  | ||||
|     # 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}') | ||||
|  | ||||
|         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') | ||||
|         close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null') | ||||
|         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_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null') | ||||
|         stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') | ||||
|         stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') | ||||
|         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') | ||||
|         strategy = get_column_def(cols, 'strategy', '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, | ||||
|                 open_rate_requested, close_rate, close_rate_requested, close_profit, | ||||
|                 stake_amount, amount, open_date, close_date, open_order_id, | ||||
|                 stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update, | ||||
|                 max_rate, sell_reason, strategy, | ||||
|                 stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, | ||||
|                 stoploss_order_id, stoploss_last_update, | ||||
|                 max_rate, min_rate, sell_reason, strategy, | ||||
|                 ticker_interval | ||||
|                 ) | ||||
|             select id, lower(exchange), | ||||
| @@ -128,9 +132,11 @@ def check_migrate(engine) -> None: | ||||
|                 open_rate, {open_rate_requested} open_rate_requested, close_rate, | ||||
|                 {close_rate_requested} close_rate_requested, close_profit, | ||||
|                 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, | ||||
|                 {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 | ||||
|                 from {table_back_name} | ||||
|              """) | ||||
| @@ -183,14 +189,20 @@ class Trade(_DECL_BASE): | ||||
|     open_order_id = Column(String) | ||||
|     # absolute value of the stop loss | ||||
|     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 | ||||
|     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 = Column(String, nullable=True, index=True) | ||||
|     # last update time of the stoploss order on exchange | ||||
|     stoploss_last_update = Column(DateTime, nullable=True) | ||||
|     # absolute value of the highest reached price | ||||
|     max_rate = Column(Float, nullable=True, default=0.0) | ||||
|     # Lowest price reached | ||||
|     min_rate = Column(Float, nullable=True) | ||||
|     sell_reason = Column(String, nullable=True) | ||||
|     strategy = Column(String, 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}, ' | ||||
|                 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): | ||||
|         """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): | ||||
|             # 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))) | ||||
|  | ||||
|         # 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 | ||||
|         if not self.stop_loss: | ||||
|             logger.debug("assigning new stop loss") | ||||
|             self.stop_loss = new_loss | ||||
|             self.stop_loss_pct = -1 * abs(stoploss) | ||||
|             self.initial_stop_loss = new_loss | ||||
|             self.initial_stop_loss_pct = -1 * abs(stoploss) | ||||
|             self.stoploss_last_update = datetime.utcnow() | ||||
|  | ||||
|         # evaluate if the stop loss needs to be updated | ||||
|         else: | ||||
|             if new_loss > self.stop_loss:  # stop losses only walk up, never down! | ||||
|                 self.stop_loss = new_loss | ||||
|                 self.stop_loss_pct = -1 * abs(stoploss) | ||||
|                 self.stoploss_last_update = datetime.utcnow() | ||||
|                 logger.debug("adjusted stop loss") | ||||
|             else: | ||||
|   | ||||
| @@ -110,6 +110,10 @@ class RPC(object): | ||||
|                     amount=round(trade.amount, 8), | ||||
|                     close_profit=fmt_close_profit, | ||||
|                     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( | ||||
|                       order['type'], order['side'], order['remaining'] | ||||
|                     ) if order else None, | ||||
|   | ||||
| @@ -194,21 +194,34 @@ class Telegram(RPC): | ||||
|             for result in results: | ||||
|                 result['date'] = result['date'].humanize() | ||||
|  | ||||
|             messages = [ | ||||
|                 "*Trade ID:* `{trade_id}`\n" | ||||
|                 "*Current Pair:* {pair}\n" | ||||
|                 "*Open Since:* `{date}`\n" | ||||
|                 "*Amount:* `{amount}`\n" | ||||
|                 "*Open Rate:* `{open_rate:.8f}`\n" | ||||
|                 "*Close Rate:* `{close_rate}`\n" | ||||
|                 "*Current Rate:* `{current_rate:.8f}`\n" | ||||
|                 "*Close Profit:* `{close_profit}`\n" | ||||
|                 "*Current Profit:* `{current_profit:.2f}%`\n" | ||||
|                 "*Open Order:* `{open_order}`".format(**result) | ||||
|                 for result in results | ||||
|             messages = [] | ||||
|             for r in results: | ||||
|                 lines = [ | ||||
|                     "*Trade ID:* `{trade_id}` `(since {date})`", | ||||
|                     "*Current Pair:* {pair}", | ||||
|                     "*Amount:* `{amount}`", | ||||
|                     "*Open Rate:* `{open_rate:.8f}`", | ||||
|                     "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", | ||||
|                     "*Current Rate:* `{current_rate:.8f}`", | ||||
|                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", | ||||
|                     "*Current Profit:* `{current_profit:.2f}%`", | ||||
|  | ||||
|                     # 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: | ||||
|                 self._send_msg(msg, bot=bot) | ||||
|  | ||||
|         except RPCException as e: | ||||
|             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 | ||||
|         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 | ||||
|         """ | ||||
|  | ||||
| @@ -254,14 +257,16 @@ class IStrategy(ABC): | ||||
|         current_rate = low or 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, | ||||
|                                               current_time=date, current_profit=current_profit, | ||||
|                                               force_stoploss=force_stoploss) | ||||
|                                               force_stoploss=force_stoploss, high=high) | ||||
|  | ||||
|         if stoplossflag.sell_flag: | ||||
|             return stoplossflag | ||||
|  | ||||
|         # Set current rate to low for backtesting sell | ||||
|         # Set current rate to high for backtesting sell | ||||
|         current_rate = high or rate | ||||
|         current_profit = trade.calc_profit_percent(current_rate) | ||||
|         experimental = self.config.get('experimental', {}) | ||||
| @@ -285,8 +290,9 @@ class IStrategy(ABC): | ||||
|  | ||||
|         return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) | ||||
|  | ||||
|     def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, | ||||
|                           current_profit: float, force_stoploss: float) -> SellCheckTuple: | ||||
|     def stop_loss_reached(self, current_rate: float, trade: Trade, | ||||
|                           current_time: datetime, current_profit: float, | ||||
|                           force_stoploss: float, high: float = None) -> SellCheckTuple: | ||||
|         """ | ||||
|         Based on current profit of the trade and configured (trailing) stoploss, | ||||
|         decides to sell or not | ||||
| @@ -294,13 +300,33 @@ class IStrategy(ABC): | ||||
|         """ | ||||
|  | ||||
|         trailing_stop = self.config.get('trailing_stop', False) | ||||
|         trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss | ||||
|                                else self.stoploss, initial=True) | ||||
|         stop_loss_value = force_stoploss if force_stoploss else self.stoploss | ||||
|  | ||||
|         # 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 | ||||
|         if ((self.stoploss is not None) and | ||||
|             (trade.stop_loss >= current_rate) and | ||||
|                 (not self.order_types.get('stoploss_on_exchange'))): | ||||
|  | ||||
|             selltype = SellType.STOP_LOSS | ||||
|             # If Trailing stop (and max-rate did move above open rate) | ||||
|             if trailing_stop and trade.open_rate != trade.max_rate: | ||||
| @@ -315,29 +341,6 @@ class IStrategy(ABC): | ||||
|             logger.debug('Stop loss hit.') | ||||
|             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) | ||||
|  | ||||
|     def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import logging | ||||
| import re | ||||
| from datetime import datetime | ||||
| from functools import reduce | ||||
| from typing import Dict, Optional | ||||
| from unittest.mock import MagicMock, PropertyMock | ||||
|  | ||||
| import arrow | ||||
| @@ -13,11 +12,11 @@ from telegram import Chat, Message, Update | ||||
|  | ||||
| from freqtrade import constants | ||||
| from freqtrade.data.converter import parse_ticker_dataframe | ||||
| from freqtrade.exchange import Exchange | ||||
| from freqtrade.edge import Edge, PairInfo | ||||
| from freqtrade.worker import Worker | ||||
| from freqtrade.exchange import Exchange | ||||
| from freqtrade.freqtradebot import FreqtradeBot | ||||
| from freqtrade.resolvers import ExchangeResolver | ||||
| from freqtrade.worker import Worker | ||||
|  | ||||
| logging.getLogger('').setLevel(logging.INFO) | ||||
|  | ||||
| @@ -96,7 +95,6 @@ def patch_freqtradebot(mocker, config) -> None: | ||||
|     :param config: Config to pass to the bot | ||||
|     :return: None | ||||
|     """ | ||||
|     patch_coinmarketcap(mocker, {'price_usd': 12345.0}) | ||||
|     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) | ||||
|     mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) | ||||
|     patch_exchange(mocker, None) | ||||
| @@ -114,7 +112,8 @@ def get_patched_worker(mocker, config) -> Worker: | ||||
|     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 | ||||
|     :param mocker: mocker to patch coinmarketcap class | ||||
|   | ||||
| @@ -28,6 +28,7 @@ class BTContainer(NamedTuple): | ||||
|     roi: float | ||||
|     trades: List[BTrade] | ||||
|     profit_perc: float | ||||
|     trailing_stop: bool = False | ||||
|  | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
| # Test 0 Minus 8% Close | ||||
| # Test 1 Minus 8% Close | ||||
| # Test with Stop-loss at 1% | ||||
| # TC1: Stop-Loss Triggered 1% loss | ||||
| tc0 = BTContainer(data=[ | ||||
| tc1 = BTContainer(data=[ | ||||
|     # D  O     H     L     C     V    B  S | ||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||
|     [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% | ||||
| # TC2: Stop-Loss Triggered 3% Loss | ||||
| tc1 = BTContainer(data=[ | ||||
| tc2 = BTContainer(data=[ | ||||
|     # D  O     H     L     C     V    B  S | ||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||
|     [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%. | ||||
| #               Entry Criteria Met | ||||
| # 	            Candle drops 20% | ||||
| # Candle Data for test 3 | ||||
| # Test with Stop-Loss at 2% | ||||
| # TC3: Trade-A: 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 | ||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||
|     [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 | ||||
| # Test with Stop-loss at 2% ROI 6% | ||||
| # TC4: Stop-Loss Triggered 2% Loss | ||||
| tc3 = BTContainer(data=[ | ||||
| tc4 = BTContainer(data=[ | ||||
|     # D  O     H     L     C     V    B  S | ||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||
|     [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)] | ||||
| ) | ||||
|  | ||||
| # Test 4 / Drops 0.5% Closes +20% | ||||
| # Test 5 / Drops 0.5% Closes +20% | ||||
| # Set stop-loss at 1% ROI 3% | ||||
| # TC5: ROI triggers 3% Gain | ||||
| tc4 = BTContainer(data=[ | ||||
| tc5 = BTContainer(data=[ | ||||
|     # D  O     H     L     C     V    B  S | ||||
|     [0, 5000, 5025, 4980, 4987, 6172, 1, 0], | ||||
|     [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 | ||||
| # Candle Data for test 6 | ||||
| # Set stop-loss at 2% ROI at 5% | ||||
| # TC6: Stop-Loss triggers 2% Loss | ||||
| tc5 = BTContainer(data=[ | ||||
| tc6 = BTContainer(data=[ | ||||
|     # D  O     H     L     C     V    B  S | ||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 0], | ||||
|     [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 | ||||
| # Candle Data for test 7 | ||||
| # Set stop-loss at 2% ROI at 3% | ||||
| # TC7: ROI Triggers 3% Gain | ||||
| tc6 = BTContainer(data=[ | ||||
| tc7 = BTContainer(data=[ | ||||
|     # D  O     H     L     C     V    B  S | ||||
|     [0, 5000, 5025, 4975, 4987, 6172, 1, 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)] | ||||
| ) | ||||
|  | ||||
|  | ||||
| # 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 = [ | ||||
|     tc0, | ||||
|     tc1, | ||||
|     tc2, | ||||
|     tc3, | ||||
|     tc4, | ||||
|     tc5, | ||||
|     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["minimal_roi"] = {"0": data.roi} | ||||
|     default_conf['ticker_interval'] = tests_ticker_interval | ||||
|     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0)) | ||||
|     default_conf["ticker_interval"] = tests_ticker_interval | ||||
|     default_conf["trailing_stop"] = data.trailing_stop | ||||
|     mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0)) | ||||
|     patch_exchange(mocker) | ||||
|     frame = _build_backtest_dataframe(data.data) | ||||
|     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 | ||||
|     caplog.set_level(logging.DEBUG) | ||||
|  | ||||
|     pair = 'UNITTEST/BTC' | ||||
|     pair = "UNITTEST/BTC" | ||||
|     # Dummy data as we mock the analyze functions | ||||
|     data_processed = {pair: DataFrame()} | ||||
|     min_date, max_date = get_timeframe({pair: frame}) | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import pytest | ||||
| from requests.exceptions import RequestException | ||||
|  | ||||
| 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(): | ||||
| @@ -40,7 +40,6 @@ def test_pair_convertion_object(): | ||||
|  | ||||
|  | ||||
| def test_fiat_convert_is_supported(mocker): | ||||
|     patch_coinmarketcap(mocker) | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|     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): | ||||
|     patch_coinmarketcap(mocker) | ||||
|  | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|  | ||||
| @@ -72,8 +70,6 @@ def test_fiat_convert_add_pair(mocker): | ||||
|  | ||||
|  | ||||
| def test_fiat_convert_find_price(mocker): | ||||
|     patch_coinmarketcap(mocker) | ||||
|  | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|  | ||||
|     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): | ||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) | ||||
|     patch_coinmarketcap(mocker) | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|     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) | ||||
|  | ||||
|  | ||||
| def test_fiat_convert_get_price(mocker): | ||||
|     patch_coinmarketcap(mocker) | ||||
|  | ||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||
|                  return_value=28000.0) | ||||
|  | ||||
| @@ -134,21 +127,18 @@ def test_fiat_convert_get_price(mocker): | ||||
|  | ||||
|  | ||||
| def test_fiat_convert_same_currencies(mocker): | ||||
|     patch_coinmarketcap(mocker) | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|  | ||||
|     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0 | ||||
|  | ||||
|  | ||||
| def test_fiat_convert_two_FIAT(mocker): | ||||
|     patch_coinmarketcap(mocker) | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|  | ||||
|     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0 | ||||
|  | ||||
|  | ||||
| def test_loadcryptomap(mocker): | ||||
|     patch_coinmarketcap(mocker) | ||||
|  | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|     assert len(fiat_convert._cryptomap) == 2 | ||||
| @@ -174,7 +164,6 @@ def test_fiat_init_network_exception(mocker): | ||||
|  | ||||
| def test_fiat_convert_without_network(mocker): | ||||
|     # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap | ||||
|     patch_coinmarketcap(mocker) | ||||
|  | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|  | ||||
| @@ -205,7 +194,6 @@ def test_fiat_invalid_response(mocker, caplog): | ||||
|  | ||||
|  | ||||
| def test_convert_amount(mocker): | ||||
|     patch_coinmarketcap(mocker) | ||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0) | ||||
|  | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|   | ||||
| @@ -14,7 +14,7 @@ from freqtrade.persistence import Trade | ||||
| from freqtrade.rpc import RPC, RPCException | ||||
| from freqtrade.rpc.fiat_convert import CryptoToFiatConverter | ||||
| 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 | ||||
|  | ||||
|  | ||||
| @@ -28,7 +28,6 @@ def prec_satoshi(a, b) -> float: | ||||
|  | ||||
| # Unit tests | ||||
| def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     mocker.patch.multiple( | ||||
|         'freqtrade.exchange.Exchange', | ||||
| @@ -60,6 +59,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: | ||||
|         'amount': 90.99181074, | ||||
|         'close_profit': None, | ||||
|         '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)' | ||||
|     } == results[0] | ||||
|  | ||||
| @@ -80,12 +83,15 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: | ||||
|         'amount': 90.99181074, | ||||
|         'close_profit': None, | ||||
|         '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)' | ||||
|     } == results[0] | ||||
|  | ||||
|  | ||||
| def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     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, | ||||
|                           limit_buy_order, limit_sell_order, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     mocker.patch.multiple( | ||||
| @@ -176,7 +181,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, | ||||
|         'freqtrade.rpc.fiat_convert.Market', | ||||
|         ticker=MagicMock(return_value={'price_usd': 15000.0}), | ||||
|     ) | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
| @@ -336,7 +340,6 @@ def test_rpc_balance_handle(default_conf, mocker): | ||||
|         'freqtrade.rpc.fiat_convert.Market', | ||||
|         ticker=MagicMock(return_value={'price_usd': 15000.0}), | ||||
|     ) | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     mocker.patch.multiple( | ||||
| @@ -391,7 +393,6 @@ def test_rpc_start(mocker, default_conf) -> None: | ||||
|  | ||||
|  | ||||
| def test_rpc_stop(mocker, default_conf) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     mocker.patch.multiple( | ||||
| @@ -416,7 +417,6 @@ def test_rpc_stop(mocker, default_conf) -> None: | ||||
|  | ||||
|  | ||||
| def test_rpc_stopbuy(mocker, default_conf) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     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, | ||||
|                             limit_sell_order, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     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: | ||||
|     default_conf['forcebuy_enable'] = True | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     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: | ||||
|     default_conf['forcebuy_enable'] = True | ||||
|     default_conf['initial_state'] = 'stopped' | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     default_conf['pairlist'] = {'method': 'VolumePairList', | ||||
|                                 '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) | ||||
|     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.state import State | ||||
| from freqtrade.strategy.interface import SellType | ||||
| from freqtrade.tests.conftest import (get_patched_freqtradebot, | ||||
|                                       log_has, patch_coinmarketcap, patch_exchange) | ||||
| from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange) | ||||
| 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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker, None) | ||||
|  | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker, None) | ||||
|     chat = Chat(0xdeadbeef, 0) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|  | ||||
|     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']['chat_id'] = 123 | ||||
|  | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch.multiple( | ||||
|         'freqtrade.exchange.Exchange', | ||||
| @@ -206,6 +201,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: | ||||
|             'amount': 90.99181074, | ||||
|             'close_profit': None, | ||||
|             '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)' | ||||
|         }]), | ||||
|         _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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch.multiple( | ||||
|         '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 | ||||
|     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 'ETH/BTC' in msg_mock.call_args_list[0][0][0] | ||||
|  | ||||
|  | ||||
| def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch.multiple( | ||||
|         '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, | ||||
|                       limit_sell_order, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch.multiple( | ||||
|         '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, | ||||
|                        limit_buy_order, limit_sell_order, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||
|     mocker.patch.multiple( | ||||
| @@ -548,7 +549,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: | ||||
|             '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_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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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, | ||||
|                           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) | ||||
|     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', 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, | ||||
|                                ticker_sell_down, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) | ||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||
|                  return_value=15000.0) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) | ||||
|     patch_exchange(mocker) | ||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||
|                  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: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) | ||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||
|                  return_value=15000.0) | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 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._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: | ||||
|     patch_coinmarketcap(mocker, value={'price_usd': 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()) | ||||
|     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, | ||||
|                             limit_buy_order, limit_sell_order, markets, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     patch_exchange(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( | ||||
|         return_value={ | ||||
| @@ -1165,7 +1148,6 @@ def test_edge_enabled(edge_conf, update, mocker) -> None: | ||||
|  | ||||
|  | ||||
| def test_help_handle(default_conf, update, mocker) -> None: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     msg_mock = MagicMock() | ||||
|     mocker.patch.multiple( | ||||
|         '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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||
|     bot = MagicMock() | ||||
|     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: | ||||
|     patch_coinmarketcap(mocker) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||
|     bot = MagicMock() | ||||
|     bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import logging | ||||
| from argparse import Namespace | ||||
| from copy import deepcopy | ||||
| from unittest.mock import MagicMock | ||||
| from pathlib import Path | ||||
|  | ||||
| import pytest | ||||
| from jsonschema import Draft4Validator, ValidationError, validate | ||||
| @@ -547,6 +548,23 @@ def test_set_loggers() -> None: | ||||
|     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: | ||||
|     default_conf['forcebuy_enable'] = True | ||||
|     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.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, | ||||
|                                               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 | ||||
|     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.open_order_id = None | ||||
|     # 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.*') | ||||
|     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: | ||||
|     freqtrade = get_patched_freqtradebot(mocker, default_conf) | ||||
|     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', | ||||
|         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) | ||||
|  | ||||
|     # 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, | ||||
|                       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) | ||||
|     patch_get_signal(freqtrade) | ||||
|     freqtrade.strategy.stop_loss_reached = \ | ||||
|         lambda current_rate, trade, current_time, force_stoploss, current_profit: SellCheckTuple( | ||||
|             sell_flag=False, sell_type=SellType.NONE) | ||||
|     freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( | ||||
|             sell_flag=False, sell_type=SellType.NONE)) | ||||
|     freqtrade.create_trade() | ||||
|  | ||||
|     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 | ||||
|     assert freqtrade.handle_trade(trade) is False | ||||
|     assert log_has(f'using positive stop loss mode: 0.01 with offset 0 ' | ||||
|                    f'since we have profit 0.2666%', | ||||
|     assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%', | ||||
|                    caplog.record_tuples) | ||||
|     assert log_has(f'adjusted stop loss', caplog.record_tuples) | ||||
|     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 | ||||
|     assert freqtrade.handle_trade(trade) is False | ||||
|     assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 ' | ||||
|                    f'since we have profit 0.2666%', | ||||
|     assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%', | ||||
|                    caplog.record_tuples) | ||||
|     assert log_has(f'adjusted stop loss', caplog.record_tuples) | ||||
|     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 log_has(f'using positive stop loss mode: 0.05 with offset 0.055 ' | ||||
|                    f'since we have profit 0.1218%', | ||||
|     assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%', | ||||
|                    caplog.record_tuples) | ||||
|     assert log_has(f'adjusted stop loss', caplog.record_tuples) | ||||
|     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.exchange == "binance" | ||||
|     assert trade.max_rate == 0.0 | ||||
|     assert trade.min_rate is None | ||||
|     assert trade.stop_loss == 0.0 | ||||
|     assert trade.initial_stop_loss == 0.0 | ||||
|     assert trade.sell_reason is None | ||||
| @@ -585,7 +586,58 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): | ||||
|                    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( | ||||
|         pair='ETH/BTC', | ||||
|         stake_amount=0.001, | ||||
| @@ -595,40 +647,24 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): | ||||
|         open_rate=1, | ||||
|     ) | ||||
|  | ||||
|     trade.adjust_stop_loss(trade.open_rate, 0.05, True) | ||||
|     assert trade.stop_loss == 0.95 | ||||
|     trade.adjust_min_max_rates(trade.open_rate) | ||||
|     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 | ||||
|     trade.adjust_stop_loss(0.96, 0.05) | ||||
|     assert trade.stop_loss == 0.95 | ||||
|     # check min adjusted, max remained | ||||
|     trade.adjust_min_max_rates(0.96) | ||||
|     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) | ||||
|     trade.adjust_stop_loss(1.3, -0.1) | ||||
|     assert round(trade.stop_loss, 8) == 1.17 | ||||
|     assert trade.max_rate == 1.3 | ||||
|     assert trade.initial_stop_loss == 0.95 | ||||
|     # check max adjusted, min remains | ||||
|     trade.adjust_min_max_rates(1.05) | ||||
|     assert trade.max_rate == 1.05 | ||||
|     assert trade.min_rate == 0.96 | ||||
|  | ||||
|     # 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.max_rate == 1.3 | ||||
|     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 | ||||
|     # current rate "in the middle" - no adjustment | ||||
|     trade.adjust_min_max_rates(1.03) | ||||
|     assert trade.max_rate == 1.05 | ||||
|     assert trade.min_rate == 0.96 | ||||
|  | ||||
|  | ||||
| def test_get_open(default_conf, fee): | ||||
|   | ||||
| @@ -4,8 +4,8 @@ | ||||
| flake8==3.7.7 | ||||
| flake8-type-annotations==0.1.0 | ||||
| flake8-tidy-imports==2.0.0 | ||||
| pytest==4.3.1 | ||||
| pytest-mock==1.10.2 | ||||
| pytest==4.4.0 | ||||
| pytest-mock==1.10.3 | ||||
| pytest-asyncio==0.10.0 | ||||
| pytest-cov==2.6.1 | ||||
| coveralls==1.7.0 | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| ccxt==1.18.406 | ||||
| ccxt==1.18.425 | ||||
| SQLAlchemy==1.3.1 | ||||
| python-telegram-bot==11.1.0 | ||||
| arrow==0.13.1 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user