diff --git a/config.json.example b/config.json.example index cae64aab5..c28345963 100644 --- a/config.json.example +++ b/config.json.example @@ -31,7 +31,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 212e9f0e9..fa01c43a9 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -161,11 +161,17 @@ 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 not min_roi_reached(trade, current_rate, datetime.utcnow()): return False - # 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 not get_signal(trade.pair, SignalType.SELL): diff --git a/freqtrade/misc.py b/freqtrade/misc.py index b01fd9fe9..bf05a9a63 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -233,7 +233,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 d9b774a9d..f7cca3585 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -250,3 +250,99 @@ def test_balance_fully_last_side(mocker): def test_balance_bigger_last_ask(mocker): mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}) assert get_target_bid({'ask': 5, 'last': 10}) == 5 + + +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