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.