diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 055fee3b2..481a219d6 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -109,7 +109,8 @@ CONF_SCHEMA = { 'properties': { 'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, - 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES} + 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, + 'stoploss_on_exchange': {'type': 'boolean'} }, 'required': ['buy', 'sell', 'stoploss'] }, diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 3ccd2369a..6e826794a 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -227,6 +227,12 @@ class Exchange(object): raise OperationalException( f'Exchange {self.name} does not support market orders.') + if order_types.get('stoploss_on_exchange', False): + if self.name is not 'Binance': + raise OperationalException( + 'On exchange stoploss is not supported for %s.' % self.name + ) + def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 005f698dd..d9a15f56a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -54,7 +54,6 @@ class FreqtradeBot(object): # Init objects self.config = config self.strategy: IStrategy = StrategyResolver(self.config).strategy - self.check_strategy_config_consistency(config, self.strategy) self.rpc: RPCManager = RPCManager(self) self.persistence = None @@ -68,16 +67,6 @@ class FreqtradeBot(object): self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist'] self._init_modules() - def check_strategy_config_consistency(self, config, strategy: IStrategy) -> None: - """ - checks if config is compatible with the given strategy - """ - # Stoploss on exchange is only implemented for binance - if strategy.stoploss_on_exchange and config.get('exchange') is not 'binance': - raise OperationalException( - 'On exchange stoploss is not supported for %s.' % config['exchange']['name'] - ) - def _init_modules(self) -> None: """ Initializes all modules and updates the config @@ -567,7 +556,7 @@ class FreqtradeBot(object): trade.update(order) - if self.strategy.stoploss_on_exchange: + if self.strategy.order_types.get('stoploss_on_exchange'): result = self.handle_stoploss_on_exchange(trade) if result: self.wallets.update() @@ -844,7 +833,7 @@ class FreqtradeBot(object): sell_type = 'stoploss' # First cancelling stoploss on exchange ... - if self.strategy.stoploss_on_exchange and trade.stoploss_order_id: + if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: self.exchange.cancel_order(trade.stoploss_order_id, trade.pair) # Execute sell and update trade record diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index b282a5938..9c850a8be 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -32,7 +32,8 @@ class DefaultStrategy(IStrategy): order_types = { 'buy': 'limit', 'sell': 'limit', - 'stoploss': 'limit' + 'stoploss': 'limit', + 'stoploss_on_exchange': False } def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d1e22850c..1073f8028 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -68,11 +68,6 @@ class IStrategy(ABC): # associated stoploss stoploss: float - # if the stoploss should be on exchange. - # if this is True then a stoploss order will be placed - # immediately after a successful buy order. - stoploss_on_exchange: bool = False - # associated ticker interval ticker_interval: str @@ -80,7 +75,8 @@ class IStrategy(ABC): order_types: Dict = { 'buy': 'limit', 'sell': 'limit', - 'stoploss': 'limit' + 'stoploss': 'limit', + 'stoploss_on_exchange': False } # run "populate_indicators" only for new candle @@ -228,7 +224,7 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - if self.stoploss_on_exchange: + if self.order_types.get('stoploss_on_exchange'): stoplossflag = SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) else: stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 57be54262..1a46ff001 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -362,7 +362,14 @@ def test_validate_order_types(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market'} + mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + default_conf['order_types'] = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'market', + 'stoploss_on_exchange': False + } + Exchange(default_conf) type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False}) @@ -374,6 +381,17 @@ def test_validate_order_types(default_conf, mocker): match=r'Exchange .* does not support market orders.'): Exchange(default_conf) + default_conf['order_types'] = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': True + } + + with pytest.raises(OperationalException, + match=r'On exchange stoploss is not supported for .*'): + Exchange(default_conf) + def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 5acf4fdcb..b6b42d1da 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -893,7 +893,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) freqtrade = FreqtradeBot(default_conf) - freqtrade.strategy.stoploss_on_exchange = True + freqtrade.strategy.order_types['stoploss_on_exchange'] = True trade = MagicMock() trade.open_order_id = None @@ -1595,7 +1595,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order) freqtrade = FreqtradeBot(default_conf) - freqtrade.strategy.stoploss_on_exchange = True + freqtrade.strategy.order_types['stoploss_on_exchange'] = True patch_get_signal(freqtrade) # Create some test data @@ -1647,7 +1647,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) freqtrade = FreqtradeBot(default_conf) - freqtrade.strategy.stoploss_on_exchange = True + freqtrade.strategy.order_types['stoploss_on_exchange'] = True patch_get_signal(freqtrade) # Create some test data @@ -2516,9 +2516,3 @@ def test_startup_messages(default_conf, mocker): default_conf['dynamic_whitelist'] = 20 freqtrade = get_patched_freqtradebot(mocker, default_conf) assert freqtrade.state is State.RUNNING - - -def test_check_consistency(default_conf, mocker, caplog): - mocker.patch('freqtrade.freqtradebot.IStrategy.stoploss_on_exchange', True) - with pytest.raises(OperationalException): - FreqtradeBot(default_conf)