From b50250139ec3171309ad62dd784b159e965245b9 Mon Sep 17 00:00:00 2001 From: misagh Date: Mon, 19 Nov 2018 20:02:26 +0100 Subject: [PATCH 01/57] Drafting stoploss on exchange --- freqtrade/strategy/interface.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 212559c8c..d1d4703a4 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -67,6 +67,11 @@ 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 @@ -214,7 +219,11 @@ class IStrategy(ABC): # Set current rate to low for backtesting sell current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, + + if self.stoploss_on_exchange: + stoplossflag = False + else: + stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, force_stoploss=force_stoploss) if stoplossflag.sell_flag: From bfbdddff26bd9296da0b046f260db1ce5778b703 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 16:24:40 +0100 Subject: [PATCH 02/57] stoploss limit order added to exchange --- freqtrade/exchange/__init__.py | 18 ++++++++++++++++++ freqtrade/strategy/interface.py | 2 ++ 2 files changed, 20 insertions(+) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index ae07e36e9..59a5da23e 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -333,6 +333,24 @@ class Exchange(object): except ccxt.BaseError as e: raise OperationalException(e) + def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: + # Only binance is supported + if not self._api.name == 'Binance': + raise NotImplementedError( + 'Stoploss limit orders are implemented only for binance as of now.') + + # Set the precision for amount and price(rate) as accepted by the exchange + amount = self.symbol_amount_prec(pair, amount) + rate = self.symbol_price_prec(pair, rate) + stop_price = self.symbol_price_prec(pair, stop_price) + + # Ensure rate is less than stop price + if stop_price >= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + + return self._api.create_order(pair, 'stop_loss', 'sell', amount, rate, {'stopPrice': stop_price}) + @retrier def get_balance(self, currency: str) -> float: if self._conf['dry_run']: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 9b7b180cc..df0e3cf72 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -233,8 +233,10 @@ class IStrategy(ABC): stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, force_stoploss=force_stoploss) + if stoplossflag.sell_flag: return stoplossflag + # Set current rate to low for backtesting sell current_rate = high or rate current_profit = trade.calc_profit_percent(current_rate) From 3b7e05e07b15c1df1bfe62bfbf264feac9877784 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 16:26:24 +0100 Subject: [PATCH 03/57] stop loss order added right after a buy order is executued --- freqtrade/freqtradebot.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8a2db84a9..f1aae3c3f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -508,6 +508,37 @@ class FreqtradeBot(object): Trade.session.add(trade) Trade.session.flush() + # Check if stoploss should be added on exchange + # If True then here immediately after buy we should + # Add the stoploss order + if self.strategy.stoploss_on_exchange: + stoploss = self.edge.stoploss if self.edge else self.strategy.stoploss + stop_price = buy_limit * (1 + stoploss) + + # limit price should be less than stop price. + # 0.98 is arbitrary here. + limit_price = stop_price * 0.98 + + order_id = self.exchange.stoploss_limit(pair=pair, amount=amount, + stop_price=stop_price, rate=limit_price)['id'] + + trade = Trade( + pair=pair, + stake_amount=stake_amount, + amount=amount, + fee_open=fee, + fee_close=fee, + stoploss=stop_price, + open_date=datetime.utcnow(), + exchange=self.exchange.id, + open_order_id=order_id, + strategy=self.strategy.get_strategy_name(), + ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] + ) + + Trade.session.add(trade) + Trade.session.flush() + # Updating wallets self.wallets.update() From bb37b56dea02fdf7e5b51e54ae7144e808ab2e08 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 16:47:52 +0100 Subject: [PATCH 04/57] adding stop loss order id to Trade --- freqtrade/persistence.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 51a8129fb..db6d526c7 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -178,6 +178,7 @@ class Trade(_DECL_BASE): # absolute value of the initial stop loss initial_stop_loss = Column(Float, nullable=True, default=0.0) # absolute value of the highest reached price + stoploss_order_id = Column(Integer, nullable=True, index=True) max_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) strategy = Column(String, nullable=True) From fad75939356188b1bf2d8ca7eb82fa5e4201ba4a Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 16:53:50 +0100 Subject: [PATCH 05/57] =?UTF-8?q?doesn=E2=80=99t=20have=20to=20create=20an?= =?UTF-8?q?other=20Trade=20for=20SL.=20can=20be=20cumulated=20into=20the?= =?UTF-8?q?=20same.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freqtrade/freqtradebot.py | 48 ++++++++++++++------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f1aae3c3f..f3537f2ab 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -479,6 +479,22 @@ class FreqtradeBot(object): order_id = self.exchange.buy(pair=pair, ordertype=self.strategy.order_types['buy'], amount=amount, rate=buy_limit)['id'] + stoploss_order_id: int = None + + # Check if stoploss should be added on exchange + # If True then here immediately after buy we should + # Add the stoploss order + if self.strategy.stoploss_on_exchange: + stoploss = self.edge.stoploss if self.edge else self.strategy.stoploss + stop_price = buy_limit * (1 + stoploss) + + # limit price should be less than stop price. + # 0.98 is arbitrary here. + limit_price = stop_price * 0.98 + + stoploss_order_id = self.exchange.stoploss_limit(pair=pair, amount=amount, + stop_price=stop_price, rate=limit_price)['id'] + self.rpc.send_msg({ 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), @@ -502,43 +518,13 @@ class FreqtradeBot(object): open_date=datetime.utcnow(), exchange=self.exchange.id, open_order_id=order_id, + stoploss_order_id=stoploss_order_id, strategy=self.strategy.get_strategy_name(), ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] ) Trade.session.add(trade) Trade.session.flush() - # Check if stoploss should be added on exchange - # If True then here immediately after buy we should - # Add the stoploss order - if self.strategy.stoploss_on_exchange: - stoploss = self.edge.stoploss if self.edge else self.strategy.stoploss - stop_price = buy_limit * (1 + stoploss) - - # limit price should be less than stop price. - # 0.98 is arbitrary here. - limit_price = stop_price * 0.98 - - order_id = self.exchange.stoploss_limit(pair=pair, amount=amount, - stop_price=stop_price, rate=limit_price)['id'] - - trade = Trade( - pair=pair, - stake_amount=stake_amount, - amount=amount, - fee_open=fee, - fee_close=fee, - stoploss=stop_price, - open_date=datetime.utcnow(), - exchange=self.exchange.id, - open_order_id=order_id, - strategy=self.strategy.get_strategy_name(), - ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] - ) - - Trade.session.add(trade) - Trade.session.flush() - # Updating wallets self.wallets.update() From da5617624c97389b0a1aabd3e95e18258e6b6348 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 17:02:02 +0100 Subject: [PATCH 06/57] cancelling stop loss order before selling --- freqtrade/freqtradebot.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f3537f2ab..744f92156 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -505,6 +505,7 @@ class FreqtradeBot(object): '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( @@ -522,6 +523,7 @@ class FreqtradeBot(object): strategy=self.strategy.get_strategy_name(), ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] ) + Trade.session.add(trade) Trade.session.flush() @@ -798,6 +800,11 @@ class FreqtradeBot(object): sell_type = 'sell' if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): sell_type = 'stoploss' + + # First cancelling stoploss on exchange ... + if self.strategy.stoploss_on_exchange and trade.stoploss_order_id: + self.exchange.cancel_order(trade.stoploss_order_id, trade.pair) + # Execute sell and update trade record order_id = self.exchange.sell(pair=str(trade.pair), ordertype=self.strategy.order_types[sell_type], From bbe8e4e49456b8ec5de23ed22901dfb1c4c82561 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 17:07:37 +0100 Subject: [PATCH 07/57] flake8 --- freqtrade/exchange/__init__.py | 3 ++- freqtrade/freqtradebot.py | 4 ++-- freqtrade/strategy/interface.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 59a5da23e..53ae6c2d7 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -349,7 +349,8 @@ class Exchange(object): raise OperationalException( 'In stoploss limit order, stop price should be more than limit price') - return self._api.create_order(pair, 'stop_loss', 'sell', amount, rate, {'stopPrice': stop_price}) + return self._api.create_order(pair, 'stop_loss', 'sell', + amount, rate, {'stopPrice': stop_price}) @retrier def get_balance(self, currency: str) -> float: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 744f92156..40734f385 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -492,8 +492,8 @@ class FreqtradeBot(object): # 0.98 is arbitrary here. limit_price = stop_price * 0.98 - stoploss_order_id = self.exchange.stoploss_limit(pair=pair, amount=amount, - stop_price=stop_price, rate=limit_price)['id'] + stoploss_order_id = self.exchange.stoploss_limit( + pair=pair, amount=amount, stop_price=stop_price, rate=limit_price)['id'] self.rpc.send_msg({ 'type': RPCMessageType.BUY_NOTIFICATION, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index df0e3cf72..9047d8807 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -231,8 +231,8 @@ class IStrategy(ABC): stoplossflag = False else: stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, - current_time=date, current_profit=current_profit, - force_stoploss=force_stoploss) + current_time=date, current_profit=current_profit, + force_stoploss=force_stoploss) if stoplossflag.sell_flag: return stoplossflag From 3a1c378325dc97b48bb1ad767fc5ff8281bdf88c Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 17:14:22 +0100 Subject: [PATCH 08/57] typing bugs --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/strategy/interface.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 40734f385..ac9fd758c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -479,13 +479,13 @@ class FreqtradeBot(object): order_id = self.exchange.buy(pair=pair, ordertype=self.strategy.order_types['buy'], amount=amount, rate=buy_limit)['id'] - stoploss_order_id: int = None + stoploss_order_id = None # Check if stoploss should be added on exchange # If True then here immediately after buy we should # Add the stoploss order if self.strategy.stoploss_on_exchange: - stoploss = self.edge.stoploss if self.edge else self.strategy.stoploss + stoploss = self.edge.stoploss(pair=pair) if self.edge else self.strategy.stoploss stop_price = buy_limit * (1 + stoploss) # limit price should be less than stop price. diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 9047d8807..30fc62f42 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -228,7 +228,7 @@ class IStrategy(ABC): current_profit = trade.calc_profit_percent(current_rate) if self.stoploss_on_exchange: - stoplossflag = False + stoplossflag = SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) else: stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, From 2461d86c8d2d0dd692edfabf4fd79162eb87fbd4 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 17:24:45 +0100 Subject: [PATCH 09/57] dry run should consider stop loss is hit on limit price --- freqtrade/freqtradebot.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ac9fd758c..281b22bc5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -805,6 +805,11 @@ class FreqtradeBot(object): if self.strategy.stoploss_on_exchange and trade.stoploss_order_id: self.exchange.cancel_order(trade.stoploss_order_id, trade.pair) + # Dry-run should consider stoploss is executed at the limit price + # So overriding limit in case of dry-run + if self.config['dry_run']: + limit = trade.stop_loss + # Execute sell and update trade record order_id = self.exchange.sell(pair=str(trade.pair), ordertype=self.strategy.order_types[sell_type], From 24df093a85ea65293b6baa653ab515049e4e0e56 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 17:41:01 +0100 Subject: [PATCH 10/57] test: only implemented for binance --- freqtrade/tests/exchange/test_exchange.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 207f14efe..5cbe5b42e 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1171,3 +1171,10 @@ def test_get_fee(default_conf, mocker): ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'get_fee', 'calculate_fee') + +def test_stoploss_limit_available_only_for_binance(default_conf, mocker): + api_mock = MagicMock() + exchange = get_patched_exchange(mocker, default_conf, api_mock) + with pytest.raises(NotImplementedError): + exchange.stoploss_limit('BTC/ETH', 1, 0.8, 0.79) + From 3418592908a8e01699e485993c1d31ffb0d4c291 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 19:25:26 +0100 Subject: [PATCH 11/57] freqtradebot test added for orders on exchange --- freqtrade/exchange/__init__.py | 2 +- freqtrade/tests/conftest.py | 7 ++-- freqtrade/tests/test_freqtradebot.py | 50 ++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 53ae6c2d7..90660c9aa 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -335,7 +335,7 @@ class Exchange(object): def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: # Only binance is supported - if not self._api.name == 'Binance': + if not self.name == 'Binance': raise NotImplementedError( 'Stoploss limit orders are implemented only for binance as of now.') diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index b6c022b45..3453b4ddf 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -27,12 +27,13 @@ def log_has(line, logs): False) -def patch_exchange(mocker, api_mock=None) -> None: +def patch_exchange(mocker, api_mock=None, id='bittrex') -> None: mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value="Bittrex")) - mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value="bittrex")) + mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) + mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) + if api_mock: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) else: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index cef89c250..af4071591 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -880,6 +880,56 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non assert call_args['rate'] == fix_price assert call_args['amount'] == stake_amount / fix_price +def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, fee, markets, limit_buy_order) -> None: + default_conf['exchange']['name'] = 'binance' + patch_RPCManager(mocker) + patch_exchange(mocker) + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss_on_exchange = True + freqtrade.strategy.stoploss = -0.05 + stake_amount = 2 + bid = 0.11 + get_bid = MagicMock(return_value=bid) + mocker.patch.multiple( + 'freqtrade.freqtradebot.FreqtradeBot', + get_target_bid=get_bid, + _get_min_pair_stake_amount=MagicMock(return_value=1) + ) + buy_mm = MagicMock(return_value={'id': limit_buy_order['id']}) + stoploss_limit = MagicMock(return_value={'id': 13434334}) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172 + }), + buy=buy_mm, + get_fee=fee, + get_markets=markets, + stoploss_limit=stoploss_limit + ) + pair = 'ETH/BTC' + print(buy_mm.call_args_list) + + assert freqtrade.execute_buy(pair, stake_amount) + assert stoploss_limit.call_count == 1 + assert get_bid.call_count == 1 + assert buy_mm.call_count == 1 + call_args = buy_mm.call_args_list[0][1] + assert call_args['pair'] == pair + assert call_args['rate'] == bid + assert call_args['amount'] == stake_amount / bid + + call_args = stoploss_limit.call_args_list[0][1] + assert call_args['pair'] == pair + assert call_args['amount'] == stake_amount / bid + assert call_args['stop_price'] == 0.11 * 0.95 + assert call_args['rate'] == 0.11 * 0.95 * 0.98 + + trade = Trade.query.first() + assert trade.is_open is True + assert trade.stoploss_order_id == 13434334 def test_process_maybe_execute_buy(mocker, default_conf) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From cc1422d448f898f5570224dda448c3995dc9d600 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 19:27:32 +0100 Subject: [PATCH 12/57] flake8 --- freqtrade/tests/test_freqtradebot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index af4071591..03bc68025 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -880,7 +880,9 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non assert call_args['rate'] == fix_price assert call_args['amount'] == stake_amount / fix_price -def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, fee, markets, limit_buy_order) -> None: + +def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, + fee, markets, limit_buy_order) -> None: default_conf['exchange']['name'] = 'binance' patch_RPCManager(mocker) patch_exchange(mocker) @@ -931,6 +933,7 @@ def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, fee, market assert trade.is_open is True assert trade.stoploss_order_id == 13434334 + def test_process_maybe_execute_buy(mocker, default_conf) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From ecb2c4dca384d08cc9ebf6a112ebb98cefe0b795 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 19:38:20 +0100 Subject: [PATCH 13/57] bloody flake8 --- freqtrade/tests/exchange/test_exchange.py | 2 +- freqtrade/tests/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 5cbe5b42e..9dbc50a66 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1172,9 +1172,9 @@ def test_get_fee(default_conf, mocker): ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'get_fee', 'calculate_fee') + def test_stoploss_limit_available_only_for_binance(default_conf, mocker): api_mock = MagicMock() exchange = get_patched_exchange(mocker, default_conf, api_mock) with pytest.raises(NotImplementedError): exchange.stoploss_limit('BTC/ETH', 1, 0.8, 0.79) - diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 03bc68025..571c89bc0 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -885,7 +885,7 @@ def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, fee, markets, limit_buy_order) -> None: default_conf['exchange']['name'] = 'binance' patch_RPCManager(mocker) - patch_exchange(mocker) + patch_exchange(mocker, id='binance') freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.stoploss_on_exchange = True freqtrade.strategy.stoploss = -0.05 From 07ac9024512e7f96185ee447243ee8d6ccd188f4 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 20:30:31 +0100 Subject: [PATCH 14/57] test exchange added --- freqtrade/exchange/__init__.py | 2 +- freqtrade/tests/conftest.py | 4 +-- freqtrade/tests/exchange/test_exchange.py | 36 +++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 90660c9aa..a88fb6ee8 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -345,7 +345,7 @@ class Exchange(object): stop_price = self.symbol_price_prec(pair, stop_price) # Ensure rate is less than stop price - if stop_price >= rate: + if stop_price <= rate: raise OperationalException( 'In stoploss limit order, stop price should be more than limit price') diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 3453b4ddf..f4c263959 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -40,8 +40,8 @@ def patch_exchange(mocker, api_mock=None, id='bittrex') -> None: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock()) -def get_patched_exchange(mocker, config, api_mock=None) -> Exchange: - patch_exchange(mocker, api_mock) +def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange: + patch_exchange(mocker, api_mock, id) exchange = Exchange(config) return exchange diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 9dbc50a66..53e77d7b6 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1178,3 +1178,39 @@ def test_stoploss_limit_available_only_for_binance(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock) with pytest.raises(NotImplementedError): exchange.stoploss_limit('BTC/ETH', 1, 0.8, 0.79) + + +def test_stoploss_limit_order(default_conf, mocker): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + order_type = 'stop_loss' + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + + with pytest.raises(OperationalException): + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + assert api_mock.create_order.call_args[0][1] == order_type + assert api_mock.create_order.call_args[0][2] == 'sell' + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] == 200 + assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} From 7faafea8a2949a94725b883e89d81a7e854ec7f5 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 21:01:39 +0100 Subject: [PATCH 15/57] added test for cancelling stop loss before sell --- freqtrade/tests/test_freqtradebot.py | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 571c89bc0..333b4d51f 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1528,6 +1528,56 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, } == last_msg +def test_execute_sell_with_stoploss_on_exchange(default_conf, + ticker, fee, ticker_sell_up, + markets, mocker) -> None: + + default_conf['exchange']['name'] = 'binance' + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + _load_markets=MagicMock(return_value={}), + get_ticker=ticker, + get_fee=fee, + get_markets=markets + ) + + stoploss_limit = MagicMock(return_value={ + 'id': 123, + 'info': { + 'foo': 'bar' + } + }) + + cancel_order = MagicMock(return_value=True) + + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order) + + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss_on_exchange = True + patch_get_signal(freqtrade) + + # Create some test data + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade + + # Increase the price and sell it + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=ticker_sell_up + ) + + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) + + trade = Trade.query.first() + assert trade + assert cancel_order.call_count == 1 + + def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 6f0025c6de194c9f3bf3dd3ea43c135c63abef04 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 21:07:33 +0100 Subject: [PATCH 16/57] documentation written --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index 62559a41e..64f0a2ea6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -26,6 +26,7 @@ The table below will list all configuration parameters. | `process_only_new_candles` | false | No | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. Can be set either in Configuration or in the strategy. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. +| `stoploss_on_exchange` | false | No | Only for binance users for now: If this parameter is on then stoploss limit order is executed immediately after buy order is done on binance. | `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached. | `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. From 1dde56790c4650fa7be00108eb807e9c0e806645 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 22 Nov 2018 21:12:49 +0100 Subject: [PATCH 17/57] final broken test fixed --- freqtrade/tests/test_freqtradebot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 333b4d51f..aad5c371f 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1533,6 +1533,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, markets, mocker) -> None: default_conf['exchange']['name'] = 'binance' + rpc_mock = patch_RPCManager(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', _load_markets=MagicMock(return_value={}), @@ -1576,6 +1577,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, trade = Trade.query.first() assert trade assert cancel_order.call_count == 1 + assert rpc_mock.call_count == 2 def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, From fea77824d09a0ebc9c3f1acf09273b799aa39e23 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 23 Nov 2018 15:17:36 +0100 Subject: [PATCH 18/57] handle stop loss on exchange added --- freqtrade/freqtradebot.py | 24 +++++++++ freqtrade/persistence.py | 5 +- freqtrade/strategy/interface.py | 1 + freqtrade/tests/test_freqtradebot.py | 75 +++++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 281b22bc5..1e5dfd175 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -572,6 +572,17 @@ class FreqtradeBot(object): trade.update(order) + # Check if stoploss on exchnage is hit first + if self.strategy.stoploss_on_exchange and trade.stoploss_order_id: + # Check if stoploss is hit + result = self.handle_stoploss_on_exchage(trade) + + # Updating wallets if stoploss is hit + if result: + self.wallets.update() + + return result + if trade.is_open and trade.open_order_id is None: # Check if we can sell our current pair result = self.handle_trade(trade) @@ -676,6 +687,19 @@ class FreqtradeBot(object): logger.info('Found no sell signals for whitelisted currencies. Trying again..') return False + def handle_stoploss_on_exchage(self, trade: Trade) -> bool: + if not trade.is_open: + raise ValueError(f'attempt to handle stoploss on exchnage for a closed trade: {trade}') + + logger.debug('Handling stoploss on exchange %s ...', trade) + order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) + if order['status'] == 'closed': + trade.sell_reason = SellType.STOPLOSS_ON_EXCHNAGE.value + trade.update(order) + return True + else: + return False + def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.edge: stoploss = self.edge.stoploss(trade.pair) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index db6d526c7..02caeeccd 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -178,7 +178,7 @@ class Trade(_DECL_BASE): # absolute value of the initial stop loss initial_stop_loss = Column(Float, nullable=True, default=0.0) # absolute value of the highest reached price - stoploss_order_id = Column(Integer, nullable=True, index=True) + stoploss_order_id = Column(String, nullable=True, index=True) max_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) strategy = Column(String, nullable=True) @@ -250,6 +250,9 @@ class Trade(_DECL_BASE): self.open_order_id = None elif order_type == 'limit' and order['side'] == 'sell': self.close(order['price']) + elif order_type == 'stop_loss_limit': + self.stoploss_order_id = None + self.close(order['price']) else: raise ValueError(f'Unknown order type: {order_type}') cleanup() diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 30fc62f42..d1e22850c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -33,6 +33,7 @@ class SellType(Enum): """ ROI = "roi" STOP_LOSS = "stop_loss" + STOPLOSS_ON_EXCHNAGE = "stoploss_on_exchange" TRAILING_STOP_LOSS = "trailing_stop_loss" SELL_SIGNAL = "sell_signal" FORCE_SELL = "force_sell" diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index aad5c371f..48918645d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -931,7 +931,7 @@ def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, trade = Trade.query.first() assert trade.is_open is True - assert trade.stoploss_order_id == 13434334 + assert trade.stoploss_order_id == '13434334' def test_process_maybe_execute_buy(mocker, default_conf) -> None: @@ -1572,7 +1572,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, get_ticker=ticker_sell_up ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellType.SELL_SIGNAL) trade = Trade.query.first() assert trade @@ -1580,6 +1581,76 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, assert rpc_mock.call_count == 2 +def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, + ticker, fee, + limit_buy_order, + markets, mocker) -> None: + default_conf['exchange']['name'] = 'binance' + rpc_mock = patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + _load_markets=MagicMock(return_value={}), + get_ticker=ticker, + get_fee=fee, + get_markets=markets + ) + + stoploss_limit = MagicMock(return_value={ + 'id': 123, + 'info': { + 'foo': 'bar' + } + }) + + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss_on_exchange = True + patch_get_signal(freqtrade) + + # Create some test data + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade + assert trade.stoploss_order_id == '123' + assert trade.open_order_id is not None + + trade.update(limit_buy_order) + + # Assuming stoploss on exchnage is hit + # stoploss_order_id should become None + # and trade should be sold at the price of stoploss + stoploss_limit_executed = MagicMock(return_value={ + "id": "123", + "timestamp": 1542707426845, + "datetime": "2018-11-20T09:50:26.845Z", + "lastTradeTimestamp": None, + "symbol": "BTC/USDT", + "type": "stop_loss_limit", + "side": "sell", + "price": 1.08801, + "amount": 90.99181074, + "cost": 99.0000000032274, + "average": 1.08801, + "filled": 90.99181074, + "remaining": 0.0, + "status": "closed", + "fee": None, + "trades": None + }) + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_limit_executed) + + freqtrade.process_maybe_execute_sell(trade) + assert trade.stoploss_order_id is None + assert trade.is_open is False + print(trade.sell_reason) + assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHNAGE.value + assert rpc_mock.call_count == 1 + + def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 5ee2faa182fd8c5fd470e50febf266648fbbaf21 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 23 Nov 2018 19:17:36 +0100 Subject: [PATCH 19/57] adding stop loss on exchange after the buy order is fulfilled not before. --- freqtrade/freqtradebot.py | 41 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 1e5dfd175..c2ef0e406 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -479,22 +479,6 @@ class FreqtradeBot(object): order_id = self.exchange.buy(pair=pair, ordertype=self.strategy.order_types['buy'], amount=amount, rate=buy_limit)['id'] - stoploss_order_id = None - - # Check if stoploss should be added on exchange - # If True then here immediately after buy we should - # Add the stoploss order - if self.strategy.stoploss_on_exchange: - stoploss = self.edge.stoploss(pair=pair) if self.edge else self.strategy.stoploss - stop_price = buy_limit * (1 + stoploss) - - # limit price should be less than stop price. - # 0.98 is arbitrary here. - limit_price = stop_price * 0.98 - - stoploss_order_id = self.exchange.stoploss_limit( - pair=pair, amount=amount, stop_price=stop_price, rate=limit_price)['id'] - self.rpc.send_msg({ 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), @@ -519,7 +503,6 @@ class FreqtradeBot(object): open_date=datetime.utcnow(), exchange=self.exchange.id, open_order_id=order_id, - stoploss_order_id=stoploss_order_id, strategy=self.strategy.get_strategy_name(), ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] ) @@ -572,16 +555,32 @@ class FreqtradeBot(object): trade.update(order) - # Check if stoploss on exchnage is hit first - if self.strategy.stoploss_on_exchange and trade.stoploss_order_id: + # Check uf trade is fulfulled in which case the stoploss + # on exchange should be added immediately if stoploss on exchnage + # is on + if self.strategy.stoploss_on_exchange and trade.is_open and \ + trade.open_order_id is None and trade.stoploss_order_id is None: + + stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss + stop_price = trade.open_rate * (1 + stoploss) + + # limit price should be less than stop price. + # 0.98 is arbitrary here. + limit_price = stop_price * 0.98 + + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price)['id'] + trade.stoploss_order_id = stoploss_order_id + + # Or Check if there is a stoploss on exchnage and it is hit + elif self.strategy.stoploss_on_exchange and trade.stoploss_order_id: # Check if stoploss is hit result = self.handle_stoploss_on_exchage(trade) # Updating wallets if stoploss is hit if result: self.wallets.update() - - return result + return result if trade.is_open and trade.open_order_id is None: # Check if we can sell our current pair From 9144a8f79df3c3839e294e4d2285a2a123aa3993 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 23 Nov 2018 20:28:01 +0100 Subject: [PATCH 20/57] tests fixed --- freqtrade/freqtradebot.py | 14 ++++-- freqtrade/tests/test_freqtradebot.py | 70 +++++++++------------------- 2 files changed, 31 insertions(+), 53 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c2ef0e406..d4ea83d25 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -559,9 +559,13 @@ class FreqtradeBot(object): # on exchange should be added immediately if stoploss on exchnage # is on if self.strategy.stoploss_on_exchange and trade.is_open and \ - trade.open_order_id is None and trade.stoploss_order_id is None: + trade.open_order_id is None and trade.stoploss_order_id is None: + + if self.edge: + stoploss = self.edge.stoploss(pair=trade.pair) + else: + stoploss = self.strategy.stoploss - stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss stop_price = trade.open_rate * (1 + stoploss) # limit price should be less than stop price. @@ -569,8 +573,10 @@ class FreqtradeBot(object): limit_price = stop_price * 0.98 stoploss_order_id = self.exchange.stoploss_limit( - pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price)['id'] - trade.stoploss_order_id = stoploss_order_id + pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price + )['id'] + + trade.stoploss_order_id = str(stoploss_order_id) # Or Check if there is a stoploss on exchnage and it is hit elif self.strategy.stoploss_on_exchange and trade.stoploss_order_id: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 48918645d..64c8e9765 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -881,57 +881,29 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non assert call_args['amount'] == stake_amount / fix_price -def test_execute_buy_with_stoploss_on_exchange(mocker, default_conf, - fee, markets, limit_buy_order) -> None: - default_conf['exchange']['name'] = 'binance' +def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None: patch_RPCManager(mocker) - patch_exchange(mocker, id='binance') + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', + return_value=limit_buy_order['amount']) + + stoploss_limit = MagicMock(return_value={'id': 13434334}) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.stoploss_on_exchange = True - freqtrade.strategy.stoploss = -0.05 - stake_amount = 2 - bid = 0.11 - get_bid = MagicMock(return_value=bid) - mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', - get_target_bid=get_bid, - _get_min_pair_stake_amount=MagicMock(return_value=1) - ) - buy_mm = MagicMock(return_value={'id': limit_buy_order['id']}) - stoploss_limit = MagicMock(return_value={'id': 13434334}) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_ticker=MagicMock(return_value={ - 'bid': 0.00001172, - 'ask': 0.00001173, - 'last': 0.00001172 - }), - buy=buy_mm, - get_fee=fee, - get_markets=markets, - stoploss_limit=stoploss_limit - ) - pair = 'ETH/BTC' - print(buy_mm.call_args_list) - assert freqtrade.execute_buy(pair, stake_amount) - assert stoploss_limit.call_count == 1 - assert get_bid.call_count == 1 - assert buy_mm.call_count == 1 - call_args = buy_mm.call_args_list[0][1] - assert call_args['pair'] == pair - assert call_args['rate'] == bid - assert call_args['amount'] == stake_amount / bid + trade = MagicMock() + trade.open_order_id = None + trade.stoploss_order_id = None + trade.is_open = True - call_args = stoploss_limit.call_args_list[0][1] - assert call_args['pair'] == pair - assert call_args['amount'] == stake_amount / bid - assert call_args['stop_price'] == 0.11 * 0.95 - assert call_args['rate'] == 0.11 * 0.95 * 0.98 - - trade = Trade.query.first() - assert trade.is_open is True + freqtrade.process_maybe_execute_sell(trade) assert trade.stoploss_order_id == '13434334' + assert stoploss_limit.call_count == 1 + assert trade.is_open is True def test_process_maybe_execute_buy(mocker, default_conf) -> None: @@ -1566,6 +1538,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, trade = Trade.query.first() assert trade + freqtrade.process_maybe_execute_sell(trade) + # Increase the price and sell it mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -1612,13 +1586,11 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, # Create some test data freqtrade.create_trade() - trade = Trade.query.first() + freqtrade.process_maybe_execute_sell(trade) assert trade assert trade.stoploss_order_id == '123' - assert trade.open_order_id is not None - - trade.update(limit_buy_order) + assert trade.open_order_id is None # Assuming stoploss on exchnage is hit # stoploss_order_id should become None From 1c2c19b12cfa60951e2169c77277452e412374b0 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 23 Nov 2018 20:47:17 +0100 Subject: [PATCH 21/57] the complex in the life of flake8 resolved --- freqtrade/freqtradebot.py | 69 ++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d4ea83d25..3d52ffffa 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -555,35 +555,8 @@ class FreqtradeBot(object): trade.update(order) - # Check uf trade is fulfulled in which case the stoploss - # on exchange should be added immediately if stoploss on exchnage - # is on - if self.strategy.stoploss_on_exchange and trade.is_open and \ - trade.open_order_id is None and trade.stoploss_order_id is None: - - if self.edge: - stoploss = self.edge.stoploss(pair=trade.pair) - else: - stoploss = self.strategy.stoploss - - stop_price = trade.open_rate * (1 + stoploss) - - # limit price should be less than stop price. - # 0.98 is arbitrary here. - limit_price = stop_price * 0.98 - - stoploss_order_id = self.exchange.stoploss_limit( - pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price - )['id'] - - trade.stoploss_order_id = str(stoploss_order_id) - - # Or Check if there is a stoploss on exchnage and it is hit - elif self.strategy.stoploss_on_exchange and trade.stoploss_order_id: - # Check if stoploss is hit + if self.strategy.stoploss_on_exchange: result = self.handle_stoploss_on_exchage(trade) - - # Updating wallets if stoploss is hit if result: self.wallets.update() return result @@ -693,18 +666,40 @@ class FreqtradeBot(object): return False def handle_stoploss_on_exchage(self, trade: Trade) -> bool: - if not trade.is_open: - raise ValueError(f'attempt to handle stoploss on exchnage for a closed trade: {trade}') + # Check uf trade is fulfulled in which case the stoploss + # on exchange should be added immediately if stoploss on exchnage + # is on + if trade.is_open and trade.open_order_id is None and trade.stoploss_order_id is None: + if self.edge: + stoploss = self.edge.stoploss(pair=trade.pair) + else: + stoploss = self.strategy.stoploss - logger.debug('Handling stoploss on exchange %s ...', trade) - order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) - if order['status'] == 'closed': - trade.sell_reason = SellType.STOPLOSS_ON_EXCHNAGE.value - trade.update(order) - return True - else: + stop_price = trade.open_rate * (1 + stoploss) + + # limit price should be less than stop price. + # 0.98 is arbitrary here. + limit_price = stop_price * 0.98 + + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price + )['id'] + + trade.stoploss_order_id = str(stoploss_order_id) return False + # Or Check if there is a stoploss on exchnage and it is hit + elif trade.stoploss_order_id: + logger.debug('Handling stoploss on exchange %s ...', trade) + order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) + if order['status'] == 'closed': + trade.sell_reason = SellType.STOPLOSS_ON_EXCHNAGE.value + trade.update(order) + return True + else: + return False + + def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.edge: stoploss = self.edge.stoploss(trade.pair) From 89eb3d9f36d960d8862bf4fb66b02f2e12a4d7c4 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 23 Nov 2018 20:49:00 +0100 Subject: [PATCH 22/57] blank line removed --- freqtrade/freqtradebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3d52ffffa..bd4e1b9e7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -699,7 +699,6 @@ class FreqtradeBot(object): else: return False - def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.edge: stoploss = self.edge.stoploss(trade.pair) From dedf1ff70340df99647d0f30cc556dcaedc3c291 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 23 Nov 2018 20:51:23 +0100 Subject: [PATCH 23/57] refactoring --- freqtrade/freqtradebot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bd4e1b9e7..335a0f76e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -669,6 +669,7 @@ class FreqtradeBot(object): # Check uf trade is fulfulled in which case the stoploss # on exchange should be added immediately if stoploss on exchnage # is on + result = False if trade.is_open and trade.open_order_id is None and trade.stoploss_order_id is None: if self.edge: stoploss = self.edge.stoploss(pair=trade.pair) @@ -684,9 +685,7 @@ class FreqtradeBot(object): stoploss_order_id = self.exchange.stoploss_limit( pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price )['id'] - trade.stoploss_order_id = str(stoploss_order_id) - return False # Or Check if there is a stoploss on exchnage and it is hit elif trade.stoploss_order_id: @@ -695,9 +694,10 @@ class FreqtradeBot(object): if order['status'] == 'closed': trade.sell_reason = SellType.STOPLOSS_ON_EXCHNAGE.value trade.update(order) - return True + result = True else: - return False + result = False + return result def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.edge: From 1a8e9ebc0f8e60239015fc17dbde7ed7bcd0f77d Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 16:53:10 +0100 Subject: [PATCH 24/57] stoploss_order_id added to migration script --- freqtrade/persistence.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 02caeeccd..364af06ce 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -91,6 +91,7 @@ def check_migrate(engine) -> None: close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null') stop_loss = get_column_def(cols, 'stop_loss', '0.0') initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') + stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') @@ -106,7 +107,7 @@ def check_migrate(engine) -> None: (id, exchange, pair, is_open, fee_open, fee_close, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, - stop_loss, initial_stop_loss, max_rate, sell_reason, strategy, + stop_loss, initial_stop_loss, stoploss_order_id, max_rate, sell_reason, strategy, ticker_interval ) select id, lower(exchange), @@ -122,7 +123,8 @@ def check_migrate(engine) -> None: {close_rate_requested} close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, - {max_rate} max_rate, {sell_reason} sell_reason, {strategy} strategy, + {stoploss_order_id} stoploss_order_id, {max_rate} max_rate, + {sell_reason} sell_reason, {strategy} strategy, {ticker_interval} ticker_interval from {table_back_name} """) @@ -177,8 +179,9 @@ class Trade(_DECL_BASE): stop_loss = Column(Float, nullable=True, default=0.0) # absolute value of the initial stop loss initial_stop_loss = Column(Float, nullable=True, default=0.0) - # absolute value of the highest reached price + # stoploss order id which is on exchange stoploss_order_id = Column(String, nullable=True, index=True) + # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) strategy = Column(String, nullable=True) From a9ec5c66993a00bb8e7d6ec57b3a65079d419144 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 17:07:35 +0100 Subject: [PATCH 25/57] simplifying if conditions --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 335a0f76e..f197d9117 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -670,7 +670,7 @@ class FreqtradeBot(object): # on exchange should be added immediately if stoploss on exchnage # is on result = False - if trade.is_open and trade.open_order_id is None and trade.stoploss_order_id is None: + if trade.is_open and not trade.open_order_id and not trade.stoploss_order_id: if self.edge: stoploss = self.edge.stoploss(pair=trade.pair) else: From afd0a054b2a6bfdac724d61f6e4a23c80cd0bc71 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 17:08:12 +0100 Subject: [PATCH 26/57] typo corrected --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f197d9117..a8736d6f1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -556,7 +556,7 @@ class FreqtradeBot(object): trade.update(order) if self.strategy.stoploss_on_exchange: - result = self.handle_stoploss_on_exchage(trade) + result = self.handle_stoploss_on_exchange(trade) if result: self.wallets.update() return result @@ -665,7 +665,7 @@ class FreqtradeBot(object): logger.info('Found no sell signals for whitelisted currencies. Trying again..') return False - def handle_stoploss_on_exchage(self, trade: Trade) -> bool: + def handle_stoploss_on_exchange(self, trade: Trade) -> bool: # Check uf trade is fulfulled in which case the stoploss # on exchange should be added immediately if stoploss on exchnage # is on From 531d9ecd0c8f15f9bf7721d6d0b691486a240740 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 17:10:51 +0100 Subject: [PATCH 27/57] docstring added --- freqtrade/freqtradebot.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a8736d6f1..369bed173 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -666,9 +666,12 @@ class FreqtradeBot(object): return False def handle_stoploss_on_exchange(self, trade: Trade) -> bool: - # Check uf trade is fulfulled in which case the stoploss - # on exchange should be added immediately if stoploss on exchnage - # is on + """ + Check if trade is fulfilled in which case the stoploss + on exchange should be added immediately if stoploss on exchnage + is enabled. + """ + result = False if trade.is_open and not trade.open_order_id and not trade.stoploss_order_id: if self.edge: From 870631f324eb785fb0faf12c4544b742dc59cb09 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 17:32:25 +0100 Subject: [PATCH 28/57] 1) comments added to handle_sl 2) dry-run force price removed --- freqtrade/freqtradebot.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 369bed173..cf22ee52a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -673,6 +673,9 @@ class FreqtradeBot(object): """ result = False + + # If trade is open and the buy order is fulfilled but there is no stoploss, + # then we add a stoploss on exchange if trade.is_open and not trade.open_order_id and not trade.stoploss_order_id: if self.edge: stoploss = self.edge.stoploss(pair=trade.pair) @@ -690,7 +693,8 @@ class FreqtradeBot(object): )['id'] trade.stoploss_order_id = str(stoploss_order_id) - # Or Check if there is a stoploss on exchnage and it is hit + # Or there is already a stoploss on exchnage. + # so we check if it is hit ... elif trade.stoploss_order_id: logger.debug('Handling stoploss on exchange %s ...', trade) order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) @@ -831,11 +835,6 @@ class FreqtradeBot(object): if self.strategy.stoploss_on_exchange and trade.stoploss_order_id: self.exchange.cancel_order(trade.stoploss_order_id, trade.pair) - # Dry-run should consider stoploss is executed at the limit price - # So overriding limit in case of dry-run - if self.config['dry_run']: - limit = trade.stop_loss - # Execute sell and update trade record order_id = self.exchange.sell(pair=str(trade.pair), ordertype=self.strategy.order_types[sell_type], From 000711b0256e541bf8dada406ae6ad8d77c24701 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 18:08:11 +0100 Subject: [PATCH 29/57] added stoploss_limit_order for dry-run --- freqtrade/tests/exchange/test_exchange.py | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 53e77d7b6..402596da8 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1214,3 +1214,39 @@ def test_stoploss_limit_order(default_conf, mocker): assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] == 200 assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} + + +def test_stoploss_limit_order_dry_run(default_conf, mocker): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + order_type = 'stop_loss' + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + + default_conf['dry_run'] = True + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + + with pytest.raises(OperationalException): + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + assert api_mock.create_order.call_args[0][1] == order_type + assert api_mock.create_order.call_args[0][2] == 'sell' + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] == 200 + assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} From b2c0b20a58c161961d13bb1fa34494516f93332e Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 18:26:04 +0100 Subject: [PATCH 30/57] added real tests for stop on exchange in dry-run --- freqtrade/exchange/__init__.py | 17 +++++++++++++++++ freqtrade/tests/exchange/test_exchange.py | 23 ++++++----------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index a88fb6ee8..fc28516f4 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -349,6 +349,23 @@ class Exchange(object): raise OperationalException( 'In stoploss limit order, stop price should be more than limit price') + if self._conf['dry_run']: + order_id = f'dry_run_buy_{randint(0, 10**6)}' + self._dry_run_open_orders[order_id] = { + 'info': {}, + 'id': order_id, + 'pair': pair, + 'price': stop_price, + 'amount': amount, + 'type': 'stop_loss_limit', + 'side': 'sell', + 'remaining': amount, + 'datetime': arrow.utcnow().isoformat(), + 'status': 'open', + 'fee': None + } + return self._dry_run_open_orders[order_id] + return self._api.create_order(pair, 'stop_loss', 'sell', amount, rate, {'stopPrice': stop_price}) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 402596da8..5ae6a031a 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1218,16 +1218,7 @@ def test_stoploss_limit_order(default_conf, mocker): def test_stoploss_limit_order_dry_run(default_conf, mocker): api_mock = MagicMock() - order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) - order_type = 'stop_loss' - - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'info': { - 'foo': 'bar' - } - }) - + order_type = 'stop_loss_limit' default_conf['dry_run'] = True mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) @@ -1243,10 +1234,8 @@ def test_stoploss_limit_order_dry_run(default_conf, mocker): assert 'id' in order assert 'info' in order - assert order['id'] == order_id - assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' - assert api_mock.create_order.call_args[0][1] == order_type - assert api_mock.create_order.call_args[0][2] == 'sell' - assert api_mock.create_order.call_args[0][3] == 1 - assert api_mock.create_order.call_args[0][4] == 200 - assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} + assert 'type' in order + + assert order['type'] == order_type + assert order['price'] == 220 + assert order['amount'] == 1 From fe8927136c6427c3351017256489c9d18a2c2094 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 18:36:07 +0100 Subject: [PATCH 31/57] typo --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index cf22ee52a..126cc485f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -693,7 +693,7 @@ class FreqtradeBot(object): )['id'] trade.stoploss_order_id = str(stoploss_order_id) - # Or there is already a stoploss on exchnage. + # Or there is already a stoploss on exchange. # so we check if it is hit ... elif trade.stoploss_order_id: logger.debug('Handling stoploss on exchange %s ...', trade) From b5192880df34ef09e6eb8b25dce0d51a64a4a777 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 19:00:59 +0100 Subject: [PATCH 32/57] [WIP] adding tests for handle_stoploss_on_exchange. --- freqtrade/tests/test_freqtradebot.py | 76 ++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 64c8e9765..4ecd287d6 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -906,6 +906,82 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None assert trade.is_open is True +def test_handle_stoploss_on_exchange(mocker, default_conf, fee, + markets, limit_buy_order, limit_sell_order) -> None: + + freqtrade = get_patched_freqtradebot(mocker, default_conf) + stoploss_limit = MagicMock(return_value={'id': 13434334}) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + + # First case: when stoploss is not yet set but the order is open + # should get the stoploss order id immediately + # and should return false as no trade actually happened + trade = MagicMock() + trade.is_open = True + trade.open_order_id = None + trade.stoploss_order_id = None + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert stoploss_limit.call_count == 1 + assert trade.stoploss_order_id == "13434334" + + trade.reset_mock() + + # Second case: when stoploss is set but it is not yet hit + # should do nothing and return false + trade.is_open = True + trade.open_order_id = None + trade.stoploss_order_id = 100 + + hanging_stoploss_order = MagicMock(return_value={'status': 'open'}) + mocker.patch('freqtrade.exchange.Exchange.get_order', hanging_stoploss_order) + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.stoploss_order_id == 100 + + trade.reset_mock() + + # Third case: when stoploss is set and it is hit + # should unset stoploss_order_id and return true + # as a trade actually happened + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + sell=MagicMock(return_value={'id': limit_sell_order['id']}), + get_fee=fee, + get_markets=markets + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade + + # trade = freqtrade.create + # trade.is_open = True + # trade.open_order_id = None + # trade.stoploss_order_id = 100 + + # stoploss_order_hit = MagicMock(return_value={'status': 'closed'}) + # mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) + + # # trade = Trade.query.first() + # # assert trade + # assert freqtrade.handle_stoploss_on_exchange(trade) is True + # time.sleep(0.01) # Race condition fix + # assert trade.is_open is True + # assert trade.stoploss_order_id is None + + def test_process_maybe_execute_buy(mocker, default_conf) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From c8a0956e1bc34fbb418de13d08eac686b3cd6581 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 19:12:00 +0100 Subject: [PATCH 33/57] fixed test handle_stoploss_on_exchange --- freqtrade/tests/test_freqtradebot.py | 68 ++++++++++++---------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4ecd287d6..9b1f40ccf 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -908,10 +908,24 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None def test_handle_stoploss_on_exchange(mocker, default_conf, fee, markets, limit_buy_order, limit_sell_order) -> None: - - freqtrade = get_patched_freqtradebot(mocker, default_conf) stoploss_limit = MagicMock(return_value={'id': 13434334}) - mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + sell=MagicMock(return_value={'id': limit_sell_order['id']}), + get_fee=fee, + get_markets=markets, + stoploss_limit=stoploss_limit + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) # First case: when stoploss is not yet set but the order is open # should get the stoploss order id immediately @@ -925,8 +939,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, assert stoploss_limit.call_count == 1 assert trade.stoploss_order_id == "13434334" - trade.reset_mock() - # Second case: when stoploss is set but it is not yet hit # should do nothing and return false trade.is_open = True @@ -939,47 +951,25 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id == 100 - trade.reset_mock() - # Third case: when stoploss is set and it is hit # should unset stoploss_order_id and return true # as a trade actually happened - patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_ticker=MagicMock(return_value={ - 'bid': 0.00001172, - 'ask': 0.00001173, - 'last': 0.00001172 - }), - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - sell=MagicMock(return_value={'id': limit_sell_order['id']}), - get_fee=fee, - get_markets=markets - ) - freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade) - freqtrade.create_trade() - trade = Trade.query.first() + trade.is_open = True + trade.open_order_id = None + trade.stoploss_order_id = 100 assert trade - # trade = freqtrade.create - # trade.is_open = True - # trade.open_order_id = None - # trade.stoploss_order_id = 100 - - # stoploss_order_hit = MagicMock(return_value={'status': 'closed'}) - # mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) - - # # trade = Trade.query.first() - # # assert trade - # assert freqtrade.handle_stoploss_on_exchange(trade) is True - # time.sleep(0.01) # Race condition fix - # assert trade.is_open is True - # assert trade.stoploss_order_id is None + stoploss_order_hit = MagicMock(return_value={ + 'status': 'closed', + 'type': 'stop_loss_limit', + 'price': 2 + }) + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) + assert freqtrade.handle_stoploss_on_exchange(trade) is True + assert trade.stoploss_order_id is None + assert trade.is_open is False def test_process_maybe_execute_buy(mocker, default_conf) -> None: From 519b1f00e2932d034afb76ee999f4273b8c7467a Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 20:12:50 +0100 Subject: [PATCH 34/57] adding strategy config consistency function --- freqtrade/freqtradebot.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 126cc485f..dc1189bb9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -54,11 +54,14 @@ 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 self.exchange = Exchange(self.config) self.wallets = Wallets(self.exchange) + # Initializing Edge only if enabled self.edge = Edge(self.config, self.exchange, self.strategy) if \ self.config.get('edge', {}).get('enabled', False) else None @@ -66,6 +69,16 @@ 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( + 'Stoploss limit orders are implemented only for binance as of now.') + def _init_modules(self) -> None: """ Initializes all modules and updates the config From 266bd7b9b62d095d7f5d703e2cd8f27439102abd Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 24 Nov 2018 21:42:15 +0100 Subject: [PATCH 35/57] error message improved --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dc1189bb9..fbabb7d90 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -77,7 +77,7 @@ class FreqtradeBot(object): # Stoploss on exchange is only implemented for binance if strategy.stoploss_on_exchange and config.get('exchange') is not 'binance': raise OperationalException( - 'Stoploss limit orders are implemented only for binance as of now.') + 'On exchange stoploss is not supported for %s.' % config.get('exchange')) def _init_modules(self) -> None: """ From 664b96173eabe2e6472e6a812e631aa6378080eb Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 10:54:36 +0100 Subject: [PATCH 36/57] removing NotImplementedError from stoploss_limit --- freqtrade/exchange/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index fc28516f4..3ccd2369a 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -334,10 +334,10 @@ class Exchange(object): raise OperationalException(e) def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: - # Only binance is supported - if not self.name == 'Binance': - raise NotImplementedError( - 'Stoploss limit orders are implemented only for binance as of now.') + """ + creates a stoploss limit order. + NOTICE: it is not supported by all exchanges. only binance is tested for now. + """ # Set the precision for amount and price(rate) as accepted by the exchange amount = self.symbol_amount_prec(pair, amount) From dcae3a26440a07cadb362999003a2d81c6486157 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 11:29:04 +0100 Subject: [PATCH 37/57] test of check_consistency added --- freqtrade/freqtradebot.py | 5 ++--- freqtrade/tests/exchange/test_exchange.py | 7 ------- freqtrade/tests/test_freqtradebot.py | 6 ++++++ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fbabb7d90..005f698dd 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -61,7 +61,6 @@ class FreqtradeBot(object): self.exchange = Exchange(self.config) self.wallets = Wallets(self.exchange) - # Initializing Edge only if enabled self.edge = Edge(self.config, self.exchange, self.strategy) if \ self.config.get('edge', {}).get('enabled', False) else None @@ -73,11 +72,11 @@ class FreqtradeBot(object): """ 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.get('exchange')) + 'On exchange stoploss is not supported for %s.' % config['exchange']['name'] + ) def _init_modules(self) -> None: """ diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 5ae6a031a..57be54262 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1173,13 +1173,6 @@ def test_get_fee(default_conf, mocker): 'get_fee', 'calculate_fee') -def test_stoploss_limit_available_only_for_binance(default_conf, mocker): - api_mock = MagicMock() - exchange = get_patched_exchange(mocker, default_conf, api_mock) - with pytest.raises(NotImplementedError): - exchange.stoploss_limit('BTC/ETH', 1, 0.8, 0.79) - - def test_stoploss_limit_order(default_conf, mocker): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 9b1f40ccf..5acf4fdcb 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2516,3 +2516,9 @@ 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) From e4744c1ba4cb428fc40da67c9b960e57b0c275b8 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 11:31:30 +0100 Subject: [PATCH 38/57] stop loss on exchanged removed from doc --- docs/configuration.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 64f0a2ea6..62559a41e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -26,7 +26,6 @@ The table below will list all configuration parameters. | `process_only_new_candles` | false | No | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. Can be set either in Configuration or in the strategy. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. -| `stoploss_on_exchange` | false | No | Only for binance users for now: If this parameter is on then stoploss limit order is executed immediately after buy order is done on binance. | `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached. | `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. From 3e29fbb17a49544c77b06e46776f53dcd6f674bc Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 17:22:56 +0100 Subject: [PATCH 39/57] stoploss on exchange added as a parameter to order_types --- freqtrade/constants.py | 3 ++- freqtrade/exchange/__init__.py | 6 ++++++ freqtrade/freqtradebot.py | 15 ++------------- freqtrade/strategy/default_strategy.py | 3 ++- freqtrade/strategy/interface.py | 10 +++------- freqtrade/tests/exchange/test_exchange.py | 20 +++++++++++++++++++- freqtrade/tests/test_freqtradebot.py | 12 +++--------- 7 files changed, 37 insertions(+), 32 deletions(-) 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) From 5e1fb11124cf67df36e29876698450714132a51a Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 17:30:06 +0100 Subject: [PATCH 40/57] documentation added for stop loss on exchange --- config_full.json.example | 3 ++- docs/configuration.md | 9 +++++---- freqtrade/constants.py | 2 +- freqtrade/exchange/__init__.py | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index b0719bcc6..6134e9cad 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -36,7 +36,8 @@ "order_types": { "buy": "limit", "sell": "limit", - "stoploss": "market" + "stoploss": "market", + "stoploss_on_exchange": "false" }, "exchange": { "name": "bittrex", diff --git a/docs/configuration.md b/docs/configuration.md index 62559a41e..03059e261 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,7 +39,7 @@ The table below will list all configuration parameters. | `ask_strategy.use_order_book` | false | No | Allows selling of open traded pair using the rates in Order Book Asks. | `ask_strategy.order_book_min` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. | `ask_strategy.order_book_max` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. -| `order_types` | None | No | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`). +| `order_types` | None | No | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). | `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode. | `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode. @@ -141,17 +141,18 @@ end up paying more then would probably have been necessary. ### Understand order_types -`order_types` contains a dict mapping order-types to market-types. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. +`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations. -If this is configured, all 3 values (`"buy"`, `"sell"` and `"stoploss"`) need to be present, otherwise the bot warn about it and will fail to start. +If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start. The below is the default which is used if this is not configured in either Strategy or configuration. ``` json "order_types": { "buy": "limit", "sell": "limit", - "stoploss": "market" + "stoploss": "market", + "stoploss_on_exchange": "false" }, ``` diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 481a219d6..86067d395 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -13,7 +13,7 @@ DEFAULT_HYPEROPT = 'DefaultHyperOpts' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite://' UNLIMITED_STAKE_AMOUNT = 'unlimited' -REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss'] +REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 6e826794a..a311fd666 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -227,7 +227,7 @@ class Exchange(object): raise OperationalException( f'Exchange {self.name} does not support market orders.') - if order_types.get('stoploss_on_exchange', False): + if order_types.get('stoploss_on_exchange'): if self.name is not 'Binance': raise OperationalException( 'On exchange stoploss is not supported for %s.' % self.name From 92930b2343494c7c2aa99b6511fa91466f03fccd Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 19:03:28 +0100 Subject: [PATCH 41/57] test fixed --- freqtrade/constants.py | 2 +- freqtrade/tests/exchange/test_exchange.py | 2 +- freqtrade/tests/strategy/test_strategy.py | 13 +++++++------ user_data/strategies/test_strategy.py | 5 +++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 86067d395..f8fb91240 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -112,7 +112,7 @@ CONF_SCHEMA = { 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'stoploss_on_exchange': {'type': 'boolean'} }, - 'required': ['buy', 'sell', 'stoploss'] + 'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] }, 'exchange': {'$ref': '#/definitions/exchange'}, 'edge': {'$ref': '#/definitions/edge'}, diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 1a46ff001..6ad84585c 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -375,7 +375,7 @@ def test_validate_order_types(default_conf, mocker): type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False}) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market'} + default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': 'false'} with pytest.raises(OperationalException, match=r'Exchange .* does not support market orders.'): diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index a38050f24..66d988075 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -188,7 +188,8 @@ def test_strategy_override_order_types(caplog): order_types = { 'buy': 'market', 'sell': 'limit', - 'stoploss': 'limit' + 'stoploss': 'limit', + 'stoploss_on_exchange': True, } config = { @@ -198,13 +199,13 @@ def test_strategy_override_order_types(caplog): resolver = StrategyResolver(config) assert resolver.strategy.order_types - for method in ['buy', 'sell', 'stoploss']: + for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']: assert resolver.strategy.order_types[method] == order_types[method] assert ('freqtrade.strategy.resolver', logging.INFO, "Override strategy 'order_types' with value in config file:" - " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}." + " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': True}." ) in caplog.record_tuples config = { @@ -262,13 +263,13 @@ def test_call_deprecated_function(result, monkeypatch): assert resolver.strategy._sell_fun_len == 2 indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata) - assert type(indicator_df) is DataFrame + assert isinstance(indicator_df, DataFrame) assert 'adx' in indicator_df.columns buydf = resolver.strategy.advise_buy(result, metadata=metadata) - assert type(buydf) is DataFrame + assert isinstance(buydf, DataFrame) assert 'buy' in buydf.columns selldf = resolver.strategy.advise_sell(result, metadata=metadata) - assert type(selldf) is DataFrame + assert isinstance(selldf, DataFrame) assert 'sell' in selldf diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index fd2e9ab75..e7804e683 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -7,7 +7,7 @@ from pandas import DataFrame # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib -import numpy # noqa +import numpy # noqa # This class is a sample. Feel free to customize it. @@ -52,7 +52,8 @@ class TestStrategy(IStrategy): order_types = { 'buy': 'limit', 'sell': 'limit', - 'stoploss': 'market' + 'stoploss': 'market', + 'stoploss_on_exchange': False } def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: From a80c984323ef8dec3aa5468ef572eca0ae966260 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 19:09:11 +0100 Subject: [PATCH 42/57] flake8 --- freqtrade/tests/exchange/test_exchange.py | 7 ++++++- freqtrade/tests/strategy/test_strategy.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 6ad84585c..d7f70d477 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -375,7 +375,12 @@ def test_validate_order_types(default_conf, mocker): type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False}) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': 'false'} + default_conf['order_types'] = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'market', + 'stoploss_on_exchange': 'false' + } with pytest.raises(OperationalException, match=r'Exchange .* does not support market orders.'): diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 66d988075..1ad774ffa 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -205,7 +205,8 @@ def test_strategy_override_order_types(caplog): assert ('freqtrade.strategy.resolver', logging.INFO, "Override strategy 'order_types' with value in config file:" - " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': True}." + " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'," + " 'stoploss_on_exchange': True}." ) in caplog.record_tuples config = { From 1ad5ccdfb0462e451da44089076b6c8b82739440 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 25 Nov 2018 19:48:46 +0100 Subject: [PATCH 43/57] dry run condition when sell occurs --- freqtrade/freqtradebot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8a2db84a9..343036d19 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -781,6 +781,10 @@ class FreqtradeBot(object): sell_type = 'sell' if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): sell_type = 'stoploss' + + if self.config.get('dry_run', False) and sell_type == 'stoploss': + limit = trade.stop_loss + # Execute sell and update trade record order_id = self.exchange.sell(pair=str(trade.pair), ordertype=self.strategy.order_types[sell_type], From b2634e8e085d8510740bf16c1293067a8e8a6b73 Mon Sep 17 00:00:00 2001 From: misagh Date: Mon, 26 Nov 2018 18:28:13 +0100 Subject: [PATCH 44/57] typo corrected --- freqtrade/freqtradebot.py | 2 +- freqtrade/strategy/interface.py | 2 +- freqtrade/tests/test_freqtradebot.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d9a15f56a..5425e8736 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -700,7 +700,7 @@ class FreqtradeBot(object): logger.debug('Handling stoploss on exchange %s ...', trade) order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) if order['status'] == 'closed': - trade.sell_reason = SellType.STOPLOSS_ON_EXCHNAGE.value + trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.update(order) result = True else: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1073f8028..141dd996c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -33,7 +33,7 @@ class SellType(Enum): """ ROI = "roi" STOP_LOSS = "stop_loss" - STOPLOSS_ON_EXCHNAGE = "stoploss_on_exchange" + STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange" TRAILING_STOP_LOSS = "trailing_stop_loss" SELL_SIGNAL = "sell_signal" FORCE_SELL = "force_sell" diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index b6b42d1da..131d7df99 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1685,7 +1685,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, assert trade.stoploss_order_id is None assert trade.is_open is False print(trade.sell_reason) - assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHNAGE.value + assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value assert rpc_mock.call_count == 1 From 17004a5a72a232b3c80389f09de97fe107439d98 Mon Sep 17 00:00:00 2001 From: misagh Date: Mon, 26 Nov 2018 18:29:41 +0100 Subject: [PATCH 45/57] documentation corrected --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 03059e261..e05405aed 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -152,7 +152,7 @@ The below is the default which is used if this is not configured in either Strat "buy": "limit", "sell": "limit", "stoploss": "market", - "stoploss_on_exchange": "false" + "stoploss_on_exchange": False }, ``` From 1f1770ad5a21e5c994fd46041b183fab6db2cc34 Mon Sep 17 00:00:00 2001 From: misagh Date: Mon, 26 Nov 2018 18:46:59 +0100 Subject: [PATCH 46/57] migration script and and error handling on stop loss order --- freqtrade/exchange/__init__.py | 19 ++++++++++++++++++- freqtrade/persistence.py | 2 +- freqtrade/tests/test_persistence.py | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index a311fd666..38e2fa317 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -372,9 +372,26 @@ class Exchange(object): } return self._dry_run_open_orders[order_id] - return self._api.create_order(pair, 'stop_loss', 'sell', + try: + return self._api.create_order(pair, 'stop_loss', 'sell', amount, rate, {'stopPrice': stop_price}) + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to place stoploss limit order on market {pair}.' + f'Tried to put a stoploss amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not place stoploss limit order on market {pair}.' + f'Tried to place stoploss amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place stoploss limit order due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + @retrier def get_balance(self, currency: str) -> float: if self._conf['dry_run']: diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 364af06ce..26b0d9d93 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -82,7 +82,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'ticker_interval'): + if not has_column(cols, 'stoploss_order_id'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 5e0647dff..cdfdef6e6 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -426,6 +426,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): max_rate FLOAT, sell_reason VARCHAR, strategy VARCHAR, + ticker_interval INTEGER, PRIMARY KEY (id), CHECK (is_open IN (0, 1)) );""" From b63535083e66a404724968c5edf6c5841c65b87e Mon Sep 17 00:00:00 2001 From: misagh Date: Mon, 26 Nov 2018 18:47:32 +0100 Subject: [PATCH 47/57] flake8 --- freqtrade/exchange/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 38e2fa317..64875af87 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -374,7 +374,7 @@ class Exchange(object): try: return self._api.create_order(pair, 'stop_loss', 'sell', - amount, rate, {'stopPrice': stop_price}) + amount, rate, {'stopPrice': stop_price}) except ccxt.InsufficientFunds as e: raise DependencyException( From 7f6fc7e90f4edde9757d3f794d396916d935da47 Mon Sep 17 00:00:00 2001 From: misagh Date: Mon, 26 Nov 2018 19:13:36 +0100 Subject: [PATCH 48/57] Lost in git ! --- freqtrade/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 7f4022d1f..3ed478ec3 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -45,7 +45,7 @@ def main(sysargv: List[str]) -> None: freqtrade = FreqtradeBot(config) state = None - while True: + while 1: state = freqtrade.worker(old_state=state) if state == State.RELOAD_CONF: freqtrade = reconfigure(freqtrade, args) From 6351fe7a7f927763aa79e28d941d60ab3e9a7fea Mon Sep 17 00:00:00 2001 From: misagh Date: Mon, 26 Nov 2018 20:24:13 +0100 Subject: [PATCH 49/57] test added: stoploss_order_id should be null after migration --- freqtrade/tests/test_persistence.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index cdfdef6e6..d0a209f40 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -472,6 +472,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.sell_reason is None assert trade.strategy is None assert trade.ticker_interval is None + assert trade.stoploss_order_id is None assert log_has("trying trades_bak1", caplog.record_tuples) assert log_has("trying trades_bak2", caplog.record_tuples) assert log_has("Running database migration - backup available as trades_bak2", From 7dbf0fed684e9af10da8c2fda45a03b9f1a3bdcb Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 27 Nov 2018 17:09:51 +0100 Subject: [PATCH 50/57] stop loss limit order type corrected --- freqtrade/exchange/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index a2857a12a..2480dbe32 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -373,7 +373,7 @@ class Exchange(object): return self._dry_run_open_orders[order_id] try: - return self._api.create_order(pair, 'stop_loss', 'sell', + return self._api.create_order(pair, 'stop_loss_limit', 'sell', amount, rate, {'stopPrice': stop_price}) except ccxt.InsufficientFunds as e: From 29f680ec5d6f5bd837788b935e7b27dc3eee77f7 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 27 Nov 2018 17:26:06 +0100 Subject: [PATCH 51/57] fix order type test --- freqtrade/tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 63f1008b9..ec7c2acae 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1150,7 +1150,7 @@ def test_get_fee(default_conf, mocker): def test_stoploss_limit_order(default_conf, mocker): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) - order_type = 'stop_loss' + order_type = 'stop_loss_limit' api_mock.create_order = MagicMock(return_value={ 'id': order_id, From da94e97c602861492cf03a38eb37c0d6e382e94e Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 28 Nov 2018 13:58:53 +0100 Subject: [PATCH 52/57] in case trade is not open, then handle_stoploss_on_exchange should not be called --- freqtrade/freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index aeb8ce50c..c72451df6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -554,7 +554,7 @@ class FreqtradeBot(object): trade.update(order) - if self.strategy.order_types.get('stoploss_on_exchange'): + if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: result = self.handle_stoploss_on_exchange(trade) if result: self.wallets.update() @@ -675,7 +675,7 @@ class FreqtradeBot(object): # If trade is open and the buy order is fulfilled but there is no stoploss, # then we add a stoploss on exchange - if trade.is_open and not trade.open_order_id and not trade.stoploss_order_id: + if not trade.open_order_id and not trade.stoploss_order_id: if self.edge: stoploss = self.edge.stoploss(pair=trade.pair) else: @@ -692,7 +692,7 @@ class FreqtradeBot(object): )['id'] trade.stoploss_order_id = str(stoploss_order_id) - # Or there is already a stoploss on exchange. + # Or the trade open and there is already a stoploss on exchange. # so we check if it is hit ... elif trade.stoploss_order_id: logger.debug('Handling stoploss on exchange %s ...', trade) From fb755880fad8cc2e0b0145544117b7243eac26dd Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 28 Nov 2018 14:16:50 +0100 Subject: [PATCH 53/57] logs added in case stop loss on exchange is hit --- freqtrade/persistence.py | 1 + freqtrade/tests/test_freqtradebot.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 26b0d9d93..64663a2fd 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -255,6 +255,7 @@ class Trade(_DECL_BASE): self.close(order['price']) elif order_type == 'stop_loss_limit': self.stoploss_order_id = None + logger.info('STOP_LOSS_LIMIT is hit for %s.', self) self.close(order['price']) else: raise ValueError(f'Unknown order type: {order_type}') diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 995a3e8ff..81ade608a 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -899,7 +899,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None assert trade.is_open is True -def test_handle_stoploss_on_exchange(mocker, default_conf, fee, +def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, markets, limit_buy_order, limit_sell_order) -> None: stoploss_limit = MagicMock(return_value={'id': 13434334}) patch_RPCManager(mocker) @@ -961,6 +961,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, }) mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True + assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog.record_tuples) assert trade.stoploss_order_id is None assert trade.is_open is False From c913fef80c5c31d8007b5d437e9d371196440695 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 28 Nov 2018 15:45:11 +0100 Subject: [PATCH 54/57] =?UTF-8?q?stop=20loss=20limit=20when=20hit,=20the?= =?UTF-8?q?=20close=20price=20is=20=E2=80=9Caverage=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freqtrade/persistence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 64663a2fd..592a88acb 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -256,7 +256,7 @@ class Trade(_DECL_BASE): elif order_type == 'stop_loss_limit': self.stoploss_order_id = None logger.info('STOP_LOSS_LIMIT is hit for %s.', self) - self.close(order['price']) + self.close(order['average']) else: raise ValueError(f'Unknown order type: {order_type}') cleanup() From 1a5465fb508ca20318e36913cfb0cd2f12a995b1 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 28 Nov 2018 19:35:10 +0100 Subject: [PATCH 55/57] logs enriched in case of stop loss on exchange, test fixed --- freqtrade/exchange/__init__.py | 8 +++++--- freqtrade/tests/test_freqtradebot.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 2480dbe32..baa9d573d 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -378,13 +378,15 @@ class Exchange(object): except ccxt.InsufficientFunds as e: raise DependencyException( - f'Insufficient funds to place stoploss limit order on market {pair}.' - f'Tried to put a stoploss amount {amount} at rate {rate} (total {rate*amount}).' + f'Insufficient funds to place stoploss limit order on market {pair}. ' + f'Tried to put a stoploss amount {amount} with ' + f'stop {stop_price} and limit {rate} (total {rate*amount}).' f'Message: {e}') except ccxt.InvalidOrder as e: raise DependencyException( f'Could not place stoploss limit order on market {pair}.' - f'Tried to place stoploss amount {amount} at rate {rate} (total {rate*amount}).' + f'Tried to place stoploss amount {amount} with ' + f'stop {stop_price} and limit {rate} (total {rate*amount}).' f'Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 81ade608a..147d47b72 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -957,7 +957,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, stoploss_order_hit = MagicMock(return_value={ 'status': 'closed', 'type': 'stop_loss_limit', - 'price': 2 + 'price': 3, + 'average': 2 }) mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True From f04655c012c90ceffb01a562e80a8d8c72f6e10c Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Nov 2018 20:13:50 +0100 Subject: [PATCH 56/57] Test exceptions in sell-stoploss --- freqtrade/tests/exchange/test_exchange.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index ec7c2acae..d1f391266 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1182,6 +1182,27 @@ def test_stoploss_limit_order(default_conf, mocker): assert api_mock.create_order.call_args[0][4] == 200 assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} + # test exception handling + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + with pytest.raises(TemporaryError): + api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + with pytest.raises(OperationalException): + api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + def test_stoploss_limit_order_dry_run(default_conf, mocker): api_mock = MagicMock() From d4f83a7516c916ea65e2013f632a75f0686c56c4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Nov 2018 20:15:56 +0100 Subject: [PATCH 57/57] Fix missing mock in test_add_stoploss_on_exchange --- freqtrade/tests/test_freqtradebot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 147d47b72..a3638d08a 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -876,6 +876,7 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None: patch_RPCManager(mocker) + patch_exchange(mocker) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])