From a8efb1e1c8f52d290fa7c4cc1c616edfabd72195 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 20 Jun 2019 03:26:02 +0300 Subject: [PATCH 1/4] test for #1948 added --- freqtrade/tests/strategy/test_interface.py | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index e384003dc..6409123e7 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -185,6 +185,39 @@ def test_min_roi_reached2(default_conf, fee) -> None: assert strategy.min_roi_reached(trade, 0.31, arrow.utcnow().shift(minutes=-2).datetime) +def test_min_roi_reached3(default_conf, fee) -> None: + + # test for issue #1948 + min_roi = {20: 0.07, + 30: 0.05, + 55: 0.30, + } + strategy = DefaultStrategy(default_conf) + strategy.minimal_roi = min_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 not 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 144e053a4e1349d9037317c3a697e77983484926 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 20 Jun 2019 03:26:25 +0300 Subject: [PATCH 2/4] fix for #1948 --- freqtrade/strategy/interface.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 68e0a7b37..353ae64b4 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -357,13 +357,13 @@ class IStrategy(ABC): # Check if time matches and current rate is above threshold 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()))) + # Get highest entry in ROI dict where key <= trade-duration + roi_list = list(filter(lambda x: x <= trade_dur, self.minimal_roi.keys())) + if not roi_list: + return False + roi_entry = max(roi_list) threshold = self.minimal_roi[roi_entry] - if current_profit > threshold: - return True - - return False + return current_profit > threshold def tickerdata_to_dataframe(self, tickerdata: Dict[str, List]) -> Dict[str, DataFrame]: """ From 7fbdf36c6481f22eef52e75941c898ce8a3f496b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 23 Jun 2019 19:23:51 +0300 Subject: [PATCH 3/4] avoid code duplication while selecting min_roi entries --- freqtrade/optimize/backtesting.py | 17 +++++++--------- freqtrade/strategy/interface.py | 33 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6cc78ad2b..e0660eacb 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -253,22 +253,19 @@ class Backtesting(object): sell = self.strategy.should_sell(trade, sell_row.open, sell_row.date, sell_row.buy, sell_row.sell, low=sell_row.low, high=sell_row.high) if sell.sell_flag: - trade_dur = int((sell_row.date - buy_row.date).total_seconds() // 60) # Special handling if high or low hit STOP_LOSS or ROI if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): # Set close_rate to stoploss closerate = trade.stop_loss 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, - self.strategy.minimal_roi.keys()))) - roi = self.strategy.minimal_roi[roi_entry] - - # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) - closerate = - (trade.open_rate * roi + trade.open_rate * - (1 + trade.fee_open)) / (trade.fee_close - 1) + roi = self.strategy.min_roi_reached_entry(trade_dur) + if roi is not None: + # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) + closerate = - (trade.open_rate * roi + trade.open_rate * + (1 + trade.fee_open)) / (trade.fee_close - 1) + else: + closerate = sell_row.open else: closerate = sell_row.open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 353ae64b4..7ec782b0b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -6,7 +6,7 @@ import logging from abc import ABC, abstractmethod from datetime import datetime from enum import Enum -from typing import Dict, List, NamedTuple, Tuple +from typing import Dict, List, NamedTuple, Optional, Tuple import warnings import arrow @@ -347,23 +347,32 @@ class IStrategy(ABC): return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) - def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: + def min_roi_reached_entry(self, trade_dur: int) -> Optional[float]: """ - Based an earlier trade and current price and ROI configuration, decides whether bot should - sell. Requires current_profit to be in percent!! - :return True if bot should sell at current rate + Based on trade duration defines the ROI entry that may have been reached. + :param trade_dur: trade duration in minutes + :return: minimal ROI entry value or None if none proper ROI entry was found. """ - - # Check if time matches and current rate is above threshold - trade_dur = (current_time.timestamp() - trade.open_date.timestamp()) / 60 - # Get highest entry in ROI dict where key <= trade-duration roi_list = list(filter(lambda x: x <= trade_dur, self.minimal_roi.keys())) if not roi_list: - return False + return None roi_entry = max(roi_list) - threshold = self.minimal_roi[roi_entry] - return current_profit > threshold + return self.minimal_roi[roi_entry] + + def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: + """ + Based on trade duration, current price and ROI configuration, decides whether bot should + 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 + trade_dur = int((current_time.timestamp() - trade.open_date.timestamp()) // 60) + roi = self.min_roi_reached_entry(trade_dur) + if roi is None: + return False + else: + return current_profit > roi def tickerdata_to_dataframe(self, tickerdata: Dict[str, List]) -> Dict[str, DataFrame]: """ From e5a8030dd7883d28797b8797f1b3207fa3623c1f Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 27 Jun 2019 16:42:10 +0300 Subject: [PATCH 4/4] comment added --- freqtrade/optimize/backtesting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e0660eacb..d963b39c5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -265,6 +265,7 @@ class Backtesting(object): closerate = - (trade.open_rate * roi + trade.open_rate * (1 + trade.fee_open)) / (trade.fee_close - 1) else: + # This should not be reached... closerate = sell_row.open else: closerate = sell_row.open