From 867fac7719d2e3c76265e8510b7d8c3d522cdaa7 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Thu, 10 May 2018 10:03:48 -0700 Subject: [PATCH 1/3] working on trailing stop loss, needs tests and verification that it works --- freqtrade/analyze.py | 13 +++++++++++-- freqtrade/persistence.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index dcb5376ce..e72978e9c 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -14,7 +14,6 @@ from freqtrade.exchange import get_ticker_history from freqtrade.persistence import Trade from freqtrade.strategy.resolver import StrategyResolver - logger = logging.getLogger(__name__) @@ -31,6 +30,7 @@ class Analyze(object): Analyze class contains everything the bot need to determine if the situation is good for buying or selling. """ + def __init__(self, config: dict) -> None: """ Init Analyze @@ -195,10 +195,19 @@ class Analyze(object): :return True if bot should sell at current rate """ current_profit = trade.calc_profit_percent(current_rate) - if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: + + if trade.stop_loss is None: + # initially adjust the stop loss to it's default value + trade.adjust_stop_loss(current_rate, self.strategy.stoploss) + + # evaluate stop loss, before we continue + if self.strategy.stoploss is not None and trade.stop_loss >= current_rate: logger.debug('Stop loss hit.') return True + # update the stop loss afterwards, after all by definition it's supposed to be hanging + trade.adjust_stop_loss(current_rate, self.strategy.stoploss) + # 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.strategy.minimal_roi.items(): diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ed81ad2ec..f3c16a709 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -95,6 +95,7 @@ class Trade(_DECL_BASE): open_date = Column(DateTime, nullable=False, default=datetime.utcnow) close_date = Column(DateTime) open_order_id = Column(String) + stop_loss = Column(Float, nullable=False, default=0.0) # absolute value of the stop loss def __repr__(self): return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format( @@ -105,6 +106,33 @@ class Trade(_DECL_BASE): arrow.get(self.open_date).humanize() if self.is_open else 'closed' ) + def adjust_stop_loss(self, current_price, stoploss): + """ + + this adjusts the stop loss to it's most recently observed + setting + :param current_price: + :param stoploss: + :return: + """ + + new_loss = Decimal(current_price * (1 - abs(stoploss))) + logger.debug("calculated stop loss at: {:.6f}".format(new_loss)) + if self.stop_loss is None: + logger.debug("assigning new stop loss") + self.stop_loss = new_loss # no stop loss assigned yet + else: + if _CONF.get('trailing_stop', True): + if new_loss > self.stop_loss: # stop losses only walk up, never down! + self.stop_loss = new_loss + logger.debug("adjusted stop loss for {:.6f} and {:.6f} to {:.6f}".format( + current_price, stoploss, self.stop_loss) + ) + else: + logger.debug("keeping current stop loss of {:.6f}".format(self.stop_loss)) + else: + print("utilizing fixed stop") + def update(self, order: Dict) -> None: """ Updates this entity with amount and actual open/close rates. From 250e84a42ab8b4b8868b0ad9f0801051552b91ec Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Thu, 10 May 2018 16:50:04 -0700 Subject: [PATCH 2/3] more work on stop losses --- freqtrade/analyze.py | 9 +++++---- freqtrade/persistence.py | 19 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index e72978e9c..3ed2181cf 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -197,16 +197,17 @@ class Analyze(object): current_profit = trade.calc_profit_percent(current_rate) if trade.stop_loss is None: - # initially adjust the stop loss to it's default value - trade.adjust_stop_loss(current_rate, self.strategy.stoploss) + # initially adjust the stop loss to the base value + trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss) - # evaluate stop loss, before we continue + # evaluate if the stoploss was hit if self.strategy.stoploss is not None and trade.stop_loss >= current_rate: logger.debug('Stop loss hit.') return True # update the stop loss afterwards, after all by definition it's supposed to be hanging - trade.adjust_stop_loss(current_rate, self.strategy.stoploss) + if 'trailing_stop' in self.config and self.config['trailing_stop']: + trade.adjust_stop_loss(current_rate, self.strategy.stoploss) # Check if time matches and current rate is above threshold time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60 diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index f3c16a709..638ab1595 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -117,21 +117,20 @@ class Trade(_DECL_BASE): """ new_loss = Decimal(current_price * (1 - abs(stoploss))) - logger.debug("calculated stop loss at: {:.6f}".format(new_loss)) + if self.stop_loss is None: logger.debug("assigning new stop loss") self.stop_loss = new_loss # no stop loss assigned yet else: - if _CONF.get('trailing_stop', True): - if new_loss > self.stop_loss: # stop losses only walk up, never down! - self.stop_loss = new_loss - logger.debug("adjusted stop loss for {:.6f} and {:.6f} to {:.6f}".format( - current_price, stoploss, self.stop_loss) - ) - else: - logger.debug("keeping current stop loss of {:.6f}".format(self.stop_loss)) + if new_loss > self.stop_loss: # stop losses only walk up, never down! + self.stop_loss = new_loss + logger.debug("adjusted stop loss") else: - print("utilizing fixed stop") + logger.debug("keeping current stop loss") + + print("{} - current price {:.6f}, calculated stop loss at: {:.6f} old loss at {:.6f}".format(self.id, current_price, + new_loss, + self.stop_loss)) def update(self, order: Dict) -> None: """ From d928be505fa09f13776d436da185ba992c88258a Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Thu, 10 May 2018 20:56:16 -0700 Subject: [PATCH 3/3] added stop loss, extra verbose logging for now, will be changed once the algorithm is prooven --- freqtrade/analyze.py | 9 +++++++++ freqtrade/persistence.py | 28 ++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 3ed2181cf..535e5c310 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -202,6 +202,15 @@ class Analyze(object): # evaluate if the stoploss was hit if self.strategy.stoploss is not None and trade.stop_loss >= current_rate: + + # just for debugging + if 'trailing_stop' in self.config and self.config['trailing_stop']: + print( + "HIT STOP: current price at {:.6f}, stop loss is {:.6f}, " + "initial stop loss was at {:.6f}, trade opened at {:.6f}".format( + current_rate, trade.stop_loss, trade.initial_stop_loss, trade.open_rate)) + print("trailing stop saved us: {:.6f}".format(trade.stop_loss - trade.initial_stop_loss)) + logger.debug('Stop loss hit.') return True diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 638ab1595..192052627 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -96,6 +96,8 @@ class Trade(_DECL_BASE): close_date = Column(DateTime) open_order_id = Column(String) stop_loss = Column(Float, nullable=False, default=0.0) # absolute value of the stop loss + initial_stop_loss = Column(Float, nullable=False, default=0.0) # absolute value of the initial stop loss + max_rate = Column(Float, nullable=False, default=0.0) # absolute value of the highest reached price def __repr__(self): return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format( @@ -118,9 +120,20 @@ class Trade(_DECL_BASE): new_loss = Decimal(current_price * (1 - abs(stoploss))) + # keeping track of the highest observed rate for this trade + if self.max_rate is None: + self.max_rate = current_price + else: + if current_price > self.max_rate: + self.max_rate = current_price + + # no stop loss assigned yet if self.stop_loss is None: logger.debug("assigning new stop loss") - self.stop_loss = new_loss # no stop loss assigned yet + self.stop_loss = new_loss + self.initial_stop_loss = new_loss + + # 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 @@ -128,9 +141,16 @@ class Trade(_DECL_BASE): else: logger.debug("keeping current stop loss") - print("{} - current price {:.6f}, calculated stop loss at: {:.6f} old loss at {:.6f}".format(self.id, current_price, - new_loss, - self.stop_loss)) + print( + "{} - current price {:.6f}, bought at {:.6f} and calculated " + "stop loss is at: {:.6f} initial stop at {:.6f}. trailing stop loss saved us: {:.6f} " + "and max observed rate was {:.6f}".format( + self.pair, current_price, self.open_rate, + self.initial_stop_loss, + self.stop_loss, self.stop_loss - self.initial_stop_loss, + self.max_rate + + )) def update(self, order: Dict) -> None: """