diff --git a/docs/backtesting.md b/docs/backtesting.md index ac7c8e11a..45759e2aa 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -79,12 +79,14 @@ Please also read about the [strategy startup period](strategy-customization.md#s Sometimes your account has certain fee rebates (fee reductions starting with a certain account size or monthly volume), which are not visible to ccxt. To account for this in backtesting, you can use `--fee 0.001` to supply this value to backtesting. -This fee must be a percentage, and will be applied twice (once for trade entry, and once for trade exit). +This fee must be a ratio, and will be applied twice (once for trade entry, and once for trade exit). ```bash freqtrade backtesting --fee 0.001 ``` +!!! Note + Only supply this option (or the corresponding configuration parameter) if you want to experiment with different fee values. By default, Backtesting fetches the default fee from the exchange pair/market info. #### Running backtest with smaller testset by using timerange diff --git a/docs/installation.md b/docs/installation.md index 27b7a94c5..267d91c8d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -270,3 +270,18 @@ The easiest way is to download install Microsoft Visual Studio Community [here]( Now you have an environment ready, the next step is [Bot Configuration](configuration.md). + +## Troubleshooting + +### MacOS installation error + +Newer versions of MacOS may have installation failed with errors like `error: command 'g++' failed with exit status 1`. + +This error will require explicit installation of the SDK Headers, which are not installed by default in this version of MacOS. +For MacOS 10.14, this can be accomplished with the below command. + +``` bash +open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg +``` + +If this file is inexistant, then you're probably on a different version of MacOS, so you may need to consult the internet for specific resolution details. diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 43eead46a..6f56790f4 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -1,4 +1,5 @@ import logging +from copy import deepcopy from typing import Any, Dict from jsonschema import Draft4Validator, validators @@ -42,15 +43,25 @@ def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]: :param conf: Config in JSON format :return: Returns the config if valid, otherwise throw an exception """ + conf_schema = deepcopy(constants.CONF_SCHEMA) + if conf.get('runmode', RunMode.OTHER) in (RunMode.DRY_RUN, RunMode.LIVE): + conf_schema['required'] = constants.SCHEMA_TRADE_REQUIRED + else: + conf_schema['required'] = constants.SCHEMA_MINIMAL_REQUIRED + # Dynamically allow empty stake-currency + # Since the minimal config specifies this too. + # It's not allowed for Dry-run or live modes + conf_schema['properties']['stake_currency']['enum'] += [''] # type: ignore + try: - FreqtradeValidator(constants.CONF_SCHEMA).validate(conf) + FreqtradeValidator(conf_schema).validate(conf) return conf except ValidationError as e: logger.critical( f"Invalid configuration. See config.json.example. Reason: {e}" ) raise ValidationError( - best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message + best_match(Draft4Validator(conf_schema).iter_errors(conf)).message ) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a7f569f63..b74e4b096 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -282,19 +282,27 @@ CONF_SCHEMA = { 'required': ['process_throttle_secs', 'allowed_risk', 'capital_available_percentage'] } }, - 'required': [ - 'exchange', - 'max_open_trades', - 'stake_currency', - 'stake_amount', - 'dry_run', - 'dry_run_wallet', - 'bid_strategy', - 'unfilledtimeout', - 'stoploss', - 'minimal_roi', - 'internals', - 'dataformat_ohlcv', - 'dataformat_trades', - ] } + +SCHEMA_TRADE_REQUIRED = [ + 'exchange', + 'max_open_trades', + 'stake_currency', + 'stake_amount', + 'dry_run', + 'dry_run_wallet', + 'bid_strategy', + 'unfilledtimeout', + 'stoploss', + 'minimal_roi', + 'internals', + 'dataformat_ohlcv', + 'dataformat_trades', +] + +SCHEMA_MINIMAL_REQUIRED = [ + 'exchange', + 'dry_run', + 'dataformat_ohlcv', + 'dataformat_trades', +] diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 2fc931a9b..04b2ca980 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -47,7 +47,7 @@ def load_backtest_data(filename) -> pd.DataFrame: utc=True, infer_datetime_format=True ) - df['profitabs'] = df['close_rate'] - df['open_rate'] + df['profit'] = df['close_rate'] - df['open_rate'] df = df.sort_values("open_time").reset_index(drop=True) return df diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f33a8cd03..373055165 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -27,6 +27,7 @@ from freqtrade.state import State from freqtrade.strategy.interface import IStrategy, SellType from freqtrade.wallets import Wallets + logger = logging.getLogger(__name__) @@ -132,12 +133,12 @@ class FreqtradeBot: self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), self.strategy.informative_pairs()) - # First process current opened trades - self.process_maybe_execute_sells(trades) + # First process current opened trades (positions) + self.exit_positions(trades) # Then looking for buy opportunities if self.get_free_open_trades(): - self.process_maybe_execute_buys() + self.enter_positions() # Check and handle any timed out open orders self.check_handle_timedout() @@ -181,6 +182,43 @@ class FreqtradeBot: open_trades = len(Trade.get_open_trades()) return max(0, self.config['max_open_trades'] - open_trades) +# +# BUY / enter positions / open trades logic and methods +# + + def enter_positions(self) -> int: + """ + Tries to execute buy orders for new trades (positions) + """ + trades_created = 0 + + whitelist = copy.deepcopy(self.active_pair_whitelist) + if not whitelist: + logger.info("Active pair whitelist is empty.") + else: + # Remove pairs for currently opened trades from the whitelist + for trade in Trade.get_open_trades(): + if trade.pair in whitelist: + whitelist.remove(trade.pair) + logger.debug('Ignoring %s in pair whitelist', trade.pair) + + if not whitelist: + logger.info("No currency pair in active pair whitelist, " + "but checking to sell open trades.") + else: + # Create entity and execute trade for each pair from whitelist + for pair in whitelist: + try: + trades_created += self.create_trade(pair) + except DependencyException as exception: + logger.warning('Unable to create trade for %s: %s', pair, exception) + + if not trades_created: + logger.debug("Found no buy signals for whitelisted currencies. " + "Trying again...") + + return trades_created + def get_target_bid(self, pair: str, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price @@ -294,65 +332,50 @@ class FreqtradeBot: # See also #2575 at github. return max(min_stake_amounts) / amount_reserve_percent - def create_trades(self) -> bool: + def create_trade(self, pair: str) -> bool: """ - Checks the implemented trading strategy for buy-signals, using the active pair whitelist. - If a pair triggers the buy_signal a new trade record gets created. - Checks pairs as long as the open trade count is below `max_open_trades`. - :return: True if at least one trade has been created. - """ - whitelist = copy.deepcopy(self.active_pair_whitelist) + Check the implemented trading strategy for buy signals. - if not whitelist: - logger.info("Active pair whitelist is empty.") + If the pair triggers the buy signal a new trade record gets created + and the buy-order opening the trade gets issued towards the exchange. + + :return: True if a trade has been created. + """ + logger.debug(f"create_trade for pair {pair}") + + if self.strategy.is_pair_locked(pair): + logger.info(f"Pair {pair} is currently locked.") return False - # Remove currently opened and latest pairs from whitelist - for trade in Trade.get_open_trades(): - if trade.pair in whitelist: - whitelist.remove(trade.pair) - logger.debug('Ignoring %s in pair whitelist', trade.pair) - - if not whitelist: - logger.info("No currency pair in active pair whitelist, " - "but checking to sell open trades.") - return False - - buycount = 0 # running get_signal on historical data fetched - for pair in whitelist: - if self.strategy.is_pair_locked(pair): - logger.info(f"Pair {pair} is currently locked.") - continue + (buy, sell) = self.strategy.get_signal( + pair, self.strategy.ticker_interval, + self.dataprovider.ohlcv(pair, self.strategy.ticker_interval)) - (buy, sell) = self.strategy.get_signal( - pair, self.strategy.ticker_interval, - self.dataprovider.ohlcv(pair, self.strategy.ticker_interval)) + if buy and not sell: + if not self.get_free_open_trades(): + logger.debug("Can't open a new trade: max number of trades is reached.") + return False - if buy and not sell: - if not self.get_free_open_trades(): - logger.debug("Can't open a new trade: max number of trades is reached") - continue + stake_amount = self.get_trade_stake_amount(pair) + if not stake_amount: + logger.debug("Stake amount is 0, ignoring possible trade for {pair}.") + return False - stake_amount = self.get_trade_stake_amount(pair) - if not stake_amount: - logger.debug("Stake amount is 0, ignoring possible trade for {pair}.") - continue + logger.info(f"Buy signal found: about create a new trade with stake_amount: " + f"{stake_amount} ...") - logger.info(f"Buy signal found: about create a new trade with stake_amount: " - f"{stake_amount} ...") + bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) + if ((bid_check_dom.get('enabled', False)) and + (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): + if self._check_depth_of_market_buy(pair, bid_check_dom): + return self.execute_buy(pair, stake_amount) + else: + return False - bidstrat_check_depth_of_market = self.config.get('bid_strategy', {}).\ - get('check_depth_of_market', {}) - if (bidstrat_check_depth_of_market.get('enabled', False)) and\ - (bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): - if self._check_depth_of_market_buy(pair, bidstrat_check_depth_of_market): - buycount += self.execute_buy(pair, stake_amount) - continue - - buycount += self.execute_buy(pair, stake_amount) - - return buycount > 0 + return self.execute_buy(pair, stake_amount) + else: + return False def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: """ @@ -377,14 +400,12 @@ class FreqtradeBot: :param pair: pair for which we want to create a LIMIT_BUY :return: None """ - stake_currency = self.config['stake_currency'] - fiat_currency = self.config.get('fiat_display_currency', None) time_in_force = self.strategy.order_time_in_force['buy'] if price: buy_limit_requested = price else: - # Calculate amount + # Calculate price buy_limit_requested = self.get_target_bid(pair) min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested) @@ -435,17 +456,6 @@ class FreqtradeBot: amount = order['amount'] buy_limit_filled_price = order['price'] - self.rpc.send_msg({ - 'type': RPCMessageType.BUY_NOTIFICATION, - 'exchange': self.exchange.name.capitalize(), - 'pair': pair, - 'limit': buy_limit_filled_price, - 'order_type': order_type, - 'stake_amount': stake_amount, - 'stake_currency': stake_currency, - 'fiat_currency': fiat_currency - }) - # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') trade = Trade( @@ -463,6 +473,8 @@ class FreqtradeBot: ticker_interval=timeframe_to_minutes(self.config['ticker_interval']) ) + self._notify_buy(trade, order_type) + # Update fees if order is closed if order_status == 'closed': self.update_trade_state(trade, order) @@ -475,121 +487,53 @@ class FreqtradeBot: return True - def process_maybe_execute_buys(self) -> None: + def _notify_buy(self, trade: Trade, order_type: str): """ - Tries to execute buy orders for trades in a safe way + Sends rpc notification when a buy occured. """ - try: - # Create entity and execute trade - if not self.create_trades(): - logger.debug('Found no buy signals for whitelisted currencies. Trying again...') - except DependencyException as exception: - logger.warning('Unable to create trade: %s', exception) + msg = { + 'type': RPCMessageType.BUY_NOTIFICATION, + 'exchange': self.exchange.name.capitalize(), + 'pair': trade.pair, + 'limit': trade.open_rate, + 'order_type': order_type, + 'stake_amount': trade.stake_amount, + 'stake_currency': self.config['stake_currency'], + 'fiat_currency': self.config.get('fiat_display_currency', None), + } - def process_maybe_execute_sells(self, trades: List[Any]) -> None: + # Send the message + self.rpc.send_msg(msg) + +# +# SELL / exit positions / close trades logic and methods +# + + def exit_positions(self, trades: List[Any]) -> int: """ - Tries to execute sell orders for trades in a safe way + Tries to execute sell orders for open trades (positions) """ - result = False + trades_closed = 0 for trade in trades: try: self.update_trade_state(trade) if (self.strategy.order_types.get('stoploss_on_exchange') and self.handle_stoploss_on_exchange(trade)): - result = True + trades_closed += 1 continue # Check if we can sell our current pair if trade.open_order_id is None and self.handle_trade(trade): - result = True + trades_closed += 1 except DependencyException as exception: logger.warning('Unable to sell trade: %s', exception) # Updating wallets if any trade occured - if result: + if trades_closed: self.wallets.update() - def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float: - """ - Get real amount for the trade - Necessary for exchanges which charge fees in base currency (e.g. binance) - """ - if order_amount is None: - order_amount = order['amount'] - # Only run for closed orders - if trade.fee_open == 0 or order['status'] == 'open': - return order_amount - - # use fee from order-dict if possible - if ('fee' in order and order['fee'] is not None and - (order['fee'].keys() >= {'currency', 'cost'})): - if (order['fee']['currency'] is not None and - order['fee']['cost'] is not None and - trade.pair.startswith(order['fee']['currency'])): - new_amount = order_amount - order['fee']['cost'] - logger.info("Applying fee on amount for %s (from %s to %s) from Order", - trade, order['amount'], new_amount) - return new_amount - - # Fallback to Trades - trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair, - trade.open_date) - - if len(trades) == 0: - logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) - return order_amount - amount = 0 - fee_abs = 0 - for exectrade in trades: - amount += exectrade['amount'] - if ("fee" in exectrade and exectrade['fee'] is not None and - (exectrade['fee'].keys() >= {'currency', 'cost'})): - # only applies if fee is in quote currency! - if (exectrade['fee']['currency'] is not None and - exectrade['fee']['cost'] is not None and - trade.pair.startswith(exectrade['fee']['currency'])): - fee_abs += exectrade['fee']['cost'] - - if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): - logger.warning(f"Amount {amount} does not match amount {trade.amount}") - raise DependencyException("Half bought? Amounts don't match") - real_amount = amount - fee_abs - if fee_abs != 0: - logger.info(f"Applying fee on amount for {trade} " - 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) - try: - order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair) - except InvalidOrderException as exception: - logger.warning('Unable to fetch order %s: %s', trade.open_order_id, exception) - return - # Try update amount (binance-fix) - try: - new_amount = self.get_real_amount(trade, order) - if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): - order['amount'] = new_amount - # Fee was applied, so set to 0 - trade.fee_open = 0 - trade.recalc_open_trade_price() - - except DependencyException as exception: - logger.warning("Could not update trade amount: %s", exception) - - trade.update(order) - - # Updating wallets when order is closed - if not trade.is_open: - self.wallets.update() + return trades_closed def get_sell_rate(self, pair: str, refresh: bool) -> float: """ @@ -963,16 +907,16 @@ class FreqtradeBot: except InvalidOrderException: logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") - ordertype = self.strategy.order_types[sell_type] + order_type = self.strategy.order_types[sell_type] if sell_reason == SellType.EMERGENCY_SELL: # Emergencysells (default to market!) - ordertype = self.strategy.order_types.get("emergencysell", "market") + order_type = self.strategy.order_types.get("emergencysell", "market") amount = self._safe_sell_amount(trade.pair, trade.amount) # Execute sell and update trade record order = self.exchange.sell(pair=str(trade.pair), - ordertype=ordertype, + ordertype=order_type, amount=amount, rate=limit, time_in_force=self.strategy.order_time_in_force['sell'] ) @@ -988,7 +932,7 @@ class FreqtradeBot: # Lock pair for one candle to prevent immediate rebuys self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['ticker_interval'])) - self._notify_sell(trade, ordertype) + self._notify_sell(trade, order_type) def _notify_sell(self, trade: Trade, order_type: str): """ @@ -1015,17 +959,99 @@ class FreqtradeBot: 'profit_percent': profit_percent, 'sell_reason': trade.sell_reason, 'open_date': trade.open_date, - 'close_date': trade.close_date or datetime.utcnow() + 'close_date': trade.close_date or datetime.utcnow(), + 'stake_currency': self.config['stake_currency'], } - # For regular case, when the configuration exists - if 'stake_currency' in self.config and 'fiat_display_currency' in self.config: - stake_currency = self.config['stake_currency'] - fiat_currency = self.config['fiat_display_currency'] + if 'fiat_display_currency' in self.config: msg.update({ - 'stake_currency': stake_currency, - 'fiat_currency': fiat_currency, + 'fiat_currency': self.config['fiat_display_currency'], }) # Send the message self.rpc.send_msg(msg) + +# +# Common update trade state methods +# + + 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) + try: + order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair) + except InvalidOrderException as exception: + logger.warning('Unable to fetch order %s: %s', trade.open_order_id, exception) + return + # Try update amount (binance-fix) + try: + new_amount = self.get_real_amount(trade, order) + if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): + order['amount'] = new_amount + # Fee was applied, so set to 0 + trade.fee_open = 0 + trade.recalc_open_trade_price() + + except DependencyException as exception: + logger.warning("Could not update trade amount: %s", exception) + + trade.update(order) + + # Updating wallets when order is closed + if not trade.is_open: + self.wallets.update() + + def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float: + """ + Get real amount for the trade + Necessary for exchanges which charge fees in base currency (e.g. binance) + """ + if order_amount is None: + order_amount = order['amount'] + # Only run for closed orders + if trade.fee_open == 0 or order['status'] == 'open': + return order_amount + + # use fee from order-dict if possible + if ('fee' in order and order['fee'] is not None and + (order['fee'].keys() >= {'currency', 'cost'})): + if (order['fee']['currency'] is not None and + order['fee']['cost'] is not None and + trade.pair.startswith(order['fee']['currency'])): + new_amount = order_amount - order['fee']['cost'] + logger.info("Applying fee on amount for %s (from %s to %s) from Order", + trade, order['amount'], new_amount) + return new_amount + + # Fallback to Trades + trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair, + trade.open_date) + + if len(trades) == 0: + logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) + return order_amount + amount = 0 + fee_abs = 0 + for exectrade in trades: + amount += exectrade['amount'] + if ("fee" in exectrade and exectrade['fee'] is not None and + (exectrade['fee'].keys() >= {'currency', 'cost'})): + # only applies if fee is in quote currency! + if (exectrade['fee']['currency'] is not None and + exectrade['fee']['cost'] is not None and + trade.pair.startswith(exectrade['fee']['currency'])): + fee_abs += exectrade['fee']['cost'] + + if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): + logger.warning(f"Amount {amount} does not match amount {trade.amount}") + raise DependencyException("Half bought? Amounts don't match") + real_amount = amount - fee_abs + if fee_abs != 0: + logger.info(f"Applying fee on amount for {trade} " + f"(from {order_amount} to {real_amount}) from Trades") + return real_amount diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 7c7f10fdc..6b2d426e7 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -122,8 +122,8 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: ) ) # Create description for sell summarizing the trade - desc = trades.apply(lambda row: f"{round(row['profitperc'], 3)}%, {row['sell_reason']}, " - f"{row['duration']} min", + desc = trades.apply(lambda row: f"{round(row['profitperc'] * 100, 1)}%, " + f"{row['sell_reason']}, {row['duration']} min", axis=1) trade_sells = go.Scatter( x=trades["close_time"], diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 512dd01f7..4e78b2d75 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -12,7 +12,8 @@ from colorama import init as colorama_init from tabulate import tabulate from freqtrade.configuration import (Configuration, TimeRange, - remove_credentials) + remove_credentials, + validate_config_consistency) from freqtrade.configuration.directory_operations import (copy_sample_files, create_userdata_dir) from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGY @@ -42,6 +43,7 @@ def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str # Ensure we do not use Exchange credentials remove_credentials(config) + validate_config_consistency(config) return config diff --git a/setup.sh b/setup.sh index c4b6e074a..fb5102e12 100755 --- a/setup.sh +++ b/setup.sh @@ -263,7 +263,7 @@ function install() { echo "-------------------------" echo "Run the bot !" echo "-------------------------" - echo "You can now use the bot by executing 'source .env/bin/activate; freqtrade'." + echo "You can now use the bot by executing 'source .env/bin/activate; freqtrade trade'." } function plot() { diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 13711c63e..60d9c3ea5 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -20,7 +20,7 @@ def test_load_backtest_data(testdatadir): filename = testdatadir / "backtest-result_test.json" bt_data = load_backtest_data(filename) assert isinstance(bt_data, DataFrame) - assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profitabs"] + assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profit"] assert len(bt_data) == 179 # Test loading from string (must yield same result) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 31632bd70..ac959cb0e 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -41,7 +41,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_trade_status() - freqtradebot.create_trades() + freqtradebot.enter_positions() results = rpc._rpc_trade_status() assert { 'trade_id': 1, @@ -116,7 +116,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_status_table(default_conf['stake_currency'], 'USD') - freqtradebot.create_trades() + freqtradebot.enter_positions() result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert "Since" in headers @@ -162,7 +162,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() assert trade @@ -217,7 +217,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -231,7 +231,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, trade.close_date = datetime.utcnow() trade.is_open = False - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -299,7 +299,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, rpc = RPC(freqtradebot) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -529,7 +529,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: msg = rpc._rpc_forcesell('all') assert msg == {'result': 'Created sell orders for all open trades.'} - freqtradebot.create_trades() + freqtradebot.enter_positions() msg = rpc._rpc_forcesell('all') assert msg == {'result': 'Created sell orders for all open trades.'} @@ -563,7 +563,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: assert cancel_order_mock.call_count == 1 assert trade.amount == filled_amount - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.filter(Trade.id == '2').first() amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it @@ -582,7 +582,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: assert cancel_order_mock.call_count == 2 assert trade.amount == amount - freqtradebot.create_trades() + freqtradebot.enter_positions() # make an limit-sell open trade mocker.patch( 'freqtrade.exchange.Exchange.get_order', @@ -613,7 +613,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, rpc = RPC(freqtradebot) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() assert trade @@ -649,7 +649,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: assert counts["current"] == 0 # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() counts = rpc._rpc_count() assert counts["current"] == 1 diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 36bb81e41..25c971bf7 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -267,7 +267,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json["max"] == 1.0 # Create some test data - ftbot.create_trades() + ftbot.enter_positions() rc = client_get(client, f"{BASE_URI}/count") assert_response(rc) assert rc.json["current"] == 1.0 @@ -333,7 +333,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li assert len(rc.json) == 1 assert rc.json == {"error": "Error querying _profit: no closed trade"} - ftbot.create_trades() + ftbot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade @@ -422,7 +422,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): assert_response(rc, 200) assert rc.json == [] - ftbot.create_trades() + ftbot.enter_positions() rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) assert len(rc.json) == 1 @@ -552,7 +552,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): assert_response(rc, 502) assert rc.json == {"error": "Error querying _forcesell: invalid argument"} - ftbot.create_trades() + ftbot.enter_positions() rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ddbc35bd5..ffc29ee12 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -148,11 +148,6 @@ def test_status(default_conf, update, mocker, fee, ticker,) -> None: default_conf['telegram']['enabled'] = False default_conf['telegram']['chat_id'] = "123" - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - fetch_ticker=ticker, - get_fee=fee, - ) msg_mock = MagicMock() status_table = MagicMock() mocker.patch.multiple( @@ -184,13 +179,8 @@ def test_status(default_conf, update, mocker, fee, ticker,) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) - # Create some test data - for _ in range(3): - freqtradebot.create_trades() - telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -236,7 +226,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: msg_mock.reset_mock() # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() # Trigger status while we have a fulfilled order for the open trade telegram._status(update=update, context=MagicMock()) @@ -285,7 +275,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: msg_mock.reset_mock() # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() telegram._status_table(update=update, context=MagicMock()) @@ -322,7 +312,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() assert trade @@ -352,7 +342,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, msg_mock.reset_mock() freqtradebot.config['max_open_trades'] = 2 # Add two other trades - freqtradebot.create_trades() + n = freqtradebot.enter_positions() + assert n == 2 trades = Trade.query.all() for trade in trades: @@ -431,7 +422,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, msg_mock.reset_mock() # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade @@ -709,7 +700,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() assert trade @@ -764,7 +755,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() # Decrease the price and sell it mocker.patch.multiple( @@ -821,7 +812,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() rpc_mock.reset_mock() # /forcesell all @@ -971,7 +962,7 @@ def test_performance_handle(default_conf, update, ticker, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() trade = Trade.query.first() assert trade @@ -1014,7 +1005,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING # Create some test data - freqtradebot.create_trades() + freqtradebot.enter_positions() msg_mock.reset_mock() telegram._count(update=update, context=MagicMock()) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index ee3d23131..531a88688 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -49,6 +49,7 @@ def test_load_config_missing_attributes(default_conf) -> None: conf = deepcopy(default_conf) conf.pop('stake_currency') + conf['runmode'] = RunMode.DRY_RUN with pytest.raises(ValidationError, match=r".*'stake_currency' is a required property.*"): validate_config_schema(conf) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 3f5b7bd54..1c17ce735 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -247,7 +247,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf freqtrade.active_pair_whitelist = ['NEO/BTC'] patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) ############################################# @@ -287,7 +287,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, freqtrade.active_pair_whitelist = ['NEO/BTC'] patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) ############################################# @@ -310,7 +310,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade is not None @@ -318,7 +318,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, assert trade.is_open assert trade.open_date is not None - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.order_by(Trade.id.desc()).first() assert trade is not None @@ -462,7 +462,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) / 0.9, 8) -def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> None: +def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -476,7 +476,7 @@ def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> No whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.create_trade('ETH/BTC') trade = Trade.query.first() assert trade is not None @@ -494,8 +494,8 @@ def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> No assert whitelist == default_conf['exchange']['pair_whitelist'] -def test_create_trades_no_stake_amount(default_conf, ticker, limit_buy_order, - fee, mocker) -> None: +def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, + fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5) @@ -509,11 +509,11 @@ def test_create_trades_no_stake_amount(default_conf, ticker, limit_buy_order, patch_get_signal(freqtrade) with pytest.raises(DependencyException, match=r'.*stake amount.*'): - freqtrade.create_trades() + freqtrade.create_trade('ETH/BTC') -def test_create_trades_minimal_amount(default_conf, ticker, limit_buy_order, - fee, mocker) -> None: +def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, + fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) buy_mock = MagicMock(return_value={'id': limit_buy_order['id']}) @@ -527,13 +527,13 @@ def test_create_trades_minimal_amount(default_conf, ticker, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.create_trade('ETH/BTC') rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount'] assert rate * amount >= default_conf['stake_amount'] -def test_create_trades_too_small_stake_amount(default_conf, ticker, limit_buy_order, - fee, mocker) -> None: +def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order, + fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) buy_mock = MagicMock(return_value={'id': limit_buy_order['id']}) @@ -549,11 +549,11 @@ def test_create_trades_too_small_stake_amount(default_conf, ticker, limit_buy_or patch_get_signal(freqtrade) - assert not freqtrade.create_trades() + assert not freqtrade.create_trade('ETH/BTC') -def test_create_trades_limit_reached(default_conf, ticker, limit_buy_order, - fee, markets, mocker) -> None: +def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order, + fee, markets, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -569,12 +569,12 @@ def test_create_trades_limit_reached(default_conf, ticker, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - assert not freqtrade.create_trades() + assert not freqtrade.create_trade('ETH/BTC') assert freqtrade.get_trade_stake_amount('ETH/BTC') is None -def test_create_trades_no_pairs_let(default_conf, ticker, limit_buy_order, fee, - mocker, caplog) -> None: +def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order, fee, + mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -588,14 +588,16 @@ def test_create_trades_no_pairs_let(default_conf, ticker, limit_buy_order, fee, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - assert freqtrade.create_trades() - assert not freqtrade.create_trades() - assert log_has("No currency pair in active pair whitelist, " - "but checking to sell open trades.", caplog) + n = freqtrade.enter_positions() + assert n == 1 + assert not log_has_re(r"No currency pair in active pair whitelist.*", caplog) + n = freqtrade.enter_positions() + assert n == 0 + assert log_has_re(r"No currency pair in active pair whitelist.*", caplog) -def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee, - mocker, caplog) -> None: +def test_enter_positions_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee, + mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -608,11 +610,12 @@ def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_ord freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - assert not freqtrade.create_trades() + n = freqtrade.enter_positions() + assert n == 0 assert log_has("Active pair whitelist is empty.", caplog) -def test_create_trades_no_signal(default_conf, fee, mocker) -> None: +def test_create_trade_no_signal(default_conf, fee, mocker) -> None: default_conf['dry_run'] = True patch_RPCManager(mocker) @@ -628,7 +631,7 @@ def test_create_trades_no_signal(default_conf, fee, mocker) -> None: Trade.query = MagicMock() Trade.query.filter = MagicMock() - assert not freqtrade.create_trades() + assert not freqtrade.create_trade('ETH/BTC') @pytest.mark.parametrize("max_open", range(0, 5)) @@ -646,7 +649,8 @@ def test_create_trades_multiple_trades(default_conf, ticker, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + n = freqtrade.enter_positions() + assert n == max_open trades = Trade.get_open_trades() assert len(trades) == max_open @@ -672,7 +676,8 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker) -> None: assert len(Trade.get_open_trades()) == 2 # Create 2 new trades using create_trades - assert freqtrade.create_trades() + assert freqtrade.create_trade('ETH/BTC') + assert freqtrade.create_trade('NEO/BTC') trades = Trade.get_open_trades() assert len(trades) == 4 @@ -987,7 +992,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None trade.is_open = True trades = [trade] - freqtrade.process_maybe_execute_sells(trades) + freqtrade.exit_positions(trades) assert trade.stoploss_order_id == '13434334' assert stoploss_limit.call_count == 1 assert trade.is_open is True @@ -1056,7 +1061,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, # should unset stoploss_order_id and return true # as a trade actually happened caplog.clear() - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1114,7 +1119,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.is_open = True trade.open_order_id = '12345' @@ -1149,7 +1154,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee, patch_get_signal(freqtrade) freqtrade.strategy.order_types['stoploss_on_exchange'] = True - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() caplog.clear() freqtrade.create_stoploss_order(trade, 200, 199) @@ -1207,7 +1212,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1295,7 +1300,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c # setting stoploss_on_exchange_interval to 60 seconds freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1376,7 +1381,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1440,27 +1445,33 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, stop_price=0.00002344 * 0.99) -def test_process_maybe_execute_buys(mocker, default_conf, caplog) -> None: +def test_enter_positions(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trades', MagicMock(return_value=False)) - freqtrade.process_maybe_execute_buys() + mock_ct = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', + MagicMock(return_value=False)) + n = freqtrade.enter_positions() + assert n == 0 assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog) + # create_trade should be called once for every pair in the whitelist. + assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist']) -def test_process_maybe_execute_buys_exception(mocker, default_conf, caplog) -> None: +def test_enter_positions_exception(mocker, default_conf, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch( - 'freqtrade.freqtradebot.FreqtradeBot.create_trades', + mock_ct = mocker.patch( + 'freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(side_effect=DependencyException) ) - freqtrade.process_maybe_execute_buys() - assert log_has('Unable to create trade: ', caplog) + n = freqtrade.enter_positions() + assert n == 0 + assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist']) + assert log_has('Unable to create trade for ETH/BTC: ', caplog) -def test_process_maybe_execute_sells(mocker, default_conf, limit_buy_order, caplog) -> None: +def test_exit_positions(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)) @@ -1473,7 +1484,8 @@ def test_process_maybe_execute_sells(mocker, default_conf, limit_buy_order, capl trade.open_order_id = '123' trade.open_fee = 0.001 trades = [trade] - assert not freqtrade.process_maybe_execute_sells(trades) + n = freqtrade.exit_positions(trades) + assert n == 0 # Test amount not modified by fee-logic assert not log_has( 'Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(trade), caplog @@ -1481,11 +1493,11 @@ def test_process_maybe_execute_sells(mocker, default_conf, limit_buy_order, capl mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81) # test amount modified by fee-logic - assert not freqtrade.process_maybe_execute_sells(trades) + n = freqtrade.exit_positions(trades) + assert n == 0 -def test_process_maybe_execute_sells_exception(mocker, default_conf, - limit_buy_order, caplog) -> None: +def test_exit_positions_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) @@ -1499,7 +1511,8 @@ def test_process_maybe_execute_sells_exception(mocker, default_conf, 'freqtrade.freqtradebot.FreqtradeBot.update_trade_state', side_effect=DependencyException() ) - freqtrade.process_maybe_execute_sells(trades) + n = freqtrade.exit_positions(trades) + assert n == 0 assert log_has('Unable to sell trade: ', caplog) @@ -1674,7 +1687,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -1697,7 +1710,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock assert trade.close_date is not None -def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, mocker) -> None: +def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order, fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -1711,7 +1724,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, patch_get_signal(freqtrade, value=(True, True)) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() # Buy and Sell triggering, so doing nothing ... trades = Trade.query.all() @@ -1720,7 +1733,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, # Buy is triggering, so buying ... patch_get_signal(freqtrade, value=(True, False)) - freqtrade.create_trades() + freqtrade.enter_positions() trades = Trade.query.all() nb_trades = len(trades) assert nb_trades == 1 @@ -1764,7 +1777,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, patch_get_signal(freqtrade, value=(True, False)) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.is_open = True @@ -1795,7 +1808,7 @@ def test_handle_trade_use_sell_signal( freqtrade = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.is_open = True @@ -1823,7 +1836,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, patch_get_signal(freqtrade) # Create trade and sell it - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -2183,7 +2196,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -2231,7 +2244,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -2281,7 +2294,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -2339,7 +2352,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c freqtrade.strategy.order_types['stoploss_on_exchange'] = True patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() Trade.session = MagicMock() @@ -2382,13 +2395,13 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade trades = [trade] - freqtrade.process_maybe_execute_sells(trades) + freqtrade.exit_positions(trades) # Increase the price and sell it mocker.patch.multiple( @@ -2432,10 +2445,10 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trades = [trade] - freqtrade.process_maybe_execute_sells(trades) + freqtrade.exit_positions(trades) assert trade assert trade.stoploss_order_id == '123' assert trade.open_order_id is None @@ -2463,7 +2476,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f }) mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_limit_executed) - freqtrade.process_maybe_execute_sells(trades) + freqtrade.exit_positions(trades) assert trade.stoploss_order_id is None assert trade.is_open is False assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value @@ -2484,7 +2497,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee, patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -2546,7 +2559,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2577,7 +2590,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2608,7 +2621,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker patch_get_signal(freqtrade) freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( sell_flag=False, sell_type=SellType.NONE)) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2638,7 +2651,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2667,7 +2680,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() amnt = trade.amount @@ -2735,7 +2748,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -2754,7 +2767,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo # reinit - should buy other pair. caplog.clear() - freqtrade.create_trades() + freqtrade.enter_positions() assert log_has(f"Pair {trade.pair} is currently locked.", caplog) @@ -2779,7 +2792,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, mocker) -> patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2812,7 +2825,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert freqtrade.handle_trade(trade) is False @@ -2867,7 +2880,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2924,7 +2937,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2987,7 +3000,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -3046,7 +3059,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() trade.update(limit_buy_order) @@ -3377,7 +3390,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade is not None @@ -3412,7 +3425,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o # Save state of current whitelist freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade is None @@ -3514,7 +3527,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trades() + freqtrade.enter_positions() trade = Trade.query.first() assert trade @@ -3604,7 +3617,7 @@ def test_process_i_am_alive(default_conf, mocker, caplog): @pytest.mark.usefixtures("init_persistence") -def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order): +def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order, caplog): default_conf['dry_run'] = True # Initialize to 2 times stake amount default_conf['dry_run_wallet'] = 0.002 @@ -3621,12 +3634,14 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order) patch_get_signal(bot) assert bot.wallets.get_free('BTC') == 0.002 - bot.create_trades() + n = bot.enter_positions() + assert n == 2 trades = Trade.query.all() assert len(trades) == 2 bot.config['max_open_trades'] = 3 - with pytest.raises( - DependencyException, - match=r"Available balance \(0 BTC\) is lower than stake amount \(0.001 BTC\)"): - bot.create_trades() + n = bot.enter_positions() + assert n == 0 + assert log_has_re(r"Unable to create trade for XRP/BTC: " + r"Available balance \(0 BTC\) is lower than stake amount \(0.001 BTC\)", + caplog) diff --git a/tests/test_integration.py b/tests/test_integration.py index 11dbca225..1108969ad 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -80,7 +80,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trades() + freqtrade.enter_positions() wallets_mock.reset_mock() Trade.session = MagicMock() @@ -90,7 +90,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, trade.stoploss_order_id = 3 trade.open_order_id = None - freqtrade.process_maybe_execute_sells(trades) + n = freqtrade.exit_positions(trades) + assert n == 2 assert should_sell_mock.call_count == 2 # Only order for 3rd trade needs to be cancelled @@ -153,7 +154,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc patch_get_signal(freqtrade) # Create 4 trades - freqtrade.create_trades() + n = freqtrade.enter_positions() + assert n == 4 trades = Trade.query.all() assert len(trades) == 4 @@ -170,7 +172,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc assert len(trades) == 5 bals = freqtrade.wallets.get_all_balances() - freqtrade.process_maybe_execute_sells(trades) + n = freqtrade.exit_positions(trades) + assert n == 1 trades = Trade.get_open_trades() # One trade sold assert len(trades) == 4 diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 9934d2493..271246517 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -119,6 +119,7 @@ def test_plot_trades(testdatadir, caplog): assert trade_sell.yaxis == 'y' assert len(trades) == len(trade_sell.x) assert trade_sell.marker.color == 'red' + assert trade_sell.text[0] == "4.0%, roi, 15 min" def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, testdatadir, caplog): diff --git a/user_data/backtest_data/.gitkeep b/user_data/backtest_data/.gitkeep deleted file mode 100644 index e69de29bb..000000000