From 665b72274c6c49f0e55cccc0411f9709c4a60d99 Mon Sep 17 00:00:00 2001 From: chrisapril Date: Wed, 20 Dec 2017 15:16:31 +0100 Subject: [PATCH] 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.