From 821e299afb77181dcaf9c66c95beccbbaf3529e6 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 6 Jan 2019 14:45:29 +0100 Subject: [PATCH 01/63] adjusting trailing stoploss on exchange --- freqtrade/freqtradebot.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4febe9dd0..6d3f7db07 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -648,7 +648,17 @@ class FreqtradeBot(object): trade.update(order) result = True else: - result = False + # check if trailing stoploss is enabled and stoploss value has changed + # in which case we cancel stoploss order and put another one with new + # value immediately + if self.config.get('trailing_stop', False) and trade.stop_loss > order['price']: + if self.exchange.cancel_order(order['id'], trade.pair): + 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 result def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: From 16472535ebabe4bf0ba3f9de77f87ae4cd33a38f Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 12:39:10 +0100 Subject: [PATCH 02/63] adding stoploss_last_update to persistence --- freqtrade/persistence.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index a14d22b98..c466f2fc1 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -83,7 +83,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'stoploss_order_id'): + if not has_column(cols, 'stoploss_last_update'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -93,6 +93,7 @@ def check_migrate(engine) -> None: 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') + stoploss_last_update = get_column_def(cols, 'stoploss_last_update', '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') @@ -111,7 +112,8 @@ 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, stoploss_order_id, max_rate, sell_reason, strategy, + stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update, + max_rate, sell_reason, strategy, ticker_interval ) select id, lower(exchange), @@ -127,9 +129,9 @@ 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, - {stoploss_order_id} stoploss_order_id, {max_rate} max_rate, - {sell_reason} sell_reason, {strategy} strategy, - {ticker_interval} ticker_interval + {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, + {max_rate} max_rate, {sell_reason} sell_reason, + {strategy} strategy, {ticker_interval} ticker_interval from {table_back_name} """) @@ -185,6 +187,8 @@ class Trade(_DECL_BASE): initial_stop_loss = Column(Float, nullable=True, default=0.0) # stoploss order id which is on exchange stoploss_order_id = Column(String, nullable=True, index=True) + # last update time of the stoploss order on exchange + stoploss_last_update = Column(DateTime, nullable=True) # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) From 4fbb9d4462d353414a99087cbe54562466fd0c7e Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 12:39:53 +0100 Subject: [PATCH 03/63] adding stoploss_on_exchange_interval to order_types dict. default to 1 minute (60) --- freqtrade/strategy/interface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 694430fd6..acf627c8a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -75,7 +75,8 @@ class IStrategy(ABC): 'buy': 'limit', 'sell': 'limit', 'stoploss': 'limit', - 'stoploss_on_exchange': False + 'stoploss_on_exchange': False, + 'stoploss_on_exchange_interval': 60, } # Optional time in force From aed855284cb3707d2fc994914441779be3115636 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 13:44:51 +0100 Subject: [PATCH 04/63] comparing with stopPrice instead of price --- freqtrade/freqtradebot.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6d3f7db07..02b55954e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -613,7 +613,7 @@ class FreqtradeBot(object): def handle_stoploss_on_exchange(self, trade: Trade) -> bool: """ Check if trade is fulfilled in which case the stoploss - on exchange should be added immediately if stoploss on exchnage + on exchange should be added immediately if stoploss on exchange is enabled. """ @@ -630,13 +630,14 @@ class FreqtradeBot(object): 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 + # 0.99 is arbitrary here. + limit_price = stop_price * 0.99 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) + trade.stoploss_last_update = datetime.now() # Or the trade open and there is already a stoploss on exchange. # so we check if it is hit ... @@ -647,17 +648,22 @@ class FreqtradeBot(object): trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.update(order) result = True - else: + elif self.config.get('trailing_stop', False): # check if trailing stoploss is enabled and stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - if self.config.get('trailing_stop', False) and trade.stop_loss > order['price']: - if self.exchange.cancel_order(order['id'], trade.pair): - 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) + if trade.stop_loss > order['info']['stopPrice']: + # we check also if the update is neccesary + update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] + if (datetime.now() - trade.stoploss_last_update).total_seconds > update_beat: + # cancelling the current stoploss on exchange first + if self.exchange.cancel_order(order['id'], trade.pair): + # creating the new one + 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 result From 9e133eb32e206c2d9c01da340f099c7cef191004 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 16:31:02 +0100 Subject: [PATCH 05/63] adding guard not to cancel the previous stop loss on exchange if market is falling quickly --- freqtrade/freqtradebot.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 02b55954e..513b27755 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -649,9 +649,19 @@ class FreqtradeBot(object): trade.update(order) result = True elif self.config.get('trailing_stop', False): - # check if trailing stoploss is enabled and stoploss value has changed + # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately + + # This is a guard: there is a situation where market is going doing down fast + # the stoploss on exchange checked previously is not hit but + # it is too late and too risky to cancel the previous stoploss + if trade.stop_loss < self.exchange.get_ticker(trade.pair)['bid']: + logger.info('stoploss on exchange update: too risky to update stoploss as ' + 'current best bid price (%s) is higher than stoploss value (%s)', + self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) + return result + if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] From 1a27258469cf9fd756203dacca721886ca5297b9 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 8 Jan 2019 16:34:23 +0100 Subject: [PATCH 06/63] condition fixed --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 513b27755..a081d364a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -656,7 +656,7 @@ class FreqtradeBot(object): # This is a guard: there is a situation where market is going doing down fast # the stoploss on exchange checked previously is not hit but # it is too late and too risky to cancel the previous stoploss - if trade.stop_loss < self.exchange.get_ticker(trade.pair)['bid']: + if trade.stop_loss <= self.exchange.get_ticker(trade.pair)['bid']: logger.info('stoploss on exchange update: too risky to update stoploss as ' 'current best bid price (%s) is higher than stoploss value (%s)', self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) From e025ad391871b2bfcd7b6584641cd0a66cc07953 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 9 Jan 2019 16:23:13 +0100 Subject: [PATCH 07/63] temp test commit --- freqtrade/freqtradebot.py | 4 +- freqtrade/tests/test_freqtradebot.py | 66 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a081d364a..5169d162c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -656,12 +656,14 @@ class FreqtradeBot(object): # This is a guard: there is a situation where market is going doing down fast # the stoploss on exchange checked previously is not hit but # it is too late and too risky to cancel the previous stoploss - if trade.stop_loss <= self.exchange.get_ticker(trade.pair)['bid']: + if trade.stop_loss > self.exchange.get_ticker(trade.pair)['bid']: logger.info('stoploss on exchange update: too risky to update stoploss as ' 'current best bid price (%s) is higher than stoploss value (%s)', self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) return result + print(trade.stop_loss) + print(order['info']['stopPrice']) if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 2b14c586b..5aa8c55a9 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1014,6 +1014,72 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, assert trade.is_open is False +def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, + markets, limit_buy_order, limit_sell_order) -> None: + # When trailing stoploss is set + stoploss_limit = MagicMock(return_value={'id': 13434334}) + 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 + ) + default_conf['trailing_stop'] = True + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss = -0.05 + 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 + + stoploss_order_hanging = MagicMock(return_value={ + 'id': 100, + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'info' : { + 'stopPrice': 1.113399999 + } + }) + + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) + + # stoploss initially at 5% + assert freqtrade.handle_trade(trade) is False + + # price jumped 2x + trade.max_rate = trade.open_rate * 2 + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + assert trade.stop_loss == (trade.open_rate * 2) * 0.95 + + stoploss_order_hit = MagicMock(return_value={ + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2 + }) + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) + assert freqtrade.handle_stoploss_on_exchange(trade) is False + 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 + + def test_process_maybe_execute_buy(mocker, default_conf) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From 3f8092192ebc664102463894d56fd18dbe825c58 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 10 Jan 2019 13:32:07 +0100 Subject: [PATCH 08/63] Update ccxt from 1.18.117 to 1.18.119 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 25c441d73..b847fb1a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.117 +ccxt==1.18.119 SQLAlchemy==1.2.15 python-telegram-bot==11.1.0 arrow==0.13.0 From 4920ee34554512cfc56ba32bd0145929881dbfe4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 10 Jan 2019 13:32:09 +0100 Subject: [PATCH 09/63] Update wrapt from 1.10.11 to 1.11.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b847fb1a9..30feb1f6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ arrow==0.13.0 cachetools==3.0.0 requests==2.21.0 urllib3==1.24.1 -wrapt==1.10.11 +wrapt==1.11.0 pandas==0.23.4 scikit-learn==0.20.2 joblib==0.13.0 From 3867f73c8ca0f9d83aa5656e05cdc1e390dc08cd Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 11 Jan 2019 13:32:07 +0100 Subject: [PATCH 10/63] Update ccxt from 1.18.119 to 1.18.120 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 30feb1f6c..380185348 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.119 +ccxt==1.18.120 SQLAlchemy==1.2.15 python-telegram-bot==11.1.0 arrow==0.13.0 From e95351fd0407ade72f475943178d8023b70ed5da Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 12 Jan 2019 13:32:06 +0100 Subject: [PATCH 11/63] Update sqlalchemy from 1.2.15 to 1.2.16 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 380185348..d7c4e2bcf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ccxt==1.18.120 -SQLAlchemy==1.2.15 +SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 cachetools==3.0.0 From 690fbeb9074b9779dcfe360311732c3efbbe16f9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 12 Jan 2019 13:32:08 +0100 Subject: [PATCH 12/63] Update joblib from 0.13.0 to 0.13.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d7c4e2bcf..505bebc7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ urllib3==1.24.1 wrapt==1.11.0 pandas==0.23.4 scikit-learn==0.20.2 -joblib==0.13.0 +joblib==0.13.1 scipy==1.2.0 jsonschema==2.6.0 numpy==1.15.4 From 9e0902e72fbe8b7ee4071ddc5fd0602151668fc3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Jan 2019 13:38:49 +0100 Subject: [PATCH 13/63] Add test for case for odd ROI dict - #1478 --- freqtrade/tests/strategy/test_interface.py | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index 08634073d..f2e8c577f 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -118,6 +118,7 @@ def test_tickerdata_to_dataframe(default_conf) -> None: def test_min_roi_reached(default_conf, fee) -> None: + # Use list to confirm sequence does not matter min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1}, {0: 0.1, 20: 0.05, 55: 0.01}] for roi in min_roi_list: @@ -143,6 +144,47 @@ def test_min_roi_reached(default_conf, fee) -> None: assert strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-1).datetime) +def test_min_roi_reached2(default_conf, fee) -> None: + + # test with ROI raising after last interval + min_roi_list = [{20: 0.07, + 30: 0.05, + 55: 0.30, + 0: 0.1 + }, + {0: 0.1, + 20: 0.07, + 30: 0.05, + 55: 0.30 + }, + ] + for roi in min_roi_list: + strategy = DefaultStrategy(default_conf) + strategy.minimal_roi = roi + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + open_date=arrow.utcnow().shift(hours=-1).datetime, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='bittrex', + open_rate=1, + ) + + assert not strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-56).datetime) + assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-56).datetime) + + assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime) + assert strategy.min_roi_reached(trade, 0.071, arrow.utcnow().shift(minutes=-39).datetime) + + assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-26).datetime) + assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-26).datetime) + + # Should not trigger with 20% profit since after 55 minutes only 30% is active. + assert not strategy.min_roi_reached(trade, 0.20, arrow.utcnow().shift(minutes=-2).datetime) + assert strategy.min_roi_reached(trade, 0.31, arrow.utcnow().shift(minutes=-2).datetime) + + def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) From e9d61eb35dce1b697de008fa570320d86409c90c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Jan 2019 13:44:50 +0100 Subject: [PATCH 14/63] Fix ROI calculation problem Prior to that all ROI entries with a key > trade_duration where active. This causes a problem if the ROI is not linearly declining --- freqtrade/strategy/interface.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a6569ec19..47ec25198 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -319,17 +319,18 @@ class IStrategy(ABC): def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: """ Based an earlier trade and current price and ROI configuration, decides whether bot should - sell + sell. Requires current_profit to be in percent!! :return True if bot should sell at current rate """ # Check if time matches and current rate is above threshold - time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60 - for duration, threshold in self.minimal_roi.items(): - if time_diff <= duration: - continue - if current_profit > threshold: - return True + trade_dur = (current_time.timestamp() - trade.open_date.timestamp()) / 60 + + # Get highest entry in ROI dict where key >= trade-duration + roi_entry = max(list(filter(lambda x: trade_dur >= x, self.minimal_roi.keys()))) + threshold = self.minimal_roi[roi_entry] + if current_profit > threshold: + return True return False From cd2bccd44198e29ade6932be23b6617dca4a5899 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Jan 2019 13:45:43 +0100 Subject: [PATCH 15/63] Have backtest use the same logic to get the ROI entry --- freqtrade/optimize/backtesting.py | 2 +- freqtrade/tests/optimize/test_backtesting.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e96a91856..80dc9a443 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -221,7 +221,7 @@ class Backtesting(object): elif sell.sell_type == (SellType.ROI): # get next entry in min_roi > to trade duration # Interface.py skips on trade_duration <= duration - roi_entry = max(list(filter(lambda x: trade_dur > x, + roi_entry = max(list(filter(lambda x: trade_dur >= x, self.strategy.minimal_roi.keys()))) roi = self.strategy.minimal_roi[roi_entry] diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 6e0ab24a4..5ab44baad 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -530,10 +530,10 @@ def test_backtest(default_conf, fee, mocker) -> None: 'open_time': [Arrow(2018, 1, 29, 18, 40, 0).datetime, Arrow(2018, 1, 30, 3, 30, 0).datetime], 'close_time': [Arrow(2018, 1, 29, 22, 35, 0).datetime, - Arrow(2018, 1, 30, 4, 15, 0).datetime], + Arrow(2018, 1, 30, 4, 10, 0).datetime], 'open_index': [78, 184], - 'close_index': [125, 193], - 'trade_duration': [235, 45], + 'close_index': [125, 192], + 'trade_duration': [235, 40], 'open_at_end': [False, False], 'open_rate': [0.104445, 0.10302485], 'close_rate': [0.104969, 0.103541], From 04786f09f44d53253532e6395dbdb25bcb7d53d9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 13 Jan 2019 13:32:06 +0100 Subject: [PATCH 16/63] Update pytest from 4.1.0 to 4.1.1 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2eeae1fbc..50d181296 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ -r requirements.txt flake8==3.6.0 -pytest==4.1.0 +pytest==4.1.1 pytest-mock==1.10.0 pytest-asyncio==0.10.0 pytest-cov==2.6.1 From a3897c990d16b7482f2f3b6579a8c7902b4f32f7 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 14 Jan 2019 13:32:07 +0100 Subject: [PATCH 17/63] Update ccxt from 1.18.120 to 1.18.126 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 505bebc7a..0ec1f1f5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.120 +ccxt==1.18.126 SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 From da04182287a648637b5133a77617187a67590d0b Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 14 Jan 2019 13:32:08 +0100 Subject: [PATCH 18/63] Update numpy from 1.15.4 to 1.16.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0ec1f1f5f..9baeacf08 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ scikit-learn==0.20.2 joblib==0.13.1 scipy==1.2.0 jsonschema==2.6.0 -numpy==1.15.4 +numpy==1.16.0 TA-Lib==0.4.17 tabulate==0.8.2 coinmarketcap==5.0.3 From cfe00c2f0c118c93e1870567eb75c195bfa91ddd Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 15 Jan 2019 11:04:32 +0100 Subject: [PATCH 19/63] initial test added for TSL on exchange --- freqtrade/freqtradebot.py | 6 ++--- freqtrade/persistence.py | 2 ++ freqtrade/tests/test_freqtradebot.py | 39 ++++++++++++++++++---------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5169d162c..477141782 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -662,18 +662,16 @@ class FreqtradeBot(object): self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) return result - print(trade.stop_loss) - print(order['info']['stopPrice']) if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] - if (datetime.now() - trade.stoploss_last_update).total_seconds > update_beat: + if (datetime.now() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first if self.exchange.cancel_order(order['id'], trade.pair): # creating the new one stoploss_order_id = self.exchange.stoploss_limit( pair=trade.pair, amount=trade.amount, - stop_price=stop_price, rate=limit_price + stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99 )['id'] trade.stoploss_order_id = str(stoploss_order_id) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index c466f2fc1..f9b34fc64 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -222,11 +222,13 @@ class Trade(_DECL_BASE): logger.debug("assigning new stop loss") self.stop_loss = new_loss self.initial_stop_loss = new_loss + self.stoploss_last_update = datetime.utcnow() # evaluate if the stop loss needs to be updated else: if new_loss > self.stop_loss: # stop losses only walk up, never down! self.stop_loss = new_loss + self.stoploss_last_update = datetime.utcnow() logger.debug("adjusted stop loss") else: logger.debug("keeping current stop loss") diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 5aa8c55a9..6d6e5fe91 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1033,9 +1033,21 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, get_markets=markets, stoploss_limit=stoploss_limit ) + + # enabling TSL default_conf['trailing_stop'] = True + + # disabling ROI + default_conf['minimal_roi']['0'] = 999999999 + freqtrade = FreqtradeBot(default_conf) + + # setting stoploss freqtrade.strategy.stoploss = -0.05 + + # setting stoploss_on_exchange_interval + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + patch_get_signal(freqtrade) freqtrade.create_trade() @@ -1051,7 +1063,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'price': 3, 'average': 2, 'info' : { - 'stopPrice': 1.113399999 + 'stopPrice': 0.000011134 } }) @@ -1061,23 +1073,22 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, assert freqtrade.handle_trade(trade) is False # price jumped 2x - trade.max_rate = trade.open_rate * 2 + mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ + 'bid': 0.00002344, + 'ask': 0.00002346, + 'last': 0.00002344 + })) + assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.stop_loss == 0.00002344 * 0.95 - assert trade.stop_loss == (trade.open_rate * 2) * 0.95 + mocker.patch('freqtrade.exchange.Exchange.cancel_order', MagicMock(return_value={ + "orderId": 100, + "status": "CANCELED", + })) - stoploss_order_hit = MagicMock(return_value={ - 'status': 'open', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2 - }) - mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is False - 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 + assert freqtrade.exchange.cancel_order.call_count == 1 def test_process_maybe_execute_buy(mocker, default_conf) -> None: From f0cfab79404048366a225af2316365eb5fbdf433 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 15 Jan 2019 11:10:28 +0100 Subject: [PATCH 20/63] flaking 8 --- freqtrade/tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 6d6e5fe91..40f5f3327 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1062,7 +1062,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'type': 'stop_loss_limit', 'price': 3, 'average': 2, - 'info' : { + 'info': { 'stopPrice': 0.000011134 } }) From 494b905d1e0ddb1ae5e497763f605604aefcb4d5 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 15 Jan 2019 13:32:08 +0100 Subject: [PATCH 21/63] Update ccxt from 1.18.126 to 1.18.131 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9baeacf08..92885045d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.126 +ccxt==1.18.131 SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 From 399d2d89a3642601355309426cf6ce9cf272cc6e Mon Sep 17 00:00:00 2001 From: gautier pialat Date: Tue, 15 Jan 2019 15:02:01 +0100 Subject: [PATCH 22/63] Win rate formula was wrong (but not the definition) --- docs/edge.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index 61abf354b..b208cb318 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -24,7 +24,7 @@ The answer comes to two factors: Means over X trades what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only If you won or not). -`W = (Number of winning trades) / (Number of losing trades)` +`W = (Number of winning trades) / (Total number of trades)` ### Risk Reward Ratio Risk Reward Ratio is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose: @@ -209,4 +209,4 @@ The full timerange specification: * Use tickframes till 2018/01/31: --timerange=-20180131 * Use tickframes since 2018/01/31: --timerange=20180131- * Use tickframes since 2018/01/31 till 2018/03/01 : --timerange=20180131-20180301 -* Use tickframes between POSIX timestamps 1527595200 1527618600: --timerange=1527595200-1527618600 \ No newline at end of file +* Use tickframes between POSIX timestamps 1527595200 1527618600: --timerange=1527595200-1527618600 From 12e81080159343025717803a2ec3a5c1d8d2fc0f Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 15 Jan 2019 15:36:41 +0100 Subject: [PATCH 23/63] checking params of cancel order and stop loss order --- freqtrade/freqtradebot.py | 8 +++++--- freqtrade/tests/test_freqtradebot.py | 15 ++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 477141782..23e1e422b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -657,9 +657,11 @@ class FreqtradeBot(object): # the stoploss on exchange checked previously is not hit but # it is too late and too risky to cancel the previous stoploss if trade.stop_loss > self.exchange.get_ticker(trade.pair)['bid']: - logger.info('stoploss on exchange update: too risky to update stoploss as ' - 'current best bid price (%s) is higher than stoploss value (%s)', - self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss) + logger.warning( + 'stoploss on exchange update: too risky to update stoploss as ' + 'current best bid price (%s) is higher than stoploss value (%s).', + self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss + ) return result if trade.stop_loss > order['info']['stopPrice']: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 40f5f3327..d0a708865 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1082,13 +1082,18 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, assert freqtrade.handle_trade(trade) is False assert trade.stop_loss == 0.00002344 * 0.95 - mocker.patch('freqtrade.exchange.Exchange.cancel_order', MagicMock(return_value={ - "orderId": 100, - "status": "CANCELED", - })) + cancel_order_mock = MagicMock() + stoploss_order_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock) assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert freqtrade.exchange.cancel_order.call_count == 1 + + cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') + stoploss_order_mock.assert_called_once_with(amount=85.25149190110828, + pair='ETH/BTC', + rate=0.00002344 * 0.95 * 0.99, + stop_price=0.00002344 * 0.95) def test_process_maybe_execute_buy(mocker, default_conf) -> None: From 1cd5abde3789a79c7acb2182ff06f716e19f94a7 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 11:22:25 +0100 Subject: [PATCH 24/63] removing unnecessary guard --- freqtrade/freqtradebot.py | 12 ------------ freqtrade/tests/test_freqtradebot.py | 1 + 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 23e1e422b..2a1415e09 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -652,18 +652,6 @@ class FreqtradeBot(object): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - - # This is a guard: there is a situation where market is going doing down fast - # the stoploss on exchange checked previously is not hit but - # it is too late and too risky to cancel the previous stoploss - if trade.stop_loss > self.exchange.get_ticker(trade.pair)['bid']: - logger.warning( - 'stoploss on exchange update: too risky to update stoploss as ' - 'current best bid price (%s) is higher than stoploss value (%s).', - self.exchange.get_ticker(trade.pair)['bid'], trade.stop_loss - ) - return result - if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index d0a708865..b86d76553 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1071,6 +1071,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, # stoploss initially at 5% assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False # price jumped 2x mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ From 29439c05d66fd069b17e8d6b8f47dd1ca053ffdc Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 11:51:54 +0100 Subject: [PATCH 25/63] adding update beat test --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2a1415e09..6334aef04 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -655,7 +655,7 @@ class FreqtradeBot(object): if trade.stop_loss > order['info']['stopPrice']: # we check also if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] - if (datetime.now() - trade.stoploss_last_update).total_seconds() > update_beat: + if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first if self.exchange.cancel_order(order['id'], trade.pair): # creating the new one diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index b86d76553..dcdae16fe 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1045,8 +1045,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, # setting stoploss freqtrade.strategy.stoploss = -0.05 - # setting stoploss_on_exchange_interval - freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + # setting stoploss_on_exchange_interval to 60 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 patch_get_signal(freqtrade) @@ -1080,14 +1080,24 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'last': 0.00002344 })) - assert freqtrade.handle_trade(trade) is False - assert trade.stop_loss == 0.00002344 * 0.95 cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock) + # stoploss should not be updated as the interval is 60 seconds + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + cancel_order_mock.assert_not_called() + stoploss_order_mock.assert_not_called() + + assert freqtrade.handle_trade(trade) is False + assert trade.stop_loss == 0.00002344 * 0.95 + + # setting stoploss_on_exchange_interval to 0 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + assert freqtrade.handle_stoploss_on_exchange(trade) is False cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') From bfb71215834fcc817bba9e5478afc15c25cc915b Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 12:16:32 +0100 Subject: [PATCH 26/63] refactoring handle_stoploss_on_exchange --- freqtrade/freqtradebot.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6334aef04..25951aba5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -652,21 +652,24 @@ class FreqtradeBot(object): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - if trade.stop_loss > order['info']['stopPrice']: - # we check also if the update is neccesary - update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] - if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: - # cancelling the current stoploss on exchange first - if self.exchange.cancel_order(order['id'], trade.pair): - # creating the new one - stoploss_order_id = self.exchange.stoploss_limit( - pair=trade.pair, amount=trade.amount, - stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99 - )['id'] - trade.stoploss_order_id = str(stoploss_order_id) + handle_trailing_stoploss_on_exchange(trade, order) return result + def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): + if trade.stop_loss > order['info']['stopPrice']: + # we check if the update is neccesary + update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] + if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: + # cancelling the current stoploss on exchange first + if self.exchange.cancel_order(order['id'], trade.pair): + # creating the new one + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, + stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99 + )['id'] + trade.stoploss_order_id = str(stoploss_order_id) + def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.edge: stoploss = self.edge.stoploss(trade.pair) From 6d588b3b0bf0f464a5cd86bb435c4887773823f1 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 14:28:52 +0100 Subject: [PATCH 27/63] trailing stop loss on exchange extracted to a separate function --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 25951aba5..544e4a997 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -652,7 +652,7 @@ class FreqtradeBot(object): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - handle_trailing_stoploss_on_exchange(trade, order) + self.handle_trailing_stoploss_on_exchange(trade, order) return result diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index dcdae16fe..fe2dd0ec2 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1080,7 +1080,6 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'last': 0.00002344 })) - cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) From cffc9ce890deae1db17d79ca4ad0cef5ce31f8e1 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 14:49:47 +0100 Subject: [PATCH 28/63] last step: stop loss on exchange added to trailing --- freqtrade/strategy/interface.py | 16 ++++++++-------- freqtrade/tests/test_freqtradebot.py | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7afb0c59f..f3ff9ba7a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -234,12 +234,9 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - 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, - current_time=date, current_profit=current_profit, - force_stoploss=force_stoploss) + 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 @@ -281,8 +278,11 @@ class IStrategy(ABC): trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss else self.stoploss, initial=True) - # evaluate if the stoploss was hit - if self.stoploss is not None and trade.stop_loss >= current_rate: + # evaluate if the stoploss was hit if stoploss is not on exchange + if self.stoploss is not None and \ + trade.stop_loss >= current_rate and \ + not self.order_types.get('stoploss_on_exchange'): + selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index fe2dd0ec2..569477fb1 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1042,6 +1042,9 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, freqtrade = FreqtradeBot(default_conf) + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + # setting stoploss freqtrade.strategy.stoploss = -0.05 From aa03a864f70b92320080abd444225d0a4fc734ba Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 15:00:35 +0100 Subject: [PATCH 29/63] comments added for TSL on exchange function --- freqtrade/freqtradebot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 544e4a997..682a850b8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -657,6 +657,14 @@ class FreqtradeBot(object): return result def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): + """ + Check to see if stoploss on exchange should be updated + in case of trailing stoploss on exchange + :param Trade: Corresponding Trade + :param order: Current on exchange stoploss order + :return: None + """ + if trade.stop_loss > order['info']['stopPrice']: # we check if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] From baa5cc5b9e4ccff978a6ea100b70d63d409d6738 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 15:10:31 +0100 Subject: [PATCH 30/63] logs enriched --- freqtrade/exchange/__init__.py | 5 ++++- freqtrade/freqtradebot.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index c74b32ad2..588504894 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -402,8 +402,11 @@ class Exchange(object): return self._dry_run_open_orders[order_id] try: - return self._api.create_order(pair, 'stop_loss_limit', 'sell', + order = self._api.create_order(pair, 'stop_loss_limit', 'sell', amount, rate, {'stopPrice': stop_price}) + logger.info('stoploss limit order added for %s. ' + 'stop price: %s. limit: %s' % (pair,stop_price,rate)) + return order except ccxt.InsufficientFunds as e: raise DependencyException( diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 682a850b8..dc5f3d8ad 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -670,6 +670,8 @@ class FreqtradeBot(object): update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first + logger.info('Trailing stoploss: cancelling current stoploss on exchange ' + 'in order to add another one ...') if self.exchange.cancel_order(order['id'], trade.pair): # creating the new one stoploss_order_id = self.exchange.stoploss_limit( From 50bc20134f67fdc56c18352cd85849929f815c92 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 15:17:28 +0100 Subject: [PATCH 31/63] adding whitespace --- 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 588504894..094d2cc29 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -403,7 +403,7 @@ class Exchange(object): try: order = self._api.create_order(pair, 'stop_loss_limit', 'sell', - amount, rate, {'stopPrice': stop_price}) + amount, rate, {'stopPrice': stop_price}) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s' % (pair,stop_price,rate)) return order From 611b48dbb99e86a53b948a5e468767d046e1459d Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 16:15:36 +0100 Subject: [PATCH 32/63] fix return value from info hash: value is in string --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dc5f3d8ad..9b5ab4bba 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -665,7 +665,7 @@ class FreqtradeBot(object): :return: None """ - if trade.stop_loss > order['info']['stopPrice']: + if trade.stop_loss > float(order['info']['stopPrice']): # we check if the update is neccesary update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 569477fb1..6c25b1a7f 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1066,7 +1066,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, 'price': 3, 'average': 2, 'info': { - 'stopPrice': 0.000011134 + 'stopPrice': '0.000011134' } }) From 5e2e96acd2aa3e3cf01ae2a6291ebea917122734 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 18:38:20 +0100 Subject: [PATCH 33/63] compatibility with edge added --- freqtrade/exchange/__init__.py | 2 +- freqtrade/strategy/interface.py | 5 +- freqtrade/tests/test_freqtradebot.py | 110 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 094d2cc29..90a33a226 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -405,7 +405,7 @@ class Exchange(object): order = self._api.create_order(pair, 'stop_loss_limit', 'sell', amount, rate, {'stopPrice': stop_price}) logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s' % (pair,stop_price,rate)) + 'stop price: %s. limit: %s' % (pair, stop_price, rate)) return order except ccxt.InsufficientFunds as e: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6dbdf72e8..88029f4d4 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -274,7 +274,6 @@ class IStrategy(ABC): """ trailing_stop = self.config.get('trailing_stop', False) - trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss else self.stoploss, initial=True) @@ -282,7 +281,6 @@ class IStrategy(ABC): if self.stoploss is not None and \ trade.stop_loss >= current_rate and \ not self.order_types.get('stoploss_on_exchange'): - selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: @@ -302,7 +300,8 @@ class IStrategy(ABC): # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = self.stoploss + stop_loss_value = force_stoploss if force_stoploss else self.stoploss + sl_offset = self.config.get('trailing_stop_positive_offset', 0.0) if 'trailing_stop_positive' in self.config and current_profit > sl_offset: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 6c25b1a7f..1149a69e9 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1109,6 +1109,116 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, stop_price=0.00002344 * 0.95) +def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, + markets, limit_buy_order, limit_sell_order) -> None: + + # When trailing stoploss is set + stoploss_limit = MagicMock(return_value={'id': 13434334}) + patch_RPCManager(mocker) + patch_exchange(mocker) + patch_edge(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 + ) + + # enabling TSL + edge_conf['trailing_stop'] = True + edge_conf['trailing_stop_positive'] = 0.01 + edge_conf['trailing_stop_positive_offset'] = 0.011 + + # disabling ROI + edge_conf['minimal_roi']['0'] = 999999999 + + freqtrade = FreqtradeBot(edge_conf) + + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + # setting stoploss + freqtrade.strategy.stoploss = -0.02 + + # setting stoploss_on_exchange_interval to 0 second + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + + patch_get_signal(freqtrade) + + freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) + + freqtrade.create_trade() + trade = Trade.query.first() + trade.is_open = True + trade.open_order_id = None + trade.stoploss_order_id = 100 + + stoploss_order_hanging = MagicMock(return_value={ + 'id': 100, + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'info': { + 'stopPrice': '0.000009384' + } + }) + + mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) + + # stoploss initially at 20% as edge dictated it. + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.stop_loss == 0.000009384 + + cancel_order_mock = MagicMock() + stoploss_order_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock) + + # price goes down 5% + mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ + 'bid': 0.00001172 * 0.95, + 'ask': 0.00001173 * 0.95, + 'last': 0.00001172 * 0.95 + })) + + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + # stoploss should remain the same + assert trade.stop_loss == 0.000009384 + + # stoploss on exchange should not be canceled + cancel_order_mock.assert_not_called() + + # price jumped 2x + mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ + 'bid': 0.00002344, + 'ask': 0.00002346, + 'last': 0.00002344 + })) + + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + # stoploss should be set to 1% as trailing is on + assert trade.stop_loss == 0.00002344 * 0.99 + cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') + stoploss_order_mock.assert_called_once_with(amount=2131074.168797954, + pair='NEO/BTC', + rate=0.00002344 * 0.99 * 0.99, + stop_price=0.00002344 * 0.99) + + def test_process_maybe_execute_buy(mocker, default_conf) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From 91c714c7d17907afc9404075eb78ef87d634d984 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 18:58:12 +0100 Subject: [PATCH 34/63] stoploss_on_exchange_interval added to full config --- config_full.json.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config_full.json.example b/config_full.json.example index 0427f8700..234722f82 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -37,7 +37,8 @@ "buy": "limit", "sell": "limit", "stoploss": "market", - "stoploss_on_exchange": "false" + "stoploss_on_exchange": "false", + "stoploss_on_exchange_interval": 60 }, "order_time_in_force": { "buy": "gtc", From da51ef40f804db5cbd7e711f345e4459d3d87379 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:04:43 +0100 Subject: [PATCH 35/63] SL interval added to CONF_SCHEMA --- freqtrade/constants.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b2393c2b7..9e5561ba3 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -112,7 +112,8 @@ CONF_SCHEMA = { 'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, - 'stoploss_on_exchange': {'type': 'boolean'} + 'stoploss_on_exchange': {'type': 'boolean'}, + 'stoploss_on_exchange_interval': {'type': 'boolean'} }, 'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] }, @@ -137,7 +138,7 @@ CONF_SCHEMA = { 'pairlist': { 'type': 'object', 'properties': { - 'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, + 'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, 'config': {'type': 'object'} }, 'required': ['method'] From 08d98773f39503f01506214fd2d39485abefcbf0 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:10:13 +0100 Subject: [PATCH 36/63] added SL interval to configuration document --- docs/configuration.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a7deaa60c..2f72da794 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -144,10 +144,10 @@ end up paying more then would probably have been necessary. ### Understand order_types -`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. +`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. 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. In case stoploss on exchange is set then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss). This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations. -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. +If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"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. ```python @@ -155,7 +155,8 @@ 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, + "stoploss_on_exchange_interval": 60 }, ``` @@ -163,6 +164,9 @@ The below is the default which is used if this is not configured in either Strat Not all exchanges support "market" orders. The following message will be shown if your exchange does not support market orders: `"Exchange does not support market orders."` +!!! Note + stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. + ### Understand order_time_in_force `order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:
**GTC (Goog Till Canceled):** From 2533112254f1fe183f993630a3b1491eb4bd72ce Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:12:52 +0100 Subject: [PATCH 37/63] added referral to stop loss documentation --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 2f72da794..bad8d8b4e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -165,7 +165,7 @@ The below is the default which is used if this is not configured in either Strat The following message will be shown if your exchange does not support market orders: `"Exchange does not support market orders."` !!! Note - stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. + stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. For more information about how stoploss works please read [the stoploss documentation](stoploss.md). ### Understand order_time_in_force `order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:
From e682eceae4f5d7b89de1d98c5834132dd629cac6 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:22:06 +0100 Subject: [PATCH 38/63] stop loss documentation added --- docs/stoploss.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 0278e7bbb..6ee7dd6e8 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -2,12 +2,17 @@ At this stage the bot contains the following stoploss support modes: -1. static stop loss, defined in either the strategy or configuration -2. trailing stop loss, defined in the configuration -3. trailing stop loss, custom positive loss, defined in configuration +1. static stop loss, defined in either the strategy or configuration. +2. trailing stop loss, defined in the configuration. +3. trailing stop loss, custom positive loss, defined in configuration. !!! Note - All stoploss properties can be configured in eihter Strategy or configuration. Configuration values override strategy values. + All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values. + +Those stoploss modes can be `on exchange` or `off exchange`. If the stoploss is `on exchange` it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. + +In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). + ## Static Stop Loss From 9d6c54791b5a1d8d2ee2b824c19368c322d03a84 Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 19:23:55 +0100 Subject: [PATCH 39/63] added note for only binance --- docs/stoploss.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/stoploss.md b/docs/stoploss.md index 6ee7dd6e8..70ce6b0f6 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -13,6 +13,9 @@ Those stoploss modes can be `on exchange` or `off exchange`. If the stoploss is In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). +!!! Note + Stoploss on exchange is only supported for Binance as of now. + ## Static Stop Loss From 75cedfafb88d9db97a88e17add8ee958d04c0efc Mon Sep 17 00:00:00 2001 From: misagh Date: Wed, 16 Jan 2019 20:03:34 +0100 Subject: [PATCH 40/63] unreachable code removed --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4febe9dd0..41938f291 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -601,7 +601,7 @@ class FreqtradeBot(object): if self.check_sell(trade, sell_rate, buy, sell): return True - break + else: logger.debug('checking sell') if self.check_sell(trade, sell_rate, buy, sell): From 97a8341436cd9c4f16e6139e3ec3de524630ff70 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 17 Jan 2019 13:32:07 +0100 Subject: [PATCH 41/63] Update ccxt from 1.18.131 to 1.18.133 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 92885045d..57fda05eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.131 +ccxt==1.18.133 SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 From 648def69cab0e80c7b3220410ebd59d457a87107 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Jan 2019 19:05:28 +0100 Subject: [PATCH 42/63] Remove unversioned install of most dev packages --- .travis.yml | 2 +- requirements-dev.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 57265fd40..3dfcf6111 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ addons: install: - cd build_helpers && ./install_ta-lib.sh; cd .. - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH -- pip install --upgrade flake8 coveralls pytest-random-order pytest-asyncio mypy +- pip install --upgrade pytest-random-order - pip install -r requirements-dev.txt - pip install -e . jobs: diff --git a/requirements-dev.txt b/requirements-dev.txt index 50d181296..870a0e1b9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,3 +6,5 @@ pytest==4.1.1 pytest-mock==1.10.0 pytest-asyncio==0.10.0 pytest-cov==2.6.1 +coveralls==1.5.1 +mypy==0.650 From a2c01916e19955c1f4ace5c83dde60041b6afc58 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Jan 2019 20:28:21 +0100 Subject: [PATCH 43/63] Add type-ignores to floatfmt tabulate supports this: https://bitbucket.org/astanin/python-tabulate/src/30554300d76f673377956f7980ba4fd2f892c671/tabulate.py?at=master&fileviewer=file-view-default#tabulate.py-1291:1294 --- freqtrade/optimize/backtesting.py | 8 ++++++-- freqtrade/optimize/edge_cli.py | 4 +++- freqtrade/rpc/telegram.py | 16 ++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 80dc9a443..38bbe13d4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -134,7 +134,9 @@ class Backtesting(object): len(results[results.profit_abs > 0]), len(results[results.profit_abs < 0]) ]) - return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") + # Ignore type as floatfmt does allow tuples but mypy does not know that + return tabulate(tabular_data, headers=headers, # type: ignore + floatfmt=floatfmt, tablefmt="pipe") def _generate_text_table_sell_reason(self, data: Dict[str, Dict], results: DataFrame) -> str: """ @@ -168,7 +170,9 @@ class Backtesting(object): len(results[results.profit_abs > 0]), len(results[results.profit_abs < 0]) ]) - return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") + # Ignore type as floatfmt does allow tuples but mypy does not know that + return tabulate(tabular_data, headers=headers, # type: ignore + floatfmt=floatfmt, tablefmt="pipe") def _store_backtest_result(self, recordfilename: str, results: DataFrame, strategyname: Optional[str] = None) -> None: diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index a98f0c877..fdae47b99 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -67,7 +67,9 @@ class EdgeCli(object): round(result[1].avg_trade_duration) ]) - return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") + # Ignore type as floatfmt does allow tuples but mypy does not know that + return tabulate(tabular_data, headers=headers, # type: ignore + floatfmt=floatfmt, tablefmt="pipe") def start(self) -> None: self.edge.calculate() diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index be2498d78..3ce7c9167 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -246,14 +246,14 @@ class Telegram(RPC): stake_cur, fiat_disp_cur ) - stats = tabulate(stats, - headers=[ - 'Day', - f'Profit {stake_cur}', - f'Profit {fiat_disp_cur}' - ], - tablefmt='simple') - message = f'Daily Profit over the last {timescale} days:\n
{stats}
' + stats_tab = tabulate(stats, + headers=[ + 'Day', + f'Profit {stake_cur}', + f'Profit {fiat_disp_cur}' + ], + tablefmt='simple') + message = f'Daily Profit over the last {timescale} days:\n
{stats_tab}
' self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML) except RPCException as e: self._send_msg(str(e), bot=bot) From cc6466388e9aa61c420fbf8db15cc6dd4c0a6aad Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Jan 2019 20:31:00 +0100 Subject: [PATCH 44/63] update mypy --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 870a0e1b9..f68fe8814 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,4 +7,4 @@ pytest-mock==1.10.0 pytest-asyncio==0.10.0 pytest-cov==2.6.1 coveralls==1.5.1 -mypy==0.650 +mypy==0.660 From e41e45413faf518bc0cf0fb86b43640cb3e09a46 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 11:58:23 +0100 Subject: [PATCH 45/63] adding tailing_stop to docs --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index bad8d8b4e..c3843cc2a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -144,7 +144,7 @@ end up paying more then would probably have been necessary. ### Understand order_types -`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. 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. In case stoploss on exchange is set then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss). +`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. 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. In case stoploss on exchange and `trailing_stop` are both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss). This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations. If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start. From 1c4ee35ecab03f4a4b1295f8b71bdd89788a4a20 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:00:02 +0100 Subject: [PATCH 46/63] =?UTF-8?q?using=20italic=20for=20=E2=80=9Coff=20exc?= =?UTF-8?q?hange=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 70ce6b0f6..78a486ab5 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -9,7 +9,7 @@ At this stage the bot contains the following stoploss support modes: !!! Note All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values. -Those stoploss modes can be `on exchange` or `off exchange`. If the stoploss is `on exchange` it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. +Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). From 89eddfd349d4ac7b04c519d61aed45f6e047273d Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:00:53 +0100 Subject: [PATCH 47/63] =?UTF-8?q?refactoring=20english=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 78a486ab5..0726aebbc 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -11,7 +11,7 @@ At this stage the bot contains the following stoploss support modes: Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. -In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. That means the interval in seconds the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). +In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). !!! Note Stoploss on exchange is only supported for Binance as of now. From 70780bb01e491945a02f3ba73975a513a95fbad4 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:02:29 +0100 Subject: [PATCH 48/63] using dict.get to fetch interval --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9b5ab4bba..56b518472 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -667,7 +667,7 @@ class FreqtradeBot(object): if trade.stop_loss > float(order['info']['stopPrice']): # we check if the update is neccesary - update_beat = self.strategy.order_types['stoploss_on_exchange_interval'] + update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat: # cancelling the current stoploss on exchange first logger.info('Trailing stoploss: cancelling current stoploss on exchange ' From a2618208ef78427fe334c90b9a875c67c6664ec4 Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:07:51 +0100 Subject: [PATCH 49/63] wrapping in parantheses instead of line breaks --- freqtrade/strategy/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 88029f4d4..08a5cf1cd 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -278,9 +278,9 @@ class IStrategy(ABC): else self.stoploss, initial=True) # evaluate if the stoploss was hit if stoploss is not on exchange - if self.stoploss is not None and \ - trade.stop_loss >= current_rate and \ - not self.order_types.get('stoploss_on_exchange'): + if ((self.stoploss is not None) and + (trade.stop_loss >= current_rate) and + (not self.order_types.get('stoploss_on_exchange'))): selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: From 87329a393c444937dda6e0487f5b888e2cba62fc Mon Sep 17 00:00:00 2001 From: misagh Date: Fri, 18 Jan 2019 12:14:00 +0100 Subject: [PATCH 50/63] adding stop loss last update to test persistence --- 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 e64a08262..be6efc2ff 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -516,6 +516,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.strategy is None assert trade.ticker_interval is None assert trade.stoploss_order_id is None + assert trade.stoploss_last_update 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 2b65e3f35cc2fba3f1ebb87b171ad0a794f2902a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 19 Jan 2019 13:32:06 +0100 Subject: [PATCH 51/63] Update ccxt from 1.18.133 to 1.18.134 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57fda05eb..9781093e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.133 +ccxt==1.18.134 SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 From b421e437ab9290ae374090e4245fbb76212d14a3 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 19 Jan 2019 13:32:07 +0100 Subject: [PATCH 52/63] Update wrapt from 1.11.0 to 1.11.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9781093e9..098aa4e19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ arrow==0.13.0 cachetools==3.0.0 requests==2.21.0 urllib3==1.24.1 -wrapt==1.11.0 +wrapt==1.11.1 pandas==0.23.4 scikit-learn==0.20.2 joblib==0.13.1 From 797ac71376474ac10b9c4dfbfd8f9c61f1e16347 Mon Sep 17 00:00:00 2001 From: Axel CHERUBIN Date: Sat, 19 Jan 2019 13:26:30 -0400 Subject: [PATCH 53/63] fix stoploss_on_exchange_interval type from boolean to number --- freqtrade/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 9e5561ba3..8fbcdfed7 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -113,7 +113,7 @@ CONF_SCHEMA = { 'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'stoploss_on_exchange': {'type': 'boolean'}, - 'stoploss_on_exchange_interval': {'type': 'boolean'} + 'stoploss_on_exchange_interval': {'type': 'number'} }, 'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] }, From 30e3b52b1e7f02b8a159acefa99627b6ad1104c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Jan 2019 20:02:37 +0100 Subject: [PATCH 54/63] catch errors found in #1491 --- freqtrade/exchange/__init__.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 90a33a226..b8d32306e 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -556,12 +556,17 @@ class Exchange(object): tickers = await asyncio.gather(*input_coroutines, return_exceptions=True) # handle caching - for pair, ticks in tickers: + for res in tickers: + if isinstance(res, Exception): + logger.warning("Async code raised an exception: %s", res.__class__.__name__) + continue + pair = res[0] + ticks = res[1] # keeping last candle time as last refreshed time of the pair if ticks: self._pairs_last_refresh_time[pair] = ticks[-1][0] // 1000 - # keeping parsed dataframe in cache - self._klines[pair] = parse_ticker_dataframe(ticks, tick_interval, fill_missing=True) + # keeping parsed dataframe in cache + self._klines[pair] = parse_ticker_dataframe(ticks, tick_interval, fill_missing=True) return tickers @retrier_async @@ -578,9 +583,12 @@ class Exchange(object): # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) # when GDAX returns a list of tickers DESC (newest first, oldest last) # Only sort if necessary to save computing time - if data and data[0][0] > data[-1][0]: - data = sorted(data, key=lambda x: x[0]) - + try: + if data and data[0][0] > data[-1][0]: + data = sorted(data, key=lambda x: x[0]) + except IndexError: + logger.exception("Error loading %s. Result was %s.", pair, data) + return pair, None logger.debug("done fetching %s ...", pair) return pair, data From 4e760e1a5e52e213be77931ed4f9586f5de7cdb1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Jan 2019 20:03:04 +0100 Subject: [PATCH 55/63] Test for errors found in 1491 fixes #1491 --- freqtrade/tests/exchange/test_exchange.py | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 29154bc39..26808e78a 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -923,6 +923,30 @@ async def test_async_get_candles_history(default_conf, mocker): assert exchange._async_get_candle_history.call_count == 2 +@pytest.mark.asyncio +async def test_async_get_candles_history_inv_result(default_conf, mocker, caplog): + + async def mock_get_candle_hist(pair, *args, **kwargs): + if pair == 'ETH/BTC': + return [[]] + else: + raise TypeError() + + exchange = get_patched_exchange(mocker, default_conf) + + # Monkey-patch async function with empty result + exchange._api_async.fetch_ohlcv = MagicMock(side_effect=mock_get_candle_hist) + + pairs = ['ETH/BTC', 'XRP/BTC'] + res = await exchange.async_get_candles_history(pairs, "5m") + assert type(res) is list + assert len(res) == 2 + assert type(res[0]) is tuple + assert type(res[1]) is TypeError + assert log_has("Error loading ETH/BTC. Result was [[]].", caplog.record_tuples) + assert log_has("Async code raised an exception: TypeError", caplog.record_tuples) + + def test_get_order_book(default_conf, mocker, order_book_l2): default_conf['exchange']['name'] = 'binance' api_mock = MagicMock() From b48430f9221c33407dc6c35d890ba916a0876a8a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Jan 2019 20:21:33 +0100 Subject: [PATCH 56/63] Return list not None --- 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 b8d32306e..e0e4d7723 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -588,7 +588,7 @@ class Exchange(object): data = sorted(data, key=lambda x: x[0]) except IndexError: logger.exception("Error loading %s. Result was %s.", pair, data) - return pair, None + return pair, [] logger.debug("done fetching %s ...", pair) return pair, data From a7336300837fc15cc7ebef8c82cf8dc0b962f1b0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 20 Jan 2019 13:32:07 +0100 Subject: [PATCH 57/63] Update ccxt from 1.18.134 to 1.18.137 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 098aa4e19..063f961bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.134 +ccxt==1.18.137 SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 From 6fb50e35c933ef4c7b673a3745894569062b02cd Mon Sep 17 00:00:00 2001 From: macd2 Date: Sun, 20 Jan 2019 14:21:42 +0100 Subject: [PATCH 58/63] JSON standard does not allow single quoted strings --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index c3843cc2a..95bf3b1ff 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -13,7 +13,7 @@ The table below will list all configuration parameters. |----------|---------|-----------|-------------| | `max_open_trades` | 3 | Yes | Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) | `stake_currency` | BTC | Yes | Crypto-currency used for trading. -| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to 'unlimited' to allow the bot to use all avaliable balance. +| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all avaliable balance. | `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes | `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. From e4a399039be75f73de892e8197f2ff9d8b6f445e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 21 Jan 2019 13:32:07 +0100 Subject: [PATCH 59/63] Update ccxt from 1.18.137 to 1.18.141 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 063f961bc..8e6720195 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.137 +ccxt==1.18.141 SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 From 1be3d57b60a4f5feef41e7178b2f0a04d2f82307 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 21 Jan 2019 19:53:14 +0100 Subject: [PATCH 60/63] Improve developer docs --- CONTRIBUTING.md | 20 ++++---------------- docs/developer.md | 14 +++++++++++++- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9a967834..3c511f44d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,10 @@ Few pointers for contributions: If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR. +## Getting started + +Best start by reading the [documentation](https://www.freqtrade.io/) to get a feel for what is possible with the bot, or head straight to the [Developer-documentation](https://www.freqtrade.io/en/latest/developer/) (WIP) which should help you getting started. + ## Before sending the PR: ### 1. Run unit tests @@ -41,12 +45,6 @@ pytest freqtrade/tests/test_.py::test_ ### 2. Test if your code is PEP8 compliant -#### Install packages - -```bash -pip3.6 install flake8 coveralls -``` - #### Run Flake8 ```bash @@ -60,22 +58,12 @@ Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using ### 3. Test if all type-hints are correct -#### Install packages - -``` bash -pip3.6 install mypy -``` - #### Run mypy ``` bash mypy freqtrade ``` -## Getting started - -Best start by reading the [documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md) to get a feel for what is possible with the bot, or head straight to the [Developer-documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/developer.md) (WIP) which should help you getting started. - ## (Core)-Committer Guide ### Process: Pull Requests diff --git a/docs/developer.md b/docs/developer.md index 64f3b9a52..6fbcdc812 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -4,8 +4,20 @@ This page is intended for developers of FreqTrade, people who want to contribute All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) where you can ask questions. +## Documentation -## Module +Documentation is available at [https://freqtrade.io](https://www.freqtrade.io/) and needs to be provided with every new feature PR. + +Special fields for the documentation (like Note boxes, ...) can be found [here](https://squidfunk.github.io/mkdocs-material/extensions/admonition/). + +## Developer setup + +To configure a development environment, use best use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ". +Alternatively (if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -r requirements-dev.txt`. + +This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`. + +## Modules ### Dynamic Pairlist From 7c71b9513cf8cbb9e75e277b01cd5455f3c464ed Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 22 Jan 2019 13:32:11 +0100 Subject: [PATCH 61/63] Update ccxt from 1.18.141 to 1.18.144 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8e6720195..25e337306 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.141 +ccxt==1.18.144 SQLAlchemy==1.2.16 python-telegram-bot==11.1.0 arrow==0.13.0 From c412cd9e5704d220035826524469db8aaf87e69d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Jan 2019 19:44:56 +0100 Subject: [PATCH 62/63] Add information about dataframe fix #1192 --- docs/bot-optimization.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 1cfae1bc4..1c622737f 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -216,12 +216,40 @@ This is the set of candles the bot should download and use for the analysis. Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. Please note that the same buy/sell signals may work with one interval, but not the other. +This setting is accessible within the strategy by using `self.ticker_interval`. ### Metadata dict The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information. Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`. +The Metadata-dict should not be modified and does not persist information across multiple calls. +Instead, have a look at the section [Storing information](#Storing-information) + +### Storing information + +Storing information can be accomplished by crating a new dictionary within the strategy class. + +The name of the variable can be choosen at will, but should be prefixed with `cust_` to avoid naming collisions with predefined strategy variables. + +```python +class Awesomestrategy(IStrategy): + # Create custom dictionary + cust_info = {} + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # Check if the entry already exists + if "crosstime" in self.cust_info[metadata["pair"]: + self.cust_info[metadata["pair"]["crosstime"] += 1 + else: + self.cust_info[metadata["pair"]["crosstime"] = 1 +``` + +!!! Warning: + The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash. + +!!! Note: + If the data is pair-specific, make sure to use pair as one of the keys in the dictionary. + ### Where is the default strategy? The default buy strategy is located in the file From 13e2f71d3008d9c6fa66bf9c53ddc53ab6cffd9e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Jan 2019 20:01:12 +0100 Subject: [PATCH 63/63] Add flake8 plugins and implement small improvements --- freqtrade/freqtradebot.py | 2 +- freqtrade/optimize/backtesting.py | 2 +- requirements-dev.txt | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2e09cf116..7d14c734c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -34,7 +34,7 @@ class FreqtradeBot(object): This is from here the bot start its logic. """ - def __init__(self, config: Dict[str, Any])-> None: + def __init__(self, config: Dict[str, Any]) -> None: """ Init all variables and object the bot need to work :param config: configuration dict, you can use the Configuration.get_config() diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 38bbe13d4..37ba2ad83 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -13,7 +13,7 @@ from typing import Any, Dict, List, NamedTuple, Optional from pandas import DataFrame from tabulate import tabulate -import freqtrade.optimize as optimize +from freqtrade import optimize from freqtrade import DependencyException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration diff --git a/requirements-dev.txt b/requirements-dev.txt index f68fe8814..4f8504c8a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,8 @@ -r requirements.txt flake8==3.6.0 +flake8-type-annotations==0.1.0 +flake8-tidy-imports==1.1.0 pytest==4.1.1 pytest-mock==1.10.0 pytest-asyncio==0.10.0