From 665b72274c6c49f0e55cccc0411f9709c4a60d99 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Wed, 20 Dec 2017 15:16:31 +0100 Subject: [PATCH 01/10] trailng stop loss --- config.json.example | 6 ++++++ freqtrade/analyze.py | 1 + freqtrade/main.py | 20 ++++++++++++++++++-- freqtrade/misc.py | 7 +++++++ freqtrade/persistence.py | 30 +++++++++++++++++++++++++++++- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/config.json.example b/config.json.example index cae64aab5..de7aa813a 100644 --- a/config.json.example +++ b/config.json.example @@ -9,6 +9,12 @@ "20": 0.02, "0": 0.04 }, + "trailing_stoploss": { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + }, "stoploss": -0.10, "bid_strategy": { "ask_last_balance": 0.0 diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index d586077db..dce5317a1 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -88,6 +88,7 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame: (dataframe['plus_di'] > 0.5) ), 'buy'] = 1 + dataframe['buy'] = 1 return dataframe diff --git a/freqtrade/main.py b/freqtrade/main.py index 941d6870a..90b358915 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -138,7 +138,20 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']): logger.debug('Stop loss hit.') return True - + + # Check if profit is positive, time matches and current rate is below trailing stop loss + if current_profit > 0: + logger.info('Check trailing stop loss...') + time_diff = (current_time - trade.open_date).total_seconds() / 60 + for duration, threshold in sorted(_CONF['trailing_stoploss'].items()): + if time_diff > float(duration): + print(current_profit, current_rate, trade.stat_max_rate) + percentage_change = ((current_rate - trade.stat_max_rate) / trade.stat_max_rate) + logger.info('Check trailing stop loss. %s < %s' % (percentage_change, -threshold)) + if percentage_change < -threshold: + logger.info('Trailing stop loss hit: %s, %s : %s < %s' % (duration, threshold, percentage_change, -threshold)) + return True + # Check if time matches and current rate is above threshold time_diff = (current_time - trade.open_date).total_seconds() / 60 for duration, threshold in sorted(_CONF['minimal_roi'].items()): @@ -159,6 +172,9 @@ def handle_trade(trade: Trade) -> bool: logger.debug('Handling %s ...', trade) current_rate = exchange.get_ticker(trade.pair)['bid'] + + # Update statistic values for trailing stoploss + trade.update_stats(current_rate) # Check if minimal roi has been reached if not min_roi_reached(trade, current_rate, datetime.utcnow()): @@ -311,7 +327,7 @@ def main() -> None: # Initialize logger logging.basicConfig( - level=args.loglevel, + level=args.loglevel,#'DEBUG',#args.loglevel, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', ) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 1dcd33842..87305736a 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -216,6 +216,13 @@ CONF_SCHEMA = { }, 'minProperties': 1 }, + 'trailing_stoploss': { + 'type': 'object', + 'patternProperties': { + '^[0-9.]+$': {'type': 'number'} + }, + 'minProperties': 0 + }, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'bid_strategy': { 'type': 'object', diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ba0ad1785..d286a6800 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -71,6 +71,10 @@ class Trade(_DECL_BASE): open_date = Column(DateTime, nullable=False, default=datetime.utcnow) close_date = Column(DateTime) open_order_id = Column(String) + stat_min_rate = Column(Float) + stat_min_rate_date = Column(DateTime) + stat_max_rate = Column(Float) + stat_max_rate_date = Column(DateTime) def __repr__(self): return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format( @@ -78,9 +82,33 @@ class Trade(_DECL_BASE): self.pair, self.amount, self.open_rate, - arrow.get(self.open_date).humanize() if self.is_open else 'closed' + arrow.get(self.open_date).humanize() if self.is_open else 'closed', + self.stat_min_rate, + self.stat_max_rate, ) + def update_stats(self, current_rate: Dict) -> None: + """ + Updates this entity statistics with current rates. + :param current_rate: current rate retrieved by exchange.get_ticker() + :return: None + """ + logger.info('Updating statistics for trade (id=%d) ...', self.id) + need_update = False + if not self.stat_min_rate or current_rate < self.stat_min_rate: + logger.info('Update stat_min_rate. %s -> %s' % (self.stat_min_rate, current_rate)) + self.stat_min_rate = current_rate + self.stat_min_rate_date = datetime.utcnow() + need_update = True + if not self.stat_max_rate or current_rate > self.stat_max_rate: + logger.info('Update stat_max_rate. %s -> %s' % (self.stat_max_rate, current_rate)) + self.stat_max_rate = current_rate + self.stat_max_rate_date = datetime.utcnow() + need_update = True + if need_update: + Trade.session.flush() + + def update(self, order: Dict) -> None: """ Updates this entity with amount and actual open/close rates. From 49b62f619dcd722bccdb9075382df0a8ed6b3485 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Wed, 20 Dec 2017 15:36:16 +0100 Subject: [PATCH 02/10] added trailing stop loss --- freqtrade/main.py | 16 ++++++++++++++++ freqtrade/misc.py | 7 +++++++ freqtrade/persistence.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 941d6870a..c1770f0c3 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -138,6 +138,19 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']): logger.debug('Stop loss hit.') return True + + # Check if profit is positive, time matches and current rate is below trailing stop loss + if current_profit > 0: + logger.info('Check trailing stop loss...') + time_diff = (current_time - trade.open_date).total_seconds() / 60 + for duration, threshold in sorted(_CONF['trailing_stoploss'].items()): + if time_diff > float(duration): + print(current_profit, current_rate, trade.stat_max_rate) + percentage_change = ((current_rate - trade.stat_max_rate) / trade.stat_max_rate) + logger.info('Check trailing stop loss. %s < %s' % (percentage_change, -threshold)) + if percentage_change < -threshold: + logger.info('Trailing stop loss hit: %s, %s : %s < %s' % (duration, threshold, percentage_change, -threshold)) + return True # Check if time matches and current rate is above threshold time_diff = (current_time - trade.open_date).total_seconds() / 60 @@ -160,6 +173,9 @@ def handle_trade(trade: Trade) -> bool: logger.debug('Handling %s ...', trade) current_rate = exchange.get_ticker(trade.pair)['bid'] + # Update statistic values for trailing stoploss + trade.update_stats(current_rate) + # Check if minimal roi has been reached if not min_roi_reached(trade, current_rate, datetime.utcnow()): return False diff --git a/freqtrade/misc.py b/freqtrade/misc.py index b01fd9fe9..1894cd62a 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -216,6 +216,13 @@ CONF_SCHEMA = { }, 'minProperties': 1 }, + 'trailing_stoploss': { + 'type': 'object', + 'patternProperties': { + '^[0-9.]+$': {'type': 'number'} + }, + 'minProperties': 0 + }, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'bid_strategy': { 'type': 'object', diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ba0ad1785..61781650b 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -71,6 +71,10 @@ class Trade(_DECL_BASE): open_date = Column(DateTime, nullable=False, default=datetime.utcnow) close_date = Column(DateTime) open_order_id = Column(String) + stat_min_rate = Column(Float) + stat_min_rate_date = Column(DateTime) + stat_max_rate = Column(Float) + stat_max_rate_date = Column(DateTime) def __repr__(self): return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format( @@ -78,9 +82,32 @@ class Trade(_DECL_BASE): self.pair, self.amount, self.open_rate, - arrow.get(self.open_date).humanize() if self.is_open else 'closed' + arrow.get(self.open_date).humanize() if self.is_open else 'closed', + self.stat_min_rate, + self.stat_max_rate, ) + def update_stats(self, current_rate: Dict) -> None: + """ + Updates this entity statistics with current rates. + :param current_rate: current rate retrieved by exchange.get_ticker() + :return: None + """ + logger.info('Updating statistics for trade (id=%d) ...', self.id) + need_update = False + if not self.stat_min_rate or current_rate < self.stat_min_rate: + logger.info('Update stat_min_rate. %s -> %s' % (self.stat_min_rate, current_rate)) + self.stat_min_rate = current_rate + self.stat_min_rate_date = datetime.utcnow() + need_update = True + if not self.stat_max_rate or current_rate > self.stat_max_rate: + logger.info('Update stat_max_rate. %s -> %s' % (self.stat_max_rate, current_rate)) + self.stat_max_rate = current_rate + self.stat_max_rate_date = datetime.utcnow() + need_update = True + if need_update: + Trade.session.flush() + def update(self, order: Dict) -> None: """ Updates this entity with amount and actual open/close rates. From bc872820fae25fd1f8f299be58b11ce1746babd7 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Wed, 20 Dec 2017 15:42:01 +0100 Subject: [PATCH 03/10] added trailing stop loss config to config.json.example --- config.json.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.json.example b/config.json.example index cae64aab5..ef01c1b00 100644 --- a/config.json.example +++ b/config.json.example @@ -9,6 +9,11 @@ "20": 0.02, "0": 0.04 }, + "trailing_stoploss": { + "120": 0.0, + "40" : 0.01, + "0" : 0.03 + }, "stoploss": -0.10, "bid_strategy": { "ask_last_balance": 0.0 From e24f4243a95b15ba2acf0eaed7a90e2143d4aee9 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Tue, 2 Jan 2018 15:15:38 +0100 Subject: [PATCH 04/10] no flushing anymore --- freqtrade/persistence.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index d286a6800..884a9c50f 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -94,20 +94,14 @@ class Trade(_DECL_BASE): :return: None """ logger.info('Updating statistics for trade (id=%d) ...', self.id) - need_update = False if not self.stat_min_rate or current_rate < self.stat_min_rate: logger.info('Update stat_min_rate. %s -> %s' % (self.stat_min_rate, current_rate)) self.stat_min_rate = current_rate self.stat_min_rate_date = datetime.utcnow() - need_update = True if not self.stat_max_rate or current_rate > self.stat_max_rate: logger.info('Update stat_max_rate. %s -> %s' % (self.stat_max_rate, current_rate)) self.stat_max_rate = current_rate self.stat_max_rate_date = datetime.utcnow() - need_update = True - if need_update: - Trade.session.flush() - def update(self, order: Dict) -> None: """ From c8405fce5985b41b828f4656a0af47bd593af9b4 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Tue, 2 Jan 2018 15:42:32 +0100 Subject: [PATCH 05/10] delete flush --- freqtrade/persistence.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 61781650b..1d62a4b99 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -94,19 +94,14 @@ class Trade(_DECL_BASE): :return: None """ logger.info('Updating statistics for trade (id=%d) ...', self.id) - need_update = False if not self.stat_min_rate or current_rate < self.stat_min_rate: logger.info('Update stat_min_rate. %s -> %s' % (self.stat_min_rate, current_rate)) self.stat_min_rate = current_rate self.stat_min_rate_date = datetime.utcnow() - need_update = True if not self.stat_max_rate or current_rate > self.stat_max_rate: logger.info('Update stat_max_rate. %s -> %s' % (self.stat_max_rate, current_rate)) self.stat_max_rate = current_rate self.stat_max_rate_date = datetime.utcnow() - need_update = True - if need_update: - Trade.session.flush() def update(self, order: Dict) -> None: """ From 5a15d50a8d4634fd3eebc3ac2d047b0ae8143462 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Tue, 2 Jan 2018 15:57:40 +0100 Subject: [PATCH 06/10] worked in comments from glonlas --- freqtrade/main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 9cdf7bf7c..dc8a93309 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -176,11 +176,15 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - time_diff = (current_time - trade.open_date).total_seconds() / 60 for duration, threshold in sorted(_CONF['trailing_stoploss'].items()): if time_diff > float(duration): - print(current_profit, current_rate, trade.stat_max_rate) percentage_change = ((current_rate - trade.stat_max_rate) / trade.stat_max_rate) logger.debug('Check trailing stop loss. %s < %s' % (percentage_change, -threshold)) if percentage_change < -threshold: - logger.debug('Trailing stop loss hit: %s, %s : %s < %s' % (duration, threshold, percentage_change, -threshold)) + logger.debug('Trailing stop loss hit: %s, %s : %s < %s' % \ + (duration, + threshold, + percentage_change, + -threshold) + ) return True # Check if time matches and current rate is above threshold From 8480f537308994a67ee89015659d6cec09615435 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Tue, 2 Jan 2018 16:05:54 +0100 Subject: [PATCH 07/10] add backtesting --- freqtrade/optimize/backtesting.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 984ca3e72..c3ce2ff6d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -105,6 +105,10 @@ def backtest(stake_amount: float, processed: Dict[str, DataFrame], # calculate win/lose forwards from buy point for row2 in ticker[row.Index + 1:].itertuples(index=True): + + # Update statistic values for trailing stoploss + trade.update_stats(row2.close, backtest=True) + if max_open_trades > 0: # Increase trade_count_lock for every iteration trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1 From df86d72f8b963c2ac91f3bd7552f8184d4c7bafb Mon Sep 17 00:00:00 2001 From: chrisapril Date: Tue, 2 Jan 2018 16:10:13 +0100 Subject: [PATCH 08/10] check if config contains trailing_stoploss --- freqtrade/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index dc8a93309..22faf9029 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -171,7 +171,7 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - return True # Check if profit is positive, time matches and current rate is below trailing stop loss - if current_profit > 0: + if current_profit > 0 and 'trailing_stoploss' in _CONF: logger.debug('Check trailing stop loss...') time_diff = (current_time - trade.open_date).total_seconds() / 60 for duration, threshold in sorted(_CONF['trailing_stoploss'].items()): From fc862f5bb99a065e01651ca5598ef10e6e60bdc2 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Tue, 2 Jan 2018 16:17:50 +0100 Subject: [PATCH 09/10] bugfix --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c3ce2ff6d..9d5e89a7a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -107,7 +107,7 @@ def backtest(stake_amount: float, processed: Dict[str, DataFrame], for row2 in ticker[row.Index + 1:].itertuples(index=True): # Update statistic values for trailing stoploss - trade.update_stats(row2.close, backtest=True) + trade.update_stats(row2.close) if max_open_trades > 0: # Increase trade_count_lock for every iteration From dfdb58edff075a72b52d7ac23430fb9fcd96e0f8 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Tue, 2 Jan 2018 16:24:23 +0100 Subject: [PATCH 10/10] bugfix backtesting --- freqtrade/optimize/backtesting.py | 1 + freqtrade/persistence.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 9d5e89a7a..2af60e0c3 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -96,6 +96,7 @@ def backtest(stake_amount: float, processed: Dict[str, DataFrame], trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 trade = Trade( + pair=pair, open_rate=row.close, open_date=row.date, stake_amount=stake_amount, diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 4b62d95e6..17c55b3f6 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -93,13 +93,12 @@ class Trade(_DECL_BASE): :param current_rate: current rate retrieved by exchange.get_ticker() :return: None """ - logger.info('Updating statistics for trade (id=%d) ...', self.id) if not self.stat_min_rate or current_rate < self.stat_min_rate: - logger.info('Update stat_min_rate. %s -> %s' % (self.stat_min_rate, current_rate)) + logger.debug('Update stat_min_rate. %s -> %s' % (self.stat_min_rate, current_rate)) self.stat_min_rate = current_rate self.stat_min_rate_date = datetime.utcnow() if not self.stat_max_rate or current_rate > self.stat_max_rate: - logger.info('Update stat_max_rate. %s -> %s' % (self.stat_max_rate, current_rate)) + logger.debug('Update stat_max_rate. %s -> %s' % (self.stat_max_rate, current_rate)) self.stat_max_rate = current_rate self.stat_max_rate_date = datetime.utcnow()