diff --git a/docs/configuration.md b/docs/configuration.md index 384775765..7aade1e59 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -29,6 +29,7 @@ The table below will list all configuration parameters. | `exchange.pair_whitelist` | [] | No | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param. | `exchange.pair_blacklist` | [] | No | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param. | `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`. +| `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision. | `telegram.enabled` | true | Yes | Enable or not the usage of Telegram. | `telegram.token` | token | No | Your Telegram bot token. Only required is `enable` is `true`. | `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required is `enable` is `true`. diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 0d01576df..039002ab2 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -281,36 +281,36 @@ def analyze_ticker(ticker_history: List[Dict]) -> DataFrame: return dataframe -def get_signal(pair: str, signal: SignalType) -> bool: +def get_signal(pair: str) -> (bool, bool): """ Calculates current signal based several technical analysis indicators :param pair: pair in format BTC_ANT or BTC-ANT - :return: True if pair is good for buying, False otherwise + :return: (True, False) if pair is good for buying and not for selling """ ticker_hist = get_ticker_history(pair) if not ticker_hist: logger.warning('Empty ticker history for pair %s', pair) - return False + return (False, False) try: dataframe = analyze_ticker(ticker_hist) except ValueError as ex: logger.warning('Unable to analyze ticker for pair %s: %s', pair, str(ex)) - return False + return (False, False) except Exception as ex: logger.exception('Unexpected error when analyzing ticker for pair %s: %s', pair, str(ex)) - return False + return (False, False) if dataframe.empty: - return False + return (False, False) latest = dataframe.iloc[-1] # Check if dataframe is out of date signal_date = arrow.get(latest['date']) if signal_date < arrow.now() - timedelta(minutes=10): - return False + return (False, False) - result = latest[signal.value] == 1 - logger.debug('%s_trigger: %s (pair=%s, signal=%s)', signal.value, latest['date'], pair, result) - return result + (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 + logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) + return (buy, sell) diff --git a/freqtrade/main.py b/freqtrade/main.py index 01f3d6e16..34ab5fa6c 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -14,7 +14,7 @@ from cachetools import cached, TTLCache from freqtrade import (DependencyException, OperationalException, __version__, exchange, persistence, rpc) -from freqtrade.analyze import SignalType, get_signal +from freqtrade.analyze import get_signal from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.misc import (State, get_state, load_config, parse_args, throttle, update_state) @@ -261,24 +261,28 @@ def handle_trade(trade: Trade) -> bool: logger.debug('Handling %s ...', trade) current_rate = exchange.get_ticker(trade.pair)['bid'] - # Check if minimal roi has been reached - if min_roi_reached(trade, current_rate, datetime.utcnow()): + (buy, sell) = (False, False) + + if _CONF.get('experimental', {}).get('use_sell_signal'): + (buy, sell) = get_signal(trade.pair) + + # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) + if not buy and min_roi_reached(trade, current_rate, datetime.utcnow()): logger.debug('Executing sell due to ROI ...') execute_sell(trade, current_rate) return True + # Experimental: Check if the trade is profitable before selling it (avoid selling at loss) + if _CONF.get('experimental', {}).get('sell_profit_only', False): + logger.debug('Checking if trade is profitable ...') + if not buy and trade.calc_profit(rate=current_rate) <= 0: + return False + # Experimental: Check if sell signal has been enabled and triggered - if _CONF.get('experimental', {}).get('use_sell_signal'): - # Experimental: Check if the trade is profitable before selling it (avoid selling at loss) - if _CONF.get('experimental', {}).get('sell_profit_only'): - logger.debug('Checking if trade is profitable ...') - if trade.calc_profit(rate=current_rate) <= 0: - return False - logger.debug('Checking sell_signal ...') - if get_signal(trade.pair, SignalType.SELL): - logger.debug('Executing sell due to sell signal ...') - execute_sell(trade, current_rate) - return True + if sell and not buy: + logger.debug('Executing sell due to sell signal ...') + execute_sell(trade, current_rate) + return True return False @@ -319,7 +323,8 @@ def create_trade(stake_amount: float) -> bool: # Pick pair based on StochRSI buy signals for _pair in whitelist: - if get_signal(_pair, SignalType.BUY): + (buy, sell) = get_signal(_pair) + if buy and not sell: pair = _pair break else: diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 58bf0154b..992693248 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -77,7 +77,7 @@ def test_authorized_only_exception(default_conf, mocker): def test_status_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', @@ -112,7 +112,7 @@ def test_status_handle(default_conf, update, ticker, mocker): def test_status_table_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple( @@ -154,7 +154,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker): def test_profit_handle( default_conf, update, ticker, ticker_sell_up, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', @@ -210,7 +210,7 @@ def test_profit_handle( def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, @@ -247,7 +247,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, @@ -308,7 +308,7 @@ def test_exec_forcesell_open_orders(default_conf, ticker, mocker): def test_forcesell_all_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, @@ -339,7 +339,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker): def test_forcesell_handle_invalid(default_conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True)) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, @@ -376,7 +376,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker): def test_performance_handle( default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', @@ -410,7 +410,7 @@ def test_performance_handle( def test_daily_handle( default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', @@ -460,7 +460,7 @@ def test_daily_handle( def test_count_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram', @@ -492,7 +492,7 @@ def test_count_handle(default_conf, update, ticker, mocker): def test_performance_handle_invalid(default_conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True)) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index d1afc4200..b8cd2f6e3 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -6,7 +6,7 @@ import arrow import pytest from pandas import DataFrame -from freqtrade.analyze import (SignalType, get_signal, parse_ticker_dataframe, +from freqtrade.analyze import (get_signal, parse_ticker_dataframe, populate_buy_trend, populate_indicators, populate_sell_trend) @@ -40,30 +40,30 @@ def test_returns_latest_buy_signal(mocker): mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch( 'freqtrade.analyze.analyze_ticker', - return_value=DataFrame([{'buy': 1, 'date': arrow.utcnow()}]) + return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) - assert get_signal('BTC-ETH', SignalType.BUY) + assert get_signal('BTC-ETH') == (True, False) mocker.patch( 'freqtrade.analyze.analyze_ticker', - return_value=DataFrame([{'buy': 0, 'date': arrow.utcnow()}]) + return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) - assert not get_signal('BTC-ETH', SignalType.BUY) + assert get_signal('BTC-ETH') == (False, True) def test_returns_latest_sell_signal(mocker): mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch( 'freqtrade.analyze.analyze_ticker', - return_value=DataFrame([{'sell': 1, 'date': arrow.utcnow()}]) + return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) ) - assert get_signal('BTC-ETH', SignalType.SELL) + assert get_signal('BTC-ETH') == (False, True) mocker.patch( 'freqtrade.analyze.analyze_ticker', - return_value=DataFrame([{'sell': 0, 'date': arrow.utcnow()}]) + return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) - assert not get_signal('BTC-ETH', SignalType.SELL) + assert get_signal('BTC-ETH') == (True, False) def test_get_signal_handles_exceptions(mocker): @@ -71,4 +71,4 @@ def test_get_signal_handles_exceptions(mocker): mocker.patch('freqtrade.analyze.analyze_ticker', side_effect=Exception('invalid ticker history ')) - assert not get_signal('BTC-ETH', SignalType.BUY) + assert get_signal('BTC-ETH') == (False, False) diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 95eb0ad82..b889a01ba 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -10,7 +10,6 @@ from sqlalchemy import create_engine import freqtrade.main as main from freqtrade import DependencyException, OperationalException -from freqtrade.analyze import SignalType from freqtrade.exchange import Exchanges from freqtrade.main import (_process, check_handle_timedout, create_trade, execute_sell, get_target_bid, handle_trade, init) @@ -52,7 +51,7 @@ def test_main_start_hyperopt(mocker): def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker, @@ -82,7 +81,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, m def test_process_exchange_failures(default_conf, ticker, health, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -99,7 +98,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker): msg_mock = MagicMock() mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=msg_mock) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker, @@ -117,8 +116,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker): def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) - mocker.patch('freqtrade.main.get_signal', - side_effect=lambda *args: False if args[1] == SignalType.SELL else True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker, @@ -140,7 +138,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, m def test_create_trade(default_conf, ticker, limit_buy_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -171,7 +169,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker): def test_create_trade_minimal_amount(default_conf, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) buy_mock = mocker.patch( 'freqtrade.main.exchange.buy', MagicMock(return_value='mocked_limit_buy') ) @@ -187,7 +185,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, mocker): def test_create_trade_no_stake_amount(default_conf, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -200,7 +198,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker): def test_create_trade_no_pairs(default_conf, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -216,7 +214,7 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker): def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -233,7 +231,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker): def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -256,6 +254,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): trade.update(limit_buy_order) assert trade.is_open is True + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) handle_trade(trade) assert trade.open_order_id == 'mocked_limit_sell' @@ -268,11 +267,57 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): assert trade.close_date is not None +def test_handle_overlpapping_signals(default_conf, ticker, mocker, caplog): + default_conf.update({'experimental': {'use_sell_signal': True}}) + mocker.patch.dict('freqtrade.main._CONF', default_conf) + + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True)) + mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value='mocked_limit_buy')) + mocker.patch('freqtrade.main.min_roi_reached', return_value=False) + + init(default_conf, create_engine('sqlite://')) + create_trade(0.001) + + # Buy and Sell triggering, so doing nothing ... + trades = Trade.query.all() + assert len(trades) == 0 + + # Buy is triggering, so buying ... + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) + create_trade(0.001) + trades = Trade.query.all() + assert len(trades) == 1 + assert trades[0].is_open is True + + # Buy and Sell are not triggering, so doing nothing ... + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, False)) + assert handle_trade(trades[0]) is False + trades = Trade.query.all() + assert len(trades) == 1 + assert trades[0].is_open is True + + # Buy and Sell are triggering, so doing nothing ... + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True)) + assert handle_trade(trades[0]) is False + trades = Trade.query.all() + assert len(trades) == 1 + assert trades[0].is_open is True + + # Sell is triggering, guess what : we are Selling! + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) + trades = Trade.query.all() + assert handle_trade(trades[0]) is True + + def test_handle_trade_roi(default_conf, ticker, mocker, caplog): default_conf.update({'experimental': {'use_sell_signal': True}}) mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -291,11 +336,11 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog): # we might just want to check if we are in a sell condition without # executing # if ROI is reached we must sell - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) assert handle_trade(trade) assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples # if ROI is reached we must sell even if sell-signal is not signalled - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) assert handle_trade(trade) assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples @@ -304,7 +349,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog): default_conf.update({'experimental': {'use_sell_signal': True}}) mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -318,11 +363,10 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog): trade = Trade.query.first() trade.is_open = True - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, False)) value_returned = handle_trade(trade) - assert ('freqtrade', logging.DEBUG, 'Checking sell_signal ...') in caplog.record_tuples assert value_returned is False - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) assert handle_trade(trade) s = 'Executing sell due to sell signal ...' assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples @@ -330,7 +374,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog): def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -475,7 +519,7 @@ def test_balance_bigger_last_ask(mocker): def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch('freqtrade.rpc.init', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', @@ -508,7 +552,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker): def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch('freqtrade.rpc.init', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', @@ -545,7 +589,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker): def test_execute_sell_without_conf(default_conf, ticker, ticker_sell_up, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch('freqtrade.rpc.init', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', @@ -582,7 +626,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.min_roi_reached', return_value=False) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -598,6 +642,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker): trade = Trade.query.first() trade.update(limit_buy_order) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) assert handle_trade(trade) is True @@ -609,7 +654,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.min_roi_reached', return_value=False) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -625,6 +670,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker): trade = Trade.query.first() trade.update(limit_buy_order) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) assert handle_trade(trade) is True @@ -636,7 +682,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.min_roi_reached', return_value=False) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -652,6 +698,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker): trade = Trade.query.first() trade.update(limit_buy_order) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) assert handle_trade(trade) is False @@ -663,7 +710,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.min_roi_reached', return_value=False) - mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False)) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), @@ -679,4 +726,6 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker): trade = Trade.query.first() trade.update(limit_buy_order) + + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True)) assert handle_trade(trade) is True