diff --git a/config.json.example b/config.json.example index f94e423eb..1f39a5df7 100644 --- a/config.json.example +++ b/config.json.example @@ -32,7 +32,8 @@ ] }, "experimental": { - "use_sell_signal": false + "use_sell_signal": false, + "sell_profit_only": false }, "telegram": { "enabled": true, diff --git a/freqtrade/main.py b/freqtrade/main.py index afbb84ec4..918c44246 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -191,13 +191,19 @@ def handle_trade(trade: Trade) -> bool: logger.debug('Handling %s ...', trade) current_rate = exchange.get_ticker(trade.pair)['bid'] + # 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 + # Check if minimal roi has been reached if min_roi_reached(trade, current_rate, datetime.utcnow()): logger.debug('Executing sell due to ROI ...') execute_sell(trade, current_rate) return True - # Check if sell signal has been enabled and triggered + # Experimental: Check if sell signal has been enabled and triggered if _CONF.get('experimental', {}).get('use_sell_signal'): logger.debug('Checking sell_signal ...') if get_signal(trade.pair, SignalType.SELL): diff --git a/freqtrade/misc.py b/freqtrade/misc.py index b3b17459f..8364863f1 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -234,7 +234,8 @@ CONF_SCHEMA = { 'experimental': { 'type': 'object', 'properties': { - 'use_sell_signal': {'type': 'boolean'} + 'use_sell_signal': {'type': 'boolean'}, + 'sell_profit_only': {'type': 'boolean'} } }, 'telegram': { diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 73a00d61b..b3660ba33 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -432,3 +432,99 @@ def test_execute_sell_without_conf(default_conf, ticker, ticker_sell_up, mocker) assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '(profit: ~6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] assert 'USD' not in rpc_mock.call_args_list[-1][0][0] + + +def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker): + default_conf['experimental'] = {} + default_conf['experimental']['sell_profit_only'] = True + + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': 0.00002172, + 'ask': 0.00002173, + 'last': 0.00002172 + }), + buy=MagicMock(return_value='mocked_limit_buy')) + + init(default_conf, create_engine('sqlite://')) + create_trade(0.001) + + trade = Trade.query.first() + trade.update(limit_buy_order) + assert handle_trade(trade) is True + + +def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker): + default_conf['experimental'] = {} + default_conf['experimental']['sell_profit_only'] = False + + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': 0.00002172, + 'ask': 0.00002173, + 'last': 0.00002172 + }), + buy=MagicMock(return_value='mocked_limit_buy')) + + init(default_conf, create_engine('sqlite://')) + create_trade(0.001) + + trade = Trade.query.first() + trade.update(limit_buy_order) + assert handle_trade(trade) is True + + +def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker): + default_conf['experimental'] = {} + default_conf['experimental']['sell_profit_only'] = True + + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': 0.00000172, + 'ask': 0.00000173, + 'last': 0.00000172 + }), + buy=MagicMock(return_value='mocked_limit_buy')) + + init(default_conf, create_engine('sqlite://')) + create_trade(0.001) + + trade = Trade.query.first() + trade.update(limit_buy_order) + assert handle_trade(trade) is False + + +def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker): + default_conf['experimental'] = {} + default_conf['experimental']['sell_profit_only'] = False + + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': 0.00000172, + 'ask': 0.00000173, + 'last': 0.00000172 + }), + buy=MagicMock(return_value='mocked_limit_buy')) + + init(default_conf, create_engine('sqlite://')) + create_trade(0.001) + + trade = Trade.query.first() + trade.update(limit_buy_order) + assert handle_trade(trade) is True