From 638bed3dac45206eb001e0512aeb938ae063c20d Mon Sep 17 00:00:00 2001 From: user Date: Wed, 7 Jul 2021 06:46:51 +0000 Subject: [PATCH 001/200] Add RangeStabilityFilterMax pairlist filter --- freqtrade/constants.py | 2 +- .../pairlist/rangestabilityfiltermax.py | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 freqtrade/plugins/pairlist/rangestabilityfiltermax.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index f4c32387b..089569842 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -28,7 +28,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', - 'SpreadFilter', 'VolatilityFilter'] + 'SpreadFilter', 'VolatilityFilter', 'RangeStabilityFilterMax'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 diff --git a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py new file mode 100644 index 000000000..98f65904b --- /dev/null +++ b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py @@ -0,0 +1,109 @@ +""" +Rate of change pairlist filter +""" +import logging +from copy import deepcopy +from typing import Any, Dict, List, Optional + +import arrow +from cachetools.ttl import TTLCache +from pandas import DataFrame + +from freqtrade.exceptions import OperationalException +from freqtrade.misc import plural +from freqtrade.plugins.pairlist.IPairList import IPairList + + +logger = logging.getLogger(__name__) + + +class RangeStabilityFilterMax(IPairList): + + def __init__(self, exchange, pairlistmanager, + config: Dict[str, Any], pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + self._days = pairlistconfig.get('lookback_days', 10) + self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', 0.02) + self._refresh_period = pairlistconfig.get('refresh_period', 1440) + + self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) + + if self._days < 1: + raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1") + if self._days > exchange.ohlcv_candle_limit('1d'): + raise OperationalException("RangeStabilityFilter requires lookback_days to not " + "exceed exchange max request size " + f"({exchange.ohlcv_candle_limit('1d')})") + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return False + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return (f"{self.name} - Filtering pairs with rate of change below " + f"{self._max_rate_of_change} over the last {plural(self._days, 'day')}.") + + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + """ + Validate trading range + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new allowlist + """ + needed_pairs = [(p, '1d') for p in pairlist if p not in self._pair_cache] + + since_ms = int(arrow.utcnow() + .floor('day') + .shift(days=-self._days - 1) + .float_timestamp) * 1000 + # Get all candles + candles = {} + if needed_pairs: + candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, + cache=False) + + if self._enabled: + for p in deepcopy(pairlist): + daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None + if not self._validate_pair_loc(p, daily_candles): + pairlist.remove(p) + return pairlist + + def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool: + """ + Validate trading range + :param pair: Pair that's currently validated + :param ticker: ticker dict as returned from ccxt.load_markets() + :return: True if the pair can stay, false if it should be removed + """ + # Check symbol in cache + cached_res = self._pair_cache.get(pair, None) + if cached_res is not None: + return cached_res + + result = False + if daily_candles is not None and not daily_candles.empty: + highest_high = daily_candles['high'].max() + lowest_low = daily_candles['low'].min() + pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 + if pct_change <= self._max_rate_of_change: + result = True + else: + self.log_once(f"Removed {pair} from whitelist, because rate of change " + f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, " + f"which is above the threshold of {self._max_rate_of_change}.", + logger.info) + result = False + self._pair_cache[pair] = result + + return result From 8b485d197a6778499f362275ce0c521bca2778a3 Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Wed, 7 Jul 2021 07:54:17 +0000 Subject: [PATCH 002/200] Update Plugins doc --- docs/includes/pairlists.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index f19c5a181..0368e8a6f 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -174,6 +174,24 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit !!! Tip This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit. +#### RangeStabilityFilterMax + +Same function as `RangeStabilityFilter` but instead of a minimum value, it uses a maximum value for rate of change, i.e. `max_rate_of_change` as seen in the example below. + +```json +"pairlists": [ + { + "method": "RangeStabilityFilterMax", + "lookback_days": 10, + "max_rate_of_change": 1.01, + "refresh_period": 1440 + } +] +``` + +!!! Tip + This Filter can be used to automatically remove pairs with extreme high/low variance over a given amount of time (`lookback_days`). + #### VolatilityFilter Volatility is the degree of historical variation of a pairs over time, is is measured by the standard deviation of logarithmic daily returns. Returns are assumed to be normally distributed, although actual distribution might be different. In a normal distribution, 68% of observations fall within one standard deviation and 95% of observations fall within two standard deviations. Assuming a volatility of 0.05 means that the expected returns for 20 out of 30 days is expected to be less than 5% (one standard deviation). Volatility is a positive ratio of the expected deviation of return and can be greater than 1.00. Please refer to the wikipedia definition of [`volatility`](https://en.wikipedia.org/wiki/Volatility_(finance)). From 8b0a02db8eef64840ac57ba6a5313047f69f6bdb Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Wed, 7 Jul 2021 08:11:13 +0000 Subject: [PATCH 003/200] Correct exception messages --- freqtrade/plugins/pairlist/rangestabilityfiltermax.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py index 98f65904b..e0cf5b9b4 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py +++ b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py @@ -31,9 +31,9 @@ class RangeStabilityFilterMax(IPairList): self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) if self._days < 1: - raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1") + raise OperationalException("RangeStabilityFilterMax requires lookback_days to be >= 1") if self._days > exchange.ohlcv_candle_limit('1d'): - raise OperationalException("RangeStabilityFilter requires lookback_days to not " + raise OperationalException("RangeStabilityFilterMax requires lookback_days to not " "exceed exchange max request size " f"({exchange.ohlcv_candle_limit('1d')})") From 365479f5e0a19a2e08e4f3309ce342d58c401d27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Jul 2021 19:36:15 +0200 Subject: [PATCH 004/200] Remove startup-candles after populating buy/sell signals closes #5242 --- freqtrade/optimize/backtesting.py | 29 ++++++++++++++------------ freqtrade/optimize/hyperopt.py | 7 +++---- tests/optimize/test_backtest_detail.py | 1 + tests/optimize/test_backtesting.py | 1 + 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index ebd4135d9..f52e9a49f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -15,7 +15,7 @@ from freqtrade.configuration import TimeRange, remove_credentials, validate_conf from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.data import history from freqtrade.data.btanalysis import trade_list_to_dataframe -from freqtrade.data.converter import trim_dataframes +from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import BacktestState, SellType from freqtrade.exceptions import DependencyException, OperationalException @@ -116,6 +116,9 @@ class Backtesting: self.wallets = Wallets(self.config, self.exchange, log=False) + self.timerange = TimeRange.parse_timerange( + None if self.config.get('timerange') is None else str(self.config.get('timerange'))) + # Get maximum required startup period self.required_startup = max([strat.startup_candle_count for strat in self.strategylist]) self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) @@ -154,14 +157,11 @@ class Backtesting: """ self.progress.init_step(BacktestState.DATALOAD, 1) - timerange = TimeRange.parse_timerange(None if self.config.get( - 'timerange') is None else str(self.config.get('timerange'))) - data = history.load_data( datadir=self.config['datadir'], pairs=self.pairlists.whitelist, timeframe=self.timeframe, - timerange=timerange, + timerange=self.timerange, startup_candles=self.required_startup, fail_without_data=True, data_format=self.config.get('dataformat_ohlcv', 'json'), @@ -174,11 +174,11 @@ class Backtesting: f'({(max_date - min_date).days} days).') # Adjust startts forward if not enough data is available - timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe), - self.required_startup, min_date) + self.timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe), + self.required_startup, min_date) self.progress.set_new_value(1) - return data, timerange + return data, self.timerange def prepare_backtest(self, enable_protections): """ @@ -223,7 +223,9 @@ class Backtesting: df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() - + # Trim startup period from analyzed dataframe + df_analyzed = trim_dataframe(df_analyzed, self.timerange, + startup_candles=self.required_startup) # To avoid using data from future, we use buy/sell signals shifted # from the previous candle df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) @@ -537,14 +539,15 @@ class Backtesting: preprocessed = self.strategy.ohlcvdata_to_dataframe(data) # Trim startup period from analyzed dataframe - preprocessed = trim_dataframes(preprocessed, timerange, self.required_startup) + preprocessed_tmp = trim_dataframes(preprocessed, timerange, self.required_startup) - if not preprocessed: + if not preprocessed_tmp: raise OperationalException( "No data left after adjusting for startup candles.") - min_date, max_date = history.get_timerange(preprocessed) - + # Use preprocessed_tmp for date generation (the trimmed dataframe). + # Backtesting will re-trim the dataframes after buy/sell signal generation. + min_date, max_date = history.get_timerange(preprocessed_tmp) logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(max_date - min_date).days} days).') diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 80ae8886e..cb6884385 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -378,16 +378,15 @@ class Hyperopt: preprocessed = self.backtesting.strategy.ohlcvdata_to_dataframe(data) - # Trim startup period from analyzed dataframe + # Trim startup period from analyzed dataframe to get correct dates for output. processed = trim_dataframes(preprocessed, timerange, self.backtesting.required_startup) - self.min_date, self.max_date = get_timerange(processed) logger.info(f'Hyperopting with data from {self.min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(self.max_date - self.min_date).days} days)..') - - dump(processed, self.data_pickle_file) + # Store non-trimmed data - will be trimmed after signal generation. + dump(preprocessed, self.data_pickle_file) def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 0bf197739..af3c1317b 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -575,6 +575,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: frame = _build_backtest_dataframe(data.data) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) + backtesting.required_startup = 0 backtesting.strategy.advise_buy = lambda a, m: frame backtesting.strategy.advise_sell = lambda a, m: frame backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 77b9e23fa..2998157a5 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -727,6 +727,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): pair='UNITTEST/BTC', datadir=testdatadir) default_conf['timeframe'] = '1m' backtesting = Backtesting(default_conf) + backtesting.required_startup = 0 backtesting._set_strategy(backtesting.strategylist[0]) backtesting.strategy.advise_buy = _trend_alternate # Override backtesting.strategy.advise_sell = _trend_alternate # Override From f705293353552e41c58f8a892c64db1c1f4f2bee Mon Sep 17 00:00:00 2001 From: George Muravei-Alkhavoi Date: Mon, 19 Jul 2021 00:25:24 +0300 Subject: [PATCH 005/200] Dataprovider caching and trimming to timerange of historical informative. --- freqtrade/data/dataprovider.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 391ed4587..aa3efbd60 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -14,7 +14,8 @@ from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.data.history import load_pair_history from freqtrade.enums import RunMode from freqtrade.exceptions import ExchangeError, OperationalException -from freqtrade.exchange import Exchange +from freqtrade.exchange import Exchange, timeframe_to_seconds +from freqtrade.configuration import TimeRange logger = logging.getLogger(__name__) @@ -31,6 +32,7 @@ class DataProvider: self._pairlists = pairlists self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {} self.__slice_index: Optional[int] = None + self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {} def _set_dataframe_max_index(self, limit_index: int): """ @@ -62,11 +64,20 @@ class DataProvider: :param pair: pair to get the data for :param timeframe: timeframe to get data for """ - return load_pair_history(pair=pair, - timeframe=timeframe or self._config['timeframe'], - datadir=self._config['datadir'], - data_format=self._config.get('dataformat_ohlcv', 'json') - ) + saved_pair = (pair, timeframe) + if saved_pair not in self.__cached_pairs_backtesting: + timerange = TimeRange.parse_timerange(None if self._config.get( + 'timerange') is None else str(self._config.get('timerange'))) + # Move informative start time respecting startup_candle_count + timerange.subtract_start(timeframe_to_seconds(timeframe) * self._config.get('startup_candle_count', 0)) + self.__cached_pairs_backtesting[saved_pair] = load_pair_history( + pair=pair, + timeframe=timeframe or self._config['timeframe'], + datadir=self._config['datadir'], + timerange=timerange, + data_format=self._config.get('dataformat_ohlcv', 'json') + ) + return self.__cached_pairs_backtesting[saved_pair].copy() def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame: """ From ab786abf7f9ebfbea04b0e1e39cca105023a95ea Mon Sep 17 00:00:00 2001 From: George Muravei-Alkhavoi Date: Mon, 19 Jul 2021 00:47:51 +0300 Subject: [PATCH 006/200] Fix intendation. --- freqtrade/data/dataprovider.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index aa3efbd60..cdee0f078 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -10,12 +10,12 @@ from typing import Any, Dict, List, Optional, Tuple from pandas import DataFrame +from freqtrade.configuration import TimeRange from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.data.history import load_pair_history from freqtrade.enums import RunMode from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exchange import Exchange, timeframe_to_seconds -from freqtrade.configuration import TimeRange logger = logging.getLogger(__name__) @@ -64,12 +64,14 @@ class DataProvider: :param pair: pair to get the data for :param timeframe: timeframe to get data for """ - saved_pair = (pair, timeframe) + saved_pair = (pair, str(timeframe)) if saved_pair not in self.__cached_pairs_backtesting: timerange = TimeRange.parse_timerange(None if self._config.get( 'timerange') is None else str(self._config.get('timerange'))) # Move informative start time respecting startup_candle_count - timerange.subtract_start(timeframe_to_seconds(timeframe) * self._config.get('startup_candle_count', 0)) + timerange.subtract_start( + timeframe_to_seconds(str(timeframe)) * self._config.get('startup_candle_count', 0) + ) self.__cached_pairs_backtesting[saved_pair] = load_pair_history( pair=pair, timeframe=timeframe or self._config['timeframe'], From 9e63bdbac9b866154fbabb8d75c48c63d26c09a2 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 04:58:20 +0700 Subject: [PATCH 007/200] feat: add buy signal name --- freqtrade/enums/__init__.py | 2 +- freqtrade/enums/signaltype.py | 7 +++++++ freqtrade/freqtradebot.py | 9 +++++---- freqtrade/optimize/backtesting.py | 4 +++- freqtrade/persistence/models.py | 3 +++ freqtrade/strategy/interface.py | 10 ++++++---- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index 78163d86f..bcf68d622 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -2,5 +2,5 @@ from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.selltype import SellType -from freqtrade.enums.signaltype import SignalType +from freqtrade.enums.signaltype import SignalType, SignalNameType from freqtrade.enums.state import State diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index d636f378a..5ba2f8b4b 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -7,3 +7,10 @@ class SignalType(Enum): """ BUY = "buy" SELL = "sell" + + +class SignalNameType(Enum): + """ + Enum to distinguish between buy and sell signals + """ + BUY_SIGNAL_NAME = "buy_signal_name" diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7a7371357..037bb954a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin): return False # running get_signal on historical data fetched - (buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) + (buy, sell, buy_signal_name) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) if buy and not sell: stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge) @@ -435,11 +435,11 @@ class FreqtradeBot(LoggingMixin): if ((bid_check_dom.get('enabled', False)) and (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): if self._check_depth_of_market_buy(pair, bid_check_dom): - return self.execute_buy(pair, stake_amount) + return self.execute_buy(pair, stake_amount, buy_signal_name=buy_signal_name) else: return False - return self.execute_buy(pair, stake_amount) + return self.execute_buy(pair, stake_amount, buy_signal_name=buy_signal_name) else: return False @@ -468,7 +468,7 @@ class FreqtradeBot(LoggingMixin): return False def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False) -> bool: + forcebuy: bool = False, buy_signal_name: str = '') -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY @@ -562,6 +562,7 @@ class FreqtradeBot(LoggingMixin): exchange=self.exchange.id, open_order_id=order_id, strategy=self.strategy.get_strategy_name(), + buy_signal_name=buy_signal_name, timeframe=timeframe_to_minutes(self.config['timeframe']) ) trade.orders.append(order_obj) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7c6b7cbc3..2b8bd1e2b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -42,6 +42,7 @@ CLOSE_IDX = 3 SELL_IDX = 4 LOW_IDX = 5 HIGH_IDX = 6 +BUY_SIGNAL_NAME_IDX = 7 class Backtesting: @@ -189,7 +190,7 @@ class Backtesting: """ # Every change to this headers list must evaluate further usages of the resulting tuple # and eventually change the constants for indexes at the top - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_signal_name'] data: Dict = {} # Create dict with data for pair, pair_data in processed.items(): @@ -332,6 +333,7 @@ class Backtesting: fee_open=self.fee, fee_close=self.fee, is_open=True, + buy_signal_name=row[BUY_SIGNAL_NAME_IDX], exchange='backtesting', ) return trade diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index b4c299120..8d77ac27a 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -257,6 +257,7 @@ class LocalTrade(): sell_reason: str = '' sell_order_status: str = '' strategy: str = '' + buy_signal_name: str = '' timeframe: Optional[int] = None def __init__(self, **kwargs): @@ -288,6 +289,7 @@ class LocalTrade(): 'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, 'stake_amount': round(self.stake_amount, 8), 'strategy': self.strategy, + 'buy_signal_name': self.buy_signal_name, 'timeframe': self.timeframe, 'fee_open': self.fee_open, @@ -703,6 +705,7 @@ class Trade(_DECL_BASE, LocalTrade): sell_reason = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True) + buy_signal_name = Column(String(100), nullable=True) timeframe = Column(Integer, nullable=True) def __init__(self, **kwargs): diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7aa7e57d9..bd77fbb21 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -13,7 +13,7 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import SellType, SignalType +from freqtrade.enums import SellType, SignalType, SignalNameType from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date @@ -506,7 +506,9 @@ class IStrategy(ABC, HyperStrategyMixin): ) return False, False - (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 + (buy, sell, buy_signal_name) = latest[SignalType.BUY.value] == 1,\ + latest[SignalType.SELL.value] == 1,\ + latest.get(SignalNameType.BUY_SIGNAL_NAME.value, '') logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) timeframe_seconds = timeframe_to_seconds(timeframe) @@ -514,8 +516,8 @@ class IStrategy(ABC, HyperStrategyMixin): current_time=datetime.now(timezone.utc), timeframe_seconds=timeframe_seconds, buy=buy): - return False, sell - return buy, sell + return False, sell, buy_signal_name + return buy, sell, buy_signal_name def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, timeframe_seconds: int, buy: bool): From 104711a9bf80b72e3541af00e2f78b5c22aaaa49 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 05:04:25 +0700 Subject: [PATCH 008/200] get_signal signature --- freqtrade/strategy/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index bd77fbb21..cbd19e156 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -478,7 +478,7 @@ class IStrategy(ABC, HyperStrategyMixin): else: raise StrategyError(message) - def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool]: + def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool, str]: """ Calculates current signal based based on the buy / sell columns of the dataframe. Used by Bot to get the signal to buy or sell @@ -489,7 +489,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ if not isinstance(dataframe, DataFrame) or dataframe.empty: logger.warning(f'Empty candle (OHLCV) data for pair {pair}') - return False, False + return False, False, '' latest_date = dataframe['date'].max() latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1] @@ -504,7 +504,7 @@ class IStrategy(ABC, HyperStrategyMixin): 'Outdated history for pair %s. Last tick is %s minutes old', pair, int((arrow.utcnow() - latest_date).total_seconds() // 60) ) - return False, False + return False, False, '' (buy, sell, buy_signal_name) = latest[SignalType.BUY.value] == 1,\ latest[SignalType.SELL.value] == 1,\ From 7d040052189d94884609bd004796f53d81ecc437 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 16:14:48 +0700 Subject: [PATCH 009/200] add test and migration --- freqtrade/enums/signaltype.py | 2 +- freqtrade/freqtradebot.py | 2 +- freqtrade/persistence/migrations.py | 7 ++++--- tests/strategy/test_interface.py | 19 ++++++++++++------- tests/test_freqtradebot.py | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index 5ba2f8b4b..6f29699f1 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -11,6 +11,6 @@ class SignalType(Enum): class SignalNameType(Enum): """ - Enum to distinguish between buy and sell signals + Enum for signal name """ BUY_SIGNAL_NAME = "buy_signal_name" diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 037bb954a..852d5bf78 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -690,7 +690,7 @@ class FreqtradeBot(LoggingMixin): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) - (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df) + (buy, sell, _) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df) logger.debug('checking sell') sell_rate = self.exchange.get_sell_rate(trade.pair, True) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 00c9b91eb..4fa1cb742 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -47,6 +47,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col min_rate = get_column_def(cols, 'min_rate', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') + buy_signal_name = get_column_def(cols, 'buy_signal_name', 'null') # If ticker-interval existed use that, else null. if has_column(cols, 'ticker_interval'): timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') @@ -80,7 +81,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stoploss_order_id, stoploss_last_update, - max_rate, min_rate, sell_reason, sell_order_status, strategy, + max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_signal_name, timeframe, open_trade_value, close_profit_abs ) select id, lower(exchange), @@ -103,7 +104,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {sell_order_status} sell_order_status, - {strategy} strategy, {timeframe} timeframe, + {strategy} strategy, {buy_signal_name} buy_signal_name, {timeframe} timeframe, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs from {table_back_name} """)) @@ -160,7 +161,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None: table_back_name = get_backup_name(tabs, 'trades_bak') # Check for latest column - if not has_column(cols, 'open_trade_value'): + if not has_column(cols, 'open_trade_value') or not has_column(cols, 'buy_signal_name'): logger.info(f'Running database migration for trades - backup: {table_back_name}') migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) # Reread columns - the above recreated the table! diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 2beb4465d..c02a43e72 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -37,15 +37,20 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): mocked_history['buy'] = 0 mocked_history.loc[1, 'sell'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, '') mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, '') mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 0 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, '') + mocked_history.loc[1, 'sell'] = 0 + mocked_history.loc[1, 'buy'] = 1 + mocked_history.loc[1, 'buy_signal_name'] = 'buy_signal_01' + + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01') def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): @@ -62,15 +67,15 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_empty(default_conf, mocker, caplog): - assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) + assert (False, False, '') == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() - assert (False, False) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) + assert (False, False, '') == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) assert log_has('Empty candle (OHLCV) data for pair bar', caplog) caplog.clear() - assert (False, False) == _STRATEGY.get_signal('baz', default_conf['timeframe'], DataFrame([])) + assert (False, False, '') == _STRATEGY.get_signal('baz', default_conf['timeframe'], DataFrame([])) assert log_has('Empty candle (OHLCV) data for pair baz', caplog) @@ -106,7 +111,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], mocked_history) + assert (False, False, '') == _STRATEGY.get_signal('xyz', default_conf['timeframe'], mocked_history) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 99e11e893..2309c96f0 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -736,7 +736,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: refresh_latest_ohlcv=refresh_mock, ) inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")]) - mocker.patch('freqtrade.strategy.interface.IStrategy.get_signal', return_value=(False, False)) + mocker.patch('freqtrade.strategy.interface.IStrategy.get_signal', return_value=(False, False, '')) mocker.patch('time.sleep', return_value=None) freqtrade = FreqtradeBot(default_conf) From ec526b3f963792e78bafaf772e01dacbdb92ba51 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 16:22:04 +0700 Subject: [PATCH 010/200] fix testcase --- tests/conftest.py | 2 +- tests/test_freqtradebot.py | 42 +++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a843d9397..58af2fe90 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -182,7 +182,7 @@ def get_patched_worker(mocker, config) -> Worker: return Worker(args=None, config=config) -def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: +def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, '')) -> None: """ :param mocker: mocker to patch IStrategy class :param value: which value IStrategy.get_signal() must return diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 2309c96f0..cc89ce00c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -515,7 +515,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None: ) default_conf['stake_amount'] = 10 freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade, value=(False, False)) + patch_get_signal(freqtrade, value=(False, False, '')) Trade.query = MagicMock() Trade.query.filter = MagicMock() @@ -1818,7 +1818,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order_open, limi assert trade.is_open is True freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is True assert trade.open_order_id == limit_sell_order['id'] @@ -1843,7 +1843,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, ) freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade, value=(True, True)) + patch_get_signal(freqtrade, value=(True, True, '')) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() @@ -1854,7 +1854,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert nb_trades == 0 # Buy is triggering, so buying ... - patch_get_signal(freqtrade, value=(True, False)) + patch_get_signal(freqtrade, value=(True, False, '')) freqtrade.enter_positions() trades = Trade.query.all() nb_trades = len(trades) @@ -1862,7 +1862,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert trades[0].is_open is True # Buy and Sell are not triggering, so doing nothing ... - patch_get_signal(freqtrade, value=(False, False)) + patch_get_signal(freqtrade, value=(False, False, '')) assert freqtrade.handle_trade(trades[0]) is False trades = Trade.query.all() nb_trades = len(trades) @@ -1870,7 +1870,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert trades[0].is_open is True # Buy and Sell are triggering, so doing nothing ... - patch_get_signal(freqtrade, value=(True, True)) + patch_get_signal(freqtrade, value=(True, True, '')) assert freqtrade.handle_trade(trades[0]) is False trades = Trade.query.all() nb_trades = len(trades) @@ -1878,7 +1878,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert trades[0].is_open is True # Sell is triggering, guess what : we are Selling! - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) trades = Trade.query.all() assert freqtrade.handle_trade(trades[0]) is True @@ -1896,7 +1896,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, ) freqtrade = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtrade, value=(True, False)) + patch_get_signal(freqtrade, value=(True, False, '')) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.enter_positions() @@ -1909,7 +1909,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, # we might just want to check if we are in a sell condition without # executing # if ROI is reached we must sell - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI", caplog) @@ -1935,10 +1935,10 @@ def test_handle_trade_use_sell_signal( trade = Trade.query.first() trade.is_open = True - patch_get_signal(freqtrade, value=(False, False)) + patch_get_signal(freqtrade, value=(False, False, '')) assert not freqtrade.handle_trade(trade) - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL", caplog) @@ -2974,7 +2974,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is False freqtrade.strategy.sell_profit_offset = 0.0 @@ -3009,7 +3009,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -3040,7 +3040,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o trade = Trade.query.first() trade.update(limit_buy_order) - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is False @@ -3072,7 +3072,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_ trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -3101,7 +3101,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_ trade = Trade.query.first() amnt = trade.amount trade.update(limit_buy_order) - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985)) assert freqtrade.handle_trade(trade) is True @@ -3220,11 +3220,11 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(True, True)) + patch_get_signal(freqtrade, value=(True, True, '')) assert freqtrade.handle_trade(trade) is False # Test if buy-signal is absent (should sell due to roi = true) - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.ROI.value @@ -3485,11 +3485,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b trade = Trade.query.first() trade.update(limit_buy_order) # Sell due to min_roi_reached - patch_get_signal(freqtrade, value=(True, True)) + patch_get_signal(freqtrade, value=(True, True, '')) assert freqtrade.handle_trade(trade) is True # Test if buy-signal is absent - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -4017,7 +4017,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_o freqtrade.wallets.update() assert trade.is_open is True - patch_get_signal(freqtrade, value=(False, True)) + patch_get_signal(freqtrade, value=(False, True, '')) assert freqtrade.handle_trade(trade) is True assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0] From d31d38a85f3a274c9a8f989c80c3a37b052b596c Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 16:39:20 +0700 Subject: [PATCH 011/200] add doc --- docs/strategy-advanced.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 3436604a9..dd38d2cbc 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -114,6 +114,32 @@ class AwesomeStrategy(IStrategy): See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks. +## Buy Signal Name + +When your strategy has multiple buy signal, you can name it. +Then you can access you buy signal on `custom_sell` + +```python +def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (dataframe['rsi'] < 35) & + (dataframe['volume'] > 0) + ), + ['buy', 'buy_signal_name']] = 1, 'buy_signal_rsi' + + return dataframe + +def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, + current_profit: float, **kwargs): + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = dataframe.iloc[-1].squeeze() + if trade.buy_signal_name == 'buy_signal_rsi' and last_candle['rsi'] > 80: + return 'sell_signal_rsi' + return None + +``` + ## Custom stoploss The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss. From ed30c023cdb3d2d1c655f75b242a84f78a712ca7 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 19:08:14 +0700 Subject: [PATCH 012/200] fix some testcase --- freqtrade/optimize/backtesting.py | 2 + freqtrade/strategy/interface.py | 1 + tests/optimize/test_backtesting.py | 1 + tests/plugins/test_pairlist.py | 10 +-- tests/rpc/test_rpc.py | 36 ++++---- tests/rpc/test_rpc_apiserver.py | 138 ++++++++++++++--------------- tests/rpc/test_rpc_telegram.py | 68 +++++++------- tests/test_freqtradebot.py | 34 +++---- 8 files changed, 148 insertions(+), 142 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2b8bd1e2b..35e524f69 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -197,6 +197,7 @@ class Backtesting: if not pair_data.empty: pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist + pair_data.loc[:, 'buy_signal_name'] = '' # cleanup if buy_signal_name is exist df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() @@ -205,6 +206,7 @@ class Backtesting: # from the previous candle df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) + df_analyzed.loc[:, 'buy_signal_name'] = df_analyzed.loc[:, 'buy_signal_name'].shift(1) df_analyzed.drop(df_analyzed.head(1).index, inplace=True) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index cbd19e156..6ff499199 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -404,6 +404,7 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug("Skipping TA Analysis for already analyzed candle") dataframe['buy'] = 0 dataframe['sell'] = 0 + dataframe['buy_signal_name'] = '' # Other Defs in strategy that want to be called every loop here # twitter_sell = self.watch_twitter_feed(dataframe, metadata) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 30d86f979..102d62273 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -482,6 +482,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: 0, # Sell 0.00099, # Low 0.0012, # High + '', # Buy Signal Name ] trade = backtesting._enter_trade(pair, row=row) assert isinstance(trade, LocalTrade) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index ae8f6e958..3dee47599 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -769,20 +769,20 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo get_tickers=tickers ) - ftbot = get_patched_freqtradebot(mocker, default_conf) - ftbot.pairlists.refresh_pairlist() + freqtrade = get_patched_freqtradebot(mocker, default_conf) + freqtrade.pairlists.refresh_pairlist() - assert len(ftbot.pairlists.whitelist) == 5 + assert len(freqtrade.pairlists.whitelist) == 5 tickers.return_value['ETH/BTC']['ask'] = 0.0 del tickers.return_value['TKN/BTC'] del tickers.return_value['LTC/BTC'] mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers) - ftbot.pairlists.refresh_pairlist() + freqtrade.pairlists.refresh_pairlist() assert log_has_re(r'Removed .* invalid ticker data.*', caplog) - assert len(ftbot.pairlists.whitelist) == 2 + assert len(freqtrade.pairlists.whitelist) == 2 @pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [ diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index cc29dc157..3d1896a4d 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -35,7 +35,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -69,6 +69,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, + 'buy_signal_name': ANY, 'timeframe': 5, 'open_order_id': ANY, 'close_date': None, @@ -135,6 +136,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, + 'buy_signal_name': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, @@ -190,7 +192,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: ) del default_conf['fiat_display_currency'] freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -237,7 +239,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -369,7 +371,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -457,7 +459,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -524,7 +526,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() with pytest.raises(RPCException, match="Error getting current tickers."): @@ -565,7 +567,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): ) default_conf['dry_run'] = False freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() @@ -610,7 +612,7 @@ def test_rpc_start(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED @@ -631,7 +633,7 @@ def test_rpc_stop(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -653,7 +655,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -685,7 +687,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED @@ -803,7 +805,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) # Create some test data @@ -836,7 +838,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) counts = rpc._rpc_count() @@ -861,7 +863,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) pair = 'ETH/BTC' trade = rpc._rpc_forcebuy(pair, None) @@ -887,7 +889,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> # Test not buying freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot.config['stake_amount'] = 0.0000001 - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) pair = 'TKN/BTC' trade = rpc._rpc_forcebuy(pair, None) @@ -900,7 +902,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) pair = 'ETH/BTC' with pytest.raises(RPCException, match=r'trader is not running'): @@ -911,7 +913,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) rpc = RPC(freqtradebot) pair = 'ETH/BTC' with pytest.raises(RPCException, match=r'Forcebuy not enabled.'): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index b8dd112c9..1ab7f178e 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -45,11 +45,11 @@ def botclient(default_conf, mocker): "password": _TEST_PASS, }}) - ftbot = get_patched_freqtradebot(mocker, default_conf) - rpc = RPC(ftbot) + freqtrade = get_patched_freqtradebot(mocker, default_conf) + rpc = RPC(freqtrade) mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', MagicMock()) apiserver = ApiServer(rpc, default_conf) - yield ftbot, TestClient(apiserver.app) + yield freqtrade, TestClient(apiserver.app) # Cleanup ... ? @@ -83,7 +83,7 @@ def assert_response(response, expected_code=200, needs_cors=True): def test_api_not_found(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/invalid_url") assert_response(rc, 404) @@ -91,7 +91,7 @@ def test_api_not_found(botclient): def test_api_ui_fallback(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, "/favicon.ico") assert rc.status_code == 200 @@ -126,7 +126,7 @@ def test_api_auth(): def test_api_unauthorized(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client.get(f"{BASE_URI}/ping") assert_response(rc, needs_cors=False) assert rc.json() == {'status': 'pong'} @@ -137,20 +137,20 @@ def test_api_unauthorized(botclient): assert rc.json() == {'detail': 'Unauthorized'} # Change only username - ftbot.config['api_server']['username'] = 'Ftrader' + freqtrade.config['api_server']['username'] = 'Ftrader' rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) assert rc.json() == {'detail': 'Unauthorized'} # Change only password - ftbot.config['api_server']['username'] = _TEST_USER - ftbot.config['api_server']['password'] = 'WrongPassword' + freqtrade.config['api_server']['username'] = _TEST_USER + freqtrade.config['api_server']['password'] = 'WrongPassword' rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) assert rc.json() == {'detail': 'Unauthorized'} - ftbot.config['api_server']['username'] = 'Ftrader' - ftbot.config['api_server']['password'] = 'WrongPassword' + freqtrade.config['api_server']['username'] = 'Ftrader' + freqtrade.config['api_server']['password'] = 'WrongPassword' rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) @@ -158,7 +158,7 @@ def test_api_unauthorized(botclient): def test_api_token_login(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client.post(f"{BASE_URI}/token/login", data=None, headers={'Authorization': _basic_auth_str('WRONG_USER', 'WRONG_PASS'), @@ -177,7 +177,7 @@ def test_api_token_login(botclient): def test_api_token_refresh(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_post(client, f"{BASE_URI}/token/login") assert_response(rc) rc = client.post(f"{BASE_URI}/token/refresh", @@ -190,12 +190,12 @@ def test_api_token_refresh(botclient): def test_api_stop_workflow(botclient): - ftbot, client = botclient - assert ftbot.state == State.RUNNING + freqtrade, client = botclient + assert freqtrade.state == State.RUNNING rc = client_post(client, f"{BASE_URI}/stop") assert_response(rc) assert rc.json() == {'status': 'stopping trader ...'} - assert ftbot.state == State.STOPPED + assert freqtrade.state == State.STOPPED # Stop bot again rc = client_post(client, f"{BASE_URI}/stop") @@ -206,7 +206,7 @@ def test_api_stop_workflow(botclient): rc = client_post(client, f"{BASE_URI}/start") assert_response(rc) assert rc.json() == {'status': 'starting trader ...'} - assert ftbot.state == State.RUNNING + assert freqtrade.state == State.RUNNING # Call start again rc = client_post(client, f"{BASE_URI}/start") @@ -358,32 +358,32 @@ def test_api_cleanup(default_conf, mocker, caplog): def test_api_reloadconf(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_post(client, f"{BASE_URI}/reload_config") assert_response(rc) assert rc.json() == {'status': 'Reloading config ...'} - assert ftbot.state == State.RELOAD_CONFIG + assert freqtrade.state == State.RELOAD_CONFIG def test_api_stopbuy(botclient): - ftbot, client = botclient - assert ftbot.config['max_open_trades'] != 0 + freqtrade, client = botclient + assert freqtrade.config['max_open_trades'] != 0 rc = client_post(client, f"{BASE_URI}/stopbuy") assert_response(rc) assert rc.json() == {'status': 'No more buy will occur from now. Run /reload_config to reset.'} - assert ftbot.config['max_open_trades'] == 0 + assert freqtrade.config['max_open_trades'] == 0 def test_api_balance(botclient, mocker, rpc_balance): - ftbot, client = botclient + freqtrade, client = botclient - ftbot.config['dry_run'] = False + freqtrade.config['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") - ftbot.wallets.update() + freqtrade.wallets.update() rc = client_get(client, f"{BASE_URI}/balance") assert_response(rc) @@ -400,8 +400,8 @@ def test_api_balance(botclient, mocker, rpc_balance): def test_api_count(botclient, mocker, ticker, fee, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -422,13 +422,13 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json()["current"] == 4 assert rc.json()["max"] == 1 - ftbot.config['max_open_trades'] = float('inf') + freqtrade.config['max_open_trades'] = float('inf') rc = client_get(client, f"{BASE_URI}/count") assert rc.json()["max"] == -1 def test_api_locks(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/locks") assert_response(rc) @@ -462,8 +462,8 @@ def test_api_locks(botclient): def test_api_show_config(botclient, mocker): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) rc = client_get(client, f"{BASE_URI}/show_config") assert_response(rc) @@ -480,8 +480,8 @@ def test_api_show_config(botclient, mocker): def test_api_daily(botclient, mocker, ticker, fee, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -498,8 +498,8 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): def test_api_trades(botclient, mocker, fee, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets) @@ -526,8 +526,8 @@ def test_api_trades(botclient, mocker, fee, markets): def test_api_trade_single(botclient, mocker, fee, ticker, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -546,8 +546,8 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): def test_api_delete_trade(botclient, mocker, fee, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( @@ -562,7 +562,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets): create_mock_trades(fee) Trade.query.session.flush() - ftbot.strategy.order_types['stoploss_on_exchange'] = True + freqtrade.strategy.order_types['stoploss_on_exchange'] = True trades = Trade.query.all() trades[1].stoploss_order_id = '1234' assert len(trades) > 2 @@ -588,7 +588,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets): def test_api_logs(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/logs") assert_response(rc) assert len(rc.json()) == 2 @@ -620,8 +620,8 @@ def test_api_logs(botclient): def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -636,8 +636,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_profit(botclient, mocker, ticker, fee, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -683,8 +683,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_stats(botclient, mocker, ticker, fee, markets,): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -711,8 +711,8 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,): def test_api_performance(botclient, fee): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) trade = Trade( pair='LTC/ETH', @@ -757,8 +757,8 @@ def test_api_performance(botclient, fee): def test_api_status(botclient, mocker, ticker, fee, markets): - ftbot, client = botclient - patch_get_signal(ftbot, (True, False)) + freqtrade, client = botclient + patch_get_signal(freqtrade, (True, False, '')) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -845,7 +845,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): def test_api_version(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/version") assert_response(rc) @@ -853,7 +853,7 @@ def test_api_version(botclient): def test_api_blacklist(botclient, mocker): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/blacklist") assert_response(rc) @@ -888,7 +888,7 @@ def test_api_blacklist(botclient, mocker): def test_api_whitelist(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/whitelist") assert_response(rc) @@ -900,7 +900,7 @@ def test_api_whitelist(botclient): def test_api_forcebuy(botclient, mocker, fee): - ftbot, client = botclient + freqtrade, client = botclient rc = client_post(client, f"{BASE_URI}/forcebuy", data='{"pair": "ETH/BTC"}') @@ -908,7 +908,7 @@ def test_api_forcebuy(botclient, mocker, fee): assert rc.json() == {"error": "Error querying /api/v1/forcebuy: Forcebuy not enabled."} # enable forcebuy - ftbot.config['forcebuy_enable'] = True + freqtrade.config['forcebuy_enable'] = True fbuy_mock = MagicMock(return_value=None) mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) @@ -990,7 +990,7 @@ def test_api_forcebuy(botclient, mocker, fee): def test_api_forcesell(botclient, mocker, ticker, fee, markets): - ftbot, client = botclient + freqtrade, client = botclient mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -999,14 +999,14 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): markets=PropertyMock(return_value=markets), _is_dry_limit_order_filled=MagicMock(return_value=False), ) - patch_get_signal(ftbot, (True, False)) + patch_get_signal(freqtrade, (True, False, '')) rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') assert_response(rc, 502) assert rc.json() == {"error": "Error querying /api/v1/forcesell: invalid argument"} - ftbot.enter_positions() + freqtrade.enter_positions() rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') @@ -1015,7 +1015,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): def test_api_pair_candles(botclient, ohlcv_history): - ftbot, client = botclient + freqtrade, client = botclient timeframe = '5m' amount = 3 @@ -1043,7 +1043,7 @@ def test_api_pair_candles(botclient, ohlcv_history): ohlcv_history.loc[1, 'buy'] = 1 ohlcv_history['sell'] = 0 - ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) + freqtrade.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) rc = client_get(client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") @@ -1081,7 +1081,7 @@ def test_api_pair_candles(botclient, ohlcv_history): def test_api_pair_history(botclient, ohlcv_history): - ftbot, client = botclient + freqtrade, client = botclient timeframe = '5m' # No pair @@ -1134,21 +1134,21 @@ def test_api_pair_history(botclient, ohlcv_history): def test_api_plot_config(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) assert rc.json() == {} - ftbot.strategy.plot_config = {'main_plot': {'sma': {}}, + freqtrade.strategy.plot_config = {'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}} rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) - assert rc.json() == ftbot.strategy.plot_config + assert rc.json() == freqtrade.strategy.plot_config assert isinstance(rc.json()['main_plot'], dict) assert isinstance(rc.json()['subplots'], dict) - ftbot.strategy.plot_config = {'main_plot': {'sma': {}}} + freqtrade.strategy.plot_config = {'main_plot': {'sma': {}}} rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) @@ -1157,7 +1157,7 @@ def test_api_plot_config(botclient): def test_api_strategies(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/strategies") @@ -1170,7 +1170,7 @@ def test_api_strategies(botclient): def test_api_strategy(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy") @@ -1185,7 +1185,7 @@ def test_api_strategy(botclient): def test_list_available_pairs(botclient): - ftbot, client = botclient + freqtrade, client = botclient rc = client_get(client, f"{BASE_URI}/available_pairs") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 782ae69c6..642b55975 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -55,7 +55,7 @@ class DummyCls(Telegram): raise Exception('test') -def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None): +def get_telegram_testobject(mocker, default_conf, mock=True, freqtrade=None): msg_mock = MagicMock() if mock: mocker.patch.multiple( @@ -63,12 +63,12 @@ def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None): _init=MagicMock(), _send_msg=msg_mock ) - if not ftbot: - ftbot = get_patched_freqtradebot(mocker, default_conf) - rpc = RPC(ftbot) + if not freqtrade: + freqtrade = get_patched_freqtradebot(mocker, default_conf) + rpc = RPC(freqtrade) telegram = Telegram(rpc, default_conf) - return telegram, ftbot, msg_mock + return telegram, freqtrade, msg_mock def test_telegram__init__(default_conf, mocker) -> None: @@ -115,11 +115,11 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None: patch_exchange(mocker) caplog.set_level(logging.DEBUG) default_conf['telegram']['enabled'] = False - bot = FreqtradeBot(default_conf) - rpc = RPC(bot) + freqtrade = FreqtradeBot(default_conf) + rpc = RPC(freqtrade) dummy = DummyCls(rpc, default_conf) - patch_get_signal(bot, (True, False)) + patch_get_signal(freqtrade, (True, False, '')) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is True assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) @@ -135,11 +135,11 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: update.message = Message(randint(1, 100), datetime.utcnow(), chat) default_conf['telegram']['enabled'] = False - bot = FreqtradeBot(default_conf) - rpc = RPC(bot) + freqtrade = FreqtradeBot(default_conf) + rpc = RPC(freqtrade) dummy = DummyCls(rpc, default_conf) - patch_get_signal(bot, (True, False)) + patch_get_signal(freqtrade, (True, False, '')) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is False assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) @@ -152,10 +152,10 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None default_conf['telegram']['enabled'] = False - bot = FreqtradeBot(default_conf) - rpc = RPC(bot) + freqtrade = FreqtradeBot(default_conf) + rpc = RPC(freqtrade) dummy = DummyCls(rpc, default_conf) - patch_get_signal(bot, (True, False)) + patch_get_signal(freqtrade, (True, False, '')) dummy.dummy_exception(update=update, context=MagicMock()) assert dummy.state['called'] is False @@ -228,7 +228,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) freqtradebot.state = State.STOPPED # Status is also enabled when stopped @@ -285,7 +285,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) freqtradebot.state = State.STOPPED # Status table is also enabled when stopped @@ -329,7 +329,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # Create some test data freqtradebot.enter_positions() @@ -400,7 +400,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # Try invalid data msg_mock.reset_mock() @@ -432,7 +432,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -486,7 +486,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -512,7 +512,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick side_effect=lambda a, b: f"{a}/{b}") telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] @@ -532,7 +532,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) freqtradebot.config['dry_run'] = False telegram._balance(update=update, context=MagicMock()) @@ -545,7 +545,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] @@ -574,7 +574,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None }) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) telegram._balance(update=update, context=MagicMock()) assert msg_mock.call_count > 1 @@ -673,7 +673,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # Create some test data freqtradebot.enter_positions() @@ -732,7 +732,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # Create some test data freqtradebot.enter_positions() @@ -793,7 +793,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # Create some test data freqtradebot.enter_positions() @@ -834,7 +834,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # Trader is not running freqtradebot.state = State.STOPPED @@ -872,7 +872,7 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # /forcebuy ETH/BTC context = MagicMock() @@ -901,7 +901,7 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) update.message.text = '/forcebuy ETH/Nonepair' telegram._forcebuy(update=update, context=MagicMock()) @@ -918,7 +918,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) context = MagicMock() context.args = [] @@ -946,7 +946,7 @@ def test_performance_handle(default_conf, update, ticker, fee, get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) # Create some test data freqtradebot.enter_positions() @@ -974,7 +974,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) freqtradebot.state = State.STOPPED telegram._count(update=update, context=MagicMock()) @@ -1003,7 +1003,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False)) + patch_get_signal(freqtradebot, (True, False, '')) telegram._locks(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'No active locks.' in msg_mock.call_args_list[0][0][0] diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index cc89ce00c..583ec7ddd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1972,16 +1972,16 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_buy_order_open def test_bot_loop_start_called_once(mocker, default_conf, caplog): - ftbot = get_patched_freqtradebot(mocker, default_conf) + freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade') - patch_get_signal(ftbot) - ftbot.strategy.bot_loop_start = MagicMock(side_effect=ValueError) - ftbot.strategy.analyze = MagicMock() + patch_get_signal(freqtrade) + freqtrade.strategy.bot_loop_start = MagicMock(side_effect=ValueError) + freqtrade.strategy.analyze = MagicMock() - ftbot.process() + freqtrade.process() assert log_has_re(r'Strategy caused the following exception.*', caplog) - assert ftbot.strategy.bot_loop_start.call_count == 1 - assert ftbot.strategy.analyze.call_count == 1 + assert freqtrade.strategy.bot_loop_start.call_count == 1 + assert freqtrade.strategy.analyze.call_count == 1 def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_order_old, open_trade, @@ -4044,14 +4044,14 @@ def test_startup_trade_reinit(default_conf, edge_conf, mocker): reinit_mock = MagicMock() mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', reinit_mock) - ftbot = get_patched_freqtradebot(mocker, default_conf) - ftbot.startup() + freqtrade = get_patched_freqtradebot(mocker, default_conf) + freqtrade.startup() assert reinit_mock.call_count == 1 reinit_mock.reset_mock() - ftbot = get_patched_freqtradebot(mocker, edge_conf) - ftbot.startup() + freqtrade = get_patched_freqtradebot(mocker, edge_conf) + freqtrade.startup() assert reinit_mock.call_count == 0 @@ -4070,17 +4070,17 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order_ get_fee=fee, ) - bot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(bot) - assert bot.wallets.get_free('BTC') == 0.002 + freqtrade = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtrade) + assert freqtrade.wallets.get_free('BTC') == 0.002 - n = bot.enter_positions() + n = freqtrade.enter_positions() assert n == 2 trades = Trade.query.all() assert len(trades) == 2 - bot.config['max_open_trades'] = 3 - n = bot.enter_positions() + freqtrade.config['max_open_trades'] = 3 + n = freqtrade.enter_positions() assert n == 0 assert log_has_re(r"Unable to create trade for XRP/BTC: " r"Available balance \(0.0 BTC\) is lower than stake amount \(0.001 BTC\)", From 3d8b2d601d04b301d76cfbc1f745c24753c0d6be Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 20:23:47 +0700 Subject: [PATCH 013/200] fix test persistance --- tests/test_persistence.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 89d07ca74..8e486d145 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -861,6 +861,7 @@ def test_to_json(default_conf, fee): open_date=arrow.utcnow().shift(hours=-2).datetime, open_rate=0.123, exchange='binance', + buy_signal_name='', open_order_id='dry_run_buy_12345' ) result = trade.to_json() @@ -910,6 +911,7 @@ def test_to_json(default_conf, fee): 'min_rate': None, 'max_rate': None, 'strategy': None, + 'buy_signal_name': '', 'timeframe': None, 'exchange': 'binance', } @@ -926,6 +928,7 @@ def test_to_json(default_conf, fee): close_date=arrow.utcnow().shift(hours=-1).datetime, open_rate=0.123, close_rate=0.125, + buy_signal_name='buys_signal_001', exchange='binance', ) result = trade.to_json() @@ -975,6 +978,7 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, + 'buy_signal_name': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', } From c558fc0b17c10e1ca2eb7bf1020d3712b6a9ab9c Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 20:40:32 +0700 Subject: [PATCH 014/200] fix feedback --- freqtrade/enums/signaltype.py | 2 +- freqtrade/persistence/migrations.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index 6f29699f1..8529c6b79 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -11,6 +11,6 @@ class SignalType(Enum): class SignalNameType(Enum): """ - Enum for signal name + Enum for signal columns """ BUY_SIGNAL_NAME = "buy_signal_name" diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 4fa1cb742..f6a345ed1 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -161,7 +161,14 @@ def check_migrate(engine, decl_base, previous_tables) -> None: table_back_name = get_backup_name(tabs, 'trades_bak') # Check for latest column - if not has_column(cols, 'open_trade_value') or not has_column(cols, 'buy_signal_name'): + if not has_column(cols, 'open_trade_value'): + logger.info(f'Running database migration for trades - backup: {table_back_name}') + migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) + # Reread columns - the above recreated the table! + inspector = inspect(engine) + cols = inspector.get_columns('trades') + + if not has_column(cols, 'buy_signal_name'): logger.info(f'Running database migration for trades - backup: {table_back_name}') migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) # Reread columns - the above recreated the table! From cbfedf8b29144325306952a3581bf56f8c912d4b Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 23:25:00 +0700 Subject: [PATCH 015/200] fix backtest testcase --- freqtrade/data/btanalysis.py | 2 +- freqtrade/optimize/backtesting.py | 2 + tests/optimize/__init__.py | 3 +- tests/optimize/test_backtest_detail.py | 449 +++++++++++++------------ 4 files changed, 238 insertions(+), 218 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index e7af5eab8..f6122c5aa 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', 'fee_open', 'fee_close', 'trade_duration', 'profit_ratio', 'profit_abs', 'sell_reason', 'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', - 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', ] + 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_signal_name'] def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7fdc70e70..843d3331e 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -453,6 +453,8 @@ class Backtesting: row_index = indexes[pair] try: row = data[pair][row_index] + print('weeee') + print(row) except IndexError: # missing Data for one pair at the end. # Warnings for this are shown during data loading diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index ca91019e6..f77e6f70f 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -18,6 +18,7 @@ class BTrade(NamedTuple): sell_reason: SellType open_tick: int close_tick: int + buy_signal_name: Optional[str] = '' class BTContainer(NamedTuple): @@ -43,7 +44,7 @@ def _get_frame_time_from_offset(offset): def _build_backtest_dataframe(data): - columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell'] + columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell', 'buy_signal_name'] frame = DataFrame.from_records(data, columns=columns) frame['date'] = frame['date'].apply(_get_frame_time_from_offset) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 0bf197739..46c8e303d 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -14,13 +14,13 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, # Test 0: Sell with signal sell in candle 3 # Test with Stop-loss at 1% tc0 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], # exit with stoploss hit - [3, 5010, 5000, 4980, 5010, 6172, 0, 1], - [4, 5010, 4987, 4977, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], # exit with stoploss hit + [3, 5010, 5000, 4980, 5010, 6172, 0, 1, ''], + [4, 5010, 4987, 4977, 4995, 6172, 0, 0, ''], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) @@ -28,13 +28,13 @@ tc0 = BTContainer(data=[ # Test 1: Stop-Loss Triggered 1% loss # Test with Stop-loss at 1% tc1 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit - [3, 4975, 5000, 4980, 4977, 6172, 0, 0], - [4, 4977, 4987, 4977, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4600, 4600, 6172, 0, 0, ''], # exit with stoploss hit + [3, 4975, 5000, 4980, 4977, 6172, 0, 0, ''], + [4, 4977, 4987, 4977, 4995, 6172, 0, 0, ''], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) @@ -43,13 +43,13 @@ tc1 = BTContainer(data=[ # Test 2: Minus 4% Low, minus 1% close # Test with Stop-Loss at 3% tc2 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4962, 4975, 6172, 0, 0], - [3, 4975, 5000, 4800, 4962, 6172, 0, 0], # exit with stoploss hit - [4, 4962, 4987, 4937, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4962, 4975, 6172, 0, 0, ''], + [3, 4975, 5000, 4800, 4962, 6172, 0, 0, ''], # exit with stoploss hit + [4, 4962, 4987, 4937, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -62,14 +62,14 @@ tc2 = BTContainer(data=[ # Trade-A: Stop-Loss Triggered 2% Loss # Trade-B: Stop-Loss Triggered 2% Loss tc3 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4800, 4975, 6172, 0, 0], # exit with stoploss hit - [3, 4975, 5000, 4950, 4962, 6172, 1, 0], - [4, 4975, 5000, 4950, 4962, 6172, 0, 0], # enter trade 2 (signal on last candle) - [5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit - [6, 4950, 4975, 4975, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4800, 4975, 6172, 0, 0, ''], # exit with stoploss hit + [3, 4975, 5000, 4950, 4962, 6172, 1, 0, ''], + [4, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], # enter trade 2 (signal on last candle) + [5, 4962, 4987, 4000, 4000, 6172, 0, 0, ''], # exit with stoploss hit + [6, 4950, 4975, 4975, 4950, 6172, 0, 0, '']], stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2), BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)] @@ -80,13 +80,13 @@ tc3 = BTContainer(data=[ # Test with Stop-loss at 2% ROI 6% # Stop-Loss Triggered 2% Loss tc4 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5750, 4850, 5750, 6172, 0, 0], # Exit with stoploss hit - [3, 4975, 5000, 4950, 4962, 6172, 0, 0], - [4, 4962, 4987, 4937, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5750, 4850, 5750, 6172, 0, 0, ''], # Exit with stoploss hit + [3, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], + [4, 4962, 4987, 4937, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) @@ -94,13 +94,13 @@ tc4 = BTContainer(data=[ # Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain # stop-loss: 1%, ROI: 3% tc5 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4980, 4987, 6172, 1, 0], - [1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5025, 4975, 4987, 6172, 0, 0], - [3, 4975, 6000, 4975, 6000, 6172, 0, 0], # ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4980, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4980, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5025, 4975, 4987, 6172, 0, 0, ''], + [3, 4975, 6000, 4975, 6000, 6172, 0, 0, ''], # ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -108,13 +108,13 @@ tc5 = BTContainer(data=[ # Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss # stop-loss: 2% ROI: 5% tc6 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5300, 4850, 5050, 6172, 0, 0], # Exit with stoploss - [3, 4975, 5000, 4950, 4962, 6172, 0, 0], - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5300, 4850, 5050, 6172, 0, 0, ''], # Exit with stoploss + [3, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) @@ -122,13 +122,13 @@ tc6 = BTContainer(data=[ # Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain # stop-loss: 2% ROI: 3% tc7 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], - [2, 4987, 5300, 4950, 5050, 6172, 0, 0], - [3, 4975, 5000, 4950, 4962, 6172, 0, 0], - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + [2, 4987, 5300, 4950, 5050, 6172, 0, 0, ''], + [3, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] ) @@ -137,12 +137,12 @@ tc7 = BTContainer(data=[ # Test 8: trailing_stop should raise so candle 3 causes a stoploss. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 2 tc8 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5000, 6172, 0, 0], - [2, 5000, 5250, 4750, 4850, 6172, 0, 0], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0, ''], + [2, 5000, 5250, 4750, 4850, 6172, 0, 0, ''], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -151,12 +151,12 @@ tc8 = BTContainer(data=[ # Test 9: trailing_stop should raise - high and low in same candle. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 3 tc9 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5000, 6172, 0, 0], - [2, 5000, 5050, 4950, 5000, 6172, 0, 0], - [3, 5000, 5200, 4550, 4850, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0, ''], + [2, 5000, 5050, 4950, 5000, 6172, 0, 0, ''], + [3, 5000, 5200, 4550, 4850, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -165,12 +165,12 @@ tc9 = BTContainer(data=[ # without applying trailing_stop_positive since stoploss_offset is at 10%. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc10 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10, trailing_stop_positive=0.03, @@ -181,12 +181,12 @@ tc10 = BTContainer(data=[ # applying a positive trailing stop of 3% since stop_positive_offset is reached. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc11 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0], - [3, 5000, 5150, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], + [3, 5000, 5150, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -197,12 +197,12 @@ tc11 = BTContainer(data=[ # applying a positive trailing stop of 3% since stop_positive_offset is reached. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc12 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 4650, 5100, 6172, 0, 0], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 4650, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -212,12 +212,12 @@ tc12 = BTContainer(data=[ # Test 13: Buy and sell ROI on same candle # stop-loss: 10% (should not apply), ROI: 1% tc13 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5100, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 4850, 5100, 6172, 0, 0], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0], - [4, 4750, 4950, 4850, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 4850, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4850, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4850, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] ) @@ -225,12 +225,12 @@ tc13 = BTContainer(data=[ # Test 14 - Buy and Stoploss on same candle # stop-loss: 5%, ROI: 10% (should not apply) tc14 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5100, 4600, 5100, 6172, 0, 0], - [2, 5100, 5251, 4850, 5100, 6172, 0, 0], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5100, 4600, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 4850, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4850, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] ) @@ -239,12 +239,12 @@ tc14 = BTContainer(data=[ # Test 15 - Buy and ROI on same candle, followed by buy and Stoploss on next candle # stop-loss: 5%, ROI: 10% (should not apply) tc15 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5100, 4900, 5100, 6172, 1, 0], - [2, 5100, 5251, 4650, 5100, 6172, 0, 0], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5100, 4900, 5100, 6172, 1, 0, ''], + [2, 5100, 5251, 4650, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4850, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1), BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)] @@ -254,13 +254,13 @@ tc15 = BTContainer(data=[ # Causes negative profit even though sell-reason is ROI. # stop-loss: 10%, ROI: 10% (should not apply), -100% after 65 minutes (limits trade duration) tc16 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], - [2, 4987, 5300, 4950, 5050, 6172, 0, 0], - [3, 4975, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + [2, 4987, 5300, 4950, 5050, 6172, 0, 0, ''], + [3, 4975, 5000, 4940, 4962, 6172, 0, 0, ''], # ForceSell on ROI (roi=-1) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -270,13 +270,13 @@ tc16 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe. tc17 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], - [2, 4987, 5300, 4950, 5050, 6172, 0, 0], - [3, 4980, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + [2, 4987, 5300, 4950, 5050, 6172, 0, 0, ''], + [3, 4980, 5000, 4940, 4962, 6172, 0, 0, ''], # ForceSell on ROI (roi=-1) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -286,13 +286,13 @@ tc17 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses open_rate as sell-price tc18 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], - [2, 4987, 5300, 4950, 5200, 6172, 0, 0], - [3, 5200, 5220, 4940, 4962, 6172, 0, 0], # Sell on ROI (sells on open) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0, ''], + [3, 5200, 5220, 4940, 4962, 6172, 0, 0, ''], # Sell on ROI (sells on open) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -301,13 +301,13 @@ tc18 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses calculated ROI (1%) as sell rate, otherwise identical to tc18 tc19 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], - [2, 4987, 5300, 4950, 5200, 6172, 0, 0], - [3, 5000, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0, ''], + [3, 5000, 5300, 4940, 4962, 6172, 0, 0, ''], # Sell on ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4550, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -316,13 +316,13 @@ tc19 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses calculated ROI (1%) as sell rate, otherwise identical to tc18 tc20 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], - [2, 4987, 5300, 4950, 5200, 6172, 0, 0], - [3, 5200, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], - [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0, ''], + [3, 5200, 5300, 4940, 4962, 6172, 0, 0, ''], # Sell on ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], + [5, 4550, 4975, 4925, 4950, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -332,12 +332,12 @@ tc20 = BTContainer(data=[ # which cannot happen in reality # stop-loss: 10%, ROI: 4%, Trailing stop adjusted at the sell candle tc21 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 4650, 5100, 6172, 0, 0], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 4650, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -348,12 +348,12 @@ tc21 = BTContainer(data=[ # applying a positive trailing stop of 3% - ROI should apply before trailing stop. # stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2 tc22 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -367,12 +367,12 @@ tc22 = BTContainer(data=[ # Stoploss would trigger in this candle too, but it's no longer relevant. # stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2, ROI adjusted in candle 3 (causing the sell) tc23 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0], - [3, 4850, 5251, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], + [3, 4850, 5251, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -383,13 +383,13 @@ tc23 = BTContainer(data=[ # Stoploss at 1%. # Stoploss wins over Sell-signal (because sell-signal is acted on in the next candle) tc24 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], - [3, 5010, 5000, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal - [4, 5010, 4987, 4977, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], + [3, 5010, 5000, 4855, 5010, 6172, 0, 1, ''], # Triggers stoploss + sellsignal + [4, 5010, 4987, 4977, 4995, 6172, 0, 0, ''], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -398,13 +398,13 @@ tc24 = BTContainer(data=[ # Stoploss at 1%. # Sell-signal wins over stoploss tc25 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], - [3, 5010, 5000, 4986, 5010, 6172, 0, 1], - [4, 5010, 4987, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], + [3, 5010, 5000, 4986, 5010, 6172, 0, 1, ''], + [4, 5010, 4987, 4855, 4995, 6172, 0, 0, ''], # Triggers stoploss + sellsignal acted on + [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) @@ -413,13 +413,13 @@ tc25 = BTContainer(data=[ # Stoploss at 10% (irrelevant), ROI at 5% (will trigger) # Sell-signal wins over stoploss tc26 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], - [3, 5010, 5251, 4986, 5010, 6172, 0, 1], # Triggers ROI, sell-signal - [4, 5010, 4987, 4855, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], + [3, 5010, 5251, 4986, 5010, 6172, 0, 1, ''], # Triggers ROI, sell-signal + [4, 5010, 4987, 4855, 4995, 6172, 0, 0, ''], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -429,13 +429,13 @@ tc26 = BTContainer(data=[ # TODO: figure out if sell-signal should win over ROI # Sell-signal wins over stoploss tc27 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], - [3, 5010, 5012, 4986, 5010, 6172, 0, 1], # sell-signal - [4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], + [3, 5010, 5012, 4986, 5010, 6172, 0, 1, ''], # sell-signal + [4, 5010, 5251, 4855, 4995, 6172, 0, 0, ''], # Triggers ROI, sell-signal acted on + [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)] ) @@ -445,12 +445,12 @@ tc27 = BTContainer(data=[ # therefore "open" will be used # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc28 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -461,12 +461,12 @@ tc28 = BTContainer(data=[ # high of stoploss candle. # stop-loss: 10%, ROI: 10% (should not apply) tc29 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Triggers trailing-stoploss - [3, 5100, 5100, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5050, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], # Triggers trailing-stoploss + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True, trailing_stop_positive=0.03, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] @@ -475,12 +475,12 @@ tc29 = BTContainer(data=[ # Test 30: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 10%, ROI: 10% (should not apply) tc30 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_stop_positive=0.01, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] @@ -489,12 +489,12 @@ tc30 = BTContainer(data=[ # Test 31: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 10%, ROI: 10% (should not apply) tc31 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, @@ -504,18 +504,33 @@ tc31 = BTContainer(data=[ # Test 32: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 1%, ROI: 10% (should not apply) tc32 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, use_custom_stoploss=True, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] ) +# Test 33: trailing_stop should be triggered immediately on trade open candle. +# stop-loss: 1%, ROI: 10% (should not apply) +tc33 = BTContainer(data=[ + # D O H L C V B S SN + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, + trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, + trailing_stop_positive=0.01, use_custom_stoploss=True, + trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1, buy_signal_name='buy_signal_01')] +) + TESTS = [ tc0, tc1, @@ -550,6 +565,7 @@ TESTS = [ tc30, tc31, tc32, + tc33, ] @@ -598,5 +614,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: for c, trade in enumerate(data.trades): res = results.iloc[c] assert res.sell_reason == trade.sell_reason.value + assert res.buy_signal_name == trade.buy_signal_name assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick) From 5d04d6ffa72812efbc74aada0471c5cc59a388df Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 23:40:32 +0700 Subject: [PATCH 016/200] fix edge testcase --- freqtrade/optimize/backtesting.py | 2 -- tests/edge/test_edge.py | 47 +++++++++++++++--------------- tests/optimize/test_backtesting.py | 1 + 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 843d3331e..7fdc70e70 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -453,8 +453,6 @@ class Backtesting: row_index = indexes[pair] try: row = data[pair][row_index] - print('weeee') - print(row) except IndexError: # missing Data for one pair at the end. # Warnings for this are shown during data loading diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 0655b3a0f..254134ce7 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -29,7 +29,6 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, tests_start_time = arrow.get(2018, 10, 3) timeframe_in_minute = 60 -_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7} # Helpers for this test file @@ -77,23 +76,23 @@ def _time_on_candle(number): # End helper functions # Open trade should be removed from the end tc0 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 1]], # enter trade (signal on last candle) + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 1, '']], # enter trade (signal on last candle) stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00, trades=[] ) # Two complete trades within dataframe(with sell hit for all) tc1 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4975, 4987, 6172, 0, 1], # enter trade (signal on last candle) - [2, 5000, 5025, 4975, 4987, 6172, 0, 0], # exit at open - [3, 5000, 5025, 4975, 4987, 6172, 1, 0], # no action - [4, 5000, 5025, 4975, 4987, 6172, 0, 0], # should enter the trade - [5, 5000, 5025, 4975, 4987, 6172, 0, 1], # no action - [6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4975, 4987, 6172, 0, 1, ''], # enter trade (signal on last candle) + [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # exit at open + [3, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], # no action + [4, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # should enter the trade + [5, 5000, 5025, 4975, 4987, 6172, 0, 1, ''], # no action + [6, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # should sell ], stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2), @@ -102,10 +101,10 @@ tc1 = BTContainer(data=[ # 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss tc2 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4600, 4987, 6172, 0, 0], # enter trade, stoploss hit - [2, 5000, 5025, 4975, 4987, 6172, 0, 0], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4600, 4987, 6172, 0, 0, ''], # enter trade, stoploss hit + [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], ], stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] @@ -113,10 +112,10 @@ tc2 = BTContainer(data=[ # 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss tc3 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4800, 4987, 6172, 0, 0], # enter trade, stoploss hit - [2, 5000, 5025, 4975, 4987, 6172, 0, 0], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4800, 4987, 6172, 0, 0, ''], # enter trade, stoploss hit + [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], ], stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] @@ -124,10 +123,10 @@ tc3 = BTContainer(data=[ # 5) Stoploss and sell are hit. should sell on stoploss tc4 = BTContainer(data=[ - # D O H L C V B S - [0, 5000, 5025, 4975, 4987, 6172, 1, 0], - [1, 5000, 5025, 4800, 4987, 6172, 0, 1], # enter trade, stoploss hit, sell signal - [2, 5000, 5025, 4975, 4987, 6172, 0, 0], + # D O H L C V B S SN + [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], + [1, 5000, 5025, 4800, 4987, 6172, 0, 1, ''], # enter trade, stoploss hit, sell signal + [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], ], stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 655464344..64795d064 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -584,6 +584,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'min_rate': [0.1038, 0.10302485], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], + 'buy_signal_name': ['', ''], }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] From 66a7070170b919ad0d020e55282ae0cb0873fdef Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Tue, 20 Jul 2021 23:56:03 +0700 Subject: [PATCH 017/200] run linter --- freqtrade/enums/__init__.py | 2 +- freqtrade/freqtradebot.py | 12 ++++++++++-- freqtrade/strategy/interface.py | 2 +- tests/optimize/test_backtest_detail.py | 7 ++++++- tests/rpc/test_rpc_apiserver.py | 6 ++++-- tests/strategy/test_interface.py | 12 ++++++++++-- tests/test_freqtradebot.py | 5 ++++- 7 files changed, 36 insertions(+), 10 deletions(-) diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index 60f984c33..71e9d7d9e 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -3,5 +3,5 @@ from freqtrade.enums.backteststate import BacktestState from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.selltype import SellType -from freqtrade.enums.signaltype import SignalType, SignalNameType +from freqtrade.enums.signaltype import SignalNameType, SignalType from freqtrade.enums.state import State diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8d3b24b10..95c769730 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -420,7 +420,11 @@ class FreqtradeBot(LoggingMixin): return False # running get_signal on historical data fetched - (buy, sell, buy_signal_name) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) + (buy, sell, buy_signal_name) = self.strategy.get_signal( + pair, + self.strategy.timeframe, + analyzed_df + ) if buy and not sell: stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge) @@ -693,7 +697,11 @@ class FreqtradeBot(LoggingMixin): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) - (buy, sell, _) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df) + (buy, sell, _) = self.strategy.get_signal( + trade.pair, + self.strategy.timeframe, + analyzed_df + ) logger.debug('checking sell') sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7158ad8e8..8ee0cea59 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -13,7 +13,7 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import SellType, SignalType, SignalNameType +from freqtrade.enums import SellType, SignalNameType, SignalType from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 46c8e303d..d1bf28d58 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -528,7 +528,12 @@ tc33 = BTContainer(data=[ stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, use_custom_stoploss=True, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1, buy_signal_name='buy_signal_01')] + trades=[BTrade( + sell_reason=SellType.TRAILING_STOP_LOSS, + open_tick=1, + close_tick=1, + buy_signal_name='buy_signal_01' + )] ) TESTS = [ diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index afd273998..74d40b611 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1185,8 +1185,10 @@ def test_api_plot_config(botclient): assert_response(rc) assert rc.json() == {} - freqtrade.strategy.plot_config = {'main_plot': {'sma': {}}, - 'subplots': {'RSI': {'rsi': {'color': 'red'}}}} + freqtrade.strategy.plot_config = { + 'main_plot': {'sma': {}}, + 'subplots': {'RSI': {'rsi': {'color': 'red'}}} + } rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) assert rc.json() == freqtrade.strategy.plot_config diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index aec07266d..3678cee4a 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -76,7 +76,11 @@ def test_get_signal_empty(default_conf, mocker, caplog): assert log_has('Empty candle (OHLCV) data for pair bar', caplog) caplog.clear() - assert (False, False, '') == _STRATEGY.get_signal('baz', default_conf['timeframe'], DataFrame([])) + assert (False, False, '') == _STRATEGY.get_signal( + 'baz', + default_conf['timeframe'], + DataFrame([]) + ) assert log_has('Empty candle (OHLCV) data for pair baz', caplog) @@ -112,7 +116,11 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False, '') == _STRATEGY.get_signal('xyz', default_conf['timeframe'], mocked_history) + assert (False, False, '') == _STRATEGY.get_signal( + 'xyz', + default_conf['timeframe'], + mocked_history + ) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 48a0f06e8..6372e6d36 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -757,7 +757,10 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: refresh_latest_ohlcv=refresh_mock, ) inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")]) - mocker.patch('freqtrade.strategy.interface.IStrategy.get_signal', return_value=(False, False, '')) + mocker.patch( + 'freqtrade.strategy.interface.IStrategy.get_signal', + return_value=(False, False, '') + ) mocker.patch('time.sleep', return_value=None) freqtrade = FreqtradeBot(default_conf) From 1ea29a918a9fb55f32f39fd098389b6427e64e3d Mon Sep 17 00:00:00 2001 From: George Muravei-Alkhavoi Date: Wed, 21 Jul 2021 00:09:09 +0300 Subject: [PATCH 019/200] Fix webserver timerange problem. --- freqtrade/rpc/api_server/api_backtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 76b4a8169..2fa66645b 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -47,15 +47,15 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac not ApiServer._bt or lastconfig.get('timeframe') != strat.timeframe or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0) + or lastconfig.get('timerange') != btconfig['timerange'] ): from freqtrade.optimize.backtesting import Backtesting ApiServer._bt = Backtesting(btconfig) - # Only reload data if timeframe or timerange changed. + # Only reload data if timeframe changed. if ( not ApiServer._bt_data or not ApiServer._bt_timerange - or lastconfig.get('timerange') != btconfig['timerange'] or lastconfig.get('stake_amount') != btconfig.get('stake_amount') or lastconfig.get('enable_protections') != btconfig.get('enable_protections') or lastconfig.get('protections') != btconfig.get('protections', []) From 49886874aafcf1763e563f55cb61fedff0d3ac60 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Wed, 21 Jul 2021 20:05:35 +0700 Subject: [PATCH 020/200] rename to buy_tag --- docs/strategy-advanced.md | 6 +++--- freqtrade/data/btanalysis.py | 2 +- freqtrade/enums/__init__.py | 2 +- freqtrade/enums/signaltype.py | 4 ++-- freqtrade/freqtradebot.py | 12 ++++++------ freqtrade/optimize/backtesting.py | 12 ++++++------ freqtrade/persistence/migrations.py | 8 ++++---- freqtrade/persistence/models.py | 8 ++++---- freqtrade/strategy/interface.py | 12 ++++++------ tests/optimize/__init__.py | 4 ++-- tests/optimize/test_backtest_detail.py | 4 ++-- tests/optimize/test_backtesting.py | 4 ++-- tests/rpc/test_rpc.py | 4 ++-- tests/strategy/test_interface.py | 2 +- tests/test_persistence.py | 10 +++++----- 15 files changed, 47 insertions(+), 47 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 0b52af17b..8ae0c1d16 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -114,7 +114,7 @@ class AwesomeStrategy(IStrategy): See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks. -## Buy Signal Name +## Buy Tag When your strategy has multiple buy signal, you can name it. Then you can access you buy signal on `custom_sell` @@ -126,7 +126,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['rsi'] < 35) & (dataframe['volume'] > 0) ), - ['buy', 'buy_signal_name']] = 1, 'buy_signal_rsi' + ['buy', 'buy_tag']] = 1, 'buy_signal_rsi' return dataframe @@ -134,7 +134,7 @@ def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_r current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() - if trade.buy_signal_name == 'buy_signal_rsi' and last_candle['rsi'] > 80: + if trade.buy_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80: return 'sell_signal_rsi' return None diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index f6122c5aa..d62712cbb 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', 'fee_open', 'fee_close', 'trade_duration', 'profit_ratio', 'profit_abs', 'sell_reason', 'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', - 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_signal_name'] + 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag'] def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index 71e9d7d9e..d803baf31 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -3,5 +3,5 @@ from freqtrade.enums.backteststate import BacktestState from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.selltype import SellType -from freqtrade.enums.signaltype import SignalNameType, SignalType +from freqtrade.enums.signaltype import SignalTagType, SignalType from freqtrade.enums.state import State diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index 8529c6b79..d2995d57a 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -9,8 +9,8 @@ class SignalType(Enum): SELL = "sell" -class SignalNameType(Enum): +class SignalTagType(Enum): """ Enum for signal columns """ - BUY_SIGNAL_NAME = "buy_signal_name" + BUY_TAG = "buy_tag" diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 95c769730..cb9157d33 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin): return False # running get_signal on historical data fetched - (buy, sell, buy_signal_name) = self.strategy.get_signal( + (buy, sell, buy_tag) = self.strategy.get_signal( pair, self.strategy.timeframe, analyzed_df @@ -431,13 +431,13 @@ class FreqtradeBot(LoggingMixin): bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) if ((bid_check_dom.get('enabled', False)) and - (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): + (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): if self._check_depth_of_market_buy(pair, bid_check_dom): - return self.execute_buy(pair, stake_amount, buy_signal_name=buy_signal_name) + return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) else: return False - return self.execute_buy(pair, stake_amount, buy_signal_name=buy_signal_name) + return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) else: return False @@ -466,7 +466,7 @@ class FreqtradeBot(LoggingMixin): return False def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False, buy_signal_name: str = '') -> bool: + forcebuy: bool = False, buy_tag: str = '') -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY @@ -569,7 +569,7 @@ class FreqtradeBot(LoggingMixin): exchange=self.exchange.id, open_order_id=order_id, strategy=self.strategy.get_strategy_name(), - buy_signal_name=buy_signal_name, + buy_tag=buy_tag, timeframe=timeframe_to_minutes(self.config['timeframe']) ) trade.orders.append(order_obj) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7fdc70e70..5099c07ef 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -43,7 +43,7 @@ CLOSE_IDX = 3 SELL_IDX = 4 LOW_IDX = 5 HIGH_IDX = 6 -BUY_SIGNAL_NAME_IDX = 7 +buy_tag_IDX = 7 class Backtesting: @@ -210,7 +210,7 @@ class Backtesting: """ # Every change to this headers list must evaluate further usages of the resulting tuple # and eventually change the constants for indexes at the top - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_signal_name'] + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag'] data: Dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) @@ -221,7 +221,7 @@ class Backtesting: if not pair_data.empty: pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist - pair_data.loc[:, 'buy_signal_name'] = '' # cleanup if buy_signal_name is exist + pair_data.loc[:, 'buy_tag'] = '' # cleanup if buy_tag is exist df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() @@ -230,7 +230,7 @@ class Backtesting: # from the previous candle df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) - df_analyzed.loc[:, 'buy_signal_name'] = df_analyzed.loc[:, 'buy_signal_name'].shift(1) + df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) df_analyzed.drop(df_analyzed.head(1).index, inplace=True) @@ -265,7 +265,7 @@ class Backtesting: # Worst case: price reaches stop_positive_offset and dives down. stop_rate = (sell_row[OPEN_IDX] * (1 + abs(self.strategy.trailing_stop_positive_offset) - - abs(self.strategy.trailing_stop_positive))) + abs(self.strategy.trailing_stop_positive))) else: # Worst case: price ticks tiny bit above open and dives down. stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct)) @@ -370,7 +370,7 @@ class Backtesting: fee_open=self.fee, fee_close=self.fee, is_open=True, - buy_signal_name=row[BUY_SIGNAL_NAME_IDX], + buy_tag=row[buy_tag_IDX], exchange='backtesting', ) return trade diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index f6a345ed1..035452437 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -47,7 +47,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col min_rate = get_column_def(cols, 'min_rate', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') - buy_signal_name = get_column_def(cols, 'buy_signal_name', 'null') + buy_tag = get_column_def(cols, 'buy_tag', 'null') # If ticker-interval existed use that, else null. if has_column(cols, 'ticker_interval'): timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') @@ -81,7 +81,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stoploss_order_id, stoploss_last_update, - max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_signal_name, + max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, timeframe, open_trade_value, close_profit_abs ) select id, lower(exchange), @@ -104,7 +104,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {sell_order_status} sell_order_status, - {strategy} strategy, {buy_signal_name} buy_signal_name, {timeframe} timeframe, + {strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs from {table_back_name} """)) @@ -168,7 +168,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None: inspector = inspect(engine) cols = inspector.get_columns('trades') - if not has_column(cols, 'buy_signal_name'): + if not has_column(cols, 'buy_tag'): logger.info(f'Running database migration for trades - backup: {table_back_name}') migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) # Reread columns - the above recreated the table! diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 058e892ec..f42166762 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -257,7 +257,7 @@ class LocalTrade(): sell_reason: str = '' sell_order_status: str = '' strategy: str = '' - buy_signal_name: str = '' + buy_tag: str = '' timeframe: Optional[int] = None def __init__(self, **kwargs): @@ -289,7 +289,7 @@ class LocalTrade(): 'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, 'stake_amount': round(self.stake_amount, 8), 'strategy': self.strategy, - 'buy_signal_name': self.buy_signal_name, + 'buy_tag': self.buy_tag, 'timeframe': self.timeframe, 'fee_open': self.fee_open, @@ -638,7 +638,7 @@ class LocalTrade(): # skip case if trailing-stop changed the stoploss already. if (trade.stop_loss == trade.initial_stop_loss - and trade.initial_stop_loss_pct != desired_stoploss): + and trade.initial_stop_loss_pct != desired_stoploss): # Stoploss value got changed logger.info(f"Stoploss for {trade} needs adjustment...") @@ -705,7 +705,7 @@ class Trade(_DECL_BASE, LocalTrade): sell_reason = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True) - buy_signal_name = Column(String(100), nullable=True) + buy_tag = Column(String(100), nullable=True) timeframe = Column(Integer, nullable=True) def __init__(self, **kwargs): diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 8ee0cea59..a5517403d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -13,7 +13,7 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import SellType, SignalNameType, SignalType +from freqtrade.enums import SellType, SignalTagType, SignalType from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date @@ -422,7 +422,7 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug("Skipping TA Analysis for already analyzed candle") dataframe['buy'] = 0 dataframe['sell'] = 0 - dataframe['buy_signal_name'] = '' + dataframe['buy_tag'] = '' # Other Defs in strategy that want to be called every loop here # twitter_sell = self.watch_twitter_feed(dataframe, metadata) @@ -525,9 +525,9 @@ class IStrategy(ABC, HyperStrategyMixin): ) return False, False, '' - (buy, sell, buy_signal_name) = latest[SignalType.BUY.value] == 1,\ + (buy, sell, buy_tag) = latest[SignalType.BUY.value] == 1,\ latest[SignalType.SELL.value] == 1,\ - latest.get(SignalNameType.BUY_SIGNAL_NAME.value, '') + latest.get(SignalTagType.BUY_TAG.value, '') logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) timeframe_seconds = timeframe_to_seconds(timeframe) @@ -535,8 +535,8 @@ class IStrategy(ABC, HyperStrategyMixin): current_time=datetime.now(timezone.utc), timeframe_seconds=timeframe_seconds, buy=buy): - return False, sell, buy_signal_name - return buy, sell, buy_signal_name + return False, sell, buy_tag + return buy, sell, buy_tag def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, timeframe_seconds: int, buy: bool): diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index f77e6f70f..240f3f871 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -18,7 +18,7 @@ class BTrade(NamedTuple): sell_reason: SellType open_tick: int close_tick: int - buy_signal_name: Optional[str] = '' + buy_tag: Optional[str] = '' class BTContainer(NamedTuple): @@ -44,7 +44,7 @@ def _get_frame_time_from_offset(offset): def _build_backtest_dataframe(data): - columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell', 'buy_signal_name'] + columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell', 'buy_tag'] frame = DataFrame.from_records(data, columns=columns) frame['date'] = frame['date'].apply(_get_frame_time_from_offset) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index d1bf28d58..f7077ef56 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -532,7 +532,7 @@ tc33 = BTContainer(data=[ sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1, - buy_signal_name='buy_signal_01' + buy_tag='buy_signal_01' )] ) @@ -619,6 +619,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: for c, trade in enumerate(data.trades): res = results.iloc[c] assert res.sell_reason == trade.sell_reason.value - assert res.buy_signal_name == trade.buy_signal_name + assert res.buy_tag == trade.buy_tag assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 64795d064..873b27b4d 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -584,7 +584,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'min_rate': [0.1038, 0.10302485], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], - 'buy_signal_name': ['', ''], + 'buy_tag': ['', ''], }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] @@ -859,7 +859,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'locks': [], 'rejected_signals': 20, 'final_balance': 1000, - }) + }) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index df8679470..889aa05cf 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -69,7 +69,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, - 'buy_signal_name': ANY, + 'buy_tag': ANY, 'timeframe': 5, 'open_order_id': ANY, 'close_date': None, @@ -136,7 +136,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, - 'buy_signal_name': ANY, + 'buy_tag': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 3678cee4a..c812f3007 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -49,7 +49,7 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, '') mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 - mocked_history.loc[1, 'buy_signal_name'] = 'buy_signal_01' + mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01' assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01') diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 8e486d145..af4979919 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -861,7 +861,7 @@ def test_to_json(default_conf, fee): open_date=arrow.utcnow().shift(hours=-2).datetime, open_rate=0.123, exchange='binance', - buy_signal_name='', + buy_tag='', open_order_id='dry_run_buy_12345' ) result = trade.to_json() @@ -911,7 +911,7 @@ def test_to_json(default_conf, fee): 'min_rate': None, 'max_rate': None, 'strategy': None, - 'buy_signal_name': '', + 'buy_tag': '', 'timeframe': None, 'exchange': 'binance', } @@ -928,7 +928,7 @@ def test_to_json(default_conf, fee): close_date=arrow.utcnow().shift(hours=-1).datetime, open_rate=0.123, close_rate=0.125, - buy_signal_name='buys_signal_001', + buy_tag='buys_signal_001', exchange='binance', ) result = trade.to_json() @@ -978,7 +978,7 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, - 'buy_signal_name': 'buys_signal_001', + 'buy_tag': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', } @@ -1323,7 +1323,7 @@ def test_Trade_object_idem(): 'get_open_trades_without_assigned_fees', 'get_open_order_trades', 'get_trades', - ) + ) # Parent (LocalTrade) should have the same attributes for item in trade: From f5a660f845163369659855883a3982bc06fc1816 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Wed, 21 Jul 2021 20:19:56 +0700 Subject: [PATCH 021/200] caps BUY_TAG_IDX --- freqtrade/optimize/backtesting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5099c07ef..fdf341e21 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -43,7 +43,7 @@ CLOSE_IDX = 3 SELL_IDX = 4 LOW_IDX = 5 HIGH_IDX = 6 -buy_tag_IDX = 7 +BUY_TAG_IDX = 7 class Backtesting: @@ -370,7 +370,7 @@ class Backtesting: fee_open=self.fee, fee_close=self.fee, is_open=True, - buy_tag=row[buy_tag_IDX], + buy_tag=row[BUY_TAG_IDX], exchange='backtesting', ) return trade From 235c1afd09dccbc714828e1618f7545ca3dfd776 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Thu, 22 Jul 2021 01:53:15 +0700 Subject: [PATCH 022/200] add buy_tag on telegram --- freqtrade/freqtradebot.py | 3 ++ freqtrade/rpc/telegram.py | 27 +++++++++++----- freqtrade/strategy/interface.py | 6 ++-- tests/conftest.py | 2 +- tests/rpc/test_rpc.py | 34 ++++++++++---------- tests/rpc/test_rpc_apiserver.py | 24 +++++++-------- tests/rpc/test_rpc_telegram.py | 53 ++++++++++++++++++-------------- tests/strategy/test_interface.py | 14 ++++----- tests/test_freqtradebot.py | 42 ++++++++++++------------- tests/test_persistence.py | 4 +-- 10 files changed, 115 insertions(+), 94 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index cb9157d33..7756aa1d7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -595,6 +595,7 @@ class FreqtradeBot(LoggingMixin): msg = { 'trade_id': trade.id, 'type': RPCMessageType.BUY, + 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, 'limit': trade.open_rate, @@ -619,6 +620,7 @@ class FreqtradeBot(LoggingMixin): msg = { 'trade_id': trade.id, 'type': RPCMessageType.BUY_CANCEL, + 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, 'limit': trade.open_rate, @@ -639,6 +641,7 @@ class FreqtradeBot(LoggingMixin): msg = { 'trade_id': trade.id, 'type': RPCMessageType.BUY_FILL, + 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, 'open_rate': trade.open_rate, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 263a3fc6d..a1f6a7e33 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -208,15 +208,25 @@ class Telegram(RPCHandler): else: msg['stake_amount_fiat'] = 0 - message = (f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}" - f" (#{msg['trade_id']})\n" - f"*Amount:* `{msg['amount']:.8f}`\n" - f"*Open Rate:* `{msg['limit']:.8f}`\n" - f"*Current Rate:* `{msg['current_rate']:.8f}`\n" - f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}") - + content = [] + content.append( + f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}" + f" (#{msg['trade_id']})\n" + ) + if msg.get('buy_tag', None): + content.append(f"*Buy Tag:* `{msg['buy_tag']}`\n") + content.append(f"*Amount:* `{msg['amount']:.8f}`\n") + content.append(f"*Open Rate:* `{msg['limit']:.8f}`\n") + content.append(f"*Current Rate:* `{msg['current_rate']:.8f}`\n") + content.append( + f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}" + ) if msg.get('fiat_currency', None): - message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}" + content.append( + f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}" + ) + + message = ''.join(content) message += ")`" return message @@ -354,6 +364,7 @@ class Telegram(RPCHandler): "*Trade ID:* `{trade_id}` `(since {open_date_hum})`", "*Current Pair:* {pair}", "*Amount:* `{amount} ({stake_amount} {base_currency})`", + "*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "", "*Open Rate:* `{open_rate:.8f}`", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Current Rate:* `{current_rate:.8f}`", diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a5517403d..0206e8839 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -508,7 +508,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ if not isinstance(dataframe, DataFrame) or dataframe.empty: logger.warning(f'Empty candle (OHLCV) data for pair {pair}') - return False, False, '' + return False, False, None latest_date = dataframe['date'].max() latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1] @@ -523,11 +523,11 @@ class IStrategy(ABC, HyperStrategyMixin): 'Outdated history for pair %s. Last tick is %s minutes old', pair, int((arrow.utcnow() - latest_date).total_seconds() // 60) ) - return False, False, '' + return False, False, None (buy, sell, buy_tag) = latest[SignalType.BUY.value] == 1,\ latest[SignalType.SELL.value] == 1,\ - latest.get(SignalTagType.BUY_TAG.value, '') + latest.get(SignalTagType.BUY_TAG.value, None) logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) timeframe_seconds = timeframe_to_seconds(timeframe) diff --git a/tests/conftest.py b/tests/conftest.py index 139d989a9..5f3a63c39 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -182,7 +182,7 @@ def get_patched_worker(mocker, config) -> Worker: return Worker(args=None, config=config) -def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, '')) -> None: +def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None)) -> None: """ :param mocker: mocker to patch IStrategy class :param value: which value IStrategy.get_signal() must return diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 889aa05cf..1e8d38841 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -35,7 +35,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -192,7 +192,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: ) del default_conf['fiat_display_currency'] freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -239,7 +239,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -371,7 +371,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -459,7 +459,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -526,7 +526,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() with pytest.raises(RPCException, match="Error getting current tickers."): @@ -567,7 +567,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): ) default_conf['dry_run'] = False freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() @@ -612,7 +612,7 @@ def test_rpc_start(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED @@ -633,7 +633,7 @@ def test_rpc_stop(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -655,7 +655,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -687,7 +687,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED @@ -805,7 +805,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) # Create some test data @@ -838,7 +838,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) counts = rpc._rpc_count() @@ -863,7 +863,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) pair = 'ETH/BTC' trade = rpc._rpc_forcebuy(pair, None) @@ -889,7 +889,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> # Test not buying freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot.config['stake_amount'] = 0 - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) pair = 'TKN/BTC' trade = rpc._rpc_forcebuy(pair, None) @@ -902,7 +902,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) pair = 'ETH/BTC' with pytest.raises(RPCException, match=r'trader is not running'): @@ -913,7 +913,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) rpc = RPC(freqtradebot) pair = 'ETH/BTC' with pytest.raises(RPCException, match=r'Forcebuy not enabled.'): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 74d40b611..f7d935b64 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -442,7 +442,7 @@ def test_api_balance(botclient, mocker, rpc_balance): def test_api_count(botclient, mocker, ticker, fee, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -504,7 +504,7 @@ def test_api_locks(botclient): def test_api_show_config(botclient, mocker): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) rc = client_get(client, f"{BASE_URI}/show_config") assert_response(rc) @@ -522,7 +522,7 @@ def test_api_show_config(botclient, mocker): def test_api_daily(botclient, mocker, ticker, fee, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -540,7 +540,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): def test_api_trades(botclient, mocker, fee, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets) @@ -568,7 +568,7 @@ def test_api_trades(botclient, mocker, fee, markets): def test_api_trade_single(botclient, mocker, fee, ticker, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -588,7 +588,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): def test_api_delete_trade(botclient, mocker, fee, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( @@ -662,7 +662,7 @@ def test_api_logs(botclient): def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -678,7 +678,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_profit(botclient, mocker, ticker, fee, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -729,7 +729,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_stats(botclient, mocker, ticker, fee, markets,): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -757,7 +757,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,): def test_api_performance(botclient, fee): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) trade = Trade( pair='LTC/ETH', @@ -803,7 +803,7 @@ def test_api_performance(botclient, fee): def test_api_status(botclient, mocker, ticker, fee, markets): freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -1044,7 +1044,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): markets=PropertyMock(return_value=markets), _is_dry_limit_order_filled=MagicMock(return_value=False), ) - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index df624e136..ab66d18e8 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -119,7 +119,7 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None: rpc = RPC(freqtrade) dummy = DummyCls(rpc, default_conf) - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is True assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) @@ -139,7 +139,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: rpc = RPC(freqtrade) dummy = DummyCls(rpc, default_conf) - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is False assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) @@ -155,7 +155,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None freqtrade = FreqtradeBot(default_conf) rpc = RPC(freqtrade) dummy = DummyCls(rpc, default_conf) - patch_get_signal(freqtrade, (True, False, '')) + patch_get_signal(freqtrade, (True, False, None)) dummy.dummy_exception(update=update, context=MagicMock()) assert dummy.state['called'] is False @@ -185,6 +185,7 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'current_rate': 1.098e-05, 'amount': 90.99181074, 'stake_amount': 90.99181074, + 'buy_tag': None, 'close_profit_pct': None, 'profit': -0.0059, 'profit_pct': -0.59, @@ -228,7 +229,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) freqtradebot.state = State.STOPPED # Status is also enabled when stopped @@ -285,7 +286,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) freqtradebot.state = State.STOPPED # Status table is also enabled when stopped @@ -329,7 +330,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # Create some test data freqtradebot.enter_positions() @@ -400,7 +401,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # Try invalid data msg_mock.reset_mock() @@ -432,7 +433,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -487,7 +488,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -513,7 +514,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick side_effect=lambda a, b: f"{a}/{b}") telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] @@ -536,7 +537,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) freqtradebot.config['dry_run'] = False telegram._balance(update=update, context=MagicMock()) @@ -549,7 +550,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] @@ -578,7 +579,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None }) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) telegram._balance(update=update, context=MagicMock()) assert msg_mock.call_count > 1 @@ -677,7 +678,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # Create some test data freqtradebot.enter_positions() @@ -736,7 +737,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # Create some test data freqtradebot.enter_positions() @@ -797,7 +798,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # Create some test data freqtradebot.enter_positions() @@ -838,7 +839,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # Trader is not running freqtradebot.state = State.STOPPED @@ -876,7 +877,7 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # /forcebuy ETH/BTC context = MagicMock() @@ -905,7 +906,7 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) update.message.text = '/forcebuy ETH/Nonepair' telegram._forcebuy(update=update, context=MagicMock()) @@ -922,7 +923,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) context = MagicMock() context.args = [] @@ -950,7 +951,7 @@ def test_performance_handle(default_conf, update, ticker, fee, get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) # Create some test data freqtradebot.enter_positions() @@ -978,7 +979,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) freqtradebot.state = State.STOPPED telegram._count(update=update, context=MagicMock()) @@ -1007,7 +1008,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, '')) + patch_get_signal(freqtradebot, (True, False, None)) telegram._locks(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'No active locks.' in msg_mock.call_args_list[0][0][0] @@ -1253,6 +1254,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: msg = { 'type': RPCMessageType.BUY, 'trade_id': 1, + 'buy_tag': 'buy_signal_01', 'exchange': 'Binance', 'pair': 'ETH/BTC', 'limit': 1.099e-05, @@ -1270,6 +1272,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: telegram.send_msg(msg) assert msg_mock.call_args[0][0] \ == '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \ + '*Buy Tag:* `buy_signal_01`\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \ @@ -1297,6 +1300,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: telegram.send_msg({ 'type': RPCMessageType.BUY_CANCEL, + 'buy_tag': 'buy_signal_01', 'trade_id': 1, 'exchange': 'Binance', 'pair': 'ETH/BTC', @@ -1314,6 +1318,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None: telegram.send_msg({ 'type': RPCMessageType.BUY_FILL, + 'buy_tag': 'buy_signal_01', 'trade_id': 1, 'exchange': 'Binance', 'pair': 'ETH/USDT', @@ -1498,6 +1503,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: telegram.send_msg({ 'type': RPCMessageType.BUY, + 'buy_tag': 'buy_signal_01', 'trade_id': 1, 'exchange': 'Binance', 'pair': 'ETH/BTC', @@ -1512,6 +1518,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'open_date': arrow.utcnow().shift(hours=-1) }) assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' + '*Buy Tag:* `buy_signal_01`\n' '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00001099`\n' '*Current Rate:* `0.00001099`\n' diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index c812f3007..792353f7a 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -38,15 +38,15 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): mocked_history['buy'] = 0 mocked_history.loc[1, 'sell'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, '') + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, '') + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 0 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, '') + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01' @@ -68,15 +68,15 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_empty(default_conf, mocker, caplog): - assert (False, False, '') == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) + assert (False, False, None) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() - assert (False, False, '') == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) + assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) assert log_has('Empty candle (OHLCV) data for pair bar', caplog) caplog.clear() - assert (False, False, '') == _STRATEGY.get_signal( + assert (False, False, None) == _STRATEGY.get_signal( 'baz', default_conf['timeframe'], DataFrame([]) @@ -116,7 +116,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False, '') == _STRATEGY.get_signal( + assert (False, False, None) == _STRATEGY.get_signal( 'xyz', default_conf['timeframe'], mocked_history diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 6372e6d36..abbef7858 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -536,7 +536,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None: ) default_conf['stake_amount'] = 10 freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade, value=(False, False, '')) + patch_get_signal(freqtrade, value=(False, False, None)) Trade.query = MagicMock() Trade.query.filter = MagicMock() @@ -1860,7 +1860,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order_open, limi assert trade.is_open is True freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is True assert trade.open_order_id == limit_sell_order['id'] @@ -1885,7 +1885,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, ) freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade, value=(True, True, '')) + patch_get_signal(freqtrade, value=(True, True, None)) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() @@ -1896,7 +1896,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert nb_trades == 0 # Buy is triggering, so buying ... - patch_get_signal(freqtrade, value=(True, False, '')) + patch_get_signal(freqtrade, value=(True, False, None)) freqtrade.enter_positions() trades = Trade.query.all() nb_trades = len(trades) @@ -1904,7 +1904,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert trades[0].is_open is True # Buy and Sell are not triggering, so doing nothing ... - patch_get_signal(freqtrade, value=(False, False, '')) + patch_get_signal(freqtrade, value=(False, False, None)) assert freqtrade.handle_trade(trades[0]) is False trades = Trade.query.all() nb_trades = len(trades) @@ -1912,7 +1912,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert trades[0].is_open is True # Buy and Sell are triggering, so doing nothing ... - patch_get_signal(freqtrade, value=(True, True, '')) + patch_get_signal(freqtrade, value=(True, True, None)) assert freqtrade.handle_trade(trades[0]) is False trades = Trade.query.all() nb_trades = len(trades) @@ -1920,7 +1920,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert trades[0].is_open is True # Sell is triggering, guess what : we are Selling! - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) trades = Trade.query.all() assert freqtrade.handle_trade(trades[0]) is True @@ -1938,7 +1938,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, ) freqtrade = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtrade, value=(True, False, '')) + patch_get_signal(freqtrade, value=(True, False, None)) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.enter_positions() @@ -1951,7 +1951,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, # we might just want to check if we are in a sell condition without # executing # if ROI is reached we must sell - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI", caplog) @@ -1977,10 +1977,10 @@ def test_handle_trade_use_sell_signal( trade = Trade.query.first() trade.is_open = True - patch_get_signal(freqtrade, value=(False, False, '')) + patch_get_signal(freqtrade, value=(False, False, None)) assert not freqtrade.handle_trade(trade) - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL", caplog) @@ -3016,7 +3016,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is False freqtrade.strategy.sell_profit_offset = 0.0 @@ -3051,7 +3051,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -3082,7 +3082,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o trade = Trade.query.first() trade.update(limit_buy_order) - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is False @@ -3114,7 +3114,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_ trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -3143,7 +3143,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_ trade = Trade.query.first() amnt = trade.amount trade.update(limit_buy_order) - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985)) assert freqtrade.handle_trade(trade) is True @@ -3262,11 +3262,11 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order trade = Trade.query.first() trade.update(limit_buy_order) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(True, True, '')) + patch_get_signal(freqtrade, value=(True, True, None)) assert freqtrade.handle_trade(trade) is False # Test if buy-signal is absent (should sell due to roi = true) - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.ROI.value @@ -3527,11 +3527,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b trade = Trade.query.first() trade.update(limit_buy_order) # Sell due to min_roi_reached - patch_get_signal(freqtrade, value=(True, True, '')) + patch_get_signal(freqtrade, value=(True, True, None)) assert freqtrade.handle_trade(trade) is True # Test if buy-signal is absent - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -4059,7 +4059,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_o freqtrade.wallets.update() assert trade.is_open is True - patch_get_signal(freqtrade, value=(False, True, '')) + patch_get_signal(freqtrade, value=(False, True, None)) assert freqtrade.handle_trade(trade) is True assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0] diff --git a/tests/test_persistence.py b/tests/test_persistence.py index af4979919..8b927be8b 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -861,7 +861,7 @@ def test_to_json(default_conf, fee): open_date=arrow.utcnow().shift(hours=-2).datetime, open_rate=0.123, exchange='binance', - buy_tag='', + buy_tag=None, open_order_id='dry_run_buy_12345' ) result = trade.to_json() @@ -911,7 +911,7 @@ def test_to_json(default_conf, fee): 'min_rate': None, 'max_rate': None, 'strategy': None, - 'buy_tag': '', + 'buy_tag': None, 'timeframe': None, 'exchange': 'binance', } From 46f2a20a98237aa86df2adf9804901635f66db16 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Thu, 22 Jul 2021 02:00:51 +0700 Subject: [PATCH 023/200] run flake8 --- tests/strategy/test_interface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 792353f7a..751f08344 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -68,7 +68,9 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_empty(default_conf, mocker, caplog): - assert (False, False, None) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) + assert (False, False, None) == _STRATEGY.get_signal( + 'foo', default_conf['timeframe'], DataFrame() + ) assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() From 25e329623ffad470264b52d8d54adc447149cebb Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Thu, 22 Jul 2021 02:11:54 +0700 Subject: [PATCH 024/200] change signature --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 0206e8839..49fae0ed6 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -497,7 +497,7 @@ class IStrategy(ABC, HyperStrategyMixin): else: raise StrategyError(message) - def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool, str]: + def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool, Optional[str]]: """ Calculates current signal based based on the buy / sell columns of the dataframe. Used by Bot to get the signal to buy or sell From 643b6b950e951ab97ed2ed78a6532b5c8a23cb5e Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Thu, 22 Jul 2021 02:23:34 +0700 Subject: [PATCH 025/200] run flake8 --- freqtrade/strategy/interface.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 49fae0ed6..a6329aaa1 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -497,7 +497,12 @@ class IStrategy(ABC, HyperStrategyMixin): else: raise StrategyError(message) - def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool, Optional[str]]: + def get_signal( + self, + pair: str, + timeframe: str, + dataframe: DataFrame + ) -> Tuple[bool, bool, Optional[str]]: """ Calculates current signal based based on the buy / sell columns of the dataframe. Used by Bot to get the signal to buy or sell From dd809f756bb378ab787685e50eea62797b63d321 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Thu, 22 Jul 2021 02:34:20 +0700 Subject: [PATCH 026/200] run mypy --- freqtrade/freqtradebot.py | 2 +- freqtrade/persistence/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7756aa1d7..d0ffddd72 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -466,7 +466,7 @@ class FreqtradeBot(LoggingMixin): return False def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False, buy_tag: str = '') -> bool: + forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index f42166762..43fbec8c0 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -257,7 +257,7 @@ class LocalTrade(): sell_reason: str = '' sell_order_status: str = '' strategy: str = '' - buy_tag: str = '' + buy_tag: Optional[str] = None timeframe: Optional[int] = None def __init__(self, **kwargs): From b01daa8bbca7d7937dfdf5c85675eaf9d598d646 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Thu, 22 Jul 2021 13:09:05 +0700 Subject: [PATCH 027/200] expose buy_tag to api --- freqtrade/rpc/api_server/api_schemas.py | 1 + tests/rpc/test_rpc_apiserver.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index c6b6a6d28..318762136 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -151,6 +151,7 @@ class TradeSchema(BaseModel): amount_requested: float stake_amount: float strategy: str + buy_tag: Optional[str] timeframe: int fee_open: Optional[float] fee_open_cost: Optional[float] diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index f7d935b64..fef44cab2 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -875,6 +875,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'sell_reason': None, 'sell_order_status': None, 'strategy': 'DefaultStrategy', + 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', } @@ -1029,6 +1030,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': 'DefaultStrategy', + 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', } From 65b4705b67b932903737dc4a70d67250435ae1a1 Mon Sep 17 00:00:00 2001 From: Kevin Julian Date: Fri, 23 Jul 2021 01:16:10 +0700 Subject: [PATCH 028/200] Update docs/strategy-advanced.md Co-authored-by: Matthias --- docs/strategy-advanced.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 8ae0c1d16..32113fede 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -116,7 +116,7 @@ See [Dataframe access](#dataframe-access) for more information about dataframe u ## Buy Tag -When your strategy has multiple buy signal, you can name it. +When your strategy has multiple buy signals, you can name the signal that triggered. Then you can access you buy signal on `custom_sell` ```python From dd923c34713b9d2a316261e516294c4a4b4f929c Mon Sep 17 00:00:00 2001 From: Kevin Julian Date: Fri, 23 Jul 2021 01:16:24 +0700 Subject: [PATCH 029/200] Update docs/strategy-advanced.md Co-authored-by: Matthias --- docs/strategy-advanced.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 32113fede..62b1533a6 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -126,7 +126,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['rsi'] < 35) & (dataframe['volume'] > 0) ), - ['buy', 'buy_tag']] = 1, 'buy_signal_rsi' + ['buy', 'buy_tag']] = (1, 'buy_signal_rsi') return dataframe From 5fe18be4b57163971329905afb9e8833f77ed154 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 01:25:15 +0700 Subject: [PATCH 030/200] add note buy_tag and split 3 assignment for get_signal --- docs/strategy-advanced.md | 4 ++++ freqtrade/strategy/interface.py | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 62b1533a6..fb78a8796 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -140,6 +140,10 @@ def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_r ``` +!!! Note + `buy_tag` is limited to 100 characters, remaining data will be truncated. + + ## Custom stoploss The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss. diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a6329aaa1..35c162c38 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -530,9 +530,10 @@ class IStrategy(ABC, HyperStrategyMixin): ) return False, False, None - (buy, sell, buy_tag) = latest[SignalType.BUY.value] == 1,\ - latest[SignalType.SELL.value] == 1,\ - latest.get(SignalTagType.BUY_TAG.value, None) + buy = latest[SignalType.BUY.value] == 1 + sell = latest[SignalType.SELL.value] == 1 + buy_tag = latest.get(SignalTagType.BUY_TAG.value, None) + logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) timeframe_seconds = timeframe_to_seconds(timeframe) From 65fc094c9f4ac42e5092aefab5aa5160b076207e Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 09:31:51 +0700 Subject: [PATCH 031/200] add to webhook-config --- docs/webhook-config.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index 8ce6edc18..288afc384 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -83,6 +83,7 @@ Possible parameters are: * `fiat_currency` * `order_type` * `current_rate` +* `buy_tag` ### Webhookbuycancel @@ -100,6 +101,7 @@ Possible parameters are: * `fiat_currency` * `order_type` * `current_rate` +* `buy_tag` ### Webhookbuyfill @@ -115,6 +117,7 @@ Possible parameters are: * `stake_amount` * `stake_currency` * `fiat_currency` +* `buy_tag` ### Webhooksell From aea5da0c738bc281bc9513f2c18ac1812ffdddda Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 11:42:43 +0700 Subject: [PATCH 032/200] changes testcase --- freqtrade/optimize/backtesting.py | 13 +- freqtrade/strategy/interface.py | 2 +- tests/optimize/__init__.py | 6 +- tests/optimize/test_backtest_detail.py | 442 ++++++++++++------------- tests/optimize/test_backtesting.py | 2 +- 5 files changed, 236 insertions(+), 229 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index fdf341e21..0c586883b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -210,7 +210,7 @@ class Backtesting: """ # Every change to this headers list must evaluate further usages of the resulting tuple # and eventually change the constants for indexes at the top - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag'] + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] data: Dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) @@ -218,10 +218,13 @@ class Backtesting: for pair, pair_data in processed.items(): self.check_abort() self.progress.increment() + has_buy_tag = 'buy_tag' in pair_data + headers = headers + ['buy_tag'] if has_buy_tag else headers if not pair_data.empty: pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist - pair_data.loc[:, 'buy_tag'] = '' # cleanup if buy_tag is exist + if has_buy_tag: + pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() @@ -230,7 +233,8 @@ class Backtesting: # from the previous candle df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) - df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) + if has_buy_tag: + df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) df_analyzed.drop(df_analyzed.head(1).index, inplace=True) @@ -361,6 +365,7 @@ class Backtesting: if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): # Enter trade + has_buy_tag = len(row) >= BUY_TAG_IDX + 1 trade = LocalTrade( pair=pair, open_rate=row[OPEN_IDX], @@ -370,7 +375,7 @@ class Backtesting: fee_open=self.fee, fee_close=self.fee, is_open=True, - buy_tag=row[BUY_TAG_IDX], + buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None, exchange='backtesting', ) return trade diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 35c162c38..f10a12fa9 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -422,7 +422,7 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug("Skipping TA Analysis for already analyzed candle") dataframe['buy'] = 0 dataframe['sell'] = 0 - dataframe['buy_tag'] = '' + dataframe['buy_tag'] = None # Other Defs in strategy that want to be called every loop here # twitter_sell = self.watch_twitter_feed(dataframe, metadata) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 240f3f871..80fce9ca5 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -18,7 +18,7 @@ class BTrade(NamedTuple): sell_reason: SellType open_tick: int close_tick: int - buy_tag: Optional[str] = '' + buy_tag: Optional[str] = None class BTContainer(NamedTuple): @@ -44,7 +44,9 @@ def _get_frame_time_from_offset(offset): def _build_backtest_dataframe(data): - columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell', 'buy_tag'] + + columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell'] + columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns frame = DataFrame.from_records(data, columns=columns) frame['date'] = frame['date'].apply(_get_frame_time_from_offset) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index f7077ef56..0d4647308 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -14,13 +14,13 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, # Test 0: Sell with signal sell in candle 3 # Test with Stop-loss at 1% tc0 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], # exit with stoploss hit - [3, 5010, 5000, 4980, 5010, 6172, 0, 1, ''], - [4, 5010, 4987, 4977, 4995, 6172, 0, 0, ''], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], # exit with stoploss hit + [3, 5010, 5000, 4980, 5010, 6172, 0, 1], + [4, 5010, 4987, 4977, 4995, 6172, 0, 0], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) @@ -28,13 +28,13 @@ tc0 = BTContainer(data=[ # Test 1: Stop-Loss Triggered 1% loss # Test with Stop-loss at 1% tc1 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4600, 4600, 6172, 0, 0, ''], # exit with stoploss hit - [3, 4975, 5000, 4980, 4977, 6172, 0, 0, ''], - [4, 4977, 4987, 4977, 4995, 6172, 0, 0, ''], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit + [3, 4975, 5000, 4980, 4977, 6172, 0, 0], + [4, 4977, 4987, 4977, 4995, 6172, 0, 0], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) @@ -43,13 +43,13 @@ tc1 = BTContainer(data=[ # Test 2: Minus 4% Low, minus 1% close # Test with Stop-Loss at 3% tc2 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4962, 4975, 6172, 0, 0, ''], - [3, 4975, 5000, 4800, 4962, 6172, 0, 0, ''], # exit with stoploss hit - [4, 4962, 4987, 4937, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4962, 4975, 6172, 0, 0], + [3, 4975, 5000, 4800, 4962, 6172, 0, 0], # exit with stoploss hit + [4, 4962, 4987, 4937, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -62,14 +62,14 @@ tc2 = BTContainer(data=[ # Trade-A: Stop-Loss Triggered 2% Loss # Trade-B: Stop-Loss Triggered 2% Loss tc3 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4800, 4975, 6172, 0, 0, ''], # exit with stoploss hit - [3, 4975, 5000, 4950, 4962, 6172, 1, 0, ''], - [4, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], # enter trade 2 (signal on last candle) - [5, 4962, 4987, 4000, 4000, 6172, 0, 0, ''], # exit with stoploss hit - [6, 4950, 4975, 4975, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4800, 4975, 6172, 0, 0], # exit with stoploss hit + [3, 4975, 5000, 4950, 4962, 6172, 1, 0], + [4, 4975, 5000, 4950, 4962, 6172, 0, 0], # enter trade 2 (signal on last candle) + [5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit + [6, 4950, 4975, 4975, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2), BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)] @@ -80,13 +80,13 @@ tc3 = BTContainer(data=[ # Test with Stop-loss at 2% ROI 6% # Stop-Loss Triggered 2% Loss tc4 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5750, 4850, 5750, 6172, 0, 0, ''], # Exit with stoploss hit - [3, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], - [4, 4962, 4987, 4937, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5750, 4850, 5750, 6172, 0, 0], # Exit with stoploss hit + [3, 4975, 5000, 4950, 4962, 6172, 0, 0], + [4, 4962, 4987, 4937, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) @@ -94,13 +94,13 @@ tc4 = BTContainer(data=[ # Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain # stop-loss: 1%, ROI: 3% tc5 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4980, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4980, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5025, 4975, 4987, 6172, 0, 0, ''], - [3, 4975, 6000, 4975, 6000, 6172, 0, 0, ''], # ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4980, 4987, 6172, 1, 0], + [1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5025, 4975, 4987, 6172, 0, 0], + [3, 4975, 6000, 4975, 6000, 6172, 0, 0], # ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -108,13 +108,13 @@ tc5 = BTContainer(data=[ # Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss # stop-loss: 2% ROI: 5% tc6 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5300, 4850, 5050, 6172, 0, 0, ''], # Exit with stoploss - [3, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5300, 4850, 5050, 6172, 0, 0], # Exit with stoploss + [3, 4975, 5000, 4950, 4962, 6172, 0, 0], + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) @@ -122,13 +122,13 @@ tc6 = BTContainer(data=[ # Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain # stop-loss: 2% ROI: 3% tc7 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], - [2, 4987, 5300, 4950, 5050, 6172, 0, 0, ''], - [3, 4975, 5000, 4950, 4962, 6172, 0, 0, ''], - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5050, 6172, 0, 0], + [3, 4975, 5000, 4950, 4962, 6172, 0, 0], + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] ) @@ -137,12 +137,12 @@ tc7 = BTContainer(data=[ # Test 8: trailing_stop should raise so candle 3 causes a stoploss. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 2 tc8 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5000, 6172, 0, 0, ''], - [2, 5000, 5250, 4750, 4850, 6172, 0, 0, ''], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0], + [2, 5000, 5250, 4750, 4850, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -151,12 +151,12 @@ tc8 = BTContainer(data=[ # Test 9: trailing_stop should raise - high and low in same candle. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 3 tc9 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5000, 6172, 0, 0, ''], - [2, 5000, 5050, 4950, 5000, 6172, 0, 0, ''], - [3, 5000, 5200, 4550, 4850, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0], + [2, 5000, 5050, 4950, 5000, 6172, 0, 0], + [3, 5000, 5200, 4550, 4850, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -165,12 +165,12 @@ tc9 = BTContainer(data=[ # without applying trailing_stop_positive since stoploss_offset is at 10%. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc10 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10, trailing_stop_positive=0.03, @@ -181,12 +181,12 @@ tc10 = BTContainer(data=[ # applying a positive trailing stop of 3% since stop_positive_offset is reached. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc11 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], - [3, 5000, 5150, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0], + [3, 5000, 5150, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -197,12 +197,12 @@ tc11 = BTContainer(data=[ # applying a positive trailing stop of 3% since stop_positive_offset is reached. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc12 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 4650, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 4650, 5100, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -212,12 +212,12 @@ tc12 = BTContainer(data=[ # Test 13: Buy and sell ROI on same candle # stop-loss: 10% (should not apply), ROI: 1% tc13 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5100, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 4850, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4850, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 4850, 5100, 6172, 0, 0], + [3, 4850, 5050, 4850, 4750, 6172, 0, 0], + [4, 4750, 4950, 4850, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] ) @@ -225,12 +225,12 @@ tc13 = BTContainer(data=[ # Test 14 - Buy and Stoploss on same candle # stop-loss: 5%, ROI: 10% (should not apply) tc14 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5100, 4600, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 4850, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5100, 4600, 5100, 6172, 0, 0], + [2, 5100, 5251, 4850, 5100, 6172, 0, 0], + [3, 4850, 5050, 4850, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] ) @@ -239,12 +239,12 @@ tc14 = BTContainer(data=[ # Test 15 - Buy and ROI on same candle, followed by buy and Stoploss on next candle # stop-loss: 5%, ROI: 10% (should not apply) tc15 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5100, 4900, 5100, 6172, 1, 0, ''], - [2, 5100, 5251, 4650, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5100, 4900, 5100, 6172, 1, 0], + [2, 5100, 5251, 4650, 5100, 6172, 0, 0], + [3, 4850, 5050, 4850, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1), BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)] @@ -254,13 +254,13 @@ tc15 = BTContainer(data=[ # Causes negative profit even though sell-reason is ROI. # stop-loss: 10%, ROI: 10% (should not apply), -100% after 65 minutes (limits trade duration) tc16 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], - [2, 4987, 5300, 4950, 5050, 6172, 0, 0, ''], - [3, 4975, 5000, 4940, 4962, 6172, 0, 0, ''], # ForceSell on ROI (roi=-1) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5050, 6172, 0, 0], + [3, 4975, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -270,13 +270,13 @@ tc16 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe. tc17 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], - [2, 4987, 5300, 4950, 5050, 6172, 0, 0, ''], - [3, 4980, 5000, 4940, 4962, 6172, 0, 0, ''], # ForceSell on ROI (roi=-1) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5050, 6172, 0, 0], + [3, 4980, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -286,13 +286,13 @@ tc17 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses open_rate as sell-price tc18 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], - [2, 4987, 5300, 4950, 5200, 6172, 0, 0, ''], - [3, 5200, 5220, 4940, 4962, 6172, 0, 0, ''], # Sell on ROI (sells on open) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4950, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0], + [3, 5200, 5220, 4940, 4962, 6172, 0, 0], # Sell on ROI (sells on open) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -301,13 +301,13 @@ tc18 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses calculated ROI (1%) as sell rate, otherwise identical to tc18 tc19 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], - [2, 4987, 5300, 4950, 5200, 6172, 0, 0, ''], - [3, 5000, 5300, 4940, 4962, 6172, 0, 0, ''], # Sell on ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4550, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0], + [3, 5000, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -316,13 +316,13 @@ tc19 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses calculated ROI (1%) as sell rate, otherwise identical to tc18 tc20 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], - [2, 4987, 5300, 4950, 5200, 6172, 0, 0, ''], - [3, 5200, 5300, 4940, 4962, 6172, 0, 0, ''], # Sell on ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0, ''], - [5, 4550, 4975, 4925, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0], + [3, 5200, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -332,12 +332,12 @@ tc20 = BTContainer(data=[ # which cannot happen in reality # stop-loss: 10%, ROI: 4%, Trailing stop adjusted at the sell candle tc21 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 4650, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 4650, 5100, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -348,12 +348,12 @@ tc21 = BTContainer(data=[ # applying a positive trailing stop of 3% - ROI should apply before trailing stop. # stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2 tc22 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -367,12 +367,12 @@ tc22 = BTContainer(data=[ # Stoploss would trigger in this candle too, but it's no longer relevant. # stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2, ROI adjusted in candle 3 (causing the sell) tc23 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], - [3, 4850, 5251, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0], + [3, 4850, 5251, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -383,13 +383,13 @@ tc23 = BTContainer(data=[ # Stoploss at 1%. # Stoploss wins over Sell-signal (because sell-signal is acted on in the next candle) tc24 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], - [3, 5010, 5000, 4855, 5010, 6172, 0, 1, ''], # Triggers stoploss + sellsignal - [4, 5010, 4987, 4977, 4995, 6172, 0, 0, ''], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5000, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal + [4, 5010, 4987, 4977, 4995, 6172, 0, 0], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -398,13 +398,13 @@ tc24 = BTContainer(data=[ # Stoploss at 1%. # Sell-signal wins over stoploss tc25 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], - [3, 5010, 5000, 4986, 5010, 6172, 0, 1, ''], - [4, 5010, 4987, 4855, 4995, 6172, 0, 0, ''], # Triggers stoploss + sellsignal acted on - [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5000, 4986, 5010, 6172, 0, 1], + [4, 5010, 4987, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) @@ -413,13 +413,13 @@ tc25 = BTContainer(data=[ # Stoploss at 10% (irrelevant), ROI at 5% (will trigger) # Sell-signal wins over stoploss tc26 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], - [3, 5010, 5251, 4986, 5010, 6172, 0, 1, ''], # Triggers ROI, sell-signal - [4, 5010, 4987, 4855, 4995, 6172, 0, 0, ''], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5251, 4986, 5010, 6172, 0, 1], # Triggers ROI, sell-signal + [4, 5010, 4987, 4855, 4995, 6172, 0, 0], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -429,13 +429,13 @@ tc26 = BTContainer(data=[ # TODO: figure out if sell-signal should win over ROI # Sell-signal wins over stoploss tc27 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0, ''], - [3, 5010, 5012, 4986, 5010, 6172, 0, 1, ''], # sell-signal - [4, 5010, 5251, 4855, 4995, 6172, 0, 0, ''], # Triggers ROI, sell-signal acted on - [5, 4995, 4995, 4995, 4950, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5012, 4986, 5010, 6172, 0, 1], # sell-signal + [4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)] ) @@ -445,12 +445,12 @@ tc27 = BTContainer(data=[ # therefore "open" will be used # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc28 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0, ''], - [2, 5100, 5251, 5100, 5100, 6172, 0, 0, ''], - [3, 4850, 5050, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, @@ -461,12 +461,12 @@ tc28 = BTContainer(data=[ # high of stoploss candle. # stop-loss: 10%, ROI: 10% (should not apply) tc29 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5050, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) - [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], # Triggers trailing-stoploss - [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Triggers trailing-stoploss + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True, trailing_stop_positive=0.03, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] @@ -475,12 +475,12 @@ tc29 = BTContainer(data=[ # Test 30: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 10%, ROI: 10% (should not apply) tc30 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_stop_positive=0.01, trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] @@ -489,12 +489,12 @@ tc30 = BTContainer(data=[ # Test 31: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 10%, ROI: 10% (should not apply) tc31 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, @@ -504,12 +504,12 @@ tc31 = BTContainer(data=[ # Test 32: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 1%, ROI: 10% (should not apply) tc32 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, ''], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, use_custom_stoploss=True, @@ -519,12 +519,12 @@ tc32 = BTContainer(data=[ # Test 33: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 1%, ROI: 10% (should not apply) tc33 = BTContainer(data=[ - # D O H L C V B S SN + # D O H L C V B S BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0, ''], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0, ''], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0, ''], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, '']], + [1, 5000, 5500, 5000, 4900, 6172, 0, 0, None], # enter trade (signal on last candle) and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, None], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, None], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]], stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, use_custom_stoploss=True, diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 873b27b4d..87f968d6b 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -584,7 +584,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'min_rate': [0.1038, 0.10302485], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], - 'buy_tag': ['', ''], + 'buy_tag': [None, None], }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] From 8032257fdf8362e63e5f60b37db62e41ccbc7fc8 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 18:30:25 +0700 Subject: [PATCH 033/200] revert test_pairlist --- tests/plugins/test_pairlist.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 510e9a0ae..b15126a33 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -925,20 +925,20 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo get_tickers=tickers ) - freqtrade = get_patched_freqtradebot(mocker, default_conf) - freqtrade.pairlists.refresh_pairlist() + ftbot = get_patched_freqtradebot(mocker, default_conf) + ftbot.pairlists.refresh_pairlist() - assert len(freqtrade.pairlists.whitelist) == 5 + assert len(ftbot.pairlists.whitelist) == 5 tickers.return_value['ETH/BTC']['ask'] = 0.0 del tickers.return_value['TKN/BTC'] del tickers.return_value['LTC/BTC'] mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers) - freqtrade.pairlists.refresh_pairlist() + ftbot.pairlists.refresh_pairlist() assert log_has_re(r'Removed .* invalid ticker data.*', caplog) - assert len(freqtrade.pairlists.whitelist) == 2 + assert len(ftbot.pairlists.whitelist) == 2 @pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [ From acfaa39e5446e0f7f8b491f92c74b305b818687b Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 18:34:18 +0700 Subject: [PATCH 034/200] revert back test_rpc_api_server --- tests/rpc/test_rpc_apiserver.py | 138 ++++++++++++++++---------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index fef44cab2..803ef7e5d 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -46,13 +46,13 @@ def botclient(default_conf, mocker): "password": _TEST_PASS, }}) - freqtrade = get_patched_freqtradebot(mocker, default_conf) - rpc = RPC(freqtrade) + ftbot = get_patched_freqtradebot(mocker, default_conf) + rpc = RPC(ftbot) mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', MagicMock()) try: apiserver = ApiServer(default_conf) apiserver.add_rpc_handler(rpc) - yield freqtrade, TestClient(apiserver.app) + yield ftbot, TestClient(apiserver.app) # Cleanup ... ? finally: ApiServer.shutdown() @@ -88,7 +88,7 @@ def assert_response(response, expected_code=200, needs_cors=True): def test_api_not_found(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/invalid_url") assert_response(rc, 404) @@ -96,7 +96,7 @@ def test_api_not_found(botclient): def test_api_ui_fallback(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, "/favicon.ico") assert rc.status_code == 200 @@ -140,7 +140,7 @@ def test_api_auth(): def test_api_unauthorized(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client.get(f"{BASE_URI}/ping") assert_response(rc, needs_cors=False) assert rc.json() == {'status': 'pong'} @@ -151,20 +151,20 @@ def test_api_unauthorized(botclient): assert rc.json() == {'detail': 'Unauthorized'} # Change only username - freqtrade.config['api_server']['username'] = 'Ftrader' + ftbot.config['api_server']['username'] = 'Ftrader' rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) assert rc.json() == {'detail': 'Unauthorized'} # Change only password - freqtrade.config['api_server']['username'] = _TEST_USER - freqtrade.config['api_server']['password'] = 'WrongPassword' + ftbot.config['api_server']['username'] = _TEST_USER + ftbot.config['api_server']['password'] = 'WrongPassword' rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) assert rc.json() == {'detail': 'Unauthorized'} - freqtrade.config['api_server']['username'] = 'Ftrader' - freqtrade.config['api_server']['password'] = 'WrongPassword' + ftbot.config['api_server']['username'] = 'Ftrader' + ftbot.config['api_server']['password'] = 'WrongPassword' rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) @@ -172,7 +172,7 @@ def test_api_unauthorized(botclient): def test_api_token_login(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client.post(f"{BASE_URI}/token/login", data=None, headers={'Authorization': _basic_auth_str('WRONG_USER', 'WRONG_PASS'), @@ -191,7 +191,7 @@ def test_api_token_login(botclient): def test_api_token_refresh(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_post(client, f"{BASE_URI}/token/login") assert_response(rc) rc = client.post(f"{BASE_URI}/token/refresh", @@ -204,12 +204,12 @@ def test_api_token_refresh(botclient): def test_api_stop_workflow(botclient): - freqtrade, client = botclient - assert freqtrade.state == State.RUNNING + ftbot, client = botclient + assert ftbot.state == State.RUNNING rc = client_post(client, f"{BASE_URI}/stop") assert_response(rc) assert rc.json() == {'status': 'stopping trader ...'} - assert freqtrade.state == State.STOPPED + assert ftbot.state == State.STOPPED # Stop bot again rc = client_post(client, f"{BASE_URI}/stop") @@ -220,7 +220,7 @@ def test_api_stop_workflow(botclient): rc = client_post(client, f"{BASE_URI}/start") assert_response(rc) assert rc.json() == {'status': 'starting trader ...'} - assert freqtrade.state == State.RUNNING + assert ftbot.state == State.RUNNING # Call start again rc = client_post(client, f"{BASE_URI}/start") @@ -399,32 +399,32 @@ def test_api_cleanup(default_conf, mocker, caplog): def test_api_reloadconf(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_post(client, f"{BASE_URI}/reload_config") assert_response(rc) assert rc.json() == {'status': 'Reloading config ...'} - assert freqtrade.state == State.RELOAD_CONFIG + assert ftbot.state == State.RELOAD_CONFIG def test_api_stopbuy(botclient): - freqtrade, client = botclient - assert freqtrade.config['max_open_trades'] != 0 + ftbot, client = botclient + assert ftbot.config['max_open_trades'] != 0 rc = client_post(client, f"{BASE_URI}/stopbuy") assert_response(rc) assert rc.json() == {'status': 'No more buy will occur from now. Run /reload_config to reset.'} - assert freqtrade.config['max_open_trades'] == 0 + assert ftbot.config['max_open_trades'] == 0 def test_api_balance(botclient, mocker, rpc_balance): - freqtrade, client = botclient + ftbot, client = botclient - freqtrade.config['dry_run'] = False + ftbot.config['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") - freqtrade.wallets.update() + ftbot.wallets.update() rc = client_get(client, f"{BASE_URI}/balance") assert_response(rc) @@ -441,8 +441,8 @@ def test_api_balance(botclient, mocker, rpc_balance): def test_api_count(botclient, mocker, ticker, fee, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -463,13 +463,13 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json()["current"] == 4 assert rc.json()["max"] == 1 - freqtrade.config['max_open_trades'] = float('inf') + ftbot.config['max_open_trades'] = float('inf') rc = client_get(client, f"{BASE_URI}/count") assert rc.json()["max"] == -1 def test_api_locks(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/locks") assert_response(rc) @@ -503,8 +503,8 @@ def test_api_locks(botclient): def test_api_show_config(botclient, mocker): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) rc = client_get(client, f"{BASE_URI}/show_config") assert_response(rc) @@ -521,8 +521,8 @@ def test_api_show_config(botclient, mocker): def test_api_daily(botclient, mocker, ticker, fee, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -539,8 +539,8 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): def test_api_trades(botclient, mocker, fee, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets) @@ -567,8 +567,8 @@ def test_api_trades(botclient, mocker, fee, markets): def test_api_trade_single(botclient, mocker, fee, ticker, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -587,8 +587,8 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): def test_api_delete_trade(botclient, mocker, fee, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( @@ -603,7 +603,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets): create_mock_trades(fee) Trade.query.session.flush() - freqtrade.strategy.order_types['stoploss_on_exchange'] = True + ftbot.strategy.order_types['stoploss_on_exchange'] = True trades = Trade.query.all() trades[1].stoploss_order_id = '1234' assert len(trades) > 2 @@ -629,7 +629,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets): def test_api_logs(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/logs") assert_response(rc) assert len(rc.json()) == 2 @@ -661,8 +661,8 @@ def test_api_logs(botclient): def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -677,8 +677,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_profit(botclient, mocker, ticker, fee, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -728,8 +728,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_stats(botclient, mocker, ticker, fee, markets,): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -756,8 +756,8 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,): def test_api_performance(botclient, fee): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) trade = Trade( pair='LTC/ETH', @@ -802,8 +802,8 @@ def test_api_performance(botclient, fee): def test_api_status(botclient, mocker, ticker, fee, markets): - freqtrade, client = botclient - patch_get_signal(freqtrade, (True, False, None)) + ftbot, client = botclient + patch_get_signal(ftbot, (True, False, None)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -891,7 +891,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): def test_api_version(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/version") assert_response(rc) @@ -899,7 +899,7 @@ def test_api_version(botclient): def test_api_blacklist(botclient, mocker): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/blacklist") assert_response(rc) @@ -934,7 +934,7 @@ def test_api_blacklist(botclient, mocker): def test_api_whitelist(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/whitelist") assert_response(rc) @@ -946,7 +946,7 @@ def test_api_whitelist(botclient): def test_api_forcebuy(botclient, mocker, fee): - freqtrade, client = botclient + ftbot, client = botclient rc = client_post(client, f"{BASE_URI}/forcebuy", data='{"pair": "ETH/BTC"}') @@ -954,7 +954,7 @@ def test_api_forcebuy(botclient, mocker, fee): assert rc.json() == {"error": "Error querying /api/v1/forcebuy: Forcebuy not enabled."} # enable forcebuy - freqtrade.config['forcebuy_enable'] = True + ftbot.config['forcebuy_enable'] = True fbuy_mock = MagicMock(return_value=None) mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) @@ -1037,7 +1037,7 @@ def test_api_forcebuy(botclient, mocker, fee): def test_api_forcesell(botclient, mocker, ticker, fee, markets): - freqtrade, client = botclient + ftbot, client = botclient mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -1046,14 +1046,14 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): markets=PropertyMock(return_value=markets), _is_dry_limit_order_filled=MagicMock(return_value=False), ) - patch_get_signal(freqtrade, (True, False, None)) + patch_get_signal(ftbot, (True, False, None)) rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') assert_response(rc, 502) assert rc.json() == {"error": "Error querying /api/v1/forcesell: invalid argument"} - freqtrade.enter_positions() + ftbot.enter_positions() rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') @@ -1062,7 +1062,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): def test_api_pair_candles(botclient, ohlcv_history): - freqtrade, client = botclient + ftbot, client = botclient timeframe = '5m' amount = 3 @@ -1090,7 +1090,7 @@ def test_api_pair_candles(botclient, ohlcv_history): ohlcv_history.loc[1, 'buy'] = 1 ohlcv_history['sell'] = 0 - freqtrade.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) + ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) rc = client_get(client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") @@ -1128,7 +1128,7 @@ def test_api_pair_candles(botclient, ohlcv_history): def test_api_pair_history(botclient, ohlcv_history): - freqtrade, client = botclient + ftbot, client = botclient timeframe = '5m' # No pair @@ -1181,23 +1181,23 @@ def test_api_pair_history(botclient, ohlcv_history): def test_api_plot_config(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) assert rc.json() == {} - freqtrade.strategy.plot_config = { + ftbot.strategy.plot_config = { 'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}} } rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) - assert rc.json() == freqtrade.strategy.plot_config + assert rc.json() == ftbot.strategy.plot_config assert isinstance(rc.json()['main_plot'], dict) assert isinstance(rc.json()['subplots'], dict) - freqtrade.strategy.plot_config = {'main_plot': {'sma': {}}} + ftbot.strategy.plot_config = {'main_plot': {'sma': {}}} rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) @@ -1206,7 +1206,7 @@ def test_api_plot_config(botclient): def test_api_strategies(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/strategies") @@ -1219,7 +1219,7 @@ def test_api_strategies(botclient): def test_api_strategy(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy") @@ -1234,7 +1234,7 @@ def test_api_strategy(botclient): def test_list_available_pairs(botclient): - freqtrade, client = botclient + ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/available_pairs") From ba0fa1120a6537d1d08af191e19b243556064f29 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 18:39:18 +0700 Subject: [PATCH 035/200] revert rename naming --- tests/rpc/test_rpc_telegram.py | 26 +++++++++++++------------- tests/test_freqtradebot.py | 34 +++++++++++++++++----------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ab66d18e8..5aeebb2d7 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -63,12 +63,12 @@ def get_telegram_testobject(mocker, default_conf, mock=True, freqtrade=None): _init=MagicMock(), _send_msg=msg_mock ) - if not freqtrade: - freqtrade = get_patched_freqtradebot(mocker, default_conf) - rpc = RPC(freqtrade) + if not ftbot: + ftbot = get_patched_freqtradebot(mocker, default_conf) + rpc = RPC(ftbot) telegram = Telegram(rpc, default_conf) - return telegram, freqtrade, msg_mock + return telegram, ftbot, msg_mock def test_telegram__init__(default_conf, mocker) -> None: @@ -115,11 +115,11 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None: patch_exchange(mocker) caplog.set_level(logging.DEBUG) default_conf['telegram']['enabled'] = False - freqtrade = FreqtradeBot(default_conf) - rpc = RPC(freqtrade) + bot = FreqtradeBot(default_conf) + rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) - patch_get_signal(freqtrade, (True, False, None)) + patch_get_signal(bot, (True, False, None)) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is True assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) @@ -135,11 +135,11 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: update.message = Message(randint(1, 100), datetime.utcnow(), chat) default_conf['telegram']['enabled'] = False - freqtrade = FreqtradeBot(default_conf) - rpc = RPC(freqtrade) + bot = FreqtradeBot(default_conf) + rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) - patch_get_signal(freqtrade, (True, False, None)) + patch_get_signal(bot, (True, False, None)) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is False assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) @@ -152,10 +152,10 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None default_conf['telegram']['enabled'] = False - freqtrade = FreqtradeBot(default_conf) - rpc = RPC(freqtrade) + bot = FreqtradeBot(default_conf) + rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) - patch_get_signal(freqtrade, (True, False, None)) + patch_get_signal(bot, (True, False, None)) dummy.dummy_exception(update=update, context=MagicMock()) assert dummy.state['called'] is False diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index abbef7858..b3a5bc409 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2014,16 +2014,16 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_buy_order_open def test_bot_loop_start_called_once(mocker, default_conf, caplog): - freqtrade = get_patched_freqtradebot(mocker, default_conf) + ftbot = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade') - patch_get_signal(freqtrade) - freqtrade.strategy.bot_loop_start = MagicMock(side_effect=ValueError) - freqtrade.strategy.analyze = MagicMock() + patch_get_signal(ftbot) + ftbot.strategy.bot_loop_start = MagicMock(side_effect=ValueError) + ftbot.strategy.analyze = MagicMock() - freqtrade.process() + ftbot.process() assert log_has_re(r'Strategy caused the following exception.*', caplog) - assert freqtrade.strategy.bot_loop_start.call_count == 1 - assert freqtrade.strategy.analyze.call_count == 1 + assert ftbot.strategy.bot_loop_start.call_count == 1 + assert ftbot.strategy.analyze.call_count == 1 def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_order_old, open_trade, @@ -4086,14 +4086,14 @@ def test_startup_trade_reinit(default_conf, edge_conf, mocker): reinit_mock = MagicMock() mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', reinit_mock) - freqtrade = get_patched_freqtradebot(mocker, default_conf) - freqtrade.startup() + ftbot = get_patched_freqtradebot(mocker, default_conf) + ftbot.startup() assert reinit_mock.call_count == 1 reinit_mock.reset_mock() - freqtrade = get_patched_freqtradebot(mocker, edge_conf) - freqtrade.startup() + ftbot = get_patched_freqtradebot(mocker, edge_conf) + ftbot.startup() assert reinit_mock.call_count == 0 @@ -4112,17 +4112,17 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order_ get_fee=fee, ) - freqtrade = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtrade) - assert freqtrade.wallets.get_free('BTC') == 0.002 + bot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(bot) + assert bot.wallets.get_free('BTC') == 0.002 - n = freqtrade.enter_positions() + n = bot.enter_positions() assert n == 2 trades = Trade.query.all() assert len(trades) == 2 - freqtrade.config['max_open_trades'] = 3 - n = freqtrade.enter_positions() + bot.config['max_open_trades'] = 3 + n = bot.enter_positions() assert n == 0 assert log_has_re(r"Unable to create trade for XRP/BTC: " r"Available balance \(0.0 BTC\) is lower than stake amount \(0.001 BTC\)", From b9c2489b73629babd1dcc918fb4ec8c826d686dd Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 18:41:29 +0700 Subject: [PATCH 036/200] remove SN --- tests/edge/test_edge.py | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 254134ce7..7bdc940df 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -76,23 +76,23 @@ def _time_on_candle(number): # End helper functions # Open trade should be removed from the end tc0 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 1, '']], # enter trade (signal on last candle) + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 1]], # enter trade (signal on last candle) stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00, trades=[] ) # Two complete trades within dataframe(with sell hit for all) tc1 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4975, 4987, 6172, 0, 1, ''], # enter trade (signal on last candle) - [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # exit at open - [3, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], # no action - [4, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # should enter the trade - [5, 5000, 5025, 4975, 4987, 6172, 0, 1, ''], # no action - [6, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], # should sell + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 1], # enter trade (signal on last candle) + [2, 5000, 5025, 4975, 4987, 6172, 0, 0], # exit at open + [3, 5000, 5025, 4975, 4987, 6172, 1, 0], # no action + [4, 5000, 5025, 4975, 4987, 6172, 0, 0], # should enter the trade + [5, 5000, 5025, 4975, 4987, 6172, 0, 1], # no action + [6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell ], stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2), @@ -101,10 +101,10 @@ tc1 = BTContainer(data=[ # 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss tc2 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4600, 4987, 6172, 0, 0, ''], # enter trade, stoploss hit - [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4600, 4987, 6172, 0, 0], # enter trade, stoploss hit + [2, 5000, 5025, 4975, 4987, 6172, 0, 0], ], stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] @@ -112,10 +112,10 @@ tc2 = BTContainer(data=[ # 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss tc3 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4800, 4987, 6172, 0, 0, ''], # enter trade, stoploss hit - [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4800, 4987, 6172, 0, 0], # enter trade, stoploss hit + [2, 5000, 5025, 4975, 4987, 6172, 0, 0], ], stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] @@ -123,10 +123,10 @@ tc3 = BTContainer(data=[ # 5) Stoploss and sell are hit. should sell on stoploss tc4 = BTContainer(data=[ - # D O H L C V B S SN - [0, 5000, 5025, 4975, 4987, 6172, 1, 0, ''], - [1, 5000, 5025, 4800, 4987, 6172, 0, 1, ''], # enter trade, stoploss hit, sell signal - [2, 5000, 5025, 4975, 4987, 6172, 0, 0, ''], + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4800, 4987, 6172, 0, 1], # enter trade, stoploss hit, sell signal + [2, 5000, 5025, 4975, 4987, 6172, 0, 0], ], stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] From 7a0cb95ffb00c1c14a8b6a62b3134017ed919214 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 20:43:27 +0700 Subject: [PATCH 037/200] fix testcase --- tests/rpc/test_rpc_telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 5aeebb2d7..b678c3363 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -55,7 +55,7 @@ class DummyCls(Telegram): raise Exception('test') -def get_telegram_testobject(mocker, default_conf, mock=True, freqtrade=None): +def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None): msg_mock = MagicMock() if mock: mocker.patch.multiple( From 0fcbe097c064df7943e21bcf7e7d3c0f6f84daf0 Mon Sep 17 00:00:00 2001 From: kevinjulian Date: Fri, 23 Jul 2021 21:06:38 +0700 Subject: [PATCH 038/200] remove blankspace --- tests/optimize/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 80fce9ca5..f29d8d585 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -44,7 +44,6 @@ def _get_frame_time_from_offset(offset): def _build_backtest_dataframe(data): - columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell'] columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns From 34c8a5afaff2c06fc0a09df0d4eba6ad2464bcc6 Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Sun, 25 Jul 2021 07:24:55 +0000 Subject: [PATCH 040/200] remove second filter, add max option --- docs/includes/pairlists.md | 24 +--- freqtrade/constants.py | 2 +- .../plugins/pairlist/rangestabilityfilter.py | 12 +- .../pairlist/rangestabilityfiltermax.py | 109 ------------------ tests/plugins/test_pairlist.py | 18 +-- 5 files changed, 26 insertions(+), 139 deletions(-) delete mode 100644 freqtrade/plugins/pairlist/rangestabilityfiltermax.py diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 0368e8a6f..e9d2f45e7 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -155,10 +155,10 @@ If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio #### RangeStabilityFilter -Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. +Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change` or above `max_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. In the below example: -If the trading range over the last 10 days is <1%, remove the pair from the whitelist. +If the trading range over the last 10 days is <1% or >99%, remove the pair from the whitelist. ```json "pairlists": [ @@ -166,6 +166,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit "method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01, + "max_rate_of_change": 0.99, "refresh_period": 1440 } ] @@ -173,24 +174,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit !!! Tip This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit. - -#### RangeStabilityFilterMax - -Same function as `RangeStabilityFilter` but instead of a minimum value, it uses a maximum value for rate of change, i.e. `max_rate_of_change` as seen in the example below. - -```json -"pairlists": [ - { - "method": "RangeStabilityFilterMax", - "lookback_days": 10, - "max_rate_of_change": 1.01, - "refresh_period": 1440 - } -] -``` - -!!! Tip - This Filter can be used to automatically remove pairs with extreme high/low variance over a given amount of time (`lookback_days`). + Additionally, it can also be used to automatically remove pairs with extreme high/low variance over a given amount of time. #### VolatilityFilter diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 089569842..f4c32387b 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -28,7 +28,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', - 'SpreadFilter', 'VolatilityFilter', 'RangeStabilityFilterMax'] + 'SpreadFilter', 'VolatilityFilter'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index a6d1820de..105568f17 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -26,6 +26,7 @@ class RangeStabilityFilter(IPairList): self._days = pairlistconfig.get('lookback_days', 10) self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01) + self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', 0.99) self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) @@ -51,7 +52,8 @@ class RangeStabilityFilter(IPairList): Short whitelist method description - used for startup-messages """ return (f"{self.name} - Filtering pairs with rate of change below " - f"{self._min_rate_of_change} over the last {plural(self._days, 'day')}.") + f"{self._min_rate_of_change} and above " + f"{self._max_rate_of_change} over the last {plural(self._days, 'day')}.") def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ @@ -104,6 +106,14 @@ class RangeStabilityFilter(IPairList): f"which is below the threshold of {self._min_rate_of_change}.", logger.info) result = False + if pct_change <= self._max_rate_of_change: + result = True + else: + self.log_once(f"Removed {pair} from whitelist, because rate of change " + f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, " + f"which is above the threshold of {self._max_rate_of_change}.", + logger.info) + result = False self._pair_cache[pair] = result return result diff --git a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py deleted file mode 100644 index e0cf5b9b4..000000000 --- a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Rate of change pairlist filter -""" -import logging -from copy import deepcopy -from typing import Any, Dict, List, Optional - -import arrow -from cachetools.ttl import TTLCache -from pandas import DataFrame - -from freqtrade.exceptions import OperationalException -from freqtrade.misc import plural -from freqtrade.plugins.pairlist.IPairList import IPairList - - -logger = logging.getLogger(__name__) - - -class RangeStabilityFilterMax(IPairList): - - def __init__(self, exchange, pairlistmanager, - config: Dict[str, Any], pairlistconfig: Dict[str, Any], - pairlist_pos: int) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - - self._days = pairlistconfig.get('lookback_days', 10) - self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', 0.02) - self._refresh_period = pairlistconfig.get('refresh_period', 1440) - - self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) - - if self._days < 1: - raise OperationalException("RangeStabilityFilterMax requires lookback_days to be >= 1") - if self._days > exchange.ohlcv_candle_limit('1d'): - raise OperationalException("RangeStabilityFilterMax requires lookback_days to not " - "exceed exchange max request size " - f"({exchange.ohlcv_candle_limit('1d')})") - - @property - def needstickers(self) -> bool: - """ - Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed - as tickers argument to filter_pairlist - """ - return False - - def short_desc(self) -> str: - """ - Short whitelist method description - used for startup-messages - """ - return (f"{self.name} - Filtering pairs with rate of change below " - f"{self._max_rate_of_change} over the last {plural(self._days, 'day')}.") - - def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: - """ - Validate trading range - :param pairlist: pairlist to filter or sort - :param tickers: Tickers (from exchange.get_tickers()). May be cached. - :return: new allowlist - """ - needed_pairs = [(p, '1d') for p in pairlist if p not in self._pair_cache] - - since_ms = int(arrow.utcnow() - .floor('day') - .shift(days=-self._days - 1) - .float_timestamp) * 1000 - # Get all candles - candles = {} - if needed_pairs: - candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, - cache=False) - - if self._enabled: - for p in deepcopy(pairlist): - daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None - if not self._validate_pair_loc(p, daily_candles): - pairlist.remove(p) - return pairlist - - def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool: - """ - Validate trading range - :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.load_markets() - :return: True if the pair can stay, false if it should be removed - """ - # Check symbol in cache - cached_res = self._pair_cache.get(pair, None) - if cached_res is not None: - return cached_res - - result = False - if daily_candles is not None and not daily_candles.empty: - highest_high = daily_candles['high'].max() - lowest_low = daily_candles['low'].min() - pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 - if pct_change <= self._max_rate_of_change: - result = True - else: - self.log_once(f"Removed {pair} from whitelist, because rate of change " - f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, " - f"which is above the threshold of {self._max_rate_of_change}.", - logger.info) - result = False - self._pair_cache[pair] = result - - return result diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index ae8f6e958..550587da9 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -412,7 +412,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "USDT", ['NANO/USDT']), ([{"method": "StaticPairList"}, {"method": "RangeStabilityFilter", "lookback_days": 10, - "min_rate_of_change": 0.01, "refresh_period": 1440}], + "min_rate_of_change": 0.01, "max_rate_of_change": 0.99, "refresh_period": 1440}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), ([{"method": "StaticPairList"}, {"method": "VolatilityFilter", "lookback_days": 3, @@ -718,15 +718,16 @@ def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers): get_patched_freqtradebot(mocker, default_conf) -@pytest.mark.parametrize('min_rate_of_change,expected_length', [ - (0.01, 5), - (0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist. +@pytest.mark.parametrize('min_rate_of_change,max_rate_of_change,expected_length', [ + (0.01, 0.99, 5), + (0.05, 0.0, 0), # Setting min rate_of_change to 5% removes all pairs from the whitelist. ]) def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history, - min_rate_of_change, expected_length): + min_rate_of_change, max_rate_of_change, expected_length): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'RangeStabilityFilter', 'lookback_days': 2, - 'min_rate_of_change': min_rate_of_change}] + 'min_rate_of_change': min_rate_of_change, + "max_rate_of_change": max_rate_of_change}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -828,9 +829,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo None, "PriceFilter requires max_value to be >= 0" ), # OperationalException expected - ({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01}, + ({"method": "RangeStabilityFilter", "lookback_days": 10, + "min_rate_of_change": 0.01, "max_rate_of_change": 0.99}, "[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below " - "0.01 over the last days.'}]", + "0.01 and above 0.99 over the last days.'}]", None ), ]) From 25c527ee678f5aa0b38bc59520d50829128716af Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 24 Jul 2021 19:30:34 -0600 Subject: [PATCH 041/200] combined exchange.buy and exchange.sell, Adding dummy mock to create_order in tests in test_freqtradebot --- freqtrade/exchange/exchange.py | 37 ++---- freqtrade/freqtradebot.py | 16 +-- tests/exchange/test_exchange.py | 68 ++++++----- tests/exchange/test_kraken.py | 7 +- tests/rpc/test_rpc.py | 2 +- tests/test_freqtradebot.py | 209 +++++++++++++++++++++----------- tests/test_wallets.py | 4 +- 7 files changed, 200 insertions(+), 143 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ed2ab0a61..54b89e451 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -689,7 +689,16 @@ class Exchange: # Order handling def create_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, params: Dict = {}) -> Dict: + rate: float, time_in_force: str = 'gtc') -> Dict: + + if self._config['dry_run']: + dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate) + return dry_order + + params = self._params.copy() + if time_in_force != 'gtc' and ordertype != 'market': + params.update({'timeInForce': time_in_force}) + try: # Set the precision for amount and price(rate) as accepted by the exchange amount = self.amount_to_precision(pair, amount) @@ -720,32 +729,6 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def buy(self, pair: str, ordertype: str, amount: float, - rate: float, time_in_force: str) -> Dict: - - if self._config['dry_run']: - dry_order = self.create_dry_run_order(pair, ordertype, "buy", amount, rate) - return dry_order - - params = self._params.copy() - if time_in_force != 'gtc' and ordertype != 'market': - params.update({'timeInForce': time_in_force}) - - return self.create_order(pair, ordertype, 'buy', amount, rate, params) - - def sell(self, pair: str, ordertype: str, amount: float, - rate: float, time_in_force: str = 'gtc') -> Dict: - - if self._config['dry_run']: - dry_order = self.create_dry_run_order(pair, ordertype, "sell", amount, rate) - return dry_order - - params = self._params.copy() - if time_in_force != 'gtc' and ordertype != 'market': - params.update({'timeInForce': time_in_force}) - - return self.create_order(pair, ordertype, 'sell', amount, rate, params) - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d430dbc48..25b8d154b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -510,9 +510,9 @@ class FreqtradeBot(LoggingMixin): logger.info(f"User requested abortion of buying {pair}") return False amount = self.exchange.amount_to_precision(pair, amount) - order = self.exchange.buy(pair=pair, ordertype=order_type, - amount=amount, rate=buy_limit_requested, - time_in_force=time_in_force) + order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy", + amount=amount, rate=buy_limit_requested, + time_in_force=time_in_force) order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') order_id = order['id'] order_status = order.get('status', None) @@ -1094,11 +1094,11 @@ class FreqtradeBot(LoggingMixin): try: # Execute sell and update trade record - order = self.exchange.sell(pair=trade.pair, - ordertype=order_type, - amount=amount, rate=limit, - time_in_force=time_in_force - ) + order = self.exchange.create_order(pair=trade.pair, + ordertype=order_type, side="sell", + amount=amount, rate=limit, + time_in_force=time_in_force + ) except InsufficientFundsError as e: logger.warning(f"Unable to place order {e}.") # Try to figure out what went wrong diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 02adf01c4..bacb7edbb 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1056,8 +1056,8 @@ def test_buy_dry_run(default_conf, mocker): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf) - order = exchange.buy(pair='ETH/BTC', ordertype='limit', - amount=1, rate=200, time_in_force='gtc') + order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", + amount=1, rate=200, time_in_force='gtc') assert 'id' in order assert 'dry_run_buy_' in order['id'] @@ -1080,8 +1080,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - order = exchange.buy(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", + amount=1, rate=200, time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1094,9 +1094,10 @@ def test_buy_prod(default_conf, mocker, exchange_name): api_mock.create_order.reset_mock() order_type = 'limit' - order = exchange.buy( + order = exchange.create_order( pair='ETH/BTC', ordertype=order_type, + side="buy", amount=1, rate=200, time_in_force=time_in_force) @@ -1110,32 +1111,32 @@ def test_buy_prod(default_conf, mocker, exchange_name): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.buy(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", + amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.buy(pair='ETH/BTC', ordertype='limit', - amount=1, rate=200, time_in_force=time_in_force) + exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", + amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.buy(pair='ETH/BTC', ordertype='market', - amount=1, rate=200, time_in_force=time_in_force) + exchange.create_order(pair='ETH/BTC', ordertype='market', side="buy", + amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.buy(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", + amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.buy(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", + amount=1, rate=200, time_in_force=time_in_force) @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -1157,8 +1158,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): order_type = 'limit' time_in_force = 'ioc' - order = exchange.buy(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", + amount=1, rate=200, time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1174,8 +1175,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): order_type = 'market' time_in_force = 'ioc' - order = exchange.buy(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", + amount=1, rate=200, time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1193,7 +1194,8 @@ def test_sell_dry_run(default_conf, mocker): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf) - order = exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200) + order = exchange.create_order(pair='ETH/BTC', ordertype='limit', + side="sell", amount=1, rate=200) assert 'id' in order assert 'dry_run_sell_' in order['id'] @@ -1216,7 +1218,8 @@ def test_sell_prod(default_conf, mocker, exchange_name): mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, + side="sell", amount=1, rate=200) assert 'id' in order assert 'info' in order @@ -1229,7 +1232,8 @@ def test_sell_prod(default_conf, mocker, exchange_name): api_mock.create_order.reset_mock() order_type = 'limit' - order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, + side="sell", amount=1, rate=200) assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'sell' @@ -1240,28 +1244,28 @@ def test_sell_prod(default_conf, mocker, exchange_name): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200) # Market orders don't require price, so the behaviour is slightly different with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.sell(pair='ETH/BTC', ordertype='market', amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -1283,8 +1287,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): order_type = 'limit' time_in_force = 'ioc' - order = exchange.sell(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", + amount=1, rate=200, time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1299,8 +1303,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): order_type = 'market' time_in_force = 'ioc' - order = exchange.sell(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", + amount=1, rate=200, time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -2186,7 +2190,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name): assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} - order = exchange.buy('ETH/BTC', 'limit', 5, 0.55, 'gtc') + order = exchange.create_order('ETH/BTC', 'limit', "sell", 5, 0.55, 'gtc') cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC') assert order['id'] == cancel_order['id'] diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index ed22cde92..eb79dfc10 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -31,8 +31,8 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") - order = exchange.buy(pair='ETH/BTC', ordertype=order_type, - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", + amount=1, rate=200, time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -63,7 +63,8 @@ def test_sell_kraken_trading_agreement(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") - order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, + side="sell", amount=1, rate=200) assert 'id' in order assert 'info' in order diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index fad24f9e2..05a4316f8 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -857,7 +857,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - buy=buy_mm + create_order=buy_mm ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 4912a2a4d..31e3a4ff7 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -169,7 +169,7 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee ) default_conf['dry_run_wallet'] = wallet @@ -384,7 +384,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order_open, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=buy_mock, + create_order=buy_mock, get_fee=fee, ) default_conf['stake_amount'] = 0.0005 @@ -404,7 +404,7 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=buy_mock, + create_order=buy_mock, get_fee=fee, ) @@ -425,7 +425,7 @@ def test_create_trade_zero_stake_amount(default_conf, ticker, limit_buy_order_op mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=buy_mock, + create_order=buy_mock, get_fee=fee, ) @@ -444,7 +444,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) default_conf['max_open_trades'] = 0 @@ -464,7 +464,7 @@ def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order_ope mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) @@ -487,7 +487,7 @@ def test_enter_positions_no_pairs_in_whitelist(default_conf, ticker, limit_buy_o mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value={'id': limit_buy_order['id']}), + create_order=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, ) default_conf['exchange']['pair_whitelist'] = [] @@ -507,7 +507,7 @@ def test_enter_positions_global_pairlock(default_conf, ticker, limit_buy_order, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value={'id': limit_buy_order['id']}), + create_order=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, ) freqtrade = FreqtradeBot(default_conf) @@ -556,7 +556,7 @@ def test_create_trades_multiple_trades(default_conf, ticker, fee, mocker, limit_ mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) freqtrade = FreqtradeBot(default_conf) @@ -577,7 +577,7 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) freqtrade = FreqtradeBot(default_conf) @@ -606,7 +606,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, limit_buy mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), fetch_order=MagicMock(return_value=limit_buy_order), get_fee=fee, ) @@ -641,7 +641,7 @@ def test_process_exchange_failures(default_conf, ticker, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(side_effect=TemporaryError) + create_order=MagicMock(side_effect=TemporaryError) ) sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None) @@ -658,7 +658,7 @@ def test_process_operational_exception(default_conf, ticker, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(side_effect=OperationalException) + create_order=MagicMock(side_effect=OperationalException) ) worker = Worker(args=None, config=default_conf) patch_get_signal(worker.freqtrade) @@ -676,7 +676,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order_open, fee, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), fetch_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) @@ -703,7 +703,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value={'id': limit_buy_order['id']}), + create_order=MagicMock(return_value={'id': limit_buy_order['id']}), fetch_order=MagicMock(return_value=limit_buy_order), get_fee=fee, ) @@ -753,7 +753,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(side_effect=TemporaryError), + create_order=MagicMock(side_effect=TemporaryError), refresh_latest_ohlcv=refresh_mock, ) inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")]) @@ -790,7 +790,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order 'ask': 0.00001173, 'last': 0.00001172 }), - buy=buy_mm, + create_order=buy_mm, get_min_pair_stake_amount=MagicMock(return_value=1), get_fee=fee, ) @@ -839,7 +839,8 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['cost'] = 100 limit_buy_order['id'] = '444' - mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order)) + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=limit_buy_order)) assert freqtrade.execute_buy(pair, stake_amount) trade = Trade.query.all()[2] assert trade @@ -855,7 +856,8 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['price'] = 0.5 limit_buy_order['cost'] = 40.495905365 limit_buy_order['id'] = '555' - mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order)) + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=limit_buy_order)) assert freqtrade.execute_buy(pair, stake_amount) trade = Trade.query.all()[3] assert trade @@ -889,7 +891,8 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['price'] = 0.5 limit_buy_order['cost'] = 0.0 limit_buy_order['id'] = '66' - mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order)) + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=limit_buy_order)) assert not freqtrade.execute_buy(pair, stake_amount) # Fail to get price... @@ -908,7 +911,7 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) - 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value=limit_buy_order), + create_order=MagicMock(return_value=limit_buy_order), get_rate=MagicMock(return_value=0.11), get_min_pair_stake_amount=MagicMock(return_value=1), get_fee=fee, @@ -970,8 +973,10 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - sell=MagicMock(return_value={'id': limit_sell_order['id']}), + create_order=MagicMock(side_effect=[ + {'id': limit_buy_order['id']}, + {'id': limit_sell_order['id']}, + ]), get_fee=fee, ) mocker.patch.multiple( @@ -1087,8 +1092,10 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog, 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - sell=MagicMock(return_value={'id': limit_sell_order['id']}), + create_order=MagicMock(side_effect=[ + {'id': limit_buy_order['id']}, + {'id': limit_sell_order['id']}, + ]), get_fee=fee, ) mocker.patch.multiple( @@ -1116,7 +1123,10 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee, limit_buy_order_open, limit_sell_order): rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) - sell_mock = MagicMock(return_value={'id': limit_sell_order['id']}) + create_order_mock = MagicMock(side_effect=[ + limit_buy_order_open, + {'id': limit_sell_order['id']} + ]) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=MagicMock(return_value={ @@ -1124,8 +1134,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee, 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value=limit_buy_order_open), - sell=sell_mock, + create_order=create_order_mock, get_fee=fee, ) mocker.patch.multiple( @@ -1147,10 +1156,10 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee, assert log_has("Selling the trade forcefully", caplog) # Should call a market sell - assert sell_mock.call_count == 1 - assert sell_mock.call_args[1]['ordertype'] == 'market' - assert sell_mock.call_args[1]['pair'] == trade.pair - assert sell_mock.call_args[1]['amount'] == trade.amount + assert create_order_mock.call_count == 2 + assert create_order_mock.call_args[1]['ordertype'] == 'market' + assert create_order_mock.call_args[1]['pair'] == trade.pair + assert create_order_mock.call_args[1]['amount'] == trade.amount # Rpc is sending first buy, then sell assert rpc_mock.call_count == 2 @@ -1171,8 +1180,10 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf, caplog, 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value=limit_buy_order_open), - sell=sell_mock, + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + sell_mock, + ]), get_fee=fee, fetch_order=MagicMock(return_value={'status': 'canceled'}), ) @@ -1212,8 +1223,10 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - sell=MagicMock(return_value={'id': limit_sell_order['id']}), + create_order=MagicMock(side_effect=[ + {'id': limit_buy_order['id']}, + {'id': limit_sell_order['id']}, + ]), get_fee=fee, ) mocker.patch.multiple( @@ -1318,8 +1331,10 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - sell=MagicMock(return_value={'id': limit_sell_order['id']}), + create_order=MagicMock(side_effect=[ + {'id': limit_buy_order['id']}, + {'id': limit_sell_order['id']}, + ]), get_fee=fee, ) mocker.patch.multiple( @@ -1391,8 +1406,10 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - sell=MagicMock(return_value={'id': limit_sell_order['id']}), + create_order=MagicMock(side_effect=[ + {'id': limit_buy_order['id']}, + {'id': limit_sell_order['id']}, + ]), get_fee=fee, ) mocker.patch.multiple( @@ -1502,8 +1519,10 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - sell=MagicMock(return_value={'id': limit_sell_order['id']}), + create_order=MagicMock(side_effect=[ + {'id': limit_buy_order['id']}, + {'id': limit_sell_order['id']}, + ]), get_fee=fee, stoploss=stoploss, ) @@ -1840,8 +1859,10 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order_open, limi 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value=limit_buy_order), - sell=MagicMock(return_value=limit_sell_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order, + limit_sell_order_open, + ]), get_fee=fee, ) freqtrade = FreqtradeBot(default_conf) @@ -1877,7 +1898,10 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) @@ -1930,7 +1954,10 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) @@ -1954,15 +1981,18 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, caplog) -def test_handle_trade_use_sell_signal( - default_conf, ticker, limit_buy_order_open, fee, mocker, caplog) -> None: +def test_handle_trade_use_sell_signal(default_conf, ticker, limit_buy_order_open, + limit_sell_order_open, fee, mocker, caplog) -> None: # use_sell_signal is True buy default caplog.set_level(logging.DEBUG) patch_RPCManager(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + limit_sell_order_open, + ]), get_fee=fee, ) @@ -1990,7 +2020,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_buy_order_open mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) freqtrade = FreqtradeBot(default_conf) @@ -2746,13 +2776,16 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=InvalidOrderException()) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) - sellmock = MagicMock(return_value={'id': '12345555'}) + create_order_mock = MagicMock(side_effect=[ + {'id': '12345554'}, + {'id': '12345555'}, + ]) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - sell=sellmock + create_order=create_order_mock, ) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -2767,7 +2800,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c freqtrade.execute_sell(trade=trade, limit=1234, sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) - assert sellmock.call_count == 1 + assert create_order_mock.call_count == 2 assert log_has('Could not cancel stoploss order abcd', caplog) @@ -2963,7 +2996,10 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - sell=MagicMock(side_effect=InsufficientFundsError()) + create_order=MagicMock(side_effect=[ + {'id': 1234553382}, + InsufficientFundsError(), + ]), ) patch_get_signal(freqtrade) @@ -2996,7 +3032,10 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) default_conf.update({ @@ -3033,7 +3072,10 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu 'ask': 0.00002173, 'last': 0.00002172 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) default_conf.update({ @@ -3064,7 +3106,10 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o 'ask': 0.00000173, 'last': 0.00000172 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) default_conf.update({ @@ -3094,7 +3139,10 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_ 'ask': 0.0000173, 'last': 0.0000172 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) default_conf.update({ @@ -3127,7 +3175,10 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_ 'ask': 0.00002173, 'last': 0.00002172 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) @@ -3245,7 +3296,10 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order 'ask': 0.0000173, 'last': 0.0000172 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) default_conf['ignore_roi_if_buy_signal'] = True @@ -3279,7 +3333,10 @@ def test_trailing_stop_loss(default_conf, limit_buy_order_open, limit_buy_order, 'ask': 0.00001099, 'last': 0.00001099 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) default_conf['trailing_stop'] = True @@ -3331,7 +3388,10 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, limit_buy_or 'ask': buy_price - 0.000001, 'last': buy_price - 0.000001 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) default_conf['trailing_stop'] = True @@ -3388,7 +3448,10 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, limit_buy_orde 'ask': buy_price - 0.000001, 'last': buy_price - 0.000001 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + ]), get_fee=fee, ) patch_whitelist(mocker, default_conf) @@ -3448,7 +3511,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, limit_buy_order_ 'ask': buy_price, 'last': buy_price }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) patch_whitelist(mocker, default_conf) @@ -3508,7 +3571,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b 'ask': 0.00000173, 'last': 0.00000172 }), - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + {'id': 1234553382}, + {'id': 1234553383} + ]), get_fee=fee, _is_dry_limit_order_filled=MagicMock(return_value=False), ) @@ -3905,7 +3972,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order_open, mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) @@ -3942,7 +4009,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value={'id': limit_buy_order['id']}), + create_order=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, ) # Save state of current whitelist @@ -4039,8 +4106,10 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_o 'ask': 0.00001173, 'last': 0.00001172 }), - buy=MagicMock(return_value=limit_buy_order_open), - sell=MagicMock(return_value=limit_sell_order_open), + create_order=MagicMock(side_effect=[ + limit_buy_order_open, + limit_sell_order_open, + ]), get_fee=fee, ) freqtrade = FreqtradeBot(default_conf) @@ -4105,7 +4174,7 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order_ mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 64db3b9cd..9f58cb71d 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -125,7 +125,7 @@ def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None: (1, None, 50, 66.66666), (0.99, None, 49.5, 66.0), (0.50, None, 25, 33.3333), - # Tests with capital ignore balance_ratio + # Tests with capital ignore balance_ratio (1, 100, 50, 0.0), (0.99, 200, 50, 66.66666), (0.99, 150, 50, 50), @@ -138,7 +138,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, - buy=MagicMock(return_value=limit_buy_order_open), + create_order=MagicMock(return_value=limit_buy_order_open), get_fee=fee ) From aa34889c047e13607a1b60ccca01cf63d6f57bab Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Jul 2021 07:14:35 +0200 Subject: [PATCH 042/200] Don't run migrations twice --- freqtrade/persistence/migrations.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 035452437..a2d88cb31 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -161,13 +161,6 @@ def check_migrate(engine, decl_base, previous_tables) -> None: table_back_name = get_backup_name(tabs, 'trades_bak') # Check for latest column - if not has_column(cols, 'open_trade_value'): - logger.info(f'Running database migration for trades - backup: {table_back_name}') - migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) - # Reread columns - the above recreated the table! - inspector = inspect(engine) - cols = inspector.get_columns('trades') - if not has_column(cols, 'buy_tag'): logger.info(f'Running database migration for trades - backup: {table_back_name}') migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) From 35bf2a59a80323f8ddc107c11406e687cc13a969 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Jul 2021 21:02:55 +0200 Subject: [PATCH 043/200] Improve test reliability (fix fluky test) --- tests/rpc/test_rpc_apiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 803ef7e5d..68f23e0fd 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -656,7 +656,7 @@ def test_api_logs(botclient): # Help debugging random test failure print(f"rc={rc.json()}") print(f"rc1={rc1.json()}") - assert rc1.json()['log_count'] == 5 + assert rc1.json()['log_count'] > 2 assert len(rc1.json()['logs']) == rc1.json()['log_count'] From 499af5c42bbfd07a30370e662014718002259de6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Jul 2021 21:04:04 +0200 Subject: [PATCH 044/200] Update webservermode docs closes #5345 --- docs/utils.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 789462de4..6395fb6f9 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -627,7 +627,7 @@ FreqUI will also show the backtesting results. ``` usage: freqtrade webserver [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] - [--userdir PATH] [-s NAME] [--strategy-path PATH] + [--userdir PATH] optional arguments: -h, --help show this help message and exit @@ -648,12 +648,6 @@ Common arguments: --userdir PATH, --user-data-dir PATH Path to userdata directory. -Strategy arguments: - -s NAME, --strategy NAME - Specify strategy class name which will be used by the - bot. - --strategy-path PATH Specify additional strategy lookup path. - ``` ## List Hyperopt results From ab3c75341508a7bdee9597f026681f1a1594a4bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Jul 2021 21:23:09 +0200 Subject: [PATCH 045/200] Fix develop_plot building --- build_helpers/publish_docker_arm64.sh | 10 +++++----- build_helpers/publish_docker_multi.sh | 2 +- docker/Dockerfile.plot | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/build_helpers/publish_docker_arm64.sh b/build_helpers/publish_docker_arm64.sh index 756d5e41d..08793d339 100755 --- a/build_helpers/publish_docker_arm64.sh +++ b/build_helpers/publish_docker_arm64.sh @@ -37,7 +37,7 @@ fi # Tag image for upload and next build step docker tag freqtrade:$TAG_ARM ${CACHE_IMAGE}:$TAG_ARM -docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot . +docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot . docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM @@ -63,13 +63,13 @@ echo "create manifests" docker manifest create --amend ${IMAGE_NAME}:${TAG} ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG} docker manifest push -p ${IMAGE_NAME}:${TAG} -docker manifest create --amend ${IMAGE_NAME}:${TAG_PLOT} ${CACHE_IMAGE}:${TAG_PLOT_ARM} ${CACHE_IMAGE}:${TAG_PLOT} +docker manifest create ${IMAGE_NAME}:${TAG_PLOT} ${CACHE_IMAGE}:${TAG_PLOT_ARM} ${CACHE_IMAGE}:${TAG_PLOT} docker manifest push -p ${IMAGE_NAME}:${TAG_PLOT} -Tag as latest for develop builds +# Tag as latest for develop builds if [ "${TAG}" = "develop" ]; then - docker tag ${IMAGE_NAME}:develop ${IMAGE_NAME}:latest - docker push ${IMAGE_NAME}:latest + docker manifest create ${IMAGE_NAME}:latest ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG} + docker manifest push -p ${IMAGE_NAME}:latest fi docker images diff --git a/build_helpers/publish_docker_multi.sh b/build_helpers/publish_docker_multi.sh index 4961cb9a7..4010eed45 100755 --- a/build_helpers/publish_docker_multi.sh +++ b/build_helpers/publish_docker_multi.sh @@ -48,7 +48,7 @@ fi # Tag image for upload and next build step docker tag freqtrade:$TAG ${CACHE_IMAGE}:$TAG -docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot . +docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot . docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT diff --git a/docker/Dockerfile.plot b/docker/Dockerfile.plot index d2fc3618a..e7f6bbb16 100644 --- a/docker/Dockerfile.plot +++ b/docker/Dockerfile.plot @@ -1,5 +1,6 @@ -ARG sourceimage=develop -FROM freqtradeorg/freqtrade:${sourceimage} +ARG sourceimage=freqtradeorg/freqtrade +ARG sourcetag=develop +FROM ${sourceimage}:${sourcetag} # Install dependencies COPY requirements-plot.txt /freqtrade/ From 6abd352c0f15d42a73d6cd85afb9e055a45c6649 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 31 Jul 2021 08:40:57 +0200 Subject: [PATCH 046/200] Add test for backtesting dataframe cache --- tests/optimize/test_backtesting.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 1c495f123..deaaf9f2f 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -739,6 +739,9 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): # 100 buys signals results = result['results'] assert len(results) == 100 + # Cached data should be 200 (no change since required_startup is 0) + assert len(backtesting.dataprovider.get_analyzed_dataframe('UNITTEST/BTC', '1m')[0]) == 200 + # One trade was force-closed at the end assert len(results.loc[results['is_open']]) == 0 @@ -794,6 +797,10 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) # make sure we don't have trades with more than configured max_open_trades assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0 + # Cached data correctly removed amounts + removed_candles = len(data[pair]) - 1 - backtesting.strategy.startup_candle_count + assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles + backtest_conf = { 'processed': processed, 'start_date': min_date, From b1cbc75e93740e3f62ec5ed6c1a5b0e2d0c4ff80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 31 Jul 2021 08:45:04 +0200 Subject: [PATCH 047/200] Properly cache pair dataframe in backtesting (without startup-range). --- freqtrade/optimize/backtesting.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index b9ce69edd..2974e49e2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -240,6 +240,9 @@ class Backtesting: df_analyzed.drop(df_analyzed.head(1).index, inplace=True) + # Update dataprovider cache + self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) + # Convert from Pandas to list for performance reasons # (Looping Pandas is slow.) data[pair] = df_analyzed.values.tolist() @@ -434,10 +437,6 @@ class Backtesting: trades: List[LocalTrade] = [] self.prepare_backtest(enable_protections) - # Update dataprovider cache - for pair, dataframe in processed.items(): - self.dataprovider._set_cached_df(pair, self.timeframe, dataframe) - # Use dict of lists with data for performance # (looping lists is a lot faster than pandas DataFrames) data: Dict = self._get_ohlcv_as_lists(processed) From 1ccc89d1e9f9f95eaebc50248936f9da289baddd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 31 Jul 2021 10:00:24 +0200 Subject: [PATCH 048/200] Store fully analyzed dataframe --- freqtrade/optimize/backtesting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2974e49e2..628289b7f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -227,7 +227,7 @@ class Backtesting: pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist df_analyzed = self.strategy.advise_sell( - self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() + self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() # Trim startup period from analyzed dataframe df_analyzed = trim_dataframe(df_analyzed, self.timerange, startup_candles=self.required_startup) @@ -245,7 +245,7 @@ class Backtesting: # Convert from Pandas to list for performance reasons # (Looping Pandas is slow.) - data[pair] = df_analyzed.values.tolist() + data[pair] = df_analyzed[headers].values.tolist() return data def _get_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple, From c5e3348b89d92087d3ad41cfb50486fbc8fae7ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 31 Jul 2021 16:36:28 +0200 Subject: [PATCH 049/200] Migrations for indexes should run in a seperate session closes #5349 --- docs/sql_cheatsheet.md | 2 +- freqtrade/persistence/migrations.py | 18 +++--- tests/test_persistence.py | 90 ----------------------------- 3 files changed, 8 insertions(+), 102 deletions(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 477396931..caa3f53a6 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -110,7 +110,7 @@ DELETE FROM trades WHERE id = 31; Freqtrade supports PostgreSQL by using SQLAlchemy, which supports multiple different database systems. Installation: -`pip install psycopg2` +`pip install psycopg2-binary` Usage: `... --db-url postgresql+psycopg2://:@localhost:5432/` diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index a2d88cb31..1839c4130 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -65,7 +65,8 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col # Schema migration necessary with engine.begin() as connection: connection.execute(text(f"alter table trades rename to {table_back_name}")) - # drop indexes on backup table + with engine.begin() as connection: + # drop indexes on backup table in new session for index in inspector.get_indexes(table_back_name): connection.execute(text(f"drop index {index['name']}")) # let SQLAlchemy create the schema as required @@ -76,7 +77,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col connection.execute(text(f"""insert into trades (id, exchange, pair, is_open, fee_open, fee_open_cost, fee_open_currency, - fee_close, fee_close_cost, fee_open_currency, open_rate, + fee_close, fee_close_cost, fee_close_currency, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, @@ -84,14 +85,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, timeframe, open_trade_value, close_profit_abs ) - select id, lower(exchange), - case - when instr(pair, '_') != 0 then - substr(pair, instr(pair, '_') + 1) || '/' || - substr(pair, 1, instr(pair, '_') - 1) - else pair - end - pair, + select id, lower(exchange), pair, is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost, {fee_open_currency} fee_open_currency, {fee_close} fee_close, {fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency, @@ -132,7 +126,9 @@ def migrate_orders_table(decl_base, inspector, engine, table_back_name: str, col with engine.begin() as connection: connection.execute(text(f"alter table orders rename to {table_back_name}")) - # drop indexes on backup table + + with engine.begin() as connection: + # drop indexes on backup table in new session for index in inspector.get_indexes(table_back_name): connection.execute(text(f"drop index {index['name']}")) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 8b927be8b..84e7702e0 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -442,96 +442,6 @@ def test_clean_dry_run_db(default_conf, fee): assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1 -def test_migrate_old(mocker, default_conf, fee): - """ - Test Database migration(starting with old pairformat) - """ - amount = 103.223 - create_table_old = """CREATE TABLE IF NOT EXISTS "trades" ( - id INTEGER NOT NULL, - exchange VARCHAR NOT NULL, - pair VARCHAR NOT NULL, - is_open BOOLEAN NOT NULL, - fee FLOAT NOT NULL, - open_rate FLOAT, - close_rate FLOAT, - close_profit FLOAT, - stake_amount FLOAT NOT NULL, - amount FLOAT, - open_date DATETIME NOT NULL, - close_date DATETIME, - open_order_id VARCHAR, - PRIMARY KEY (id), - CHECK (is_open IN (0, 1)) - );""" - insert_table_old = """INSERT INTO trades (exchange, pair, is_open, open_order_id, fee, - open_rate, stake_amount, amount, open_date) - VALUES ('binance', 'BTC_ETC', 1, '123123', {fee}, - 0.00258580, {stake}, {amount}, - '2017-11-28 12:44:24.000000') - """.format(fee=fee.return_value, - stake=default_conf.get("stake_amount"), - amount=amount - ) - insert_table_old2 = """INSERT INTO trades (exchange, pair, is_open, fee, - open_rate, close_rate, stake_amount, amount, open_date) - VALUES ('binance', 'BTC_ETC', 0, {fee}, - 0.00258580, 0.00268580, {stake}, {amount}, - '2017-11-28 12:44:24.000000') - """.format(fee=fee.return_value, - stake=default_conf.get("stake_amount"), - amount=amount - ) - engine = create_engine('sqlite://') - mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine) - - # Create table using the old format - with engine.begin() as connection: - connection.execute(text(create_table_old)) - connection.execute(text(insert_table_old)) - connection.execute(text(insert_table_old2)) - # Run init to test migration - init_db(default_conf['db_url'], default_conf['dry_run']) - - assert len(Trade.query.filter(Trade.id == 1).all()) == 1 - trade = Trade.query.filter(Trade.id == 1).first() - assert trade.fee_open == fee.return_value - assert trade.fee_close == fee.return_value - assert trade.open_rate_requested is None - assert trade.close_rate_requested is None - assert trade.is_open == 1 - assert trade.amount == amount - assert trade.amount_requested == amount - assert trade.stake_amount == default_conf.get("stake_amount") - assert trade.pair == "ETC/BTC" - assert trade.exchange == "binance" - assert trade.max_rate == 0.0 - assert trade.stop_loss == 0.0 - assert trade.initial_stop_loss == 0.0 - assert trade.open_trade_value == trade._calc_open_trade_value() - assert trade.close_profit_abs is None - assert trade.fee_open_cost is None - assert trade.fee_open_currency is None - assert trade.fee_close_cost is None - assert trade.fee_close_currency is None - assert trade.timeframe is None - - trade = Trade.query.filter(Trade.id == 2).first() - assert trade.close_rate is not None - assert trade.is_open == 0 - assert trade.open_rate_requested is None - assert trade.close_rate_requested is None - assert trade.close_rate is not None - assert pytest.approx(trade.close_profit_abs) == trade.calc_profit() - assert trade.sell_order_status is None - - # Should've created one order - assert len(Order.query.all()) == 1 - order = Order.query.first() - assert order.order_id == '123123' - assert order.ft_order_side == 'buy' - - def test_migrate_new(mocker, default_conf, fee, caplog): """ Test Database migration (starting with new pairformat) From 6f8519d0a3fdaf5e58a70b53730209943b7d8619 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 31 Jul 2021 17:43:10 +0200 Subject: [PATCH 050/200] Add environment variable support --- docs/configuration.md | 50 +++++++++++++------ freqtrade/configuration/configuration.py | 6 +++ freqtrade/configuration/environment_vars.py | 54 +++++++++++++++++++++ freqtrade/constants.py | 1 + tests/test_configuration.py | 35 ++++++++++++- 5 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 freqtrade/configuration/environment_vars.py diff --git a/docs/configuration.md b/docs/configuration.md index 5d4b1f2c3..fd4806fe6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -11,6 +11,37 @@ Per default, the bot loads the configuration from the `config.json` file, locate You can specify a different configuration file used by the bot with the `-c/--config` command-line option. +If you used the [Quick start](installation.md/#quick-start) method for installing +the bot, the installation script should have already created the default configuration file (`config.json`) for you. + +If the default configuration file is not created we recommend to use `freqtrade new-config --config config.json` to generate a basic configuration file. + +The Freqtrade configuration file is to be written in JSON format. + +Additionally to the standard JSON syntax, you may use one-line `// ...` and multi-line `/* ... */` comments in your configuration files and trailing commas in the lists of parameters. + +Do not worry if you are not familiar with JSON format -- simply open the configuration file with an editor of your choice, make some changes to the parameters you need, save your changes and, finally, restart the bot or, if it was previously stopped, run it again with the changes you made to the configuration. The bot validates the syntax of the configuration file at startup and will warn you if you made any errors editing it, pointing out problematic lines. + +### Environment variables + +Set options in the Freqtrade configuration via environment variables. +This takes priority over the corresponding value in configuration or strategy. + +Environment variables must be prefixed with `FREQTRADE__` to be loaded to the freqtrade configuration. + +`__` serves as level separator, so the format used should correspond to `FREQTRADE__{section}__{key}`. +As such - an environment variable defined as `export FREQTRADE__STAKE_AMOUNT=200` would result in `{stake_amount: 200}`. + +A more complex example might be `export FREQTRADE__EXCHANGE__KEY=` to keep your exchange key secret. This will move the value to the `exchange.key` section of the configuration. +Using this scheme, all configuration settings will also be available as environment variables. + +Please note that Environment variables will overwrite corresponding settings in your configuration, but command line Arguments will always win. + +!!! Note + Environment variables detected are logged at startup - so if you can't find why a value is not what you think it should be based on the configuration, make sure it's not loaded from an environment variable. + +### Multiple configuration files + Multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream. !!! Tip "Use multiple configuration files to keep secrets secret" @@ -22,17 +53,6 @@ Multiple configuration files can be specified and used by the bot or the bot can The 2nd file should only specify what you intend to override. If a key is in more than one of the configurations, then the "last specified configuration" wins (in the above example, `config-private.json`). -If you used the [Quick start](installation.md/#quick-start) method for installing -the bot, the installation script should have already created the default configuration file (`config.json`) for you. - -If the default configuration file is not created we recommend you to use `freqtrade new-config --config config.json` to generate a basic configuration file. - -The Freqtrade configuration file is to be written in JSON format. - -Additionally to the standard JSON syntax, you may use one-line `// ...` and multi-line `/* ... */` comments in your configuration files and trailing commas in the lists of parameters. - -Do not worry if you are not familiar with JSON format -- simply open the configuration file with an editor of your choice, make some changes to the parameters you need, save your changes and, finally, restart the bot or, if it was previously stopped, run it again with the changes you made to the configuration. The bot validates the syntax of the configuration file at startup and will warn you if you made any errors editing it, pointing out problematic lines. - ## Configuration parameters The table below will list all configuration parameters available. @@ -41,6 +61,7 @@ Freqtrade can also load many options via command line (CLI) arguments (check out The prevalence for all Options is as follows: - CLI arguments override any other option +- [Environment Variables](#environment-variables) - Configuration files are used in sequence (the last file wins) and override Strategy configurations. - Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table. @@ -526,9 +547,10 @@ Once you will be happy with your bot performance running in the Dry-run mode, yo ## Switch to production mode -In production mode, the bot will engage your money. Be careful, since a wrong -strategy can lose all your money. Be aware of what you are doing when -you run it in production mode. +In production mode, the bot will engage your money. Be careful, since a wrong strategy can lose all your money. +Be aware of what you are doing when you run it in production mode. + +When switching to Production mode, please make sure to use a different / fresh database to avoid dry-run trades messing with your exchange money and eventually tainting your statistics. ### Setup your exchange account diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index bd30adcae..4dd5b7203 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -11,6 +11,7 @@ from freqtrade import constants from freqtrade.configuration.check_exchange import check_exchange from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir +from freqtrade.configuration.environment_vars import enironment_vars_to_dict from freqtrade.configuration.load_config import load_config_file, load_file from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, RunMode from freqtrade.exceptions import OperationalException @@ -71,6 +72,11 @@ class Configuration: # Merge config options, overwriting old values config = deep_merge_dicts(load_config_file(path), config) + + # Load environment variables + env_data = enironment_vars_to_dict() + config = deep_merge_dicts(env_data, config) + config['config_files'] = files # Normalize config if 'internals' not in config: diff --git a/freqtrade/configuration/environment_vars.py b/freqtrade/configuration/environment_vars.py new file mode 100644 index 000000000..4c190ed04 --- /dev/null +++ b/freqtrade/configuration/environment_vars.py @@ -0,0 +1,54 @@ +import logging +import os +from typing import Any, Dict + +from freqtrade.constants import ENV_VAR_PREFIX +from freqtrade.misc import deep_merge_dicts + + +logger = logging.getLogger(__name__) + + +def get_var_typed(val): + try: + return int(val) + except ValueError: + try: + return float(val) + except ValueError: + if val.lower() in ('t', 'true'): + return True + elif val.lower() in ('f', 'false'): + return False + # keep as string + return val + + +def flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, Any]: + """ + Environment variables must be prefixed with FREQTRADE. + FREQTRADE__{section}__{key} + :param env_dict: Dictionary to validate - usually os.environ + :param prefix: Prefix to consider (usually FREQTRADE__) + :return: Nested dict based on available and relevant variables. + """ + relevant_vars: Dict[str, Any] = {} + + for env_var, val in sorted(env_dict.items()): + if env_var.startswith(prefix): + logger.info(f"Loading variable '{env_var}'") + key = env_var.replace(prefix, '') + for k in reversed(key.split('__')): + val = {k.lower(): get_var_typed(val) if type(val) != dict else val} + relevant_vars = deep_merge_dicts(val, relevant_vars) + + return relevant_vars + + +def enironment_vars_to_dict() -> Dict[str, Any]: + """ + Read environment variables and return a nested dict for relevant variables + Relevant variables must follow the FREQTRADE__{section}__{key} pattern + :return: Nested dict based on available and relevant variables. + """ + return flat_vars_to_nested_dict(os.environ.copy(), ENV_VAR_PREFIX) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 2f93ace1c..b48644c58 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -47,6 +47,7 @@ USERPATH_STRATEGIES = 'strategies' USERPATH_NOTEBOOKS = 'notebooks' TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent'] +ENV_VAR_PREFIX = 'FREQTRADE__' # Define decimals per coin for outputs diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 34db892b2..7012333e9 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -18,8 +18,9 @@ from freqtrade.configuration.deprecated_settings import (check_conflicting_setti process_deprecated_setting, process_removed_setting, process_temporary_deprecated_settings) +from freqtrade.configuration.environment_vars import flat_vars_to_nested_dict from freqtrade.configuration.load_config import load_config_file, load_file, log_config_error_range -from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL +from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre @@ -1349,3 +1350,35 @@ def test_process_deprecated_ticker_interval(mocker, default_conf, caplog): with pytest.raises(OperationalException, match=r"Both 'timeframe' and 'ticker_interval' detected."): process_temporary_deprecated_settings(config) + + +def test_flat_vars_to_nested_dict(caplog): + + test_args = { + 'FREQTRADE__EXCHANGE__SOME_SETTING': 'true', + 'FREQTRADE__EXCHANGE__SOME_FALSE_SETTING': 'false', + 'FREQTRADE__EXCHANGE__CONFIG__whatever': 'sometime', + 'FREQTRADE__ASK_STRATEGY__PRICE_SIDE': 'bid', + 'FREQTRADE__ASK_STRATEGY__cccc': '500', + 'FREQTRADE__STAKE_AMOUNT': '200.05', + 'NOT_RELEVANT': '200.0', # Will be ignored + } + expected = { + 'stake_amount': 200.05, + 'ask_strategy': { + 'price_side': 'bid', + 'cccc': 500, + }, + 'exchange': { + 'config': { + 'whatever': 'sometime', + }, + 'some_setting': True, + 'some_false_setting': False, + } + } + res = flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX) + assert res == expected + + assert log_has("Loading variable 'FREQTRADE__EXCHANGE__SOME_SETTING'", caplog) + assert not log_has("Loading variable 'NOT_RELEVANT'", caplog) From 90a61b17650dac04b4863ec417b8c661ebf888b2 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 31 Jul 2021 19:52:55 -0600 Subject: [PATCH 051/200] Changed tests in tests/test_persistence.py to use usdt prices --- tests/conftest.py | 89 +++++++- tests/test_persistence.py | 459 +++++++++++++++++++++++++------------- 2 files changed, 388 insertions(+), 160 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5f3a63c39..1924e1f95 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -812,7 +812,7 @@ def shitcoinmarkets(markets): "future": False, "active": True }, - }) + }) return shitmarkets @@ -1115,7 +1115,7 @@ def order_book_l2_usd(): [25.576, 262.016], [25.577, 178.557], [25.578, 78.614] - ], + ], 'timestamp': None, 'datetime': None, 'nonce': 2372149736 @@ -2084,3 +2084,88 @@ def saved_hyperopt_results(): ].total_seconds() return hyperopt_res + + +@pytest.fixture(scope='function') +def limit_buy_order_usdt_open(): + return { + 'id': 'mocked_limit_buy', + 'type': 'limit', + 'side': 'buy', + 'symbol': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, + 'price': 2.00, + 'amount': 30.0, + 'filled': 0.0, + 'cost': 60.0, + 'remaining': 30.0, + 'status': 'open' + } + + +@pytest.fixture(scope='function') +def limit_buy_order_usdt(limit_buy_order_usdt_open): + order = deepcopy(limit_buy_order_usdt_open) + order['status'] = 'closed' + order['filled'] = order['amount'] + order['remaining'] = 0.0 + return order + + +@pytest.fixture +def limit_sell_order_usdt_open(): + return { + 'id': 'mocked_limit_sell', + 'type': 'limit', + 'side': 'sell', + 'pair': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, + 'price': 2.20, + 'amount': 30.0, + 'filled': 0.0, + 'remaining': 30.0, + 'status': 'open' + } + + +@pytest.fixture +def limit_sell_order_usdt(limit_sell_order_usdt_open): + order = deepcopy(limit_sell_order_usdt_open) + order['remaining'] = 0.0 + order['filled'] = order['amount'] + order['status'] = 'closed' + return order + + +@pytest.fixture(scope='function') +def market_buy_order_usdt(): + return { + 'id': 'mocked_market_buy', + 'type': 'market', + 'side': 'buy', + 'symbol': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'price': 2.00, + 'amount': 30.0, + 'filled': 30.0, + 'remaining': 0.0, + 'status': 'closed' + } + + +@pytest.fixture +def market_sell_order_usdt(): + return { + 'id': 'mocked_limit_sell', + 'type': 'market', + 'side': 'sell', + 'symbol': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'price': 2.20, + 'amount': 30.0, + 'filled': 30.0, + 'remaining': 0.0, + 'status': 'closed' + } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 84e7702e0..43c97e658 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring, C0103 import logging from datetime import datetime, timedelta, timezone +from math import isclose from pathlib import Path from types import FunctionType from unittest.mock import MagicMock @@ -64,40 +65,39 @@ def test_init_dryrun_db(default_conf, tmpdir): @pytest.mark.usefixtures("init_persistence") -def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog): +def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog): """ - On this test we will buy and sell a crypto currency. + On this test we will buy and sell a crypto currency. + fee: 0.25% quote + open_rate: 2.00 quote + close_rate: 2.20 quote + amount: = 30.0 crypto + stake_amount + 1x,-1x: 60.0 quote + borrowed + 1x: 0 quote + open_value: (amount * open_rate) ± (amount * open_rate * fee) + 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + amount_closed: + 1x, 3x : amount + close_value: + 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest + binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 + total_profit: + 1x, 3x : close_value - open_value + binance,kraken 1x: 65.835 - 60.15 = 5.685 + total_profit_ratio: + 1x, 3x : ((close_value/open_value) - 1) * leverage + binance 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 - Buy - - Buy: 90.99181073 Crypto at 0.00001099 BTC - (90.99181073*0.00001099 = 0.0009999 BTC) - - Buying fee: 0.25% - - Total cost of buy trade: 0.001002500 BTC - ((90.99181073*0.00001099) + ((90.99181073*0.00001099)*0.0025)) - - Sell - - Sell: 90.99181073 Crypto at 0.00001173 BTC - (90.99181073*0.00001173 = 0,00106733394 BTC) - - Selling fee: 0.25% - - Total cost of sell trade: 0.001064666 BTC - ((90.99181073*0.00001173) - ((90.99181073*0.00001173)*0.0025)) - - Profit/Loss: +0.000062166 BTC - (Sell:0.001064666 - Buy:0.001002500) - Profit/Loss percentage: 0.0620 - ((0.001064666/0.001002500)-1 = 6.20%) - - :param limit_buy_order: - :param limit_sell_order: - :return: """ trade = Trade( id=2, - pair='ETH/BTC', - stake_amount=0.001, - open_rate=0.01, - amount=5, + pair='ADA/USDT', + stake_amount=60.0, + open_rate=2.0, + amount=30.0, is_open=True, open_date=arrow.utcnow().datetime, fee_open=fee.return_value, @@ -109,35 +109,36 @@ def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog): assert trade.close_date is None trade.open_order_id = 'something' - trade.update(limit_buy_order) + trade.update(limit_buy_order_usdt) assert trade.open_order_id is None - assert trade.open_rate == 0.00001099 + assert trade.open_rate == 2.00 assert trade.close_profit is None assert trade.close_date is None assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, " - r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).", + r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).", caplog) caplog.clear() trade.open_order_id = 'something' - trade.update(limit_sell_order) + trade.update(limit_sell_order_usdt) assert trade.open_order_id is None - assert trade.close_rate == 0.00001173 - assert trade.close_profit == 0.06201058 + assert trade.close_rate == 2.20 + assert trade.close_profit == round(0.0945137157107232, 8) assert trade.close_date is not None assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, " - r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).", + r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).", caplog) + caplog.clear() @pytest.mark.usefixtures("init_persistence") -def test_update_market_order(market_buy_order, market_sell_order, fee, caplog): +def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee, caplog): trade = Trade( id=1, - pair='ETH/BTC', - stake_amount=0.001, - amount=5, - open_rate=0.01, + pair='ADA/USDT', + stake_amount=60.0, + open_rate=2.0, + amount=30.0, is_open=True, fee_open=fee.return_value, fee_close=fee.return_value, @@ -146,61 +147,60 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog): ) trade.open_order_id = 'something' - trade.update(market_buy_order) + trade.update(market_buy_order_usdt) assert trade.open_order_id is None - assert trade.open_rate == 0.00004099 + assert trade.open_rate == 2.0 assert trade.close_profit is None assert trade.close_date is None assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, " - r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).", + r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).", caplog) caplog.clear() trade.is_open = True trade.open_order_id = 'something' - trade.update(market_sell_order) + trade.update(market_sell_order_usdt) assert trade.open_order_id is None - assert trade.close_rate == 0.00004173 - assert trade.close_profit == 0.01297561 + assert trade.close_rate == 2.2 + assert trade.close_profit == round(0.0945137157107232, 8) assert trade.close_date is not None assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, " - r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).", + r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).", caplog) @pytest.mark.usefixtures("init_persistence") -def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee): +def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - open_rate=0.01, - amount=5, + pair='ADA/USDT', + stake_amount=60.0, + open_rate=2.0, + amount=30.0, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', ) trade.open_order_id = 'something' - trade.update(limit_buy_order) - assert trade._calc_open_trade_value() == 0.0010024999999225068 + trade.update(limit_buy_order_usdt) + assert trade._calc_open_trade_value() == 60.15 + trade.update(limit_sell_order_usdt) + assert isclose(trade.calc_close_trade_value(), 65.835) - trade.update(limit_sell_order) - assert trade.calc_close_trade_value() == 0.0010646656050132426 - - # Profit in BTC - assert trade.calc_profit() == 0.00006217 + # Profit in USDT + assert trade.calc_profit() == 5.685 # Profit in percent - assert trade.calc_profit_ratio() == 0.06201058 + assert trade.calc_profit_ratio() == round(0.0945137157107232, 8) @pytest.mark.usefixtures("init_persistence") -def test_trade_close(limit_buy_order, limit_sell_order, fee): +def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - open_rate=0.01, - amount=5, + pair='ADA/USDT', + stake_amount=60.0, + open_rate=2.0, + amount=30.0, is_open=True, fee_open=fee.return_value, fee_close=fee.return_value, @@ -210,9 +210,9 @@ def test_trade_close(limit_buy_order, limit_sell_order, fee): assert trade.close_profit is None assert trade.close_date is None assert trade.is_open is True - trade.close(0.02) + trade.close(2.2) assert trade.is_open is False - assert trade.close_profit == 0.99002494 + assert trade.close_profit == round(0.0945137157107232, 8) assert trade.close_date is not None new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime, @@ -220,34 +220,34 @@ def test_trade_close(limit_buy_order, limit_sell_order, fee): # Close should NOT update close_date if the trade has been closed already assert trade.is_open is False trade.close_date = new_date - trade.close(0.02) + trade.close(2.2) assert trade.close_date == new_date @pytest.mark.usefixtures("init_persistence") -def test_calc_close_trade_price_exception(limit_buy_order, fee): +def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - open_rate=0.1, - amount=5, + pair='ADA/USDT', + stake_amount=60.0, + open_rate=2.0, + amount=30.0, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', ) trade.open_order_id = 'something' - trade.update(limit_buy_order) + trade.update(limit_buy_order_usdt) assert trade.calc_close_trade_value() == 0.0 @pytest.mark.usefixtures("init_persistence") -def test_update_open_order(limit_buy_order): +def test_update_open_order(limit_buy_order_usdt): trade = Trade( - pair='ETH/BTC', - stake_amount=1.00, - open_rate=0.01, - amount=5, + pair='ADA/USDT', + stake_amount=60.0, + open_rate=2.0, + amount=30.0, fee_open=0.1, fee_close=0.1, exchange='binance', @@ -257,8 +257,8 @@ def test_update_open_order(limit_buy_order): assert trade.close_profit is None assert trade.close_date is None - limit_buy_order['status'] = 'open' - trade.update(limit_buy_order) + limit_buy_order_usdt['status'] = 'open' + trade.update(limit_buy_order_usdt) assert trade.open_order_id is None assert trade.close_profit is None @@ -266,127 +266,270 @@ def test_update_open_order(limit_buy_order): @pytest.mark.usefixtures("init_persistence") -def test_update_invalid_order(limit_buy_order): +def test_update_invalid_order(limit_buy_order_usdt): trade = Trade( - pair='ETH/BTC', - stake_amount=1.00, - amount=5, - open_rate=0.001, + pair='ADA/USDT', + stake_amount=60.0, + amount=30.0, + open_rate=2.0, fee_open=0.1, fee_close=0.1, exchange='binance', ) - limit_buy_order['type'] = 'invalid' + limit_buy_order_usdt['type'] = 'invalid' with pytest.raises(ValueError, match=r'Unknown order type'): - trade.update(limit_buy_order) + trade.update(limit_buy_order_usdt) @pytest.mark.usefixtures("init_persistence") -def test_calc_open_trade_value(limit_buy_order, fee): +def test_calc_open_trade_value(limit_buy_order_usdt, fee): + # 10 minute limit trade on Binance/Kraken + # fee: 0.25 %, 0.3% quote + # open_rate: 2.00 quote + # amount: = 30.0 crypto + # stake_amount + # 1x, -1x: 60.0 quote + # open_value: (amount * open_rate) ± (amount * open_rate * fee) + # 0.25% fee + # 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + # 0.3% fee + # 1x, 3x: 30 * 2 + 30 * 2 * 0.003 = 60.18 quote trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - amount=5, - open_rate=0.00001099, + pair='ADA/USDT', + stake_amount=60.0, + amount=30.0, + open_rate=2.0, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', ) trade.open_order_id = 'open_trade' - trade.update(limit_buy_order) # Buy @ 0.00001099 + trade.update(limit_buy_order_usdt) # Buy @ 2.0 # Get the open rate price with the standard fee rate - assert trade._calc_open_trade_value() == 0.0010024999999225068 + assert trade._calc_open_trade_value() == 60.15 trade.fee_open = 0.003 # Get the open rate price with a custom fee rate - assert trade._calc_open_trade_value() == 0.001002999999922468 + assert trade._calc_open_trade_value() == 60.18 @pytest.mark.usefixtures("init_persistence") -def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee): +def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - amount=5, - open_rate=0.00001099, + pair='ADA/USDT', + stake_amount=60.0, + amount=30.0, + open_rate=2.0, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', ) trade.open_order_id = 'close_trade' - trade.update(limit_buy_order) # Buy @ 0.00001099 + trade.update(limit_buy_order_usdt) # Buy @ 2.0 # Get the close rate price with a custom close rate and a regular fee rate - assert trade.calc_close_trade_value(rate=0.00001234) == 0.0011200318470471794 - + assert trade.calc_close_trade_value(rate=2.5) == 74.8125 # Get the close rate price with a custom close rate and a custom fee rate - assert trade.calc_close_trade_value(rate=0.00001234, fee=0.003) == 0.0011194704275749754 - + assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 74.775 # Test when we apply a Sell order, and ask price with a custom fee rate - trade.update(limit_sell_order) - assert trade.calc_close_trade_value(fee=0.005) == 0.0010619972701635854 + trade.update(limit_sell_order_usdt) + assert trade.calc_close_trade_value(fee=0.005) == 65.67 @pytest.mark.usefixtures("init_persistence") -def test_calc_profit(limit_buy_order, limit_sell_order, fee): +def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee): + """ + 10 minute limit trade on Binance/Kraken at 1x, 3x leverage + arguments: + fee: + 0.25% quote + 0.30% quote + interest_rate: 0.05% per 4 hrs + open_rate: 2.0 quote + close_rate: + 1.9 quote + 2.1 quote + 2.2 quote + amount: = 30.0 crypto + stake_amount + 1x,-1x: 60.0 quote + 3x,-3x: 20.0 quote + hours: 1/6 (10 minutes) + borrowed + 1x: 0 quote + 3x: 40 quote + -1x: 30 crypto + -3x: 30 crypto + time-periods: + kraken: (1 + 1) 4hr_periods = 2 4hr_periods + binance: 1/24 24hr_periods + interest: borrowed * interest_rate * time-periods + 1x : / + binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote + kraken 3x: 40 * 0.0005 * 2 = 0.040 quote + binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto + kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto + open_value: (amount * open_rate) ± (amount * open_rate * fee) + 0.0025 fee + 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.85 quote + 0.003 fee: Is only applied to close rate in this test + amount_closed: + 1x, 3x = amount + -1x, -3x = amount + interest + binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto + kraken -1x,-3x: 30 + 0.03 = 30.03 crypto + close_value: + equations: + 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest + -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + 2.1 quote + bin,krak 1x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) = 62.8425 + bin 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.0008333333 = 62.8416666667 + krak 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.040 = 62.8025 + bin -1x,-3x: (30.000625 * 2.1) + (30.000625 * 2.1 * 0.0025) = 63.15881578125 + krak -1x,-3x: (30.03 * 2.1) + (30.03 * 2.1 * 0.0025) = 63.2206575 + 1.9 quote + bin,krak 1x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) = 56.8575 + bin 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.0008333333 = 56.85666667 + krak 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.040 = 56.8175 + bin -1x,-3x: (30.000625 * 1.9) + (30.000625 * 1.9 * 0.0025) = 57.14369046875 + krak -1x,-3x: (30.03 * 1.9) + (30.03 * 1.9 * 0.0025) = 57.1996425 + 2.2 quote + bin,krak 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 + bin 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 + krak 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 + bin -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.1663784375 + krak -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 + total_profit: + equations: + 1x, 3x : close_value - open_value + -1x,-3x: open_value - close_value + 2.1 quote + binance,kraken 1x: 62.8425 - 60.15 = 2.6925 + binance 3x: 62.84166667 - 60.15 = 2.69166667 + kraken 3x: 62.8025 - 60.15 = 2.6525 + binance -1x,-3x: 59.850 - 63.15881578125 = -3.308815781249997 + kraken -1x,-3x: 59.850 - 63.2206575 = -3.3706575 + 1.9 quote + binance,kraken 1x: 56.8575 - 60.15 = -3.2925 + binance 3x: 56.85666667 - 60.15 = -3.29333333 + kraken 3x: 56.8175 - 60.15 = -3.3325 + binance -1x,-3x: 59.850 - 57.14369046875 = 2.7063095312499996 + kraken -1x,-3x: 59.850 - 57.1996425 = 2.6503575 + 2.2 quote + binance,kraken 1x: 65.835 - 60.15 = 5.685 + binance 3x: 65.83416667 - 60.15 = 5.68416667 + kraken 3x: 65.795 - 60.15 = 5.645 + binance -1x,-3x: 59.850 - 66.1663784375 = -6.316378437499999 + kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 + total_profit_ratio: + equations: + 1x, 3x : ((close_value/open_value) - 1) * leverage + -1x,-3x: (1 - (close_value/open_value)) * leverage + 2.1 quote + binance,kraken 1x: (62.8425 / 60.15) - 1 = 0.04476309226932673 + binance 3x: ((62.84166667 / 60.15) - 1)*3 = 0.13424771421446402 + kraken 3x: ((62.8025 / 60.15) - 1)*3 = 0.13229426433915248 + binance -1x: 1 - (63.15881578125 / 59.850) = -0.05528514254385963 + binance -3x: (1 - (63.15881578125 / 59.850))*3 = -0.1658554276315789 + kraken -1x: 1 - (63.2206575 / 59.850) = -0.05631842105263152 + kraken -3x: (1 - (63.2206575 / 59.850))*3 = -0.16895526315789455 + 1.9 quote + binance,kraken 1x: (56.8575 / 60.15) - 1 = -0.05473815461346632 + binance 3x: ((56.85666667 / 60.15) - 1)*3 = -0.16425602643391513 + kraken 3x: ((56.8175 / 60.15) - 1)*3 = -0.16620947630922667 + binance -1x: 1 - (57.14369046875 / 59.850) = 0.045218204365079395 + binance -3x: (1 - (57.14369046875 / 59.850))*3 = 0.13565461309523819 + kraken -1x: 1 - (57.1996425 / 59.850) = 0.04428333333333334 + kraken -3x: (1 - (57.1996425 / 59.850))*3 = 0.13285000000000002 + 2.2 quote + binance,kraken 1x: (65.835 / 60.15) - 1 = 0.0945137157107232 + binance 3x: ((65.83416667 / 60.15) - 1)*3 = 0.2834995845386534 + kraken 3x: ((65.795 / 60.15) - 1)*3 = 0.2815461346633419 + binance -1x: 1 - (66.1663784375 / 59.850) = -0.1055368159983292 + binance -3x: (1 - (66.1663784375 / 59.850))*3 = -0.3166104479949876 + kraken -1x: 1 - (66.231165 / 59.850) = -0.106619298245614 + kraken -3x: (1 - (66.231165 / 59.850))*3 = -0.319857894736842 + fee: 0.003, 1x + close_value: + 2.1 quote: (30.00 * 2.1) - (30.00 * 2.1 * 0.003) = 62.811 + 1.9 quote: (30.00 * 1.9) - (30.00 * 1.9 * 0.003) = 56.829 + 2.2 quote: (30.00 * 2.2) - (30.00 * 2.2 * 0.003) = 65.802 + total_profit + fee: 0.003, 1x + 2.1 quote: 62.811 - 60.15 = 2.6610000000000014 + 1.9 quote: 56.829 - 60.15 = -3.320999999999998 + 2.2 quote: 65.802 - 60.15 = 5.652000000000008 + total_profit_ratio + fee: 0.003, 1x + 2.1 quote: (62.811 / 60.15) - 1 = 0.04423940149625927 + 1.9 quote: (56.829 / 60.15) - 1 = -0.05521197007481293 + 2.2 quote: (65.802 / 60.15) - 1 = 0.09396508728179565 + """ trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - amount=5, - open_rate=0.00001099, + pair='ADA/USDT', + stake_amount=60.0, + amount=30.0, + open_rate=2.0, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', ) trade.open_order_id = 'something' - trade.update(limit_buy_order) # Buy @ 0.00001099 + trade.update(limit_buy_order_usdt) # Buy @ 2.0 # Custom closing rate and regular fee rate - # Higher than open rate - assert trade.calc_profit(rate=0.00001234) == 0.00011753 - # Lower than open rate - assert trade.calc_profit(rate=0.00000123) == -0.00089086 + # Higher than open rate - 2.1 quote + assert trade.calc_profit(rate=2.1) == 2.6925 + # Lower than open rate - 1.9 quote + assert trade.calc_profit(rate=1.9) == round(-3.292499999999997, 8) - # Custom closing rate and custom fee rate - # Higher than open rate - assert trade.calc_profit(rate=0.00001234, fee=0.003) == 0.00011697 - # Lower than open rate - assert trade.calc_profit(rate=0.00000123, fee=0.003) == -0.00089092 + # fee 0.003 + # Higher than open rate - 2.1 quote + assert trade.calc_profit(rate=2.1, fee=0.003) == 2.661 + # Lower than open rate - 1.9 quote + assert trade.calc_profit(rate=1.9, fee=0.003) == round(-3.320999999999998, 8) - # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173 - trade.update(limit_sell_order) - assert trade.calc_profit() == 0.00006217 + # Test when we apply a Sell order. Sell higher than open rate @ 2.2 + trade.update(limit_sell_order_usdt) + assert trade.calc_profit() == round(5.684999999999995, 8) # Test with a custom fee rate on the close trade - assert trade.calc_profit(fee=0.003) == 0.00006163 + assert trade.calc_profit(fee=0.003) == round(5.652000000000008, 8) @pytest.mark.usefixtures("init_persistence") -def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee): +def test_calc_profit_ratio(limit_buy_order_usdt, limit_sell_order_usdt, fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - amount=5, - open_rate=0.00001099, + pair='ADA/USDT', + stake_amount=60.0, + amount=30.0, + open_rate=2.0, fee_open=fee.return_value, fee_close=fee.return_value, - exchange='binance', + exchange='binance' ) trade.open_order_id = 'something' - trade.update(limit_buy_order) # Buy @ 0.00001099 + trade.update(limit_buy_order_usdt) # Buy @ 2.0 - # Get percent of profit with a custom rate (Higher than open rate) - assert trade.calc_profit_ratio(rate=0.00001234) == 0.11723875 + # Higher than open rate - 2.1 quote + assert trade.calc_profit_ratio(rate=2.1) == round(0.04476309226932673, 8) + # Lower than open rate - 1.9 quote + assert trade.calc_profit_ratio(rate=1.9) == round(-0.05473815461346632, 8) - # Get percent of profit with a custom rate (Lower than open rate) - assert trade.calc_profit_ratio(rate=0.00000123) == -0.88863828 + # fee 0.003 + # Higher than open rate - 2.1 quote + assert trade.calc_profit_ratio(rate=2.1, fee=0.003) == round(0.04423940149625927, 8) + # Lower than open rate - 1.9 quote + assert trade.calc_profit_ratio(rate=1.9, fee=0.003) == round(-0.05521197007481293, 8) - # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173 - trade.update(limit_sell_order) - assert trade.calc_profit_ratio() == 0.06201058 + # Test when we apply a Sell order. Sell higher than open rate @ 2.2 + trade.update(limit_sell_order_usdt) + assert trade.calc_profit_ratio() == round(0.0945137157107232, 8) # Test with a custom fee rate on the close trade - assert trade.calc_profit_ratio(fee=0.003) == 0.06147824 + assert trade.calc_profit_ratio(fee=0.003) == round(0.09396508728179565, 8) trade.open_trade_value = 0.0 assert trade.calc_profit_ratio(fee=0.003) == 0.0 @@ -397,7 +540,7 @@ def test_clean_dry_run_db(default_conf, fee): # Simulate dry_run entries trade = Trade( - pair='ETH/BTC', + pair='ADA/USDT', stake_amount=0.001, amount=123.0, fee_open=fee.return_value, @@ -664,9 +807,9 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): def test_adjust_stop_loss(fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - amount=5, + pair='ADA/USDT', + stake_amount=30.0, + amount=30, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', @@ -716,9 +859,9 @@ def test_adjust_stop_loss(fee): def test_adjust_min_max_rates(fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, - amount=5, + pair='ADA/USDT', + stake_amount=30.0, + amount=30.0, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', @@ -897,11 +1040,11 @@ def test_to_json(default_conf, fee): def test_stoploss_reinitialization(default_conf, fee): init_db(default_conf['db_url']) trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, + pair='ADA/USDT', + stake_amount=30.0, fee_open=fee.return_value, open_date=arrow.utcnow().shift(hours=-2).datetime, - amount=10, + amount=30.0, fee_close=fee.return_value, exchange='binance', open_rate=1, @@ -956,11 +1099,11 @@ def test_stoploss_reinitialization(default_conf, fee): def test_update_fee(fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, + pair='ADA/USDT', + stake_amount=30.0, fee_open=fee.return_value, open_date=arrow.utcnow().shift(hours=-2).datetime, - amount=10, + amount=30.0, fee_close=fee.return_value, exchange='binance', open_rate=1, @@ -995,11 +1138,11 @@ def test_update_fee(fee): def test_fee_updated(fee): trade = Trade( - pair='ETH/BTC', - stake_amount=0.001, + pair='ADA/USDT', + stake_amount=30.0, fee_open=fee.return_value, open_date=arrow.utcnow().shift(hours=-2).datetime, - amount=10, + amount=30.0, fee_close=fee.return_value, exchange='binance', open_rate=1, From e9ef9a6d28c9d9ea571dd218e24512e11c6da8af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 10:31:35 +0200 Subject: [PATCH 052/200] Use .view() to convert dates to enums part of #5314 - fixing deprecation warning. --- freqtrade/data/history/jsondatahandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py index 990e75bd9..24d6e814b 100644 --- a/freqtrade/data/history/jsondatahandler.py +++ b/freqtrade/data/history/jsondatahandler.py @@ -62,7 +62,7 @@ class JsonDataHandler(IDataHandler): filename = self._pair_data_filename(self._datadir, pair, timeframe) _data = data.copy() # Convert date to int - _data['date'] = _data['date'].astype(np.int64) // 1000 // 1000 + _data['date'] = _data['date'].view(np.int64) // 1000 // 1000 # Reset index, select only appropriate columns and save as json _data.reset_index(drop=True).loc[:, self._columns].to_json( From 047df0c2128cc821a41c5e8118629c3d52bc32b6 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 1 Aug 2021 03:01:47 -0600 Subject: [PATCH 053/200] Removed leverage references --- tests/test_persistence.py | 151 +++++++++++--------------------------- 1 file changed, 41 insertions(+), 110 deletions(-) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 43c97e658..f7bcad806 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -73,22 +73,20 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca close_rate: 2.20 quote amount: = 30.0 crypto stake_amount - 1x,-1x: 60.0 quote + 60.0 quote borrowed - 1x: 0 quote - open_value: (amount * open_rate) ± (amount * open_rate * fee) - 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote - amount_closed: - 1x, 3x : amount + 0 quote + open_value: (amount * open_rate) + (amount * open_rate * fee) + 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote close_value: - 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest - binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 + (amount * close_rate) - (amount * close_rate * fee) - interest + (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 total_profit: - 1x, 3x : close_value - open_value - binance,kraken 1x: 65.835 - 60.15 = 5.685 + close_value - open_value + 65.835 - 60.15 = 5.685 total_profit_ratio: - 1x, 3x : ((close_value/open_value) - 1) * leverage - binance 1x: ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 + ((close_value/open_value) - 1) * leverage + ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 """ @@ -283,17 +281,18 @@ def test_update_invalid_order(limit_buy_order_usdt): @pytest.mark.usefixtures("init_persistence") def test_calc_open_trade_value(limit_buy_order_usdt, fee): - # 10 minute limit trade on Binance/Kraken - # fee: 0.25 %, 0.3% quote - # open_rate: 2.00 quote - # amount: = 30.0 crypto - # stake_amount - # 1x, -1x: 60.0 quote - # open_value: (amount * open_rate) ± (amount * open_rate * fee) - # 0.25% fee - # 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote - # 0.3% fee - # 1x, 3x: 30 * 2 + 30 * 2 * 0.003 = 60.18 quote + """ + fee: 0.25 %, 0.3% quote + open_rate: 2.00 quote + amount: = 30.0 crypto + stake_amount + 60.0 quote + open_value: (amount * open_rate) + (amount * open_rate * fee) + 0.25% fee + 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + 0.3% fee + 30 * 2 + 30 * 2 * 0.003 = 60.18 quote + """ trade = Trade( pair='ADA/USDT', stake_amount=60.0, @@ -339,12 +338,10 @@ def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee @pytest.mark.usefixtures("init_persistence") def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee): """ - 10 minute limit trade on Binance/Kraken at 1x, 3x leverage arguments: fee: 0.25% quote 0.30% quote - interest_rate: 0.05% per 4 hrs open_rate: 2.0 quote close_rate: 1.9 quote @@ -352,117 +349,51 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee): 2.2 quote amount: = 30.0 crypto stake_amount - 1x,-1x: 60.0 quote - 3x,-3x: 20.0 quote - hours: 1/6 (10 minutes) - borrowed - 1x: 0 quote - 3x: 40 quote - -1x: 30 crypto - -3x: 30 crypto - time-periods: - kraken: (1 + 1) 4hr_periods = 2 4hr_periods - binance: 1/24 24hr_periods - interest: borrowed * interest_rate * time-periods - 1x : / - binance 3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote - kraken 3x: 40 * 0.0005 * 2 = 0.040 quote - binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto - kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto - open_value: (amount * open_rate) ± (amount * open_rate * fee) + 60.0 quote + open_value: (amount * open_rate) + (amount * open_rate * fee) 0.0025 fee - 1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote - -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.85 quote + 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote + 30 * 2 - 30 * 2 * 0.0025 = 59.85 quote 0.003 fee: Is only applied to close rate in this test - amount_closed: - 1x, 3x = amount - -1x, -3x = amount + interest - binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto - kraken -1x,-3x: 30 + 0.03 = 30.03 crypto close_value: equations: - 1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest - -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + (amount_closed * close_rate) - (amount_closed * close_rate * fee) 2.1 quote - bin,krak 1x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) = 62.8425 - bin 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.0008333333 = 62.8416666667 - krak 3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.040 = 62.8025 - bin -1x,-3x: (30.000625 * 2.1) + (30.000625 * 2.1 * 0.0025) = 63.15881578125 - krak -1x,-3x: (30.03 * 2.1) + (30.03 * 2.1 * 0.0025) = 63.2206575 + (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) = 62.8425 1.9 quote - bin,krak 1x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) = 56.8575 - bin 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.0008333333 = 56.85666667 - krak 3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.040 = 56.8175 - bin -1x,-3x: (30.000625 * 1.9) + (30.000625 * 1.9 * 0.0025) = 57.14369046875 - krak -1x,-3x: (30.03 * 1.9) + (30.03 * 1.9 * 0.0025) = 57.1996425 + (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) = 56.8575 2.2 quote - bin,krak 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 - bin 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667 - krak 3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795 - bin -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.1663784375 - krak -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025) = 66.231165 + (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 total_profit: equations: - 1x, 3x : close_value - open_value - -1x,-3x: open_value - close_value + close_value - open_value 2.1 quote - binance,kraken 1x: 62.8425 - 60.15 = 2.6925 - binance 3x: 62.84166667 - 60.15 = 2.69166667 - kraken 3x: 62.8025 - 60.15 = 2.6525 - binance -1x,-3x: 59.850 - 63.15881578125 = -3.308815781249997 - kraken -1x,-3x: 59.850 - 63.2206575 = -3.3706575 + 62.8425 - 60.15 = 2.6925 1.9 quote - binance,kraken 1x: 56.8575 - 60.15 = -3.2925 - binance 3x: 56.85666667 - 60.15 = -3.29333333 - kraken 3x: 56.8175 - 60.15 = -3.3325 - binance -1x,-3x: 59.850 - 57.14369046875 = 2.7063095312499996 - kraken -1x,-3x: 59.850 - 57.1996425 = 2.6503575 + 56.8575 - 60.15 = -3.2925 2.2 quote - binance,kraken 1x: 65.835 - 60.15 = 5.685 - binance 3x: 65.83416667 - 60.15 = 5.68416667 - kraken 3x: 65.795 - 60.15 = 5.645 - binance -1x,-3x: 59.850 - 66.1663784375 = -6.316378437499999 - kraken -1x,-3x: 59.850 - 66.231165 = -6.381165 + 65.835 - 60.15 = 5.685 total_profit_ratio: equations: - 1x, 3x : ((close_value/open_value) - 1) * leverage - -1x,-3x: (1 - (close_value/open_value)) * leverage + ((close_value/open_value) - 1) * leverage 2.1 quote - binance,kraken 1x: (62.8425 / 60.15) - 1 = 0.04476309226932673 - binance 3x: ((62.84166667 / 60.15) - 1)*3 = 0.13424771421446402 - kraken 3x: ((62.8025 / 60.15) - 1)*3 = 0.13229426433915248 - binance -1x: 1 - (63.15881578125 / 59.850) = -0.05528514254385963 - binance -3x: (1 - (63.15881578125 / 59.850))*3 = -0.1658554276315789 - kraken -1x: 1 - (63.2206575 / 59.850) = -0.05631842105263152 - kraken -3x: (1 - (63.2206575 / 59.850))*3 = -0.16895526315789455 + (62.8425 / 60.15) - 1 = 0.04476309226932673 1.9 quote - binance,kraken 1x: (56.8575 / 60.15) - 1 = -0.05473815461346632 - binance 3x: ((56.85666667 / 60.15) - 1)*3 = -0.16425602643391513 - kraken 3x: ((56.8175 / 60.15) - 1)*3 = -0.16620947630922667 - binance -1x: 1 - (57.14369046875 / 59.850) = 0.045218204365079395 - binance -3x: (1 - (57.14369046875 / 59.850))*3 = 0.13565461309523819 - kraken -1x: 1 - (57.1996425 / 59.850) = 0.04428333333333334 - kraken -3x: (1 - (57.1996425 / 59.850))*3 = 0.13285000000000002 + (56.8575 / 60.15) - 1 = -0.05473815461346632 2.2 quote - binance,kraken 1x: (65.835 / 60.15) - 1 = 0.0945137157107232 - binance 3x: ((65.83416667 / 60.15) - 1)*3 = 0.2834995845386534 - kraken 3x: ((65.795 / 60.15) - 1)*3 = 0.2815461346633419 - binance -1x: 1 - (66.1663784375 / 59.850) = -0.1055368159983292 - binance -3x: (1 - (66.1663784375 / 59.850))*3 = -0.3166104479949876 - kraken -1x: 1 - (66.231165 / 59.850) = -0.106619298245614 - kraken -3x: (1 - (66.231165 / 59.850))*3 = -0.319857894736842 - fee: 0.003, 1x + (65.835 / 60.15) - 1 = 0.0945137157107232 + fee: 0.003 close_value: 2.1 quote: (30.00 * 2.1) - (30.00 * 2.1 * 0.003) = 62.811 1.9 quote: (30.00 * 1.9) - (30.00 * 1.9 * 0.003) = 56.829 2.2 quote: (30.00 * 2.2) - (30.00 * 2.2 * 0.003) = 65.802 total_profit - fee: 0.003, 1x + fee: 0.003 2.1 quote: 62.811 - 60.15 = 2.6610000000000014 1.9 quote: 56.829 - 60.15 = -3.320999999999998 2.2 quote: 65.802 - 60.15 = 5.652000000000008 total_profit_ratio - fee: 0.003, 1x + fee: 0.003 2.1 quote: (62.811 / 60.15) - 1 = 0.04423940149625927 1.9 quote: (56.829 / 60.15) - 1 = -0.05521197007481293 2.2 quote: (65.802 / 60.15) - 1 = 0.09396508728179565 From 056bc93bc6a6772dc55ffd993e08ec571b6d80c2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 19:16:26 +0200 Subject: [PATCH 054/200] backtesting needs startup_candle_count fixes informative-pair loading being different between --strategy-list and --strategy. --- freqtrade/optimize/backtesting.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 628289b7f..45e60e013 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -122,6 +122,8 @@ class Backtesting: # Get maximum required startup period self.required_startup = max([strat.startup_candle_count for strat in self.strategylist]) + # Add maximum startup candle count to configuration for informative pairs support + self.config['startup_candle_count'] = self.required_startup self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) self.progress = BTProgress() From 059c32b0675607986af0dc2c00738d3b4106bcce Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Mon, 2 Aug 2021 02:49:49 +0000 Subject: [PATCH 055/200] Check for and default to 'None' --- .../plugins/pairlist/rangestabilityfilter.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 105568f17..63184c726 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -26,7 +26,7 @@ class RangeStabilityFilter(IPairList): self._days = pairlistconfig.get('lookback_days', 10) self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01) - self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', 0.99) + self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', None) self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) @@ -106,14 +106,16 @@ class RangeStabilityFilter(IPairList): f"which is below the threshold of {self._min_rate_of_change}.", logger.info) result = False - if pct_change <= self._max_rate_of_change: - result = True - else: - self.log_once(f"Removed {pair} from whitelist, because rate of change " - f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, " - f"which is above the threshold of {self._max_rate_of_change}.", - logger.info) - result = False + if self._max_rate_of_change: + if pct_change <= self._max_rate_of_change: + result = True + else: + self.log_once( + f"Removed {pair} from whitelist, because rate of change " + f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, " + f"which is above the threshold of {self._max_rate_of_change}.", + logger.info) + result = False self._pair_cache[pair] = result return result From b3f057e7c0dbf9a45c1889b4a57a57e7b5646f1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 03:01:13 +0000 Subject: [PATCH 056/200] Bump isort from 5.9.2 to 5.9.3 Bumps [isort](https://github.com/pycqa/isort) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.9.2...5.9.3) --- updated-dependencies: - dependency-name: isort dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 537936a86..70e61e71a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,7 +13,7 @@ pytest-asyncio==0.15.1 pytest-cov==2.12.1 pytest-mock==3.6.1 pytest-random-order==1.0.4 -isort==5.9.2 +isort==5.9.3 # Convert jupyter notebooks to markdown documents nbconvert==6.1.0 From 4f05d31b942f5eacc108ad39d91cd2f81f65aa54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 03:01:19 +0000 Subject: [PATCH 057/200] Bump ccxt from 1.53.72 to 1.54.24 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.53.72 to 1.54.24. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.53.72...1.54.24) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3be66bff7..bd1510380 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.21.1 pandas==1.3.1 -ccxt==1.53.72 +ccxt==1.54.24 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 From 4afcea9a1ccdadbb124363ef1009e0eb8c70514b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 03:01:25 +0000 Subject: [PATCH 058/200] Bump fastapi from 0.67.0 to 0.68.0 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.67.0 to 0.68.0. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.67.0...0.68.0) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3be66bff7..15040270e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ python-rapidjson==1.4 sdnotify==0.3.2 # API Server -fastapi==0.67.0 +fastapi==0.68.0 uvicorn==0.14.0 pyjwt==2.1.0 aiofiles==0.7.0 From 849b8197a93de3ea9b5a9d796ddfd74095923c78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 03:01:30 +0000 Subject: [PATCH 059/200] Bump scipy from 1.7.0 to 1.7.1 Bumps [scipy](https://github.com/scipy/scipy) from 1.7.0 to 1.7.1. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.7.0...v1.7.1) --- updated-dependencies: - dependency-name: scipy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 83e23e3ec..d7f22634b 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.7.0 +scipy==1.7.1 scikit-learn==0.24.2 scikit-optimize==0.8.1 filelock==3.0.12 From 7fd3fc98c0135990f8d29e08aab0e8d8893bfd1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 03:01:35 +0000 Subject: [PATCH 060/200] Bump types-requests from 2.25.0 to 2.25.1 Bumps [types-requests](https://github.com/python/typeshed) from 2.25.0 to 2.25.1. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 537936a86..e260f8fb8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -21,5 +21,5 @@ nbconvert==6.1.0 # mypy types types-cachetools==0.1.9 types-filelock==0.1.4 -types-requests==2.25.0 +types-requests==2.25.1 types-tabulate==0.1.1 From 3a19e1610de5aef8468e475e23a2d033b3303f05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 03:01:38 +0000 Subject: [PATCH 061/200] Bump mkdocs-material from 7.2.1 to 7.2.2 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 7.2.1 to 7.2.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/7.2.1...7.2.2) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 6a904c951..047821f2d 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.2 -mkdocs-material==7.2.1 +mkdocs-material==7.2.2 mdx_truly_sane_lists==1.2 pymdown-extensions==8.2 From b63eda3a2b7e694a32d524c0413a30f737ef9d8d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Aug 2021 07:15:09 +0200 Subject: [PATCH 062/200] Some minor cleanup and improved test coverage --- freqtrade/plugins/pairlist/rangestabilityfilter.py | 7 +++++-- tests/plugins/test_pairlist.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 63184c726..ef7f2cbcb 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -51,9 +51,12 @@ class RangeStabilityFilter(IPairList): """ Short whitelist method description - used for startup-messages """ + max_rate_desc = "" + if self._max_rate_of_change: + max_rate_desc = (f" and above {self._max_rate_of_change}") return (f"{self.name} - Filtering pairs with rate of change below " - f"{self._min_rate_of_change} and above " - f"{self._max_rate_of_change} over the last {plural(self._days, 'day')}.") + f"{self._min_rate_of_change}{max_rate_desc} over the " + f"last {plural(self._days, 'day')}.") def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index a3f5b0875..5f0701a22 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -425,8 +425,12 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "USDT", ['NANO/USDT']), ([{"method": "StaticPairList"}, {"method": "RangeStabilityFilter", "lookback_days": 10, - "min_rate_of_change": 0.01, "max_rate_of_change": 0.99, "refresh_period": 1440}], + "min_rate_of_change": 0.01, "refresh_period": 1440}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), + ([{"method": "StaticPairList"}, + {"method": "RangeStabilityFilter", "lookback_days": 10, + "max_rate_of_change": 0.01, "refresh_period": 1440}], + "BTC", []), # All removed because of max_rate_of_change being 0.017 ([{"method": "StaticPairList"}, {"method": "VolatilityFilter", "lookback_days": 3, "min_volatility": 0.002, "max_volatility": 0.004, "refresh_period": 1440}], @@ -985,6 +989,12 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo None, "PriceFilter requires max_value to be >= 0" ), # OperationalException expected + ({"method": "RangeStabilityFilter", "lookback_days": 10, + "min_rate_of_change": 0.01}, + "[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below " + "0.01 over the last days.'}]", + None + ), ({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01, "max_rate_of_change": 0.99}, "[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below " From 3c5f06d5c054f939b53b6a0e6ad61ef6af7092fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Aug 2021 08:31:33 +0200 Subject: [PATCH 063/200] Update tests/exchange/test_exchange.py --- tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index bacb7edbb..a2ce09eab 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2190,7 +2190,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name): assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} - order = exchange.create_order('ETH/BTC', 'limit', "sell", 5, 0.55, 'gtc') + order = exchange.create_order('ETH/BTC', 'limit', "buy", 5, 0.55, 'gtc') cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC') assert order['id'] == cancel_order['id'] From c981641441fc329f788e31e599629ee16df9061b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Aug 2021 20:17:58 +0200 Subject: [PATCH 064/200] Don't fail if strategy doesn't contain sell signal --- freqtrade/strategy/interface.py | 8 +++++--- tests/strategy/test_interface.py | 25 +++++++++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index f10a12fa9..6f3e047eb 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -483,8 +483,6 @@ class IStrategy(ABC, HyperStrategyMixin): message = "No dataframe returned (return statement missing?)." elif 'buy' not in dataframe: message = "Buy column not set." - elif 'sell' not in dataframe: - message = "Sell column not set." elif df_len != len(dataframe): message = message_template.format("length") elif df_close != dataframe["close"].iloc[-1]: @@ -531,7 +529,11 @@ class IStrategy(ABC, HyperStrategyMixin): return False, False, None buy = latest[SignalType.BUY.value] == 1 - sell = latest[SignalType.SELL.value] == 1 + + sell = False + if SignalType.SELL.value in latest: + sell = latest[SignalType.SELL.value] == 1 + buy_tag = latest.get(SignalTagType.BUY_TAG.value, None) logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 751f08344..d8c87506c 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -126,6 +126,27 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) +def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history): + # default_conf defines a 5m interval. we check interval * 2 + 5m + # this is necessary as the last candle is removed (partial candles) by default + ohlcv_history.loc[1, 'date'] = arrow.utcnow() + # Take a copy to correctly modify the call + mocked_history = ohlcv_history.copy() + # Intentionally don't set sell column + # mocked_history['sell'] = 0 + mocked_history['buy'] = 0 + mocked_history.loc[1, 'buy'] = 1 + + caplog.set_level(logging.INFO) + mocker.patch.object(_STRATEGY, 'assert_df') + + assert (True, False, None) == _STRATEGY.get_signal( + 'xyz', + default_conf['timeframe'], + mocked_history + ) + + def test_ignore_expired_candle(default_conf): default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) @@ -197,10 +218,6 @@ def test_assert_df(ohlcv_history, caplog): match="Buy column not set"): _STRATEGY.assert_df(ohlcv_history.drop('buy', axis=1), len(ohlcv_history), ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date']) - with pytest.raises(StrategyError, - match="Sell column not set"): - _STRATEGY.assert_df(ohlcv_history.drop('sell', axis=1), len(ohlcv_history), - ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date']) _STRATEGY.disable_dataframe_checks = True caplog.clear() From e70a742005e286ecb7e1385ec6af97caf6b3a363 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Aug 2021 21:12:10 +0200 Subject: [PATCH 065/200] Reorder space methods in hyperopt --- freqtrade/optimize/hyperopt.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index cb6884385..7022f67c5 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -264,17 +264,14 @@ class Hyperopt: def generate_optimizer(self, raw_params: List[Any], iteration=None) -> Dict: """ - Used Optimize function. Called once per epoch to optimize whatever is configured. + Used Optimize function. + Called once per epoch to optimize whatever is configured. Keep this function as optimized as possible! """ backtest_start_time = datetime.now(timezone.utc) params_dict = self._get_params_dict(self.dimensions, raw_params) # Apply parameters - if HyperoptTools.has_space(self.config, 'roi'): - self.backtesting.strategy.minimal_roi = ( # type: ignore - self.custom_hyperopt.generate_roi_table(params_dict)) - if HyperoptTools.has_space(self.config, 'buy'): self.backtesting.strategy.advise_buy = ( # type: ignore self.custom_hyperopt.buy_strategy_generator(params_dict)) @@ -283,6 +280,10 @@ class Hyperopt: self.backtesting.strategy.advise_sell = ( # type: ignore self.custom_hyperopt.sell_strategy_generator(params_dict)) + if HyperoptTools.has_space(self.config, 'roi'): + self.backtesting.strategy.minimal_roi = ( # type: ignore + self.custom_hyperopt.generate_roi_table(params_dict)) + if HyperoptTools.has_space(self.config, 'stoploss'): self.backtesting.strategy.stoploss = params_dict['stoploss'] From 4ab03f7e378bc4dd6e2031c1b199180fdb70e3d3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Aug 2021 21:17:56 +0200 Subject: [PATCH 066/200] Don't load fallback methods for autohyperopt --- freqtrade/optimize/hyperopt.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 7022f67c5..0ed3d4cbf 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -102,16 +102,17 @@ class Hyperopt: self.num_epochs_saved = 0 self.current_best_epoch: Optional[Dict[str, Any]] = None - # Populate functions here (hasattr is slow so should not be run during "regular" operations) - if hasattr(self.custom_hyperopt, 'populate_indicators'): - self.backtesting.strategy.advise_indicators = ( # type: ignore - self.custom_hyperopt.populate_indicators) # type: ignore - if hasattr(self.custom_hyperopt, 'populate_buy_trend'): - self.backtesting.strategy.advise_buy = ( # type: ignore - self.custom_hyperopt.populate_buy_trend) # type: ignore - if hasattr(self.custom_hyperopt, 'populate_sell_trend'): - self.backtesting.strategy.advise_sell = ( # type: ignore - self.custom_hyperopt.populate_sell_trend) # type: ignore + if not self.auto_hyperopt: + # Populate "fallback" functions here (hasattr is slow so should not be run during "regular" operations) + if hasattr(self.custom_hyperopt, 'populate_indicators'): + self.backtesting.strategy.advise_indicators = ( # type: ignore + self.custom_hyperopt.populate_indicators) # type: ignore + if hasattr(self.custom_hyperopt, 'populate_buy_trend'): + self.backtesting.strategy.advise_buy = ( # type: ignore + self.custom_hyperopt.populate_buy_trend) # type: ignore + if hasattr(self.custom_hyperopt, 'populate_sell_trend'): + self.backtesting.strategy.advise_sell = ( # type: ignore + self.custom_hyperopt.populate_sell_trend) # type: ignore # Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set if self.config.get('use_max_market_positions', True): From dfc17f2bd1f3779c68475dde405c314b83a85f90 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Aug 2021 07:21:11 +0200 Subject: [PATCH 067/200] Fix ci failure --- freqtrade/optimize/hyperopt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 0ed3d4cbf..a69e5a5a2 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -103,7 +103,8 @@ class Hyperopt: self.current_best_epoch: Optional[Dict[str, Any]] = None if not self.auto_hyperopt: - # Populate "fallback" functions here (hasattr is slow so should not be run during "regular" operations) + # Populate "fallback" functions here + # (hasattr is slow so should not be run during "regular" operations) if hasattr(self.custom_hyperopt, 'populate_indicators'): self.backtesting.strategy.advise_indicators = ( # type: ignore self.custom_hyperopt.populate_indicators) # type: ignore From f11f5d17e91e05e2f2ac7729ccf0b4f29a352bd6 Mon Sep 17 00:00:00 2001 From: axel Date: Sat, 31 Jul 2021 00:05:45 -0400 Subject: [PATCH 068/200] add feature custom entry price for live --- docs/strategy-advanced.md | 29 +++++++++- freqtrade/constants.py | 2 + freqtrade/freqtradebot.py | 73 ++++++++++++++++++++++++ freqtrade/resolvers/strategy_resolver.py | 1 + freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/rpc.py | 1 + freqtrade/strategy/interface.py | 58 +++++++++++++++++++ tests/strategy/test_interface.py | 28 +++++++++ tests/test_freqtradebot.py | 30 ++++++++++ 9 files changed, 222 insertions(+), 1 deletion(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 0704473fb..8c99f1d2e 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -357,6 +357,33 @@ See [Dataframe access](#dataframe-access) for more information about dataframe u --- +## Custom order entry price rules + +By default, freqtrade use the orderbook to automatically set an order price, you also have the option to create custom order prices based on your strategy. + +You can use this feature by setting the `use_custom_entry_price` option to `true` in config and creating a custom_entry_price function. + +### Custom order entry price exemple +``` python +from datetime import datetime, timedelta, timezone +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + def custom_entry_price(self, pair: str, current_time: datetime, + current_rate, **kwargs) -> float: + + dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, + timeframe=self.timeframe) + entryprice = dataframe['bollinger_10_lowerband'].iat[-1] + + return entryprice + +``` + + ## Custom order timeout rules Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section. @@ -366,7 +393,7 @@ However, freqtrade also offers a custom callback for both order types, which all !!! Note Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances. -### Custom order timeout example +## Custom order timeout example A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below. It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b48644c58..f118ac700 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -433,6 +433,8 @@ SCHEMA_MINIMAL_REQUIRED = [ CANCEL_REASON = { "TIMEOUT": "cancelled due to timeout", + "ENTRYPRICECHANGED": "Custom entry price changed", + "EXITPRICECHANGED": "Custom exit price changed", "PARTIALLY_FILLED_KEEP_OPEN": "partially filled - keeping order open", "PARTIALLY_FILLED": "partially filled", "FULLY_CANCELLED": "fully cancelled", diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 09aa06adf..4abc7e508 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -169,6 +169,7 @@ class FreqtradeBot(LoggingMixin): with self._sell_lock: # Check and handle any timed out open orders self.check_handle_timedout() + self.check_handle_custom_entryprice_outdated() # Protect from collisions with forcesell. # Without this, freqtrade my try to recreate stoploss_on_exchange orders @@ -480,6 +481,14 @@ class FreqtradeBot(LoggingMixin): else: # Calculate price buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy") + if self.config.get('use_custom_entry_price', False): + buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy") + custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, + default_retval=stake_amount)( + pair=pair, current_time=datetime.now(timezone.utc), + current_rate=buy_rate) + + buy_limit_requested = custom_entry_price if not buy_limit_requested: raise PricingError('Could not determine buy price.') @@ -911,6 +920,70 @@ class FreqtradeBot(LoggingMixin): order=order))): self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT']) + def _check_entryprice_outdated(self, side: str, order: dict) -> bool: + """ + Check if entry price is outdated by comparing it to the new prefered entry price + , and if the order is still open and price outdated + """ + #print("check_entryprice_outdated") + if self.config.get('use_custom_entry_price', False): + order_prefered_entry_price = order['price'] # order['trade'] + + #print(order) + #order_open_rate_requested = order.trade['open_rate_requested'] + #print("order_trade_object : {}".format(order['trade'])) + + # get pep from strategy data provider + pair = order['symbol'] + old_prefered_entry_price = order_prefered_entry_price + #new_prefered_entry_price = self.strategy.custom_info[pair]['pep_long'].iloc[-1] #buy_limit_requested + new_prefered_entry_price = self.strategy.entryprice + + old_prefered_entry_price_rounded = self.exchange.price_to_precision(pair, order_prefered_entry_price) + new_prefered_entry_price_rounded = self.exchange.price_to_precision(pair, new_prefered_entry_price) + + if old_prefered_entry_price_rounded != new_prefered_entry_price_rounded: + print("order['symbol']: {}".format(order['symbol'])) + print("new_prefered_entry_price: {}, old_prefered_entry_price: {}".format(new_prefered_entry_price, old_prefered_entry_price)) + print("rounded new pep: {}, rounded old pep: {}".format(new_prefered_entry_price_rounded, old_prefered_entry_price_rounded)) + print("Delta in prefered entry price, order to cancel") + return True + else: + return False + else: + return False + + def check_handle_custom_entryprice_outdated(self) -> None: + """ + Check if any orders prefered entryprice change and cancel if necessary + :return: None + """ + + for trade in Trade.get_open_order_trades(): + try: + if not trade.open_order_id: + continue + order = self.exchange.fetch_order(trade.open_order_id, trade.pair) + except (ExchangeError): + logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc()) + continue + + fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) + + # Refresh entryprice value if order is open + if (order['status'] == 'open'): + self.strategy.entryprice = strategy_safe_wrapper(self.strategy.custom_entry_price)( + pair=trade.pair, current_time=datetime.now(timezone.utc), + current_rate=trade.open_rate_requested) + + if (order['side'] == 'buy' and (order['status'] == 'open') and ( + self._check_entryprice_outdated('buy', order))): + self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ENTRYPRICECHANGED']) + + elif (order['side'] == 'sell' and (order['status'] == 'open') and ( + self._check_entryprice_outdated('sell', order))): + self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['EXITPRICECHANGED']) + def cancel_all_open_orders(self) -> None: """ Cancel all orders that are currently open diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 1239b78b3..3248ed385 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -79,6 +79,7 @@ class StrategyResolver(IResolver): ("trailing_stop_positive_offset", 0.0), ("trailing_only_offset_is_reached", None), ("use_custom_stoploss", None), + ("use_custom_entry_price", None), ("process_only_new_candles", None), ("order_types", None), ("order_time_in_force", None), diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 318762136..c66a01490 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -129,6 +129,7 @@ class ShowConfig(BaseModel): trailing_stop_positive_offset: Optional[float] trailing_only_offset_is_reached: Optional[bool] use_custom_stoploss: Optional[bool] + use_custom_entry_price: Optional[bool] timeframe: Optional[str] timeframe_ms: int timeframe_min: int diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 902975fde..2983c1dfa 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -116,6 +116,7 @@ class RPC: 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'), 'use_custom_stoploss': config.get('use_custom_stoploss'), + 'use_custom_entry_price': config.get('use_custom_entry_price'), 'bot_name': config.get('bot_name', 'freqtrade'), 'timeframe': config.get('timeframe'), 'timeframe_ms': timeframe_to_msecs(config['timeframe'] diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6f3e047eb..af5be2711 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -69,6 +69,10 @@ class IStrategy(ABC, HyperStrategyMixin): # associated stoploss stoploss: float + # custom order price + entryprice: Optional[float] = None + exitprice: Optional[float] = None + # trailing stoploss trailing_stop: bool = False trailing_stop_positive: Optional[float] = None @@ -280,6 +284,24 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.stoploss + def custom_entry_price(self, pair: str, current_time: datetime, current_rate: float, + **kwargs) -> float: + """ + Custom entry price logic, returning the new entry price. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None, orderbook is used to set entry price + Only called when use_custom_entry_price is set to True. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: New entry price value if provided + """ + return self.entryprice + def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]: """ @@ -635,6 +657,42 @@ class IStrategy(ABC, HyperStrategyMixin): # logger.debug(f"{trade.pair} - No sell signal.") return SellCheckTuple(sell_type=SellType.NONE) + def entry_price_reached(self, pair: str, current_rate: float, + current_time: datetime, low: float = None, + high: float = None, side: str = "long") -> bool: + """ + Based on current candle low ,decides if entry price was reached + :param current_rate: current rate + :param low: Low value of this candle, only set in backtesting + :param high: High value of this candle, only set in backtesting + """ + + if self.use_custom_entry_price: + entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None + )(pair=pair, + current_time=current_time, + current_rate=current_rate) + # Sanity check - error cases will return None + if side == "long": + if entry_price_value > low: + return True + else: + logger.info(f"Entry failed because entry price {entry_price_value} \ + higher than candle low in long side") + return False + + elif side == "short": + if entry_price_value < high: + return True + else: + logger.info(f"Entry failed because entry price {entry_price_value} \ + higher than candle high in short side") + return False + + else: + logger.warning("CustomEntryPrice function did not return valid entry price") + return False + def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, current_profit: float, force_stoploss: float, low: float = None, diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index d8c87506c..7102c1a49 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -431,6 +431,34 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili strategy.custom_stoploss = original_stopvalue +@pytest.mark.parametrize( + 'current_rate, exp_custom_entry', 'expected_result', 'use_custom_entry_price', 'custom_entry' [ + # Profit, adjusted stoploss(absolute), profit for 2nd call, enable trailing, + # enable custom stoploss, expected after 1st call, expected after 2nd call + (99, 98, False, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # custom_entry_price pice - (price * 0.01) + (97.8, 98, True, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # price stayed under entry price + (97.8, 98, True, True, lambda current_rate, **kwargs: current_rate + (current_rate * 0.01)), # entry price over current price + (99.9, 98, True, False, None), # feature not activated + ]) +def test_entry_price_reached(default_conf, current_rate, exp_custom_entry, candle_ohlc, + expected_result, use_custom_entry_price, custom_entry) -> None: + + default_conf.update({'strategy': 'DefaultStrategy'}) + + + strategy = StrategyResolver.load_strategy(default_conf) + + strategy.use_custom_entry_price = use_custom_entry_price + custom_entry_price = custom_entry + if use_custom_entry_price: + strategy.custom_entry_price = custom_entry(current_rate) + + now = arrow.utcnow().datetime + entry_flag = strategy.entry_price_reached(current_rate=current_rate, low= None, high=None) + + + pass + def test_custom_sell(default_conf, fee, caplog) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index b1e02a99b..f6b8f5544 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -904,6 +904,36 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order with pytest.raises(PricingError, match="Could not determine buy price."): freqtrade.execute_buy(pair, stake_amount) +def test_execute_buy_custom_entry_price(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + default_conf.update({'use_custom_entry_price': True}) + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) + stake_amount = 3 + bid = 2304 + buy_rate_mock = MagicMock(return_value=bid) + buy_mm = MagicMock(return_value=limit_buy_order_open) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_rate=buy_rate_mock, + fetch_ticker=MagicMock(return_value={ + 'bid': 2304, + 'ask': 0.00001173, + 'last': 2304 + }), + buy=buy_mm, + get_min_pair_stake_amount=MagicMock(return_value=1), + get_fee=fee, + ) + pair = 'ETH/USDT' + + # Test calling with custom entry price option activated + limit_buy_order_open['id'] = '55' + assert freqtrade.execute_buy(pair, stake_amount) + # Make sure get_rate called to provide current_rate param to custom_entry_price + assert buy_rate_mock.call_count == 1 + def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From 5284112b69828b91d42a398c2d6cad9c1a151121 Mon Sep 17 00:00:00 2001 From: axel Date: Sun, 1 Aug 2021 02:09:59 -0400 Subject: [PATCH 069/200] fix in custom entry function output,remove changes related to outdated prices, doc exemple minor changes --- docs/strategy-advanced.md | 4 +- freqtrade/freqtradebot.py | 65 --------------------------------- freqtrade/strategy/interface.py | 10 ++--- 3 files changed, 5 insertions(+), 74 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 8c99f1d2e..a8e54bbcf 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -373,7 +373,7 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods def custom_entry_price(self, pair: str, current_time: datetime, - current_rate, **kwargs) -> float: + proposed_rate, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) @@ -393,7 +393,7 @@ However, freqtrade also offers a custom callback for both order types, which all !!! Note Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances. -## Custom order timeout example +### Custom order timeout example A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below. It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4abc7e508..c8e930a36 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -169,7 +169,6 @@ class FreqtradeBot(LoggingMixin): with self._sell_lock: # Check and handle any timed out open orders self.check_handle_timedout() - self.check_handle_custom_entryprice_outdated() # Protect from collisions with forcesell. # Without this, freqtrade my try to recreate stoploss_on_exchange orders @@ -920,70 +919,6 @@ class FreqtradeBot(LoggingMixin): order=order))): self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT']) - def _check_entryprice_outdated(self, side: str, order: dict) -> bool: - """ - Check if entry price is outdated by comparing it to the new prefered entry price - , and if the order is still open and price outdated - """ - #print("check_entryprice_outdated") - if self.config.get('use_custom_entry_price', False): - order_prefered_entry_price = order['price'] # order['trade'] - - #print(order) - #order_open_rate_requested = order.trade['open_rate_requested'] - #print("order_trade_object : {}".format(order['trade'])) - - # get pep from strategy data provider - pair = order['symbol'] - old_prefered_entry_price = order_prefered_entry_price - #new_prefered_entry_price = self.strategy.custom_info[pair]['pep_long'].iloc[-1] #buy_limit_requested - new_prefered_entry_price = self.strategy.entryprice - - old_prefered_entry_price_rounded = self.exchange.price_to_precision(pair, order_prefered_entry_price) - new_prefered_entry_price_rounded = self.exchange.price_to_precision(pair, new_prefered_entry_price) - - if old_prefered_entry_price_rounded != new_prefered_entry_price_rounded: - print("order['symbol']: {}".format(order['symbol'])) - print("new_prefered_entry_price: {}, old_prefered_entry_price: {}".format(new_prefered_entry_price, old_prefered_entry_price)) - print("rounded new pep: {}, rounded old pep: {}".format(new_prefered_entry_price_rounded, old_prefered_entry_price_rounded)) - print("Delta in prefered entry price, order to cancel") - return True - else: - return False - else: - return False - - def check_handle_custom_entryprice_outdated(self) -> None: - """ - Check if any orders prefered entryprice change and cancel if necessary - :return: None - """ - - for trade in Trade.get_open_order_trades(): - try: - if not trade.open_order_id: - continue - order = self.exchange.fetch_order(trade.open_order_id, trade.pair) - except (ExchangeError): - logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc()) - continue - - fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) - - # Refresh entryprice value if order is open - if (order['status'] == 'open'): - self.strategy.entryprice = strategy_safe_wrapper(self.strategy.custom_entry_price)( - pair=trade.pair, current_time=datetime.now(timezone.utc), - current_rate=trade.open_rate_requested) - - if (order['side'] == 'buy' and (order['status'] == 'open') and ( - self._check_entryprice_outdated('buy', order))): - self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ENTRYPRICECHANGED']) - - elif (order['side'] == 'sell' and (order['status'] == 'open') and ( - self._check_entryprice_outdated('sell', order))): - self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['EXITPRICECHANGED']) - def cancel_all_open_orders(self) -> None: """ Cancel all orders that are currently open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index af5be2711..401934f7a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -69,10 +69,6 @@ class IStrategy(ABC, HyperStrategyMixin): # associated stoploss stoploss: float - # custom order price - entryprice: Optional[float] = None - exitprice: Optional[float] = None - # trailing stoploss trailing_stop: bool = False trailing_stop_positive: Optional[float] = None @@ -284,7 +280,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.stoploss - def custom_entry_price(self, pair: str, current_time: datetime, current_rate: float, + def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, **kwargs) -> float: """ Custom entry price logic, returning the new entry price. @@ -296,11 +292,11 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime - :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New entry price value if provided """ - return self.entryprice + return proposed_rate def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]: From 3d8c3ffd38dc8f08edf91e11ba19d57ac6765125 Mon Sep 17 00:00:00 2001 From: axel Date: Sun, 1 Aug 2021 02:21:23 -0400 Subject: [PATCH 070/200] fix syntax error in unit test --- tests/strategy/test_interface.py | 41 ++++++++++---------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 7102c1a49..f973c5bf7 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -38,20 +38,15 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): mocked_history['buy'] = 0 mocked_history.loc[1, 'sell'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 0 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None) - mocked_history.loc[1, 'sell'] = 0 - mocked_history.loc[1, 'buy'] = 1 - mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01' - - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01') + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False) def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): @@ -68,21 +63,15 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_empty(default_conf, mocker, caplog): - assert (False, False, None) == _STRATEGY.get_signal( - 'foo', default_conf['timeframe'], DataFrame() - ) + assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() - assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) + assert (False, False) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) assert log_has('Empty candle (OHLCV) data for pair bar', caplog) caplog.clear() - assert (False, False, None) == _STRATEGY.get_signal( - 'baz', - default_conf['timeframe'], - DataFrame([]) - ) + assert (False, False) == _STRATEGY.get_signal('baz', default_conf['timeframe'], DataFrame([])) assert log_has('Empty candle (OHLCV) data for pair baz', caplog) @@ -118,11 +107,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False, None) == _STRATEGY.get_signal( - 'xyz', - default_conf['timeframe'], - mocked_history - ) + assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], mocked_history) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) @@ -432,13 +417,13 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili @pytest.mark.parametrize( - 'current_rate, exp_custom_entry', 'expected_result', 'use_custom_entry_price', 'custom_entry' [ - # Profit, adjusted stoploss(absolute), profit for 2nd call, enable trailing, + 'current_rate, expected_result, use_custom_entry_price, custom_entry', [ + # current rate, expected result value, profit for 2nd call, enable trailing, # enable custom stoploss, expected after 1st call, expected after 2nd call - (99, 98, False, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # custom_entry_price pice - (price * 0.01) - (97.8, 98, True, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # price stayed under entry price - (97.8, 98, True, True, lambda current_rate, **kwargs: current_rate + (current_rate * 0.01)), # entry price over current price - (99.9, 98, True, False, None), # feature not activated + (99, False, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # custom_entry_price pice - (price * 0.01) + (97.8, True, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # price stayed under entry price + (97.8, True, True, lambda current_rate, **kwargs: current_rate + (current_rate * 0.01)), # entry price over current price + (99.9, True, False, None), # feature not activated ]) def test_entry_price_reached(default_conf, current_rate, exp_custom_entry, candle_ohlc, expected_result, use_custom_entry_price, custom_entry) -> None: From d9c9b7d7fc6449d60c451ff8e3eb72726e16a9ea Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 16:02:10 -0400 Subject: [PATCH 071/200] restore interface test file --- tests/strategy/test_interface.py | 57 ++++++++++++-------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index f973c5bf7..d8c87506c 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -38,15 +38,20 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): mocked_history['buy'] = 0 mocked_history.loc[1, 'sell'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 0 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None) + mocked_history.loc[1, 'sell'] = 0 + mocked_history.loc[1, 'buy'] = 1 + mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01' + + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01') def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): @@ -63,15 +68,21 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_empty(default_conf, mocker, caplog): - assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) + assert (False, False, None) == _STRATEGY.get_signal( + 'foo', default_conf['timeframe'], DataFrame() + ) assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() - assert (False, False) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) + assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) assert log_has('Empty candle (OHLCV) data for pair bar', caplog) caplog.clear() - assert (False, False) == _STRATEGY.get_signal('baz', default_conf['timeframe'], DataFrame([])) + assert (False, False, None) == _STRATEGY.get_signal( + 'baz', + default_conf['timeframe'], + DataFrame([]) + ) assert log_has('Empty candle (OHLCV) data for pair baz', caplog) @@ -107,7 +118,11 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], mocked_history) + assert (False, False, None) == _STRATEGY.get_signal( + 'xyz', + default_conf['timeframe'], + mocked_history + ) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) @@ -416,34 +431,6 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili strategy.custom_stoploss = original_stopvalue -@pytest.mark.parametrize( - 'current_rate, expected_result, use_custom_entry_price, custom_entry', [ - # current rate, expected result value, profit for 2nd call, enable trailing, - # enable custom stoploss, expected after 1st call, expected after 2nd call - (99, False, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # custom_entry_price pice - (price * 0.01) - (97.8, True, True, lambda current_rate, **kwargs: current_rate - (current_rate * 0.01)), # price stayed under entry price - (97.8, True, True, lambda current_rate, **kwargs: current_rate + (current_rate * 0.01)), # entry price over current price - (99.9, True, False, None), # feature not activated - ]) -def test_entry_price_reached(default_conf, current_rate, exp_custom_entry, candle_ohlc, - expected_result, use_custom_entry_price, custom_entry) -> None: - - default_conf.update({'strategy': 'DefaultStrategy'}) - - - strategy = StrategyResolver.load_strategy(default_conf) - - strategy.use_custom_entry_price = use_custom_entry_price - custom_entry_price = custom_entry - if use_custom_entry_price: - strategy.custom_entry_price = custom_entry(current_rate) - - now = arrow.utcnow().datetime - entry_flag = strategy.entry_price_reached(current_rate=current_rate, low= None, high=None) - - - pass - def test_custom_sell(default_conf, fee, caplog) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) From 53fb8b05e72587d334e1a5865a9a6773b523edae Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 16:19:29 -0400 Subject: [PATCH 072/200] remove short logic in entry_price_reached function --- freqtrade/strategy/interface.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 401934f7a..366ae3504 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -281,7 +281,7 @@ class IStrategy(ABC, HyperStrategyMixin): return self.stoploss def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, - **kwargs) -> float: + **kwargs) -> float: """ Custom entry price logic, returning the new entry price. @@ -654,8 +654,8 @@ class IStrategy(ABC, HyperStrategyMixin): return SellCheckTuple(sell_type=SellType.NONE) def entry_price_reached(self, pair: str, current_rate: float, - current_time: datetime, low: float = None, - high: float = None, side: str = "long") -> bool: + current_time: datetime, low: float = None, + high: float = None) -> bool: """ Based on current candle low ,decides if entry price was reached :param current_rate: current rate @@ -668,23 +668,12 @@ class IStrategy(ABC, HyperStrategyMixin): )(pair=pair, current_time=current_time, current_rate=current_rate) - # Sanity check - error cases will return None - if side == "long": + + if entry_price_value is not None: if entry_price_value > low: return True else: - logger.info(f"Entry failed because entry price {entry_price_value} \ - higher than candle low in long side") return False - - elif side == "short": - if entry_price_value < high: - return True - else: - logger.info(f"Entry failed because entry price {entry_price_value} \ - higher than candle high in short side") - return False - else: logger.warning("CustomEntryPrice function did not return valid entry price") return False From 00939b63f2bb05394af2023694b30b15be73ceef Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 16:25:18 -0400 Subject: [PATCH 073/200] flake 8 fixes --- freqtrade/freqtradebot.py | 2 +- freqtrade/strategy/interface.py | 8 ++++---- tests/test_freqtradebot.py | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c8e930a36..5b60c0ea3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -483,7 +483,7 @@ class FreqtradeBot(LoggingMixin): if self.config.get('use_custom_entry_price', False): buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy") custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, - default_retval=stake_amount)( + default_retval=stake_amount)( pair=pair, current_time=datetime.now(timezone.utc), current_rate=buy_rate) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 366ae3504..750f6f39b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -664,10 +664,10 @@ class IStrategy(ABC, HyperStrategyMixin): """ if self.use_custom_entry_price: - entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None - )(pair=pair, - current_time=current_time, - current_rate=current_rate) + entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None)( + pair=pair, + current_time=current_time, + current_rate=current_rate) if entry_price_value is not None: if entry_price_value > low: diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index f6b8f5544..6aaa17094 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -904,7 +904,8 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order with pytest.raises(PricingError, match="Could not determine buy price."): freqtrade.execute_buy(pair, stake_amount) -def test_execute_buy_custom_entry_price(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: + +def test_execute_buy_custom_entry_price(mocker, default_conf, fee, limit_buy_order_open) -> None: patch_RPCManager(mocker) patch_exchange(mocker) default_conf.update({'use_custom_entry_price': True}) From 42e24d8b4b76a4e850c8b4687166f0bc79b82d22 Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 16:35:16 -0400 Subject: [PATCH 074/200] remove price change cancel reason in contants, will be added in another PR --- freqtrade/constants.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index f118ac700..b48644c58 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -433,8 +433,6 @@ SCHEMA_MINIMAL_REQUIRED = [ CANCEL_REASON = { "TIMEOUT": "cancelled due to timeout", - "ENTRYPRICECHANGED": "Custom entry price changed", - "EXITPRICECHANGED": "Custom exit price changed", "PARTIALLY_FILLED_KEEP_OPEN": "partially filled - keeping order open", "PARTIALLY_FILLED": "partially filled", "FULLY_CANCELLED": "fully cancelled", From 16146357b329a759654c302e391e304848adc951 Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 16:39:35 -0400 Subject: [PATCH 075/200] reuse buy_limit_requested as rate input for custom entry price --- freqtrade/freqtradebot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5b60c0ea3..ffe223899 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -481,11 +481,10 @@ class FreqtradeBot(LoggingMixin): # Calculate price buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy") if self.config.get('use_custom_entry_price', False): - buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy") custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=stake_amount)( pair=pair, current_time=datetime.now(timezone.utc), - current_rate=buy_rate) + current_rate=buy_limit_requested) buy_limit_requested = custom_entry_price From b3dafb378e32c39e6eba60e61b2acfc8ea79bf08 Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 16:54:28 -0400 Subject: [PATCH 076/200] remove use_custom_entry_price as a config option --- freqtrade/freqtradebot.py | 11 +++++------ freqtrade/resolvers/strategy_resolver.py | 1 - freqtrade/rpc/api_server/api_schemas.py | 1 - freqtrade/rpc/rpc.py | 1 - freqtrade/strategy/interface.py | 23 ++++++++++------------- tests/test_freqtradebot.py | 1 - 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ffe223899..ad8d0b9c4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -480,13 +480,12 @@ class FreqtradeBot(LoggingMixin): else: # Calculate price buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy") - if self.config.get('use_custom_entry_price', False): - custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, - default_retval=stake_amount)( - pair=pair, current_time=datetime.now(timezone.utc), - current_rate=buy_limit_requested) + custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, + default_retval=stake_amount)( + pair=pair, current_time=datetime.now(timezone.utc), + current_rate=buy_limit_requested) - buy_limit_requested = custom_entry_price + buy_limit_requested = custom_entry_price if not buy_limit_requested: raise PricingError('Could not determine buy price.') diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 3248ed385..1239b78b3 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -79,7 +79,6 @@ class StrategyResolver(IResolver): ("trailing_stop_positive_offset", 0.0), ("trailing_only_offset_is_reached", None), ("use_custom_stoploss", None), - ("use_custom_entry_price", None), ("process_only_new_candles", None), ("order_types", None), ("order_time_in_force", None), diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index c66a01490..318762136 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -129,7 +129,6 @@ class ShowConfig(BaseModel): trailing_stop_positive_offset: Optional[float] trailing_only_offset_is_reached: Optional[bool] use_custom_stoploss: Optional[bool] - use_custom_entry_price: Optional[bool] timeframe: Optional[str] timeframe_ms: int timeframe_min: int diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 2983c1dfa..902975fde 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -116,7 +116,6 @@ class RPC: 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'), 'use_custom_stoploss': config.get('use_custom_stoploss'), - 'use_custom_entry_price': config.get('use_custom_entry_price'), 'bot_name': config.get('bot_name', 'freqtrade'), 'timeframe': config.get('timeframe'), 'timeframe_ms': timeframe_to_msecs(config['timeframe'] diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 750f6f39b..d04524687 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -288,7 +288,6 @@ class IStrategy(ABC, HyperStrategyMixin): For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ When not implemented by a strategy, returns None, orderbook is used to set entry price - Only called when use_custom_entry_price is set to True. :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime @@ -662,21 +661,19 @@ class IStrategy(ABC, HyperStrategyMixin): :param low: Low value of this candle, only set in backtesting :param high: High value of this candle, only set in backtesting """ + entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None)( + pair=pair, + current_time=current_time, + current_rate=current_rate) - if self.use_custom_entry_price: - entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None)( - pair=pair, - current_time=current_time, - current_rate=current_rate) - - if entry_price_value is not None: - if entry_price_value > low: - return True - else: - return False + if entry_price_value is not None: + if entry_price_value > low: + return True else: - logger.warning("CustomEntryPrice function did not return valid entry price") return False + else: + logger.warning("CustomEntryPrice function did not return valid entry price") + return False def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, current_profit: float, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 6aaa17094..08e02cffb 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -908,7 +908,6 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order def test_execute_buy_custom_entry_price(mocker, default_conf, fee, limit_buy_order_open) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - default_conf.update({'use_custom_entry_price': True}) freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) stake_amount = 3 From b644233eada98d37b435a38f4fc69cecdda3c451 Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 17:03:27 -0400 Subject: [PATCH 077/200] rename custom_entry_price kwarg to align it to the interface --- freqtrade/freqtradebot.py | 2 +- freqtrade/strategy/interface.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ad8d0b9c4..2592ccc91 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -483,7 +483,7 @@ class FreqtradeBot(LoggingMixin): custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=stake_amount)( pair=pair, current_time=datetime.now(timezone.utc), - current_rate=buy_limit_requested) + proposed_rate=buy_limit_requested) buy_limit_requested = custom_entry_price diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d04524687..1cbc334f0 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -664,7 +664,7 @@ class IStrategy(ABC, HyperStrategyMixin): entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None)( pair=pair, current_time=current_time, - current_rate=current_rate) + proposed_rate=current_rate) if entry_price_value is not None: if entry_price_value > low: From bc3e6deb1c1efa5c9c3120f4c2d07c33bff5ef47 Mon Sep 17 00:00:00 2001 From: axel Date: Tue, 3 Aug 2021 17:09:52 -0400 Subject: [PATCH 078/200] remove specific test for buy with custom entry --- tests/test_freqtradebot.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 08e02cffb..b1e02a99b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -905,36 +905,6 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order freqtrade.execute_buy(pair, stake_amount) -def test_execute_buy_custom_entry_price(mocker, default_conf, fee, limit_buy_order_open) -> None: - patch_RPCManager(mocker) - patch_exchange(mocker) - freqtrade = FreqtradeBot(default_conf) - freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) - stake_amount = 3 - bid = 2304 - buy_rate_mock = MagicMock(return_value=bid) - buy_mm = MagicMock(return_value=limit_buy_order_open) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_rate=buy_rate_mock, - fetch_ticker=MagicMock(return_value={ - 'bid': 2304, - 'ask': 0.00001173, - 'last': 2304 - }), - buy=buy_mm, - get_min_pair_stake_amount=MagicMock(return_value=1), - get_fee=fee, - ) - pair = 'ETH/USDT' - - # Test calling with custom entry price option activated - limit_buy_order_open['id'] = '55' - assert freqtrade.execute_buy(pair, stake_amount) - # Make sure get_rate called to provide current_rate param to custom_entry_price - assert buy_rate_mock.call_count == 1 - - def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch.multiple( From 6410a6528be7a34da85baacb259340f2d7afdba0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 06:46:21 +0200 Subject: [PATCH 079/200] Add missing methods to advanced strategy template --- freqtrade/strategy/interface.py | 6 +-- .../subtemplates/strategy_methods_advanced.j2 | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6f3e047eb..bf5cc10af 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -288,10 +288,10 @@ class IStrategy(ABC, HyperStrategyMixin): time. This method is not called when sell signal is set. This method should be overridden to create sell signals that depend on trade parameters. For - example you could implement a stoploss relative to candle when trade was opened, or a custom - 1:2 risk-reward ROI. + example you could implement a sell relative to the candle when the trade was opened, + or a custom 1:2 risk-reward ROI. - Custom sell reason max length is 64. Exceeding this limit will raise OperationalException. + Custom sell reason max length is 64. Exceeding characters will be removed. :param pair: Pair that's currently analyzed :param trade: trade object. diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index 2a9ac0690..2df23f365 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -12,6 +12,23 @@ def bot_loop_start(self, **kwargs) -> None: """ pass +def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate: float, + proposed_stake: float, min_stake: float, max_stake: float, + **kwargs) -> float: + """ + Customize stake size for each new trade. This method is not called when edge module is + enabled. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_stake: A stake amount proposed by the bot. + :param min_stake: Minimal stake size allowed by exchange. + :param max_stake: Balance available for trading. + :return: A stake size, which is between min_stake and max_stake. + """ + return proposed_stake + use_custom_stoploss = True def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', @@ -38,6 +55,30 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', """ return self.stoploss +def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]': + """ + Custom sell signal logic indicating that specified position should be sold. Returning a + string or True from this method is equal to setting sell signal on a candle at specified + time. This method is not called when sell signal is set. + + This method should be overridden to create sell signals that depend on trade parameters. For + example you could implement a sell relative to the candle when the trade was opened, + or a custom 1:2 risk-reward ROI. + + Custom sell reason max length is 64. Exceeding characters will be removed. + + :param pair: Pair that's currently analyzed + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return: To execute sell, return a string with custom sell reason or True. Otherwise return + None or False. + """ + return None + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: 'datetime', **kwargs) -> bool: """ From 74a5cb3c21d33bcee48a64470c4d9117efa86fc3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 06:49:53 +0200 Subject: [PATCH 080/200] Remove protections from full config They are supposed to be configured in the strategy --- config_examples/config_full.example.json | 27 ------------------------ 1 file changed, 27 deletions(-) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index d404391a4..3ca413281 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -78,33 +78,6 @@ "refresh_period": 1440 } ], - "protections": [ - { - "method": "StoplossGuard", - "lookback_period_candles": 60, - "trade_limit": 4, - "stop_duration_candles": 60, - "only_per_pair": false - }, - { - "method": "CooldownPeriod", - "stop_duration_candles": 20 - }, - { - "method": "MaxDrawdown", - "lookback_period_candles": 200, - "trade_limit": 20, - "stop_duration_candles": 10, - "max_allowed_drawdown": 0.2 - }, - { - "method": "LowProfitPairs", - "lookback_period_candles": 360, - "trade_limit": 1, - "stop_duration_candles": 2, - "required_profit": 0.02 - } - ], "exchange": { "name": "binance", "sandbox": false, From 800b2eeaf0e5629c5f5fe8877ae414859db25e50 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Aug 2021 06:38:15 +0200 Subject: [PATCH 081/200] Load protections as part of backtest() this enables different values in hyperopt per epoch --- freqtrade/optimize/backtesting.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 45e60e013..3079e326d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -146,6 +146,8 @@ class Backtesting: # since a "perfect" stoploss-sell is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False + + def _load_protections(self, strategy: IStrategy): if self.config.get('enable_protections', False): conf = self.config if hasattr(strategy, 'protections'): @@ -194,6 +196,7 @@ class Backtesting: Trade.reset_trades() self.rejected_trades = 0 self.dataprovider.clear_cache() + self._load_protections(self.strategy) def check_abort(self): """ From 544e0da6c2c84cc918978dce0146089b011744f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Aug 2021 07:10:04 +0200 Subject: [PATCH 082/200] Add protection parameter space --- freqtrade/commands/cli_options.py | 2 +- freqtrade/optimize/hyperopt.py | 17 +++++++++++++++-- freqtrade/optimize/hyperopt_auto.py | 3 +++ freqtrade/optimize/hyperopt_interface.py | 7 +++++++ freqtrade/strategy/hyper.py | 16 +++++++++++----- 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index f56a2bf18..215ed3f6e 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -218,7 +218,7 @@ AVAILABLE_CLI_OPTIONS = { "spaces": Arg( '--spaces', help='Specify which parameters to hyperopt. Space-separated list.', - choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'default'], + choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection', 'default'], nargs='+', default='default', ), diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index a69e5a5a2..18ed66b4a 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -66,6 +66,7 @@ class Hyperopt: def __init__(self, config: Dict[str, Any]) -> None: self.buy_space: List[Dimension] = [] self.sell_space: List[Dimension] = [] + self.protection_space: List[Dimension] = [] self.roi_space: List[Dimension] = [] self.stoploss_space: List[Dimension] = [] self.trailing_space: List[Dimension] = [] @@ -191,6 +192,8 @@ class Hyperopt: result['buy'] = {p.name: params.get(p.name) for p in self.buy_space} if HyperoptTools.has_space(self.config, 'sell'): result['sell'] = {p.name: params.get(p.name) for p in self.sell_space} + if HyperoptTools.has_space(self.config, 'protection'): + result['protection'] = {p.name: params.get(p.name) for p in self.protection_space} if HyperoptTools.has_space(self.config, 'roi'): result['roi'] = {str(k): v for k, v in self.custom_hyperopt.generate_roi_table(params).items()} @@ -241,6 +244,10 @@ class Hyperopt: """ Assign the dimensions in the hyperoptimization space. """ + if self.auto_hyperopt and HyperoptTools.has_space(self.config, 'protection'): + # Protections can only be optimized when using the Parameter interface + logger.debug("Hyperopt has 'protection' space") + self.protection_space = self.custom_hyperopt.protection_space() if HyperoptTools.has_space(self.config, 'buy'): logger.debug("Hyperopt has 'buy' space") @@ -261,8 +268,8 @@ class Hyperopt: if HyperoptTools.has_space(self.config, 'trailing'): logger.debug("Hyperopt has 'trailing' space") self.trailing_space = self.custom_hyperopt.trailing_space() - self.dimensions = (self.buy_space + self.sell_space + self.roi_space + - self.stoploss_space + self.trailing_space) + self.dimensions = (self.buy_space + self.sell_space + self.protection_space + + self.roi_space + self.stoploss_space + self.trailing_space) def generate_optimizer(self, raw_params: List[Any], iteration=None) -> Dict: """ @@ -282,6 +289,12 @@ class Hyperopt: self.backtesting.strategy.advise_sell = ( # type: ignore self.custom_hyperopt.sell_strategy_generator(params_dict)) + if HyperoptTools.has_space(self.config, 'protection'): + for attr_name, attr in self.backtesting.strategy.enumerate_parameters('protection'): + if attr.optimize: + # noinspection PyProtectedMember + attr.value = params_dict[attr_name] + if HyperoptTools.has_space(self.config, 'roi'): self.backtesting.strategy.minimal_roi = ( # type: ignore self.custom_hyperopt.generate_roi_table(params_dict)) diff --git a/freqtrade/optimize/hyperopt_auto.py b/freqtrade/optimize/hyperopt_auto.py index f86204406..03f7dd21e 100644 --- a/freqtrade/optimize/hyperopt_auto.py +++ b/freqtrade/optimize/hyperopt_auto.py @@ -73,6 +73,9 @@ class HyperOptAuto(IHyperOpt): def sell_indicator_space(self) -> List['Dimension']: return self._get_indicator_space('sell', 'sell_indicator_space') + def protection_space(self) -> List['Dimension']: + return self._get_indicator_space('protection', 'indicator_space') + def generate_roi_table(self, params: Dict) -> Dict[int, float]: return self._get_func('generate_roi_table')(params) diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 889854cad..500798627 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -57,6 +57,13 @@ class IHyperOpt(ABC): """ raise OperationalException(_format_exception_message('sell_strategy_generator', 'sell')) + def protection_space(self) -> List[Dimension]: + """ + Create a protection space. + Only supported by the Parameter interface. + """ + raise OperationalException(_format_exception_message('indicator_space', 'protection')) + def indicator_space(self) -> List[Dimension]: """ Create an indicator space. diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py index b067e19d5..e143dda7e 100644 --- a/freqtrade/strategy/hyper.py +++ b/freqtrade/strategy/hyper.py @@ -283,6 +283,7 @@ class HyperStrategyMixin(object): self.config = config self.ft_buy_params: List[BaseParameter] = [] self.ft_sell_params: List[BaseParameter] = [] + self.ft_protection_params: List[BaseParameter] = [] self._load_hyper_params(config.get('runmode') == RunMode.HYPEROPT) @@ -292,11 +293,11 @@ class HyperStrategyMixin(object): :param category: :return: """ - if category not in ('buy', 'sell', None): - raise OperationalException('Category must be one of: "buy", "sell", None.') + if category not in ('buy', 'sell', 'protection', None): + raise OperationalException('Category must be one of: "buy", "sell", "protection", None.') if category is None: - params = self.ft_buy_params + self.ft_sell_params + params = self.ft_buy_params + self.ft_sell_params + self.ft_protection_params else: params = getattr(self, f"ft_{category}_params") @@ -324,9 +325,10 @@ class HyperStrategyMixin(object): params: Dict = { 'buy': list(cls.detect_parameters('buy')), 'sell': list(cls.detect_parameters('sell')), + 'protection': list(cls.detect_parameters('protection')), } params.update({ - 'count': len(params['buy'] + params['sell']) + 'count': len(params['buy'] + params['sell'] + params['protection']) }) return params @@ -340,9 +342,12 @@ class HyperStrategyMixin(object): self._ft_params_from_file = params buy_params = deep_merge_dicts(params.get('buy', {}), getattr(self, 'buy_params', {})) sell_params = deep_merge_dicts(params.get('sell', {}), getattr(self, 'sell_params', {})) + protection_params = deep_merge_dicts(params.get('protection', {}), + getattr(self, 'protection_params', {})) self._load_params(buy_params, 'buy', hyperopt) self._load_params(sell_params, 'sell', hyperopt) + self._load_params(protection_params, 'protection', hyperopt) def load_params_from_file(self) -> Dict: filename_str = getattr(self, '__file__', '') @@ -397,7 +402,8 @@ class HyperStrategyMixin(object): """ params = { 'buy': {}, - 'sell': {} + 'sell': {}, + 'protection': {}, } for name, p in self.enumerate_parameters(): if not p.optimize or not p.in_space: From 091bf7c4d2940e72ed00639b362b8aff1d009f84 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Aug 2021 07:14:31 +0200 Subject: [PATCH 083/200] Output protection space --- freqtrade/optimize/hyperopt_tools.py | 8 +++++--- freqtrade/plugins/protections/iprotection.py | 6 +++--- tests/optimize/test_hyperopt.py | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 439016c14..94b724d9a 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -82,8 +82,8 @@ class HyperoptTools(): """ Tell if the space value is contained in the configuration """ - # The 'trailing' space is not included in the 'default' set of spaces - if space == 'trailing': + # 'trailing' and 'protection spaces are not included in the 'default' set of spaces + if space in ('trailing', 'protection'): return any(s in config['spaces'] for s in [space, 'all']) else: return any(s in config['spaces'] for s in [space, 'all', 'default']) @@ -149,7 +149,7 @@ class HyperoptTools(): if print_json: result_dict: Dict = {} - for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing']: + for s in ['buy', 'sell', 'protection', 'roi', 'stoploss', 'trailing']: HyperoptTools._params_update_for_json(result_dict, params, non_optimized, s) print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE)) @@ -158,6 +158,8 @@ class HyperoptTools(): non_optimized) HyperoptTools._params_pretty_print(params, 'sell', "Sell hyperspace params:", non_optimized) + HyperoptTools._params_pretty_print(params, 'protection', + "Protection hyperspace params:", non_optimized) HyperoptTools._params_pretty_print(params, 'roi', "ROI table:", non_optimized) HyperoptTools._params_pretty_print(params, 'stoploss', "Stoploss:", non_optimized) HyperoptTools._params_pretty_print(params, 'trailing', "Trailing stop:", non_optimized) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index d034beefc..e6bb49064 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -27,17 +27,17 @@ class IProtection(LoggingMixin, ABC): self._protection_config = protection_config tf_in_min = timeframe_to_minutes(config['timeframe']) if 'stop_duration_candles' in protection_config: - self._stop_duration_candles = protection_config.get('stop_duration_candles', 1) + self._stop_duration_candles = int(protection_config.get('stop_duration_candles', 1)) self._stop_duration = (tf_in_min * self._stop_duration_candles) else: self._stop_duration_candles = None self._stop_duration = protection_config.get('stop_duration', 60) if 'lookback_period_candles' in protection_config: - self._lookback_period_candles = protection_config.get('lookback_period_candles', 1) + self._lookback_period_candles = int(protection_config.get('lookback_period_candles', 1)) self._lookback_period = tf_in_min * self._lookback_period_candles else: self._lookback_period_candles = None - self._lookback_period = protection_config.get('lookback_period', 60) + self._lookback_period = int(protection_config.get('lookback_period', 60)) LoggingMixin.__init__(self, logger) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 14fea573f..ef4b85beb 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -577,6 +577,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: "20.0": 0.02, "50.0": 0.01, "110.0": 0}, + 'protection': {}, 'sell': {'sell-adx-enabled': False, 'sell-adx-value': 0, 'sell-fastd-enabled': True, @@ -592,7 +593,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: 'trailing_stop_positive': 0.02, 'trailing_stop_positive_offset': 0.07}}, 'params_dict': optimizer_param, - 'params_not_optimized': {'buy': {}, 'sell': {}}, + 'params_not_optimized': {'buy': {}, 'protection': {}, 'sell': {}}, 'results_metrics': ANY, 'total_profit': 3.1e-08 } From a6454cfc39453dbceb9fbba2bf896b45d64334ee Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 07:14:47 +0200 Subject: [PATCH 084/200] Autoenable protections when protection-space is selected --- freqtrade/optimize/hyperopt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 18ed66b4a..3eab709da 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -247,6 +247,8 @@ class Hyperopt: if self.auto_hyperopt and HyperoptTools.has_space(self.config, 'protection'): # Protections can only be optimized when using the Parameter interface logger.debug("Hyperopt has 'protection' space") + # Enable Protections if protection space is selected. + self.config['enable_protections'] = True self.protection_space = self.custom_hyperopt.protection_space() if HyperoptTools.has_space(self.config, 'buy'): From ceed3c663b3eb5498d2eeff232f4122945e58126 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 07:15:14 +0200 Subject: [PATCH 085/200] Document using protections --- docs/hyperopt.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 4fba925d0..95a1cfcc5 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -326,7 +326,7 @@ There are four parameter types each suited for different purposes. !!! Warning Hyperoptable parameters cannot be used in `populate_indicators` - as hyperopt does not recalculate indicators for each epoch, so the starting value would be used in this case. -### Optimizing an indicator parameter +## Optimizing an indicator parameter Assuming you have a simple strategy in mind - a EMA cross strategy (2 Moving averages crossing) - and you'd like to find the ideal parameters for this strategy. @@ -413,6 +413,94 @@ While this strategy is most likely too simple to provide consistent profit, it s While this may slow down the hyperopt startup speed, the overall performance will increase as the Hyperopt execution itself may pick the same value for multiple epochs (changing other values). You should however try to use space ranges as small as possible. Every new column will require more memory, and every possibility hyperopt can try will increase the search space. +## Optimizing protections + +Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only. + +The strategy will simply need to define the "protections" entry as property returning a list of protection configurations. + +``` python +from pandas import DataFrame +from functools import reduce + +import talib.abstract as ta + +from freqtrade.strategy import IStrategy +from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter +import freqtrade.vendor.qtpylib.indicators as qtpylib + +class MyAwesomeStrategy(IStrategy): + stoploss = -0.05 + timeframe = '15m' + # Define the parameter spaces + coolback_lookback = IntParameter(2, 48, default=5, space="protection", optimize=True) + stop_duration = IntParameter(12, 200, default=5, space="protection", optimize=True) + use_stop_protection = CategoricalParameter([True, False], default=True, space="protection", optimize=True) + + + @property + def protections(self): + prot = [] + + prot.append({ + "method": "CooldownPeriod", + "stop_duration_candles": self.coolback_lookback.value + }) + if self.use_stop_protection.value: + prot.append({ + "method": "StoplossGuard", + "lookback_period_candles": 24 * 3, + "trade_limit": 4, + "stop_duration_candles": self.stop_duration.value, + "only_per_pair": False + }) + + return protection + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # ... + +``` + +You can then run hyperopt as follows: +`freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy MyAwesomeStrategy --spaces protection` + +!!! Note + The protection space is not part of the default space, and is only available with the Parameters Hyperopt interface, not with the legacy hyperopt interface (which required separate hyperopt files). + Freqtrade will also automatically change the "--enable-protections" flag if the protection space is selected. + +### Migrating from previous property setups + +A migration from a previous setup is pretty simple, and can be accomplished by converting the protections entry to a property. +In simple terms, the following configuration will be converted to the below. + +``` python +class MyAwesomeStrategy(IStrategy): + protections = [ + { + "method": "CooldownPeriod", + "stop_duration_candles": 4 + } + ] +``` + +Result + +``` python +class MyAwesomeStrategy(IStrategy): + + @property + def protections(self): + return [ + { + "method": "CooldownPeriod", + "stop_duration_candles": 4 + } + ] +``` + +You will then obviously also change potential interesting entries to parameters to allow hyper-optimization. + ## Loss-functions Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results. From a661e0db6e373ade53690a6214f2e7b2b84ea333 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 19:43:16 +0200 Subject: [PATCH 086/200] Deprecate protections from config --- docs/hyperopt.md | 4 + docs/includes/protections.md | 149 ++++++++++-------- .../configuration/deprecated_settings.py | 3 + tests/test_configuration.py | 13 +- 4 files changed, 101 insertions(+), 68 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 95a1cfcc5..e11c93748 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -469,6 +469,10 @@ You can then run hyperopt as follows: The protection space is not part of the default space, and is only available with the Parameters Hyperopt interface, not with the legacy hyperopt interface (which required separate hyperopt files). Freqtrade will also automatically change the "--enable-protections" flag if the protection space is selected. +!!! Warning + If protections are defined as property, entries from the configuration will be ignored. + It is therefore recommended to not define protections in the configuration. + ### Migrating from previous property setups A migration from a previous setup is pretty simple, and can be accomplished by converting the protections entry to a property. diff --git a/docs/includes/protections.md b/docs/includes/protections.md index 5dcc83738..0757d2f6d 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -15,6 +15,10 @@ All protection end times are rounded up to the next candle to avoid sudden, unex !!! Note "Backtesting" Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag. +!!! Warning "Setting protections from the configuration" + Setting protections from the configuration via `"protections": [],` key should be considered deprecated and will be removed in a future version. + It is also no longer guaranteed that your protections apply to the strategy in cases where the strategy defines [protections as property](hyperopt.md#optimizing-protections). + ### Available Protections * [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window. @@ -47,15 +51,17 @@ This applies across all pairs, unless `only_per_pair` is set to true, which will The below example stops trading for all pairs for 4 candles after the last trade if the bot hit stoploss 4 times within the last 24 candles. ``` python -protections = [ - { - "method": "StoplossGuard", - "lookback_period_candles": 24, - "trade_limit": 4, - "stop_duration_candles": 4, - "only_per_pair": False - } -] +@property +def protections(self): + return [ + { + "method": "StoplossGuard", + "lookback_period_candles": 24, + "trade_limit": 4, + "stop_duration_candles": 4, + "only_per_pair": False + } + ] ``` !!! Note @@ -69,15 +75,17 @@ protections = [ The below sample stops trading for 12 candles if max-drawdown is > 20% considering all pairs - with a minimum of `trade_limit` trades - within the last 48 candles. If desired, `lookback_period` and/or `stop_duration` can be used. ``` python -protections = [ - { - "method": "MaxDrawdown", - "lookback_period_candles": 48, - "trade_limit": 20, - "stop_duration_candles": 12, - "max_allowed_drawdown": 0.2 - }, -] +@property +def protections(self): + return [ + { + "method": "MaxDrawdown", + "lookback_period_candles": 48, + "trade_limit": 20, + "stop_duration_candles": 12, + "max_allowed_drawdown": 0.2 + }, + ] ``` #### Low Profit Pairs @@ -88,15 +96,17 @@ If that ratio is below `required_profit`, that pair will be locked for `stop_dur The below example will stop trading a pair for 60 minutes if the pair does not have a required profit of 2% (and a minimum of 2 trades) within the last 6 candles. ``` python -protections = [ - { - "method": "LowProfitPairs", - "lookback_period_candles": 6, - "trade_limit": 2, - "stop_duration": 60, - "required_profit": 0.02 - } -] +@property +def protections(self): + return [ + { + "method": "LowProfitPairs", + "lookback_period_candles": 6, + "trade_limit": 2, + "stop_duration": 60, + "required_profit": 0.02 + } + ] ``` #### Cooldown Period @@ -106,12 +116,14 @@ protections = [ The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down". ``` python -protections = [ - { - "method": "CooldownPeriod", - "stop_duration_candles": 2 - } -] +@property +def protections(self): + return [ + { + "method": "CooldownPeriod", + "stop_duration_candles": 2 + } + ] ``` !!! Note @@ -136,39 +148,42 @@ from freqtrade.strategy import IStrategy class AwesomeStrategy(IStrategy) timeframe = '1h' - protections = [ - { - "method": "CooldownPeriod", - "stop_duration_candles": 5 - }, - { - "method": "MaxDrawdown", - "lookback_period_candles": 48, - "trade_limit": 20, - "stop_duration_candles": 4, - "max_allowed_drawdown": 0.2 - }, - { - "method": "StoplossGuard", - "lookback_period_candles": 24, - "trade_limit": 4, - "stop_duration_candles": 2, - "only_per_pair": False - }, - { - "method": "LowProfitPairs", - "lookback_period_candles": 6, - "trade_limit": 2, - "stop_duration_candles": 60, - "required_profit": 0.02 - }, - { - "method": "LowProfitPairs", - "lookback_period_candles": 24, - "trade_limit": 4, - "stop_duration_candles": 2, - "required_profit": 0.01 - } - ] + + @property + def protections(self): + return [ + { + "method": "CooldownPeriod", + "stop_duration_candles": 5 + }, + { + "method": "MaxDrawdown", + "lookback_period_candles": 48, + "trade_limit": 20, + "stop_duration_candles": 4, + "max_allowed_drawdown": 0.2 + }, + { + "method": "StoplossGuard", + "lookback_period_candles": 24, + "trade_limit": 4, + "stop_duration_candles": 2, + "only_per_pair": False + }, + { + "method": "LowProfitPairs", + "lookback_period_candles": 6, + "trade_limit": 2, + "stop_duration_candles": 60, + "required_profit": 0.02 + }, + { + "method": "LowProfitPairs", + "lookback_period_candles": 24, + "trade_limit": 4, + "stop_duration_candles": 2, + "required_profit": 0.01 + } + ] # ... ``` diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 1b162f7c9..529d4cd6a 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -110,3 +110,6 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: "Please remove 'ticker_interval' from your configuration to continue operating." ) config['timeframe'] = config['ticker_interval'] + + if 'protections' in config: + logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7012333e9..9dce304c7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1330,7 +1330,7 @@ def test_process_removed_setting(mocker, default_conf, caplog): 'sectionB', 'somesetting') -def test_process_deprecated_ticker_interval(mocker, default_conf, caplog): +def test_process_deprecated_ticker_interval(default_conf, caplog): message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." config = deepcopy(default_conf) process_temporary_deprecated_settings(config) @@ -1352,6 +1352,17 @@ def test_process_deprecated_ticker_interval(mocker, default_conf, caplog): process_temporary_deprecated_settings(config) +def test_process_deprecated_protections(default_conf, caplog): + message = "DEPRECATED: Setting 'protections' in the configuration is deprecated." + config = deepcopy(default_conf) + process_temporary_deprecated_settings(config) + assert not log_has(message, caplog) + + config['protections'] = [] + process_temporary_deprecated_settings(config) + assert log_has(message, caplog) + + def test_flat_vars_to_nested_dict(caplog): test_args = { From 3c412233338afbd1648786e0b5dbaf484d0988e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 20:01:28 +0200 Subject: [PATCH 087/200] Add test for protections-hyperopt --- docs/hyperopt.md | 4 ++-- freqtrade/strategy/hyper.py | 3 ++- tests/optimize/test_hyperopt.py | 8 ++++++++ tests/strategy/strats/hyperoptable_strategy.py | 11 +++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index e11c93748..f869b9ac1 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -433,7 +433,7 @@ class MyAwesomeStrategy(IStrategy): stoploss = -0.05 timeframe = '15m' # Define the parameter spaces - coolback_lookback = IntParameter(2, 48, default=5, space="protection", optimize=True) + cooldown_lookback = IntParameter(2, 48, default=5, space="protection", optimize=True) stop_duration = IntParameter(12, 200, default=5, space="protection", optimize=True) use_stop_protection = CategoricalParameter([True, False], default=True, space="protection", optimize=True) @@ -444,7 +444,7 @@ class MyAwesomeStrategy(IStrategy): prot.append({ "method": "CooldownPeriod", - "stop_duration_candles": self.coolback_lookback.value + "stop_duration_candles": self.cooldown_lookback.value }) if self.use_stop_protection.value: prot.append({ diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py index e143dda7e..b9f586ac5 100644 --- a/freqtrade/strategy/hyper.py +++ b/freqtrade/strategy/hyper.py @@ -294,7 +294,8 @@ class HyperStrategyMixin(object): :return: """ if category not in ('buy', 'sell', 'protection', None): - raise OperationalException('Category must be one of: "buy", "sell", "protection", None.') + raise OperationalException( + 'Category must be one of: "buy", "sell", "protection", None.') if category is None: params = self.ft_buy_params + self.ft_sell_params + self.ft_protection_params diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index ef4b85beb..077fb516d 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -1003,6 +1003,8 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: hyperopt_conf.update({ 'strategy': 'HyperoptableStrategy', 'user_data_dir': Path(tmpdir), + 'hyperopt_random_state': 42, + 'spaces': ['buy', 'sell', 'protection'] }) hyperopt = Hyperopt(hyperopt_conf) assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) @@ -1010,12 +1012,18 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert hyperopt.backtesting.strategy.buy_rsi.value == 35 + assert hyperopt.backtesting.strategy.sell_rsi.value == 74 + assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range assert isinstance(buy_rsi_range, range) # Range from 0 - 50 (inclusive) assert len(list(buy_rsi_range)) == 51 hyperopt.start() + # All values should've changed. + assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value != 30 + assert hyperopt.backtesting.strategy.buy_rsi.value != 35 + assert hyperopt.backtesting.strategy.sell_rsi.value != 74 def test_SKDecimal(): diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index cc4734e13..9d332e243 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -64,6 +64,17 @@ class HyperoptableStrategy(IStrategy): sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', load=False) + protection_cooldown_lookback = IntParameter([0, 50], default=30) + + @property + def protections(self): + prot = [] + + prot.append({ + "method": "CooldownPeriod", + "stop_duration_candles": self.protection_cooldown_lookback.value + }) + return prot def informative_pairs(self): """ From b73768acd1244d5863bcf27fe868f6dfaa11f4f0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 20:10:32 +0200 Subject: [PATCH 088/200] Fix bug in property overwriting prevention --- freqtrade/resolvers/strategy_resolver.py | 2 +- tests/optimize/test_hyperopt.py | 2 +- tests/strategy/test_interface.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 1239b78b3..0a18809b4 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -119,7 +119,7 @@ class StrategyResolver(IResolver): - default (if not None) """ if (attribute in config - and not isinstance(getattr(type(strategy), 'my_property', None), property)): + and not isinstance(getattr(type(strategy), attribute, None), property)): # Ensure Properties are not overwritten setattr(strategy, attribute, config[attribute]) logger.info("Override strategy '%s' with value in config file: %s.", diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 077fb516d..4fba80bc3 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -1004,7 +1004,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: 'strategy': 'HyperoptableStrategy', 'user_data_dir': Path(tmpdir), 'hyperopt_random_state': 42, - 'spaces': ['buy', 'sell', 'protection'] + 'spaces': ['all'] }) hyperopt = Hyperopt(hyperopt_conf) assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index d8c87506c..9593cd02e 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -734,7 +734,7 @@ def test_auto_hyperopt_interface(default_conf): assert isinstance(all_params, dict) assert len(all_params['buy']) == 2 assert len(all_params['sell']) == 2 - assert all_params['count'] == 4 + assert all_params['count'] == 5 strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy') From ad0e4a8567592edb9078a27cbc2c0a8bd5fd178a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 20:52:56 +0200 Subject: [PATCH 089/200] Add BooleanParameter --- docs/hyperopt.md | 13 ++++++----- freqtrade/plugins/protections/iprotection.py | 3 +++ freqtrade/strategy/__init__.py | 4 ++-- freqtrade/strategy/hyper.py | 22 +++++++++++++++++++ freqtrade/templates/base_strategy.py.j2 | 4 ++-- freqtrade/templates/sample_strategy.py | 4 ++-- .../strategy/strats/hyperoptable_strategy.py | 14 +++++++----- tests/strategy/test_interface.py | 18 ++++++++++++--- 8 files changed, 61 insertions(+), 21 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index f869b9ac1..96f9ff177 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -253,7 +253,7 @@ We continue to define hyperoptable parameters: class MyAwesomeStrategy(IStrategy): buy_adx = DecimalParameter(20, 40, decimals=1, default=30.1, space="buy") buy_rsi = IntParameter(20, 40, default=30, space="buy") - buy_adx_enabled = CategoricalParameter([True, False], default=True, space="buy") + buy_adx_enabled = BooleanParameter(default=True, space="buy") buy_rsi_enabled = CategoricalParameter([True, False], default=False, space="buy") buy_trigger = CategoricalParameter(["bb_lower", "macd_cross_signal"], default="bb_lower", space="buy") ``` @@ -316,6 +316,7 @@ There are four parameter types each suited for different purposes. * `DecimalParameter` - defines a floating point parameter with a limited number of decimals (default 3). Should be preferred instead of `RealParameter` in most cases. * `RealParameter` - defines a floating point parameter with upper and lower boundaries and no precision limit. Rarely used as it creates a space with a near infinite number of possibilities. * `CategoricalParameter` - defines a parameter with a predetermined number of choices. +* `BooleanParameter` - Shorthand for `CategoricalParameter([True, False])` - great for "enable" parameters. !!! Tip "Disabling parameter optimization" Each parameter takes two boolean parameters: @@ -336,8 +337,8 @@ from functools import reduce import talib.abstract as ta -from freqtrade.strategy import IStrategy -from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter +from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, + IStrategy, IntParameter) import freqtrade.vendor.qtpylib.indicators as qtpylib class MyAwesomeStrategy(IStrategy): @@ -425,8 +426,8 @@ from functools import reduce import talib.abstract as ta -from freqtrade.strategy import IStrategy -from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter +from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, + IStrategy, IntParameter) import freqtrade.vendor.qtpylib.indicators as qtpylib class MyAwesomeStrategy(IStrategy): @@ -435,7 +436,7 @@ class MyAwesomeStrategy(IStrategy): # Define the parameter spaces cooldown_lookback = IntParameter(2, 48, default=5, space="protection", optimize=True) stop_duration = IntParameter(12, 200, default=5, space="protection", optimize=True) - use_stop_protection = CategoricalParameter([True, False], default=True, space="protection", optimize=True) + use_stop_protection = BooleanParameter(default=True, space="protection", optimize=True) @property diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index e6bb49064..e0a89e334 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -25,6 +25,9 @@ class IProtection(LoggingMixin, ABC): def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None: self._config = config self._protection_config = protection_config + self._stop_duration_candles: Optional[int] = None + self._lookback_period_candles: Optional[int] = None + tf_in_min = timeframe_to_minutes(config['timeframe']) if 'stop_duration_candles' in protection_config: self._stop_duration_candles = int(protection_config.get('stop_duration_candles', 1)) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index bd49165df..be655fc33 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1,7 +1,7 @@ # flake8: noqa: F401 from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date, timeframe_to_prev_date, timeframe_to_seconds) -from freqtrade.strategy.hyper import (CategoricalParameter, DecimalParameter, IntParameter, - RealParameter) +from freqtrade.strategy.hyper import (BooleanParameter, CategoricalParameter, DecimalParameter, + IntParameter, RealParameter) from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.strategy_helper import merge_informative_pair, stoploss_from_open diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py index b9f586ac5..dad282d7e 100644 --- a/freqtrade/strategy/hyper.py +++ b/freqtrade/strategy/hyper.py @@ -270,6 +270,28 @@ class CategoricalParameter(BaseParameter): return [self.value] +class BooleanParameter(CategoricalParameter): + + def __init__(self, *, default: Optional[Any] = None, + space: Optional[str] = None, optimize: bool = True, load: bool = True, **kwargs): + """ + Initialize hyperopt-optimizable Boolean Parameter. + It's a shortcut to `CategoricalParameter([True, False])`. + :param default: A default value. If not specified, first item from specified space will be + used. + :param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if + parameter field + name is prefixed with 'buy_' or 'sell_'. + :param optimize: Include parameter in hyperopt optimizations. + :param load: Load parameter value from {space}_params. + :param kwargs: Extra parameters to skopt.space.Categorical. + """ + + categories = [True, False] + super().__init__(categories=categories, default=default, space=space, optimize=optimize, + load=load, **kwargs) + + class HyperStrategyMixin(object): """ A helper base class which allows HyperOptAuto class to reuse implementations of buy/sell diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 13fc0853a..06d7cbc5c 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -6,8 +6,8 @@ import numpy as np # noqa import pandas as pd # noqa from pandas import DataFrame -from freqtrade.strategy import IStrategy -from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter +from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, + IStrategy, IntParameter) # -------------------------------- # Add your lib to import here diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 282b2f8e2..574819949 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -6,8 +6,8 @@ import numpy as np # noqa import pandas as pd # noqa from pandas import DataFrame -from freqtrade.strategy import IStrategy -from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter +from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, + IStrategy, IntParameter) # -------------------------------- # Add your lib to import here diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index 9d332e243..88bdd078e 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -4,7 +4,8 @@ import talib.abstract as ta from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib -from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy, RealParameter +from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy, + RealParameter) class HyperoptableStrategy(IStrategy): @@ -64,16 +65,17 @@ class HyperoptableStrategy(IStrategy): sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', load=False) + protection_enabled = BooleanParameter(default=True) protection_cooldown_lookback = IntParameter([0, 50], default=30) @property def protections(self): prot = [] - - prot.append({ - "method": "CooldownPeriod", - "stop_duration_candles": self.protection_cooldown_lookback.value - }) + if self.protection_enabled.value: + prot.append({ + "method": "CooldownPeriod", + "stop_duration_candles": self.protection_cooldown_lookback.value + }) return prot def informative_pairs(self): diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 9593cd02e..0ad6d6f32 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -16,8 +16,8 @@ from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.optimize.space import SKDecimal from freqtrade.persistence import PairLocks, Trade from freqtrade.resolvers import StrategyResolver -from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, DecimalParameter, - IntParameter, RealParameter) +from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, CategoricalParameter, + DecimalParameter, IntParameter, RealParameter) from freqtrade.strategy.interface import SellCheckTuple from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from tests.conftest import log_has, log_has_re @@ -717,6 +717,17 @@ def test_hyperopt_parameters(): assert len(list(catpar.range)) == 3 assert list(catpar.range) == ['buy_rsi', 'buy_macd', 'buy_none'] + boolpar = BooleanParameter(default=True, space='buy') + assert boolpar.value is True + assert isinstance(boolpar.get_space(''), Categorical) + assert isinstance(boolpar.range, list) + assert len(list(boolpar.range)) == 1 + + boolpar.in_space = True + assert len(list(boolpar.range)) == 2 + + assert list(boolpar.range) == [True, False] + def test_auto_hyperopt_interface(default_conf): default_conf.update({'strategy': 'HyperoptableStrategy'}) @@ -734,7 +745,8 @@ def test_auto_hyperopt_interface(default_conf): assert isinstance(all_params, dict) assert len(all_params['buy']) == 2 assert len(all_params['sell']) == 2 - assert all_params['count'] == 5 + # Number of Hyperoptable parameters + assert all_params['count'] == 6 strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy') From 2cf781f3dd8a0c87ec08b0ff08ebb2a5b63d6786 Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 4 Aug 2021 18:32:39 -0400 Subject: [PATCH 090/200] add freqtradebot execute_buy test in custom entry price case --- tests/test_freqtradebot.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index b1e02a99b..7ea67162d 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -904,6 +904,15 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order with pytest.raises(PricingError, match="Could not determine buy price."): freqtrade.execute_buy(pair, stake_amount) + # In case of custom entry price + limit_buy_order['status'] = 'open' + limit_buy_order['id'] = '5566' + freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.77 + assert freqtrade.execute_buy(pair, stake_amount) + trade = Trade.query.all()[6] + assert trade + assert trade.open_rate_requested == 0.77 + def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From d9b9eecd4d039cc0bc37baeb4075481b2f628b56 Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 4 Aug 2021 18:47:14 -0400 Subject: [PATCH 091/200] remove entry price reached method --- freqtrade/strategy/interface.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1cbc334f0..3d9ad0915 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -652,29 +652,6 @@ class IStrategy(ABC, HyperStrategyMixin): # logger.debug(f"{trade.pair} - No sell signal.") return SellCheckTuple(sell_type=SellType.NONE) - def entry_price_reached(self, pair: str, current_rate: float, - current_time: datetime, low: float = None, - high: float = None) -> bool: - """ - Based on current candle low ,decides if entry price was reached - :param current_rate: current rate - :param low: Low value of this candle, only set in backtesting - :param high: High value of this candle, only set in backtesting - """ - entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None)( - pair=pair, - current_time=current_time, - proposed_rate=current_rate) - - if entry_price_value is not None: - if entry_price_value > low: - return True - else: - return False - else: - logger.warning("CustomEntryPrice function did not return valid entry price") - return False - def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, current_profit: float, force_stoploss: float, low: float = None, From f9f519fd3c55f7e2f2040ec9cf3592bc22154711 Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 4 Aug 2021 18:54:17 -0400 Subject: [PATCH 092/200] add custom_exit_price function to interface --- freqtrade/strategy/interface.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 3d9ad0915..e1aba7d7a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -297,6 +297,23 @@ class IStrategy(ABC, HyperStrategyMixin): """ return proposed_rate + def custom_exit_price(self, pair: str, current_time: datetime, proposed_rate: float, + **kwargs) -> float: + """ + Custom exit price logic, returning the new exit price. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None, orderbook is used to set exit price + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: New exit price value if provided + """ + return proposed_rate + def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]: """ From f243ad4af0b8b1a5e8350db05f0717b16734bd27 Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 4 Aug 2021 19:09:55 -0400 Subject: [PATCH 093/200] add custom_exit_price in strategy interface --- freqtrade/strategy/interface.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index e1aba7d7a..f7d0a0aae 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -297,8 +297,9 @@ class IStrategy(ABC, HyperStrategyMixin): """ return proposed_rate - def custom_exit_price(self, pair: str, current_time: datetime, proposed_rate: float, - **kwargs) -> float: + def custom_exit_price(self, pair: str, trade: Trade, + current_time: datetime, proposed_rate: float, + current_rate: float, current_profit: float, **kwargs) -> float: """ Custom exit price logic, returning the new exit price. @@ -307,8 +308,10 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, returns None, orderbook is used to set exit price :param pair: Pair that's currently analyzed + :param trade: trade object. :param current_time: datetime object, containing the current datetime :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New exit price value if provided """ From cb3b0cf311d43583d18fe1b67878a8e1d286b834 Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 4 Aug 2021 23:09:40 -0400 Subject: [PATCH 094/200] add custom_exit_price in interface and freqtradebot --- freqtrade/freqtradebot.py | 8 ++++++++ freqtrade/strategy/interface.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2592ccc91..8d50b46e3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1082,6 +1082,14 @@ class FreqtradeBot(LoggingMixin): and self.strategy.order_types['stoploss_on_exchange']: limit = trade.stop_loss + # set custom_exit_price if available + current_profit = trade.calc_profit_ratio(limit) + limit = strategy_safe_wrapper(self.strategy.custom_exit_price, + default_retval=limit)( + pair=trade.pair, trade=trade, + current_time=datetime.now(timezone.utc), + proposed_rate=limit, current_profit=current_profit) + # First cancelling stoploss on exchange ... if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: try: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index f7d0a0aae..4c0e1c8a9 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -299,7 +299,7 @@ class IStrategy(ABC, HyperStrategyMixin): def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, - current_rate: float, current_profit: float, **kwargs) -> float: + current_profit: float, **kwargs) -> float: """ Custom exit price logic, returning the new exit price. From eee5f174fca4954e9a20d46c0cbded00547dcd49 Mon Sep 17 00:00:00 2001 From: Kamontat Chantrachirathumrong Date: Thu, 5 Aug 2021 20:33:15 +0700 Subject: [PATCH 095/200] update link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8cba78136..e10c15c11 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ hesitate to read the source code and understand the mechanism of this bot. Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange. - [X] [Bittrex](https://bittrex.com/) -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists)) +- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist)) - [X] [Kraken](https://kraken.com/) - [X] [FTX](https://ftx.com) - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ From 90c194de1f6ef83a8899bd0ed7873656f70c802a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 5 Aug 2021 19:27:38 +0200 Subject: [PATCH 096/200] Align Readme and documentation index in supported exchange lists --- README.md | 2 +- docs/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e10c15c11..78ea3cecd 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ hesitate to read the source code and understand the mechanism of this bot. Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange. -- [X] [Bittrex](https://bittrex.com/) - [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist)) +- [X] [Bittrex](https://bittrex.com/) - [X] [Kraken](https://kraken.com/) - [X] [FTX](https://ftx.com) - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ diff --git a/docs/index.md b/docs/index.md index 8077cd303..05eaa7552 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,7 +36,7 @@ Freqtrade is a crypto-currency algorithmic trading software developed in python Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange. -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](exchanges.md#blacklists)) +- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist)) - [X] [Bittrex](https://bittrex.com/) - [X] [FTX](https://ftx.com) - [X] [Kraken](https://kraken.com/) From 0aeebc9d53dad23f8ed2f02b0b91b11d6276cbea Mon Sep 17 00:00:00 2001 From: axel Date: Thu, 5 Aug 2021 17:57:45 -0400 Subject: [PATCH 097/200] add test for custom exit price --- tests/test_freqtradebot.py | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 7ea67162d..c73e51dec 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2724,6 +2724,70 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) } == last_msg +def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: + rpc_mock = patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + _is_dry_limit_order_filled=MagicMock(return_value=False), + ) + patch_whitelist(mocker, default_conf) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False) + + # Create some test data + freqtrade.enter_positions() + rpc_mock.reset_mock() + + trade = Trade.query.first() + assert trade + assert freqtrade.strategy.confirm_trade_exit.call_count == 0 + + # Increase the price and sell it + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker_sell_up + ) + + freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) + + # Set a custom exit price + freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05 + + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)) + + # Sell price must be different to default bid price + + assert freqtrade.strategy.confirm_trade_exit.call_count == 1 + + assert rpc_mock.call_count == 1 + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'trade_id': 1, + 'type': RPCMessageType.SELL, + 'exchange': 'Binance', + 'pair': 'ETH/BTC', + 'gain': 'profit', + 'limit': 1.170e-05, + 'amount': 91.07468123, + 'order_type': 'limit', + 'open_rate': 1.098e-05, + 'current_rate': 1.173e-05, + 'profit_amount': 6.041e-05, + 'profit_ratio': 0.06025919, + 'stake_currency': 'BTC', + 'fiat_currency': 'USD', + 'sell_reason': SellType.SELL_SIGNAL.value, + 'open_date': ANY, + 'close_date': ANY, + 'close_rate': ANY, + } == last_msg + + def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 84d082033b21cbffb02f4fd283a8bd33936733e4 Mon Sep 17 00:00:00 2001 From: axel Date: Thu, 5 Aug 2021 18:00:31 -0400 Subject: [PATCH 098/200] fix default retval for strategy custom_entry_price --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8d50b46e3..99f5d2894 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -481,7 +481,7 @@ class FreqtradeBot(LoggingMixin): # Calculate price buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy") custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, - default_retval=stake_amount)( + default_retval=buy_limit_requested)( pair=pair, current_time=datetime.now(timezone.utc), proposed_rate=buy_limit_requested) From 0985b11267b890aeeccaed79ebc4ed9644c39f99 Mon Sep 17 00:00:00 2001 From: axel Date: Thu, 5 Aug 2021 18:16:16 -0400 Subject: [PATCH 099/200] add doc for custom exit price --- docs/strategy-advanced.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index a8e54bbcf..f59cb8ef5 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -357,13 +357,13 @@ See [Dataframe access](#dataframe-access) for more information about dataframe u --- -## Custom order entry price rules +## Custom order price rules By default, freqtrade use the orderbook to automatically set an order price, you also have the option to create custom order prices based on your strategy. -You can use this feature by setting the `use_custom_entry_price` option to `true` in config and creating a custom_entry_price function. +You can use this feature by creating a custom_entry_price function in your strategy file to customize entry prices and custom_exit_price for exits. -### Custom order entry price exemple +### Custom order entry and exit price exemple ``` python from datetime import datetime, timedelta, timezone from freqtrade.persistence import Trade @@ -373,13 +373,23 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods def custom_entry_price(self, pair: str, current_time: datetime, - proposed_rate, **kwargs) -> float: + proposed_rate, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - entryprice = dataframe['bollinger_10_lowerband'].iat[-1] + proposed_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] - return entryprice + return proposed_entryprice + + def custom_exit_price(self, pair: str, trade: Trade, + current_time: datetime, proposed_rate: float, + current_profit: float, **kwargs) -> float: + + dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, + timeframe=self.timeframe) + proposed_exitprice = dataframe['bollinger_10_upperband'].iat[-1] + + return proposed_exitprice ``` From b9356a55644979c494f65e3235ab3b38a8cee1b1 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 6 Aug 2021 16:19:36 -0600 Subject: [PATCH 100/200] Autopep8 formatting --- freqtrade/commands/build_config_commands.py | 2 +- freqtrade/commands/deploy_commands.py | 14 ++++----- freqtrade/commands/hyperopt_commands.py | 8 ++--- freqtrade/configuration/check_exchange.py | 8 ++--- freqtrade/configuration/config_validation.py | 4 +-- .../configuration/deprecated_settings.py | 2 +- freqtrade/constants.py | 2 +- freqtrade/edge/edge_positioning.py | 6 ++-- freqtrade/main.py | 2 +- freqtrade/optimize/hyperopt.py | 6 ++-- freqtrade/optimize/hyperopt_tools.py | 2 +- freqtrade/optimize/optimize_reports.py | 6 ++-- freqtrade/plot/plotting.py | 4 +-- freqtrade/plugins/pairlist/IPairList.py | 2 +- freqtrade/plugins/pairlist/VolumePairList.py | 8 ++--- freqtrade/plugins/pairlistmanager.py | 14 ++++----- .../plugins/protections/stoploss_guard.py | 6 ++-- freqtrade/resolvers/__init__.py | 3 -- freqtrade/resolvers/strategy_resolver.py | 2 +- freqtrade/rpc/api_server/api_v1.py | 4 +-- freqtrade/rpc/fiat_convert.py | 2 +- freqtrade/rpc/rpc_manager.py | 1 + freqtrade/rpc/telegram.py | 22 +++++++------- freqtrade/strategy/strategy_helper.py | 2 +- tests/config_test_comments.json | 26 ++++++++-------- tests/optimize/test_hyperopt.py | 2 +- tests/plugins/test_protections.py | 4 +-- tests/rpc/test_fiat_convert.py | 6 ++-- tests/rpc/test_rpc_apiserver.py | 6 ++-- tests/test_arguments.py | 2 +- tests/test_configuration.py | 30 +++++++++---------- 31 files changed, 104 insertions(+), 104 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index b3f912433..852cab92e 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -193,7 +193,7 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: selections['exchange'] = render_template( templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections - ) + ) except TemplateNotFound: selections['exchange'] = render_template( templatefile="subtemplates/exchange_generic.j2", diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index cc0d653b9..eb65579e2 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -38,15 +38,15 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st indicators = render_template_with_fallback( templatefile=f"subtemplates/indicators_{subtemplate}.j2", templatefallbackfile=f"subtemplates/indicators_{fallback}.j2", - ) + ) buy_trend = render_template_with_fallback( templatefile=f"subtemplates/buy_trend_{subtemplate}.j2", templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2", - ) + ) sell_trend = render_template_with_fallback( templatefile=f"subtemplates/sell_trend_{subtemplate}.j2", templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2", - ) + ) plot_config = render_template_with_fallback( templatefile=f"subtemplates/plot_config_{subtemplate}.j2", templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2", @@ -97,19 +97,19 @@ def deploy_new_hyperopt(hyperopt_name: str, hyperopt_path: Path, subtemplate: st buy_guards = render_template_with_fallback( templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2", templatefallbackfile=f"subtemplates/hyperopt_buy_guards_{fallback}.j2", - ) + ) sell_guards = render_template_with_fallback( templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2", templatefallbackfile=f"subtemplates/hyperopt_sell_guards_{fallback}.j2", - ) + ) buy_space = render_template_with_fallback( templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2", templatefallbackfile=f"subtemplates/hyperopt_buy_space_{fallback}.j2", - ) + ) sell_space = render_template_with_fallback( templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2", templatefallbackfile=f"subtemplates/hyperopt_sell_space_{fallback}.j2", - ) + ) strategy_text = render_template(templatefile='base_hyperopt.py.j2', arguments={"hyperopt": hyperopt_name, diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 5a2727795..4694d1111 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -187,7 +187,7 @@ def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> Li x for x in epochs if x['results_metrics'].get( 'trade_count', x['results_metrics'].get('total_trades') - ) < filteroptions['filter_max_trades'] + ) < filteroptions['filter_max_trades'] ] return epochs @@ -239,7 +239,7 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: x for x in epochs if x['results_metrics'].get( 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 - ) < filteroptions['filter_max_avg_profit'] + ) < filteroptions['filter_max_avg_profit'] ] if filteroptions['filter_min_total_profit'] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) @@ -247,7 +247,7 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: x for x in epochs if x['results_metrics'].get( 'profit', x['results_metrics'].get('profit_total_abs', 0) - ) > filteroptions['filter_min_total_profit'] + ) > filteroptions['filter_min_total_profit'] ] if filteroptions['filter_max_total_profit'] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) @@ -255,7 +255,7 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: x for x in epochs if x['results_metrics'].get( 'profit', x['results_metrics'].get('profit_total_abs', 0) - ) < filteroptions['filter_max_total_profit'] + ) < filteroptions['filter_max_total_profit'] ] return epochs diff --git a/freqtrade/configuration/check_exchange.py b/freqtrade/configuration/check_exchange.py index f282447d4..c4f038103 100644 --- a/freqtrade/configuration/check_exchange.py +++ b/freqtrade/configuration/check_exchange.py @@ -51,10 +51,10 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool: if not is_exchange_known_ccxt(exchange): raise OperationalException( - f'Exchange "{exchange}" is not known to the ccxt library ' - f'and therefore not available for the bot.\n' - f'The following exchanges are available for Freqtrade: ' - f'{", ".join(available_exchanges())}' + f'Exchange "{exchange}" is not known to the ccxt library ' + f'and therefore not available for the bot.\n' + f'The following exchanges are available for Freqtrade: ' + f'{", ".join(available_exchanges())}' ) valid, reason = validate_exchange(exchange) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index aad03e983..85ff4408f 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -115,7 +115,7 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None: if conf.get('stoploss') == 0.0: raise OperationalException( 'The config stoploss needs to be different from 0 to avoid problems with sell orders.' - ) + ) # Skip if trailing stoploss is not activated if not conf.get('trailing_stop', False): return @@ -180,7 +180,7 @@ def _validate_protections(conf: Dict[str, Any]) -> None: raise OperationalException( "Protections must specify either `stop_duration` or `stop_duration_candles`.\n" f"Please fix the protection {prot.get('method')}" - ) + ) if ('lookback_period' in prot and 'lookback_period_candles' in prot): raise OperationalException( diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 1b162f7c9..e59e51f87 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -108,5 +108,5 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: raise OperationalException( "Both 'timeframe' and 'ticker_interval' detected." "Please remove 'ticker_interval' from your configuration to continue operating." - ) + ) config['timeframe'] = config['ticker_interval'] diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b48644c58..de4bc99b4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -280,7 +280,7 @@ CONF_SCHEMA = { 'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS, 'default': 'off' - }, + }, } }, 'reload': {'type': 'boolean'}, diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 977b7e4ec..243043d31 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -231,12 +231,12 @@ class Edge: 'Minimum expectancy and minimum winrate are met only for %s,' ' so other pairs are filtered out.', self._final_pairs - ) + ) else: logger.info( 'Edge removed all pairs as no pair with minimum expectancy ' 'and minimum winrate was found !' - ) + ) return self._final_pairs @@ -247,7 +247,7 @@ class Edge: final = [] for pair, info in self._cached_pairs.items(): if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ - info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): + info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): final.append({ 'Pair': pair, 'Winrate': info.winrate, diff --git a/freqtrade/main.py b/freqtrade/main.py index 84d4b24f8..2fd3d32bb 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -44,7 +44,7 @@ def main(sysargv: List[str] = None) -> None: "as `freqtrade trade [options...]`.\n" "To see the full list of options available, please use " "`freqtrade --help` or `freqtrade --help`." - ) + ) except SystemExit as e: return_code = e diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index a69e5a5a2..d40bbb73b 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -444,9 +444,9 @@ class Hyperopt: ' [', progressbar.ETA(), ', ', progressbar.Timer(), ']', ] with progressbar.ProgressBar( - max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False, - widgets=widgets - ) as pbar: + max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False, + widgets=widgets + ) as pbar: EVALS = ceil(self.total_epochs / jobs) for i in range(EVALS): # Correct the number of epochs to be processed for the last diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 439016c14..51f1f977a 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -203,7 +203,7 @@ class HyperoptTools(): elif space == "roi": result = result[:-1] + f'{appendix}\n' minimal_roi_result = rapidjson.dumps({ - str(k): v for k, v in (space_params or no_params).items() + str(k): v for k, v in (space_params or no_params).items() }, default=str, indent=4, number_mode=rapidjson.NM_NATIVE) result += f"minimal_roi = {minimal_roi_result}" elif space == "trailing": diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index eefacbbab..7bb60228a 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -31,7 +31,7 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N filename = Path.joinpath( recordfilename.parent, f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' - ).with_suffix(recordfilename.suffix) + ).with_suffix(recordfilename.suffix) file_dump_json(filename, stats) latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) @@ -173,7 +173,7 @@ def generate_strategy_comparison(all_results: Dict) -> List[Dict]: for strategy, results in all_results.items(): tabular_data.append(_generate_result_line( results['results'], results['config']['dry_run_wallet'], strategy) - ) + ) try: max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'], value_col='profit_ratio') @@ -604,7 +604,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency']) stake_amount = round_coin_value( strat_results['stake_amount'], strat_results['stake_currency'] - ) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited' + ) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited' message = ("No trades made. " f"Your starting balance was {start_balance}, " diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 061460975..2fbf343ce 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -334,8 +334,8 @@ def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots: ) elif indicator_b not in data: logger.info( - 'fill_to: "%s" ignored. Reason: This indicator is not ' - 'in your strategy.', indicator_b + 'fill_to: "%s" ignored. Reason: This indicator is not ' + 'in your strategy.', indicator_b ) return fig diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index 74348b1a7..bfde2ace0 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -144,7 +144,7 @@ class IPairList(LoggingMixin, ABC): markets = self._exchange.markets if not markets: raise OperationalException( - 'Markets not loaded. Make sure that exchange is initialized correctly.') + 'Markets not loaded. Make sure that exchange is initialized correctly.') sanitized_whitelist: List[str] = [] for pair in pairlist: diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index d6b8aaaa3..901fde2d0 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -120,9 +120,9 @@ class VolumePairList(IPairList): # Use fresh pairlist # Check if pair quote currency equals to the stake currency. filtered_tickers = [ - v for k, v in tickers.items() - if (self._exchange.get_pair_quote_currency(k) == self._stake_currency - and v[self._sort_key] is not None)] + v for k, v in tickers.items() + if (self._exchange.get_pair_quote_currency(k) == self._stake_currency + and v[self._sort_key] is not None)] pairlist = [s['symbol'] for s in filtered_tickers] pairlist = self.filter_pairlist(pairlist, tickers) @@ -197,7 +197,7 @@ class VolumePairList(IPairList): if self._min_value > 0: filtered_tickers = [ - v for v in filtered_tickers if v[self._sort_key] > self._min_value] + v for v in filtered_tickers if v[self._sort_key] > self._min_value] sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key]) diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py index 03f4760b8..face79729 100644 --- a/freqtrade/plugins/pairlistmanager.py +++ b/freqtrade/plugins/pairlistmanager.py @@ -28,13 +28,13 @@ class PairListManager(): self._tickers_needed = False for pairlist_handler_config in self._config.get('pairlists', None): pairlist_handler = PairListResolver.load_pairlist( - pairlist_handler_config['method'], - exchange=exchange, - pairlistmanager=self, - config=config, - pairlistconfig=pairlist_handler_config, - pairlist_pos=len(self._pairlist_handlers) - ) + pairlist_handler_config['method'], + exchange=exchange, + pairlistmanager=self, + config=config, + pairlistconfig=pairlist_handler_config, + pairlist_pos=len(self._pairlist_handlers) + ) self._tickers_needed |= pairlist_handler.needstickers self._pairlist_handlers.append(pairlist_handler) diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 45d393411..40edf1204 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -54,9 +54,9 @@ class StoplossGuard(IProtection): trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) trades = [trade for trade in trades1 if (str(trade.sell_reason) in ( - SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value, - SellType.STOPLOSS_ON_EXCHANGE.value) - and trade.close_profit and trade.close_profit < 0)] + SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value, + SellType.STOPLOSS_ON_EXCHANGE.value) + and trade.close_profit and trade.close_profit < 0)] if len(trades) < self._trade_limit: return False, None, None diff --git a/freqtrade/resolvers/__init__.py b/freqtrade/resolvers/__init__.py index ef24bf481..2f70a788a 100644 --- a/freqtrade/resolvers/__init__.py +++ b/freqtrade/resolvers/__init__.py @@ -8,6 +8,3 @@ from freqtrade.resolvers.exchange_resolver import ExchangeResolver from freqtrade.resolvers.pairlist_resolver import PairListResolver from freqtrade.resolvers.protection_resolver import ProtectionResolver from freqtrade.resolvers.strategy_resolver import StrategyResolver - - - diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 1239b78b3..82942bd68 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -50,7 +50,7 @@ class StrategyResolver(IResolver): if 'timeframe' not in config: logger.warning( "DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'." - ) + ) strategy.timeframe = strategy.ticker_interval if strategy._ft_params_from_file: diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 61d69707e..f2361fda8 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -199,8 +199,8 @@ def pair_history(pair: str, timeframe: str, timerange: str, strategy: str, config=Depends(get_config)): config = deepcopy(config) config.update({ - 'strategy': strategy, - }) + 'strategy': strategy, + }) return RPC._rpc_analysed_history_full(config, pair, timeframe, timerange) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 199e6a7db..cdc09b437 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -62,7 +62,7 @@ class CryptoToFiatConverter: # If the request is not a 429 error we want to raise the normal error logger.error( "Could not load FIAT Cryptocurrency map for the following problem: {}".format( - request_exception + request_exception ) ) except (Exception) as exception: diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 67842e849..8085ece94 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -15,6 +15,7 @@ class RPCManager: """ Class to manage RPC objects (Telegram, API, ...) """ + def __init__(self, freqtrade) -> None: """ Initializes all enabled rpc modules """ self.registered_modules: List[RPCHandler] = [] diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index a1f6a7e33..a988d2b60 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -77,7 +77,6 @@ class Telegram(RPCHandler): """ This class handles all telegram communication """ def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None: - """ Init the Telegram call, and init the super class RPCHandler :param rpc: instance of RPC Helper class @@ -270,7 +269,7 @@ class Telegram(RPCHandler): noti = '' if msg_type == RPCMessageType.SELL: sell_noti = self._config['telegram'] \ - .get('notification_settings', {}).get(str(msg_type), {}) + .get('notification_settings', {}).get(str(msg_type), {}) # For backward compatibility sell still can be string if isinstance(sell_noti, str): noti = sell_noti @@ -278,7 +277,7 @@ class Telegram(RPCHandler): noti = sell_noti.get(str(msg['sell_reason']), default_noti) else: noti = self._config['telegram'] \ - .get('notification_settings', {}).get(str(msg_type), default_noti) + .get('notification_settings', {}).get(str(msg_type), default_noti) if noti == 'off': logger.info(f"Notification '{msg_type}' not sent.") @@ -541,7 +540,7 @@ class Telegram(RPCHandler): f"`{first_trade_date}`\n" f"*Latest Trade opened:* `{latest_trade_date}\n`" f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`" - ) + ) if stats['closed_trade_count'] > 0: markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") @@ -576,13 +575,14 @@ class Telegram(RPCHandler): sell_reasons_msg = tabulate( sell_reasons_tabulate, headers=['Sell Reason', 'Sells', 'Wins', 'Losses'] - ) + ) durations = stats['durations'] - duration_msg = tabulate([ - ['Wins', str(timedelta(seconds=durations['wins'])) - if durations['wins'] != 'N/A' else 'N/A'], - ['Losses', str(timedelta(seconds=durations['losses'])) - if durations['losses'] != 'N/A' else 'N/A'] + duration_msg = tabulate( + [ + ['Wins', str(timedelta(seconds=durations['wins'])) + if durations['wins'] != 'N/A' else 'N/A'], + ['Losses', str(timedelta(seconds=durations['losses'])) + if durations['losses'] != 'N/A' else 'N/A'] ], headers=['', 'Avg. Duration'] ) @@ -1100,7 +1100,7 @@ class Telegram(RPCHandler): if reload_able: reply_markup = InlineKeyboardMarkup([ [InlineKeyboardButton("Refresh", callback_data=callback_path)], - ]) + ]) else: reply_markup = InlineKeyboardMarkup([[]]) msg += "\nUpdated: {}".format(datetime.now().ctime()) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 22b6f0be5..e089ebf31 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -38,7 +38,7 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, # Detailed explanation in https://github.com/freqtrade/freqtrade/issues/4073 informative['date_merge'] = ( informative["date"] + pd.to_timedelta(minutes_inf, 'm') - pd.to_timedelta(minutes, 'm') - ) + ) else: raise ValueError("Tried to merge a faster timeframe to a slower timeframe." "This would create new rows, and can throw off your regular indicators.") diff --git a/tests/config_test_comments.json b/tests/config_test_comments.json index 48a087dec..19d82c454 100644 --- a/tests/config_test_comments.json +++ b/tests/config_test_comments.json @@ -6,8 +6,8 @@ */ "stake_currency": "BTC", "stake_amount": 0.05, - "fiat_display_currency": "USD", // C++-style comment - "amount_reserve_percent" : 0.05, // And more, tabs before this comment + "fiat_display_currency": "USD", // C++-style comment + "amount_reserve_percent": 0.05, // And more, tabs before this comment "dry_run": false, "timeframe": "5m", "trailing_stop": false, @@ -15,15 +15,15 @@ "trailing_stop_positive_offset": 0.0051, "trailing_only_offset_is_reached": false, "minimal_roi": { - "40": 0.0, - "30": 0.01, - "20": 0.02, - "0": 0.04 + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 }, "stoploss": -0.10, "unfilledtimeout": { "buy": 10, - "sell": 30, // Trailing comma should also be accepted now + "sell": 30, // Trailing comma should also be accepted now }, "bid_strategy": { "use_order_book": false, @@ -34,7 +34,7 @@ "bids_to_ask_delta": 1 } }, - "ask_strategy":{ + "ask_strategy": { "use_order_book": false, "order_book_min": 1, "order_book_max": 9 @@ -64,7 +64,9 @@ "key": "your_exchange_key", "secret": "your_exchange_secret", "password": "", - "ccxt_config": {"enableRateLimit": true}, + "ccxt_config": { + "enableRateLimit": true + }, "ccxt_async_config": { "enableRateLimit": false, "rateLimit": 500, @@ -103,8 +105,8 @@ "remove_pumps": false }, "telegram": { -// We can now comment out some settings -// "enabled": true, + // We can now comment out some settings + // "enabled": true, "enabled": false, "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" @@ -124,4 +126,4 @@ }, "strategy": "DefaultStrategy", "strategy_path": "user_data/strategies/" -} +} \ No newline at end of file diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 14fea573f..b5197e73f 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -399,7 +399,7 @@ def test_hyperopt_format_results(hyperopt): 'rejected_signals': 2, 'backtest_start_time': 1619718665, 'backtest_end_time': 1619718665, - } + } results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result, Arrow(2017, 11, 14, 19, 32, 00), Arrow(2017, 12, 14, 19, 32, 00), market_change=0) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 9ec47dade..c0a9ae72a 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -93,7 +93,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog): Trade.query.session.add(generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, - )) + )) assert not freqtrade.protections.global_stop() assert not log_has_re(message, caplog) @@ -150,7 +150,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair Trade.query.session.add(generate_mock_trade( pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, profit_rate=0.9, - )) + )) assert not freqtrade.protections.stop_per_pair(pair) assert not freqtrade.protections.global_stop() diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 5174f9416..9fb1122f5 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -139,9 +139,9 @@ def test_fiat_too_many_requests_response(mocker, caplog): assert length_cryptomap == 0 assert fiat_convert._backoff > datetime.datetime.now().timestamp() assert log_has( - 'Too many requests for Coingecko API, backing off and trying again later.', - caplog - ) + 'Too many requests for Coingecko API, backing off and trying again later.', + caplog + ) def test_fiat_invalid_response(mocker, caplog): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 68f23e0fd..1517b6fcc 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -942,7 +942,7 @@ def test_api_whitelist(botclient): "whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'], "length": 4, "method": ["StaticPairList"] - } + } def test_api_forcebuy(botclient, mocker, fee): @@ -1033,7 +1033,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', - } + } def test_api_forcesell(botclient, mocker, ticker, fee, markets): @@ -1215,7 +1215,7 @@ def test_api_strategies(botclient): 'DefaultStrategy', 'HyperoptableStrategy', 'TestStrategyLegacy' - ]} + ]} def test_api_strategy(botclient): diff --git a/tests/test_arguments.py b/tests/test_arguments.py index fd6f162fd..5374881fa 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -125,7 +125,7 @@ def test_parse_args_backtesting_custom() -> None: '--strategy-list', 'DefaultStrategy', 'SampleStrategy' - ] + ] call_args = Arguments(args).get_parsed_arg() assert call_args['config'] == ['test_conf.json'] assert call_args['verbosity'] == 0 diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7012333e9..f97ccd488 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1130,17 +1130,17 @@ def test_pairlist_resolving_fallback(mocker): @pytest.mark.parametrize("setting", [ - ("ask_strategy", "use_sell_signal", True, - None, "use_sell_signal", False), - ("ask_strategy", "sell_profit_only", True, - None, "sell_profit_only", False), - ("ask_strategy", "sell_profit_offset", 0.1, - None, "sell_profit_offset", 0.01), - ("ask_strategy", "ignore_roi_if_buy_signal", True, - None, "ignore_roi_if_buy_signal", False), - ("ask_strategy", "ignore_buying_expired_candle_after", 5, - None, "ignore_buying_expired_candle_after", 6), - ]) + ("ask_strategy", "use_sell_signal", True, + None, "use_sell_signal", False), + ("ask_strategy", "sell_profit_only", True, + None, "sell_profit_only", False), + ("ask_strategy", "sell_profit_offset", 0.1, + None, "sell_profit_offset", 0.01), + ("ask_strategy", "ignore_roi_if_buy_signal", True, + None, "ignore_roi_if_buy_signal", False), + ("ask_strategy", "ignore_buying_expired_candle_after", 5, + None, "ignore_buying_expired_candle_after", 6), +]) def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog): patched_configuration_load_config_file(mocker, default_conf) @@ -1180,10 +1180,10 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca @pytest.mark.parametrize("setting", [ - ("experimental", "use_sell_signal", False), - ("experimental", "sell_profit_only", True), - ("experimental", "ignore_roi_if_buy_signal", True), - ]) + ("experimental", "use_sell_signal", False), + ("experimental", "sell_profit_only", True), + ("experimental", "ignore_roi_if_buy_signal", True), +]) def test_process_removed_settings(mocker, default_conf, setting): patched_configuration_load_config_file(mocker, default_conf) From 5393c55b513028a3159da23a19e241050766651b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Aug 2021 16:01:20 +0200 Subject: [PATCH 101/200] Document min_value for VolumePairList closes #5260 --- docs/includes/pairlists.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 995e49a2d..b92b05af9 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -58,7 +58,7 @@ This option must be configured along with `exchange.skip_pair_validation` in the When used in the chain of Pairlist Handlers in a non-leading position (after StaticPairList and other Pairlist Filters), `VolumePairList` considers outputs of previous Pairlist Handlers, adding its sorting/selection of the pairs by the trading volume. -When used on the leading position of the chain of Pairlist Handlers, it does not consider `pair_whitelist` configuration setting, but selects the top assets from all available markets (with matching stake-currency) on the exchange. +When used in the leading position of the chain of Pairlist Handlers, the `pair_whitelist` configuration setting is ignored. Instead, `VolumePairList` selects the top assets from all available markets with matching stake-currency on the exchange. The `refresh_period` setting allows to define the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). The pairlist cache (`refresh_period`) on `VolumePairList` is only applicable to generating pairlists. @@ -74,11 +74,14 @@ Filtering instances (not the first position in the list) will not apply any cach "method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume", + "min_value": 0, "refresh_period": 1800 } ], ``` +You can define a minimum volume with `min_value` - which will filter out pairs with a volume lower than the specified value in the specified timerange. + `VolumePairList` can also operate in an advanced mode to build volume over a given timerange of specified candle size. It utilizes exchange historical candle data, builds a typical price (calculated by (open+high+low)/3) and multiplies the typical price with every candle's volume. The sum is the `quoteVolume` over the given range. This allows different scenarios, for a more smoothened volume, when using longer ranges with larger candle sizes, or the opposite when using a short range with small candles. For convenience `lookback_days` can be specified, which will imply that 1d candles will be used for the lookback. In the example below the pairlist would be created based on the last 7 days: @@ -89,6 +92,7 @@ For convenience `lookback_days` can be specified, which will imply that 1d candl "method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume", + "min_value": 0, "refresh_period": 86400, "lookback_days": 7 } From cf70b34ff0504dcbffc1106bfbefd521bc1c3357 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Aug 2021 16:15:42 +0200 Subject: [PATCH 102/200] Add min_value to all volumepairlist samples --- docs/includes/pairlists.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index b92b05af9..6e23c9003 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -113,6 +113,7 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl "method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume", + "min_value": 0, "refresh_period": 3600, "lookback_timeframe": "1h", "lookback_period": 72 From 7eaadb2630a99c8f467751bdff071b52cdb098b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Aug 2021 09:50:50 +0200 Subject: [PATCH 103/200] Add custom-* methods to bot-basics closes #5370 --- docs/bot-basics.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 943af0362..44181abfa 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -35,7 +35,7 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Calls `check_buy_timeout()` strategy callback for open buy orders. * Calls `check_sell_timeout()` strategy callback for open sell orders. * Verifies existing positions and eventually places sell orders. - * Considers stoploss, ROI and sell-signal. + * Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`. * Determine sell-price based on `ask_strategy` configuration setting. * Before a sell order is placed, `confirm_trade_exit()` strategy callback is called. * Check if trade-slots are still available (if `max_open_trades` is reached). @@ -52,9 +52,10 @@ This loop will be repeated again and again until the bot is stopped. * Load historic data for configured pairlist. * Calls `bot_loop_start()` once. * Calculate indicators (calls `populate_indicators()` once per pair). -* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair) -* Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy) +* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair). * Loops per candle simulating entry and exit points. + * Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy). + * Call `custom_stoploss()` and `custom_sell()` to find custom exit points. * Generate backtest report output !!! Note From 0ae4eccea5ad7cd773e0d0e309617bfcf6a1e4ae Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Aug 2021 10:05:28 +0200 Subject: [PATCH 104/200] Refactor Hyperopt-list and hyperopt-show to reduce some duplicate code --- freqtrade/commands/hyperopt_commands.py | 180 +------------------ freqtrade/optimize/hyperopt_epoch_filters.py | 142 +++++++++++++++ freqtrade/optimize/hyperopt_tools.py | 28 ++- 3 files changed, 174 insertions(+), 176 deletions(-) create mode 100644 freqtrade/optimize/hyperopt_epoch_filters.py diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 4694d1111..089529d15 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -1,6 +1,6 @@ import logging from operator import itemgetter -from typing import Any, Dict, List +from typing import Any, Dict from colorama import init as colorama_init @@ -28,30 +28,12 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: no_details = config.get('hyperopt_list_no_details', False) no_header = False - filteroptions = { - 'only_best': config.get('hyperopt_list_best', False), - 'only_profitable': config.get('hyperopt_list_profitable', False), - 'filter_min_trades': config.get('hyperopt_list_min_trades', 0), - 'filter_max_trades': config.get('hyperopt_list_max_trades', 0), - 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), - 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), - 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), - 'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None), - 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), - 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None), - 'filter_min_objective': config.get('hyperopt_list_min_objective', None), - 'filter_max_objective': config.get('hyperopt_list_max_objective', None), - } - results_file = get_latest_hyperopt_file( config['user_data_dir'] / 'hyperopt_results', config.get('hyperoptexportfilename')) # Previous evaluations - epochs = HyperoptTools.load_previous_results(results_file) - total_epochs = len(epochs) - - epochs = hyperopt_filter_epochs(epochs, filteroptions) + epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config) if print_colorized: colorama_init(autoreset=True) @@ -59,7 +41,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: if not export_csv: try: print(HyperoptTools.get_result_table(config, epochs, total_epochs, - not filteroptions['only_best'], + not config.get('hyperopt_list_best', False), print_colorized, 0)) except KeyboardInterrupt: print('User interrupted..') @@ -71,7 +53,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: if epochs and export_csv: HyperoptTools.export_csv_file( - config, epochs, total_epochs, not filteroptions['only_best'], export_csv + config, epochs, total_epochs, not config.get('hyperopt_list_best', False), export_csv ) @@ -91,26 +73,9 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: n = config.get('hyperopt_show_index', -1) - filteroptions = { - 'only_best': config.get('hyperopt_list_best', False), - 'only_profitable': config.get('hyperopt_list_profitable', False), - 'filter_min_trades': config.get('hyperopt_list_min_trades', 0), - 'filter_max_trades': config.get('hyperopt_list_max_trades', 0), - 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), - 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), - 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), - 'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None), - 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), - 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None), - 'filter_min_objective': config.get('hyperopt_list_min_objective', None), - 'filter_max_objective': config.get('hyperopt_list_max_objective', None) - } - # Previous evaluations - epochs = HyperoptTools.load_previous_results(results_file) - total_epochs = len(epochs) + epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config) - epochs = hyperopt_filter_epochs(epochs, filteroptions) filtered_epochs = len(epochs) if n > filtered_epochs: @@ -137,138 +102,3 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: HyperoptTools.show_epoch_details(val, total_epochs, print_json, no_header, header_str="Epoch details") - - -def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List: - """ - Filter our items from the list of hyperopt results - TODO: after 2021.5 remove all "legacy" mode queries. - """ - if filteroptions['only_best']: - epochs = [x for x in epochs if x['is_best']] - if filteroptions['only_profitable']: - epochs = [x for x in epochs if x['results_metrics'].get( - 'profit', x['results_metrics'].get('profit_total', 0)) > 0] - - epochs = _hyperopt_filter_epochs_trade_count(epochs, filteroptions) - - epochs = _hyperopt_filter_epochs_duration(epochs, filteroptions) - - epochs = _hyperopt_filter_epochs_profit(epochs, filteroptions) - - epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions) - - logger.info(f"{len(epochs)} " + - ("best " if filteroptions['only_best'] else "") + - ("profitable " if filteroptions['only_profitable'] else "") + - "epochs found.") - return epochs - - -def _hyperopt_filter_epochs_trade(epochs: List, trade_count: int): - """ - Filter epochs with trade-counts > trades - """ - return [ - x for x in epochs - if x['results_metrics'].get( - 'trade_count', x['results_metrics'].get('total_trades', 0) - ) > trade_count - ] - - -def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> List: - - if filteroptions['filter_min_trades'] > 0: - epochs = _hyperopt_filter_epochs_trade(epochs, filteroptions['filter_min_trades']) - - if filteroptions['filter_max_trades'] > 0: - epochs = [ - x for x in epochs - if x['results_metrics'].get( - 'trade_count', x['results_metrics'].get('total_trades') - ) < filteroptions['filter_max_trades'] - ] - return epochs - - -def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List: - - def get_duration_value(x): - # Duration in minutes ... - if 'duration' in x['results_metrics']: - return x['results_metrics']['duration'] - else: - # New mode - if 'holding_avg_s' in x['results_metrics']: - avg = x['results_metrics']['holding_avg_s'] - return avg // 60 - raise OperationalException( - "Holding-average not available. Please omit the filter on average time, " - "or rerun hyperopt with this version") - - if filteroptions['filter_min_avg_time'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - epochs = [ - x for x in epochs - if get_duration_value(x) > filteroptions['filter_min_avg_time'] - ] - if filteroptions['filter_max_avg_time'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - epochs = [ - x for x in epochs - if get_duration_value(x) < filteroptions['filter_max_avg_time'] - ] - - return epochs - - -def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: - - if filteroptions['filter_min_avg_profit'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - epochs = [ - x for x in epochs - if x['results_metrics'].get( - 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 - ) > filteroptions['filter_min_avg_profit'] - ] - if filteroptions['filter_max_avg_profit'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - epochs = [ - x for x in epochs - if x['results_metrics'].get( - 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 - ) < filteroptions['filter_max_avg_profit'] - ] - if filteroptions['filter_min_total_profit'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - epochs = [ - x for x in epochs - if x['results_metrics'].get( - 'profit', x['results_metrics'].get('profit_total_abs', 0) - ) > filteroptions['filter_min_total_profit'] - ] - if filteroptions['filter_max_total_profit'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - epochs = [ - x for x in epochs - if x['results_metrics'].get( - 'profit', x['results_metrics'].get('profit_total_abs', 0) - ) < filteroptions['filter_max_total_profit'] - ] - return epochs - - -def _hyperopt_filter_epochs_objective(epochs: List, filteroptions: dict) -> List: - - if filteroptions['filter_min_objective'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - - epochs = [x for x in epochs if x['loss'] < filteroptions['filter_min_objective']] - if filteroptions['filter_max_objective'] is not None: - epochs = _hyperopt_filter_epochs_trade(epochs, 0) - - epochs = [x for x in epochs if x['loss'] > filteroptions['filter_max_objective']] - - return epochs diff --git a/freqtrade/optimize/hyperopt_epoch_filters.py b/freqtrade/optimize/hyperopt_epoch_filters.py new file mode 100644 index 000000000..2084c5ba8 --- /dev/null +++ b/freqtrade/optimize/hyperopt_epoch_filters.py @@ -0,0 +1,142 @@ +import logging +from typing import List + +from freqtrade.exceptions import OperationalException + + +logger = logging.getLogger(__name__) + + +def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List: + """ + Filter our items from the list of hyperopt results + TODO: after 2021.5 remove all "legacy" mode queries. + """ + if filteroptions['only_best']: + epochs = [x for x in epochs if x['is_best']] + if filteroptions['only_profitable']: + epochs = [x for x in epochs if x['results_metrics'].get( + 'profit', x['results_metrics'].get('profit_total', 0)) > 0] + + epochs = _hyperopt_filter_epochs_trade_count(epochs, filteroptions) + + epochs = _hyperopt_filter_epochs_duration(epochs, filteroptions) + + epochs = _hyperopt_filter_epochs_profit(epochs, filteroptions) + + epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions) + + logger.info(f"{len(epochs)} " + + ("best " if filteroptions['only_best'] else "") + + ("profitable " if filteroptions['only_profitable'] else "") + + "epochs found.") + return epochs + + +def _hyperopt_filter_epochs_trade(epochs: List, trade_count: int): + """ + Filter epochs with trade-counts > trades + """ + return [ + x for x in epochs + if x['results_metrics'].get( + 'trade_count', x['results_metrics'].get('total_trades', 0) + ) > trade_count + ] + + +def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> List: + + if filteroptions['filter_min_trades'] > 0: + epochs = _hyperopt_filter_epochs_trade(epochs, filteroptions['filter_min_trades']) + + if filteroptions['filter_max_trades'] > 0: + epochs = [ + x for x in epochs + if x['results_metrics'].get( + 'trade_count', x['results_metrics'].get('total_trades') + ) < filteroptions['filter_max_trades'] + ] + return epochs + + +def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List: + + def get_duration_value(x): + # Duration in minutes ... + if 'duration' in x['results_metrics']: + return x['results_metrics']['duration'] + else: + # New mode + if 'holding_avg_s' in x['results_metrics']: + avg = x['results_metrics']['holding_avg_s'] + return avg // 60 + raise OperationalException( + "Holding-average not available. Please omit the filter on average time, " + "or rerun hyperopt with this version") + + if filteroptions['filter_min_avg_time'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + epochs = [ + x for x in epochs + if get_duration_value(x) > filteroptions['filter_min_avg_time'] + ] + if filteroptions['filter_max_avg_time'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + epochs = [ + x for x in epochs + if get_duration_value(x) < filteroptions['filter_max_avg_time'] + ] + + return epochs + + +def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: + + if filteroptions['filter_min_avg_profit'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + epochs = [ + x for x in epochs + if x['results_metrics'].get( + 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 + ) > filteroptions['filter_min_avg_profit'] + ] + if filteroptions['filter_max_avg_profit'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + epochs = [ + x for x in epochs + if x['results_metrics'].get( + 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 + ) < filteroptions['filter_max_avg_profit'] + ] + if filteroptions['filter_min_total_profit'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + epochs = [ + x for x in epochs + if x['results_metrics'].get( + 'profit', x['results_metrics'].get('profit_total_abs', 0) + ) > filteroptions['filter_min_total_profit'] + ] + if filteroptions['filter_max_total_profit'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + epochs = [ + x for x in epochs + if x['results_metrics'].get( + 'profit', x['results_metrics'].get('profit_total_abs', 0) + ) < filteroptions['filter_max_total_profit'] + ] + return epochs + + +def _hyperopt_filter_epochs_objective(epochs: List, filteroptions: dict) -> List: + + if filteroptions['filter_min_objective'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + + epochs = [x for x in epochs if x['loss'] < filteroptions['filter_min_objective']] + if filteroptions['filter_max_objective'] is not None: + epochs = _hyperopt_filter_epochs_trade(epochs, 0) + + epochs = [x for x in epochs if x['loss'] > filteroptions['filter_max_objective']] + + return epochs diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 52aa85c84..ed9583bdc 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -4,7 +4,7 @@ import logging from copy import deepcopy from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple import numpy as np import rapidjson @@ -15,6 +15,7 @@ from pandas import isna, json_normalize from freqtrade.constants import FTHYPT_FILEVERSION, USERPATH_STRATEGIES from freqtrade.exceptions import OperationalException from freqtrade.misc import deep_merge_dicts, round_coin_value, round_dict, safe_value_fallback2 +from freqtrade.optimize.hyperopt_epoch_filters import hyperopt_filter_epochs logger = logging.getLogger(__name__) @@ -130,6 +131,31 @@ class HyperoptTools(): logger.info(f"Loaded {len(epochs)} previous evaluations from disk.") return epochs + @staticmethod + def load_filtered_results(results_file: Path, config: Dict[str, Any]) -> Tuple[List, int]: + filteroptions = { + 'only_best': config.get('hyperopt_list_best', False), + 'only_profitable': config.get('hyperopt_list_profitable', False), + 'filter_min_trades': config.get('hyperopt_list_min_trades', 0), + 'filter_max_trades': config.get('hyperopt_list_max_trades', 0), + 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), + 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), + 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), + 'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None), + 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), + 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None), + 'filter_min_objective': config.get('hyperopt_list_min_objective', None), + 'filter_max_objective': config.get('hyperopt_list_max_objective', None), + } + + # Previous evaluations + epochs = HyperoptTools.load_previous_results(results_file) + total_epochs = len(epochs) + + epochs = hyperopt_filter_epochs(epochs, filteroptions) + + return epochs, total_epochs + @staticmethod def show_epoch_details(results, total_epochs: int, print_json: bool, no_header: bool = False, header_str: str = None) -> None: From faf16a64e507dc684d527a804abef266e39c26c6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Aug 2021 10:22:45 +0200 Subject: [PATCH 105/200] Remove legacy hyperopt file support --- freqtrade/optimize/hyperopt_epoch_filters.py | 50 +- tests/commands/test_commands.py | 464 +++++++++---------- tests/conftest.py | 132 ------ 3 files changed, 249 insertions(+), 397 deletions(-) diff --git a/freqtrade/optimize/hyperopt_epoch_filters.py b/freqtrade/optimize/hyperopt_epoch_filters.py index 2084c5ba8..b70db94af 100644 --- a/freqtrade/optimize/hyperopt_epoch_filters.py +++ b/freqtrade/optimize/hyperopt_epoch_filters.py @@ -10,13 +10,12 @@ logger = logging.getLogger(__name__) def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List: """ Filter our items from the list of hyperopt results - TODO: after 2021.5 remove all "legacy" mode queries. """ if filteroptions['only_best']: epochs = [x for x in epochs if x['is_best']] if filteroptions['only_profitable']: - epochs = [x for x in epochs if x['results_metrics'].get( - 'profit', x['results_metrics'].get('profit_total', 0)) > 0] + epochs = [x for x in epochs + if x['results_metrics'].get('profit_total', 0) > 0] epochs = _hyperopt_filter_epochs_trade_count(epochs, filteroptions) @@ -38,10 +37,7 @@ def _hyperopt_filter_epochs_trade(epochs: List, trade_count: int): Filter epochs with trade-counts > trades """ return [ - x for x in epochs - if x['results_metrics'].get( - 'trade_count', x['results_metrics'].get('total_trades', 0) - ) > trade_count + x for x in epochs if x['results_metrics'].get('total_trades', 0) > trade_count ] @@ -53,9 +49,7 @@ def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> Li if filteroptions['filter_max_trades'] > 0: epochs = [ x for x in epochs - if x['results_metrics'].get( - 'trade_count', x['results_metrics'].get('total_trades') - ) < filteroptions['filter_max_trades'] + if x['results_metrics'].get('total_trades') < filteroptions['filter_max_trades'] ] return epochs @@ -64,16 +58,12 @@ def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List: def get_duration_value(x): # Duration in minutes ... - if 'duration' in x['results_metrics']: - return x['results_metrics']['duration'] - else: - # New mode - if 'holding_avg_s' in x['results_metrics']: - avg = x['results_metrics']['holding_avg_s'] - return avg // 60 - raise OperationalException( - "Holding-average not available. Please omit the filter on average time, " - "or rerun hyperopt with this version") + if 'holding_avg_s' in x['results_metrics']: + avg = x['results_metrics']['holding_avg_s'] + return avg // 60 + raise OperationalException( + "Holding-average not available. Please omit the filter on average time, " + "or rerun hyperopt with this version") if filteroptions['filter_min_avg_time'] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) @@ -97,33 +87,29 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: epochs = _hyperopt_filter_epochs_trade(epochs, 0) epochs = [ x for x in epochs - if x['results_metrics'].get( - 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 - ) > filteroptions['filter_min_avg_profit'] + if x['results_metrics'].get('profit_mean', 0) * 100 + > filteroptions['filter_min_avg_profit'] ] if filteroptions['filter_max_avg_profit'] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) epochs = [ x for x in epochs - if x['results_metrics'].get( - 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 - ) < filteroptions['filter_max_avg_profit'] + if x['results_metrics'].get('profit_mean', 0) * 100 + < filteroptions['filter_max_avg_profit'] ] if filteroptions['filter_min_total_profit'] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) epochs = [ x for x in epochs - if x['results_metrics'].get( - 'profit', x['results_metrics'].get('profit_total_abs', 0) - ) > filteroptions['filter_min_total_profit'] + if x['results_metrics'].get('profit_total_abs', 0) + > filteroptions['filter_min_total_profit'] ] if filteroptions['filter_max_total_profit'] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) epochs = [ x for x in epochs - if x['results_metrics'].get( - 'profit', x['results_metrics'].get('profit_total_abs', 0) - ) < filteroptions['filter_max_total_profit'] + if x['results_metrics'].get('profit_total_abs', 0) + < filteroptions['filter_max_total_profit'] ] return epochs diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index c0268038a..80dd04b27 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -938,241 +938,239 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): pytest.fail(f'Expected well formed JSON, but failed to parse: {captured.out}') -def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, - saved_hyperopt_results_legacy, tmpdir): +def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir): csv_file = Path(tmpdir) / "test.csv" - for res in (saved_hyperopt_results, saved_hyperopt_results_legacy): - mocker.patch( - 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results', - MagicMock(return_value=res) - ) + mocker.patch( + 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results', + MagicMock(return_value=saved_hyperopt_results) + ) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", - " 6/12", " 7/12", " 8/12", " 9/12", " 10/12", - " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--best", - "--no-details", - "--no-color", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 1/12", " 5/12", " 10/12"]) - assert all(x not in captured.out - for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12", - " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--profitable", - "--no-details", - "--no-color", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 2/12", " 10/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", - " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--profitable", - "--no-color", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params", - "Sell hyperspace params", "ROI table", "Stoploss"]) - assert all(x not in captured.out - for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", - " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - "--min-trades", "20", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"]) - args = [ - "hyperopt-list", - "--profitable", - "--no-details", - "--no-color", - "--max-trades", "20", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 2/12", " 10/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", - " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--profitable", - "--no-details", - "--no-color", - "--min-avg-profit", "0.11", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 2/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", - " 10/12", " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - "--max-avg-profit", "0.10", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 1/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", - " 11/12"]) - assert all(x not in captured.out - for x in [" 2/12", " 4/12", " 10/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - "--min-total-profit", "0.4", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 10/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", - " 9/12", " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - "--max-total-profit", "0.4", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", - " 9/12", " 11/12"]) - assert all(x not in captured.out - for x in [" 4/12", " 10/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - "--min-objective", "0.1", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 10/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", - " 9/12", " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--max-objective", "0.1", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", - " 9/12", " 11/12"]) - assert all(x not in captured.out - for x in [" 4/12", " 10/12", " 12/12"]) - args = [ - "hyperopt-list", - "--profitable", - "--no-details", - "--no-color", - "--min-avg-time", "2000", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 10/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", - " 8/12", " 9/12", " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - "--max-avg-time", "1500", - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - assert all(x in captured.out - for x in [" 2/12", " 6/12"]) - assert all(x not in captured.out - for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" - " 9/12", " 10/12", " 11/12", " 12/12"]) - args = [ - "hyperopt-list", - "--no-details", - "--no-color", - "--export-csv", - str(csv_file), - ] - pargs = get_args(args) - pargs['config'] = None - start_hyperopt_list(pargs) - captured = capsys.readouterr() - log_has("CSV file created: test_file.csv", caplog) - assert csv_file.is_file() - line = csv_file.read_text() - assert ('Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,"3,930.0 m",0.43662' in line - or "Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,2 days 17:30:00,0.43662" in line) - csv_file.unlink() + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", + " 6/12", " 7/12", " 8/12", " 9/12", " 10/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--best", + "--no-details", + "--no-color", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 5/12", " 10/12"]) + assert all(x not in captured.out + for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-details", + "--no-color", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-color", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 10/12", "Best result:", "Buy hyperspace params", + "Sell hyperspace params", "ROI table", "Stoploss"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--min-trades", "20", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-details", + "--no-color", + "--max-trades", "20", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-details", + "--no-color", + "--min-avg-profit", "0.11", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 10/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--max-avg-profit", "0.10", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12"]) + assert all(x not in captured.out + for x in [" 2/12", " 4/12", " 10/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--min-total-profit", "0.4", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", + " 9/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--max-total-profit", "0.4", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", + " 9/12", " 11/12"]) + assert all(x not in captured.out + for x in [" 4/12", " 10/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--min-objective", "0.1", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", + " 9/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--max-objective", "0.1", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", + " 9/12", " 11/12"]) + assert all(x not in captured.out + for x in [" 4/12", " 10/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-details", + "--no-color", + "--min-avg-time", "2000", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", + " 8/12", " 9/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--max-avg-time", "1500", + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 6/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" + " 9/12", " 10/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--export-csv", + str(csv_file), + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + log_has("CSV file created: test_file.csv", caplog) + assert csv_file.is_file() + line = csv_file.read_text() + assert ('Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,"3,930.0 m",0.43662' in line + or "Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,2 days 17:30:00,0.43662" in line) + csv_file.unlink() def test_hyperopt_show(mocker, capsys, saved_hyperopt_results): diff --git a/tests/conftest.py b/tests/conftest.py index 1924e1f95..0c9a96e2b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1814,138 +1814,6 @@ def open_trade(): ) -@pytest.fixture -def saved_hyperopt_results_legacy(): - return [ - { - 'loss': 0.4366182531161519, - 'params_dict': { - 'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501 - 'results_metrics': {'trade_count': 2, 'avg_profit': -1.254995, 'median_profit': -1.2222, 'total_profit': -0.00125625, 'profit': -2.50999, 'duration': 3930.0}, # noqa: E501 - 'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501 - 'total_profit': -0.00125625, - 'current_epoch': 1, - 'is_initial_point': True, - 'is_best': True - }, { - 'loss': 20.0, - 'params_dict': { - 'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 334, 'roi_t2': 683, 'roi_t3': 140, 'roi_p1': 0.06403981740598495, 'roi_p2': 0.055519840060645045, 'roi_p3': 0.3253712811342459, 'stoploss': -0.338070047333259}, # noqa: E501 - 'params_details': { - 'buy': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, # noqa: E501 - 'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501 - 'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501 - 'stoploss': {'stoploss': -0.338070047333259}}, - 'results_metrics': {'trade_count': 1, 'avg_profit': 0.12357, 'median_profit': -1.2222, 'total_profit': 6.185e-05, 'profit': 0.12357, 'duration': 1200.0}, # noqa: E501 - 'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501 - 'total_profit': 6.185e-05, - 'current_epoch': 2, - 'is_initial_point': True, - 'is_best': False - }, { - 'loss': 14.241196856510731, - 'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501 - 'results_metrics': {'trade_count': 621, 'avg_profit': -0.43883302093397747, 'median_profit': -1.2222, 'total_profit': -0.13639474, 'profit': -272.515306, 'duration': 1691.207729468599}, # noqa: E501 - 'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501 - 'total_profit': -0.13639474, - 'current_epoch': 3, - 'is_initial_point': True, - 'is_best': False - }, { - 'loss': 100000, - 'params_dict': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1402, 'roi_t2': 676, 'roi_t3': 215, 'roi_p1': 0.06264755784937427, 'roi_p2': 0.14258587851894644, 'roi_p3': 0.20671291201040828, 'stoploss': -0.11818343570194478}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, # noqa: E501 - 'results_metrics': {'trade_count': 0, 'avg_profit': None, 'median_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, # noqa: E501 - 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501 - 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_best': False - }, { - 'loss': 0.22195522184191518, - 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501 - 'results_metrics': {'trade_count': 14, 'avg_profit': -0.3539515, 'median_profit': -1.2222, 'total_profit': -0.002480140000000001, 'profit': -4.955321, 'duration': 3402.8571428571427}, # noqa: E501 - 'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501 - 'total_profit': -0.002480140000000001, - 'current_epoch': 5, - 'is_initial_point': True, - 'is_best': True - }, { - 'loss': 0.545315889154162, - 'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501 - 'results_metrics': {'trade_count': 39, 'avg_profit': -0.21400679487179478, 'median_profit': -1.2222, 'total_profit': -0.0041773, 'profit': -8.346264999999997, 'duration': 636.9230769230769}, # noqa: E501 - 'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501 - 'total_profit': -0.0041773, - 'current_epoch': 6, - 'is_initial_point': True, - 'is_best': False - }, { - 'loss': 4.713497421432944, - 'params_dict': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 771, 'roi_t2': 620, 'roi_t3': 145, 'roi_p1': 0.0586919200378493, 'roi_p2': 0.04984118697312542, 'roi_p3': 0.37521058680247044, 'stoploss': -0.14613268022709905}, # noqa: E501 - 'params_details': { - 'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501 - 'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501 - 'results_metrics': {'trade_count': 318, 'avg_profit': -0.39833954716981146, 'median_profit': -1.2222, 'total_profit': -0.06339929, 'profit': -126.67197600000004, 'duration': 3140.377358490566}, # noqa: E501 - 'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501 - 'total_profit': -0.06339929, - 'current_epoch': 7, - 'is_initial_point': True, - 'is_best': False - }, { - 'loss': 20.0, # noqa: E501 - 'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501 - 'results_metrics': {'trade_count': 1, 'avg_profit': 0.0, 'median_profit': 0.0, 'total_profit': 0.0, 'profit': 0.0, 'duration': 5340.0}, # noqa: E501 - 'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501 - 'total_profit': 0.0, - 'current_epoch': 8, - 'is_initial_point': True, - 'is_best': False - }, { - 'loss': 2.4731817780991223, - 'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501 - 'results_metrics': {'trade_count': 229, 'avg_profit': -0.38433433624454144, 'median_profit': -1.2222, 'total_profit': -0.044050070000000004, 'profit': -88.01256299999999, 'duration': 6505.676855895196}, # noqa: E501 - 'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501 - 'total_profit': -0.044050070000000004, # noqa: E501 - 'current_epoch': 9, - 'is_initial_point': True, - 'is_best': False - }, { - 'loss': -0.2604606005845212, # noqa: E501 - 'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501 - 'results_metrics': {'trade_count': 4, 'avg_profit': 0.1080385, 'median_profit': -1.2222, 'total_profit': 0.00021629, 'profit': 0.432154, 'duration': 2850.0}, # noqa: E501 - 'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501 - 'total_profit': 0.00021629, - 'current_epoch': 10, - 'is_initial_point': True, - 'is_best': True - }, { - 'loss': 4.876465945994304, # noqa: E501 - 'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501 - 'results_metrics': {'trade_count': 117, 'avg_profit': -1.2698609145299145, 'median_profit': -1.2222, 'total_profit': -0.07436117, 'profit': -148.573727, 'duration': 4282.5641025641025}, # noqa: E501 - 'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501 - 'total_profit': -0.07436117, - 'current_epoch': 11, - 'is_initial_point': True, - 'is_best': False - }, { - 'loss': 100000, - 'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501 - 'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501 - 'results_metrics': {'trade_count': 0, 'avg_profit': None, 'median_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, # noqa: E501 - 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501 - 'total_profit': 0, - 'current_epoch': 12, - 'is_initial_point': True, - 'is_best': False - } - ] - - @pytest.fixture def saved_hyperopt_results(): hyperopt_res = [ From be240566ba1df4eb282b79f17e69adbd02a2e401 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Aug 2021 10:57:20 +0200 Subject: [PATCH 106/200] Fix random test failure --- tests/strategy/test_interface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 0ad6d6f32..dc51f0811 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -552,6 +552,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> def test_is_pair_locked(default_conf): default_conf.update({'strategy': 'DefaultStrategy'}) PairLocks.timeframe = default_conf['timeframe'] + PairLocks.use_db = True strategy = StrategyResolver.load_strategy(default_conf) # No lock should be present assert len(PairLocks.get_pair_locks(None)) == 0 From 3bd0c3d0098bbcab9a9b8444e785ff43fbfc9fe7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Aug 2021 11:02:54 +0200 Subject: [PATCH 107/200] Remove legacy code from export to csv --- freqtrade/optimize/hyperopt_tools.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index ed9583bdc..b51db4db2 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -459,21 +459,14 @@ class HyperoptTools(): trials['Best'] = '' trials['Stake currency'] = config['stake_currency'] - if 'results_metrics.total_trades' in trials: - base_metrics = ['Best', 'current_epoch', 'results_metrics.total_trades', - 'results_metrics.profit_mean', 'results_metrics.profit_median', - 'results_metrics.profit_total', - 'Stake currency', - 'results_metrics.profit_total_abs', 'results_metrics.holding_avg', - 'loss', 'is_initial_point', 'is_best'] - perc_multi = 100 - else: - perc_multi = 1 - base_metrics = ['Best', 'current_epoch', 'results_metrics.trade_count', - 'results_metrics.avg_profit', 'results_metrics.median_profit', - 'results_metrics.total_profit', - 'Stake currency', 'results_metrics.profit', 'results_metrics.duration', - 'loss', 'is_initial_point', 'is_best'] + base_metrics = ['Best', 'current_epoch', 'results_metrics.total_trades', + 'results_metrics.profit_mean', 'results_metrics.profit_median', + 'results_metrics.profit_total', + 'Stake currency', + 'results_metrics.profit_total_abs', 'results_metrics.holding_avg', + 'loss', 'is_initial_point', 'is_best'] + perc_multi = 100 + param_metrics = [("params_dict."+param) for param in results[0]['params_dict'].keys()] trials = trials[base_metrics + param_metrics] @@ -501,11 +494,6 @@ class HyperoptTools(): trials['Avg profit'] = trials['Avg profit'].apply( lambda x: f'{x * perc_multi:,.2f}%' if not isna(x) else "" ) - if perc_multi == 1: - trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: f'{x:,.1f} m' if isinstance( - x, float) else f"{x.total_seconds() // 60:,.1f} m" if not isna(x) else "" - ) trials['Objective'] = trials['Objective'].apply( lambda x: f'{x:,.5f}' if x != 100000 else "" ) From 32e8e3b24258e1a7d12bde1d107d222ef1ee8fd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 03:01:19 +0000 Subject: [PATCH 108/200] Bump types-cachetools from 0.1.9 to 0.1.10 Bumps [types-cachetools](https://github.com/python/typeshed) from 0.1.9 to 0.1.10. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c1f7d6486..7b7727bd5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,7 +19,7 @@ isort==5.9.3 nbconvert==6.1.0 # mypy types -types-cachetools==0.1.9 +types-cachetools==0.1.10 types-filelock==0.1.4 types-requests==2.25.1 types-tabulate==0.1.1 From bad25b753c679218557fce23488b13bfe20bc609 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 03:01:25 +0000 Subject: [PATCH 109/200] Bump ccxt from 1.54.24 to 1.54.62 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.54.24 to 1.54.62. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.54.24...1.54.62) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6c2ef56c3..60175c12f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.21.1 pandas==1.3.1 -ccxt==1.54.24 +ccxt==1.54.62 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 From 59626b4ffcaa7f348d8e6986b274d32a65b54009 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 03:01:27 +0000 Subject: [PATCH 110/200] Bump types-tabulate from 0.1.1 to 0.8.2 Bumps [types-tabulate](https://github.com/python/typeshed) from 0.1.1 to 0.8.2. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c1f7d6486..cbb59be4e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,4 +22,4 @@ nbconvert==6.1.0 types-cachetools==0.1.9 types-filelock==0.1.4 types-requests==2.25.1 -types-tabulate==0.1.1 +types-tabulate==0.8.2 From b89a993890224ead95a8652bc0a83dde89c87838 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 04:47:41 +0000 Subject: [PATCH 111/200] Bump types-filelock from 0.1.4 to 0.1.5 Bumps [types-filelock](https://github.com/python/typeshed) from 0.1.4 to 0.1.5. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-filelock dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8f0cc0b34..7128721c5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,6 +20,6 @@ nbconvert==6.1.0 # mypy types types-cachetools==0.1.10 -types-filelock==0.1.4 +types-filelock==0.1.5 types-requests==2.25.1 types-tabulate==0.8.2 From 47f641d12f9d7813f80369f2691a1633168331a1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Aug 2021 07:03:13 +0200 Subject: [PATCH 112/200] Remove hyperopt-pickle result support --- freqtrade/optimize/hyperopt_tools.py | 17 ++++------------- tests/optimize/test_hyperopt_tools.py | 27 +++------------------------ 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index b51db4db2..0bb6aba15 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -89,18 +89,6 @@ class HyperoptTools(): else: return any(s in config['spaces'] for s in [space, 'all', 'default']) - @staticmethod - def _read_results_pickle(results_file: Path) -> List: - """ - Read hyperopt results from pickle file - LEGACY method - new files are written as json and cannot be read with this method. - """ - from joblib import load - - logger.info(f"Reading pickled epochs from '{results_file}'") - data = load(results_file) - return data - @staticmethod def _read_results(results_file: Path) -> List: """ @@ -120,7 +108,10 @@ class HyperoptTools(): epochs: List = [] if results_file.is_file() and results_file.stat().st_size > 0: if results_file.suffix == '.pickle': - epochs = HyperoptTools._read_results_pickle(results_file) + raise OperationalException( + "Legacy hyperopt results are no longer supported." + "Please rerun hyperopt or use an older version to load this file." + ) else: epochs = HyperoptTools._read_results(results_file) # Detection of some old format, without 'is_best' field saved diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index 44b4a7a03..d59a44da7 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -10,7 +10,7 @@ import rapidjson from freqtrade.constants import FTHYPT_FILEVERSION from freqtrade.exceptions import OperationalException from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer -from tests.conftest import log_has, log_has_re +from tests.conftest import log_has # Functions for recurrent object patching @@ -37,31 +37,10 @@ def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None: assert len(hyperopt_epochs) == 2 -def test_load_previous_results(testdatadir, caplog) -> None: - - results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle' - - hyperopt_epochs = HyperoptTools.load_previous_results(results_file) - - assert len(hyperopt_epochs) == 5 - assert log_has_re(r"Reading pickled epochs from .*", caplog) - - caplog.clear() - - # Modern version - results_file = testdatadir / 'strategy_SampleStrategy.fthypt' - - hyperopt_epochs = HyperoptTools.load_previous_results(results_file) - - assert len(hyperopt_epochs) == 5 - assert log_has_re(r"Reading epochs from .*", caplog) - - def test_load_previous_results2(mocker, testdatadir, caplog) -> None: - mocker.patch('freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results_pickle', - return_value=[{'asdf': '222'}]) results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle' - with pytest.raises(OperationalException, match=r"The file .* incompatible.*"): + with pytest.raises(OperationalException, + match=r"Legacy hyperopt results are no longer supported.*"): HyperoptTools.load_previous_results(results_file) From 5919992ad2ab67ad3aaffd92bdaa6ccec30fd537 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 05:24:31 +0000 Subject: [PATCH 113/200] Bump types-requests from 2.25.1 to 2.25.6 Bumps [types-requests](https://github.com/python/typeshed) from 2.25.1 to 2.25.6. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7128721c5..9629bbea1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -21,5 +21,5 @@ nbconvert==6.1.0 # mypy types types-cachetools==0.1.10 types-filelock==0.1.5 -types-requests==2.25.1 +types-requests==2.25.6 types-tabulate==0.8.2 From f17942b68fa23a5dc727cba463e92f0a71e8cfb3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Aug 2021 11:18:18 +0200 Subject: [PATCH 114/200] Fix random test failure --- freqtrade/optimize/backtesting.py | 3 +++ tests/optimize/test_backtesting.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3079e326d..85137a8ef 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -130,6 +130,9 @@ class Backtesting: self.abort = False def __del__(self): + self.cleanup() + + def cleanup(self): LoggingMixin.show_output = True PairLocks.use_db = True Trade.use_db = True diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index deaaf9f2f..b859e9017 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -535,6 +535,8 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: trade = backtesting._enter_trade(pair, row=row) assert trade is None + backtesting.cleanup() + def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_sell_signal'] = False From 927ac24f82f66c3932936ea451f39efde193adfd Mon Sep 17 00:00:00 2001 From: pcassimans Date: Mon, 9 Aug 2021 14:21:59 +0200 Subject: [PATCH 115/200] Update README.md Fix Typo of Kukoin to Kucoin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78ea3cecd..309fab94b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even Exchanges confirmed working by the community: - [X] [Bitvavo](https://bitvavo.com/) -- [X] [Kukoin](https://www.kucoin.com/) +- [X] [Kucoin](https://www.kucoin.com/) ## Documentation From 519c256b88e31f8347bd5bf7395a08d0c774fdae Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Aug 2021 14:43:35 +0200 Subject: [PATCH 116/200] Fix kucoin typo in index.md as well --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 05eaa7552..fd3b8f224 100644 --- a/docs/index.md +++ b/docs/index.md @@ -47,7 +47,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual, Exchanges confirmed working by the community: - [X] [Bitvavo](https://bitvavo.com/) -- [X] [Kukoin](https://www.kucoin.com/) +- [X] [Kucoin](https://www.kucoin.com/) ## Requirements From a5f796bc9764040be80478deb246fe08b814be9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Aug 2021 14:53:18 +0200 Subject: [PATCH 117/200] refactor ohlcvdata_to_dataframe to advise_all_indicators --- freqtrade/edge/edge_positioning.py | 2 +- freqtrade/optimize/backtesting.py | 5 +++-- freqtrade/optimize/hyperopt.py | 2 +- freqtrade/strategy/interface.py | 2 +- tests/data/test_history.py | 6 +++--- tests/optimize/test_backtesting.py | 18 +++++++++--------- tests/optimize/test_hyperopt.py | 24 ++++++++++++------------ tests/strategy/test_interface.py | 8 ++++---- 8 files changed, 34 insertions(+), 33 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 243043d31..f12b1b37d 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -151,7 +151,7 @@ class Edge: # Fake run-mode to Edge prior_rm = self.config['runmode'] self.config['runmode'] = RunMode.EDGE - preprocessed = self.strategy.ohlcvdata_to_dataframe(data) + preprocessed = self.strategy.advise_all_indicators(data) self.config['runmode'] = prior_rm # Print timeframe diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 85137a8ef..825d1dd25 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -533,7 +533,8 @@ class Backtesting: 'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']), } - def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange): + def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, DataFrame], + timerange: TimeRange): self.progress.init_step(BacktestState.ANALYZE, 0) logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) @@ -552,7 +553,7 @@ class Backtesting: max_open_trades = 0 # need to reprocess data every time to populate signals - preprocessed = self.strategy.ohlcvdata_to_dataframe(data) + preprocessed = self.strategy.advise_all_indicators(data) # Trim startup period from analyzed dataframe preprocessed_tmp = trim_dataframes(preprocessed, timerange, self.required_startup) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 0db78aa39..901900121 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -394,7 +394,7 @@ class Hyperopt: data, timerange = self.backtesting.load_bt_data() logger.info("Dataload complete. Calculating indicators") - preprocessed = self.backtesting.strategy.ohlcvdata_to_dataframe(data) + preprocessed = self.backtesting.strategy.advise_all_indicators(data) # Trim startup period from analyzed dataframe to get correct dates for output. processed = trim_dataframes(preprocessed, timerange, self.backtesting.required_startup) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index bf5cc10af..d4f10301a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -732,7 +732,7 @@ class IStrategy(ABC, HyperStrategyMixin): else: return current_profit > roi - def ohlcvdata_to_dataframe(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: + def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: """ Populates indicators for given candle (OHLCV) data (for multiple pairs) Does not run advise_buy or advise_sell! diff --git a/tests/data/test_history.py b/tests/data/test_history.py index d203d0792..9cfe861ea 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -381,7 +381,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) - data = strategy.ohlcvdata_to_dataframe( + data = strategy.advise_all_indicators( load_data( datadir=testdatadir, timeframe='1m', @@ -399,7 +399,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) - data = strategy.ohlcvdata_to_dataframe( + data = strategy.advise_all_indicators( load_data( datadir=testdatadir, timeframe='1m', @@ -424,7 +424,7 @@ def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> No strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange('index', 'index', 200, 250) - data = strategy.ohlcvdata_to_dataframe( + data = strategy.advise_all_indicators( load_data( datadir=testdatadir, timeframe='5m', diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index b859e9017..8e3d4063a 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -85,7 +85,7 @@ def simple_backtest(config, contour, mocker, testdatadir) -> None: backtesting._set_strategy(backtesting.strategylist[0]) data = load_data_test(contour, testdatadir) - processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) assert isinstance(processed, dict) results = backtesting.backtest( @@ -107,7 +107,7 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'): patch_exchange(mocker) backtesting = Backtesting(conf) backtesting._set_strategy(backtesting.strategylist[0]) - processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) return { 'processed': processed, @@ -289,7 +289,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: backtesting._set_strategy(backtesting.strategylist[0]) assert backtesting.config == default_conf assert backtesting.timeframe == '5m' - assert callable(backtesting.strategy.ohlcvdata_to_dataframe) + assert callable(backtesting.strategy.advise_all_indicators) assert callable(backtesting.strategy.advise_buy) assert callable(backtesting.strategy.advise_sell) assert isinstance(backtesting.strategy.dp, DataProvider) @@ -335,14 +335,14 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: fill_up_missing=True) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + processed = backtesting.strategy.advise_all_indicators(data) assert len(processed['UNITTEST/BTC']) == 102 # Load strategy to compare the result between Backtesting function and strategy are the same default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) - processed2 = strategy.ohlcvdata_to_dataframe(data) + processed2 = strategy.advise_all_indicators(data) assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC']) @@ -549,7 +549,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: timerange = TimeRange('date', None, 1517227800, 0) data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], timerange=timerange) - processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) result = backtesting.backtest( processed=processed, @@ -614,7 +614,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None timerange = TimeRange.parse_timerange('1510688220-1510700340') data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'], timerange=timerange) - processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) results = backtesting.backtest( processed=processed, @@ -633,7 +633,7 @@ def test_processed(default_conf, mocker, testdatadir) -> None: backtesting._set_strategy(backtesting.strategylist[0]) dict_of_tickerrows = load_data_test('raise', testdatadir) - dataframes = backtesting.strategy.ohlcvdata_to_dataframe(dict_of_tickerrows) + dataframes = backtesting.strategy.advise_all_indicators(dict_of_tickerrows) dataframe = dataframes['UNITTEST/BTC'] cols = dataframe.columns # assert the dataframe got some of the indicator columns @@ -782,7 +782,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) backtesting.strategy.advise_buy = _trend_alternate_hold # Override backtesting.strategy.advise_sell = _trend_alternate_hold # Override - processed = backtesting.strategy.ohlcvdata_to_dataframe(data) + processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) backtest_conf = { 'processed': processed, diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index d146e84f1..0ca79d268 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -351,7 +351,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: del hyperopt_conf['timeframe'] hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -426,7 +426,7 @@ def test_hyperopt_format_results(hyperopt): def test_populate_indicators(hyperopt, testdatadir) -> None: data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True) - dataframes = hyperopt.backtesting.strategy.ohlcvdata_to_dataframe(data) + dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data) dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'}) @@ -438,7 +438,7 @@ def test_populate_indicators(hyperopt, testdatadir) -> None: def test_buy_strategy_generator(hyperopt, testdatadir) -> None: data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True) - dataframes = hyperopt.backtesting.strategy.ohlcvdata_to_dataframe(data) + dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data) dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'}) @@ -463,7 +463,7 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None: def test_sell_strategy_generator(hyperopt, testdatadir) -> None: data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True) - dataframes = hyperopt.backtesting.strategy.ohlcvdata_to_dataframe(data) + dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data) dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'}) @@ -660,7 +660,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None: }) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -713,7 +713,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None: hyperopt_conf.update({'print_json': True}) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -761,7 +761,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: }) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.start() @@ -805,7 +805,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non hyperopt_conf.update({'spaces': 'roi stoploss'}) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) del hyperopt.custom_hyperopt.__class__.buy_strategy_generator @@ -844,7 +844,7 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None: hyperopt_conf.update({'spaces': 'all', }) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) del hyperopt.custom_hyperopt.__class__.buy_strategy_generator @@ -886,7 +886,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: hyperopt_conf.update({'spaces': 'buy'}) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) # TODO: sell_strategy_generator() is actually not called because @@ -940,7 +940,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: hyperopt_conf.update({'spaces': 'sell', }) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) # TODO: buy_strategy_generator() is actually not called because @@ -985,7 +985,7 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> No hyperopt_conf.update({'spaces': space}) hyperopt = Hyperopt(hyperopt_conf) - hyperopt.backtesting.strategy.ohlcvdata_to_dataframe = MagicMock() + hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) delattr(hyperopt.custom_hyperopt.__class__, method) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index dc51f0811..686a36c96 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -228,25 +228,25 @@ def test_assert_df(ohlcv_history, caplog): _STRATEGY.disable_dataframe_checks = False -def test_ohlcvdata_to_dataframe(default_conf, testdatadir) -> None: +def test_advise_all_indicators(default_conf, testdatadir) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange.parse_timerange('1510694220-1510700340') data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, fill_up_missing=True) - processed = strategy.ohlcvdata_to_dataframe(data) + processed = strategy.advise_all_indicators(data) assert len(processed['UNITTEST/BTC']) == 102 # partial candle was removed -def test_ohlcvdata_to_dataframe_copy(mocker, default_conf, testdatadir) -> None: +def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators') timerange = TimeRange.parse_timerange('1510694220-1510700340') data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, fill_up_missing=True) - strategy.ohlcvdata_to_dataframe(data) + strategy.advise_all_indicators(data) assert aimock.call_count == 1 # Ensure that a copy of the dataframe is passed to advice_indicators assert aimock.call_args_list[0][0][0] is not data From 895b912c719da2f613c5ae5da47cfb375bf771b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Aug 2021 14:40:02 +0200 Subject: [PATCH 118/200] Fix recently introduced lookahead bias in backtesting closes #5388 --- freqtrade/optimize/backtesting.py | 4 +++- tests/optimize/test_backtesting.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 825d1dd25..3a864173f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -466,6 +466,8 @@ class Backtesting: for i, pair in enumerate(data): row_index = indexes[pair] try: + # Row is treated as "current incomplete candle". + # Buy / sell signals are shifted by 1 to compensate for this. row = data[pair][row_index] except IndexError: # missing Data for one pair at the end. @@ -476,8 +478,8 @@ class Backtesting: if row[DATE_IDX] > tmp: continue - row_index += 1 self.dataprovider._set_dataframe_max_index(row_index) + row_index += 1 indexes[pair] = row_index # without positionstacking, we can only have one open trade per pair. diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 8e3d4063a..ff9b81c30 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument import random +from datetime import timedelta from pathlib import Path from unittest.mock import MagicMock, PropertyMock @@ -741,8 +742,13 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): # 100 buys signals results = result['results'] assert len(results) == 100 - # Cached data should be 200 (no change since required_startup is 0) - assert len(backtesting.dataprovider.get_analyzed_dataframe('UNITTEST/BTC', '1m')[0]) == 200 + # Cached data should be 199 (missing 1 candle at the start) + analyzed_df = backtesting.dataprovider.get_analyzed_dataframe('UNITTEST/BTC', '1m')[0] + assert len(analyzed_df) == 199 + # Expect last candle to be 1 below end date (as the last candle is assumed as "incomplete" + # during backtesting) + expected_last_candle_date = backtest_conf['end_date'] - timedelta(minutes=1) + assert analyzed_df.iloc[-1]['date'].to_pydatetime() == expected_last_candle_date # One trade was force-closed at the end assert len(results.loc[results['is_open']]) == 0 @@ -774,7 +780,8 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) data = trim_dictlist(data, -500) # Remove data for one pair from the beginning of the data - data[pair] = data[pair][tres:].reset_index() + if tres > 0: + data[pair] = data[pair][tres:].reset_index() default_conf['timeframe'] = '5m' backtesting = Backtesting(default_conf) @@ -800,8 +807,11 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0 # Cached data correctly removed amounts - removed_candles = len(data[pair]) - 1 - backtesting.strategy.startup_candle_count + offset = 2 if tres == 0 else 1 + removed_candles = len(data[pair]) - offset - backtesting.strategy.startup_candle_count assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles + assert len(backtesting.dataprovider.get_analyzed_dataframe( + 'NXT/BTC', '5m')[0]) == len(data['NXT/BTC']) - 2 - backtesting.strategy.startup_candle_count backtest_conf = { 'processed': processed, From 5bfb9edf02922c38aef0f501634eb116c74f7515 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Aug 2021 15:42:17 +0200 Subject: [PATCH 119/200] Only query date once from list --- freqtrade/optimize/backtesting.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3a864173f..5efb5101f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -323,14 +323,14 @@ class Backtesting: return sell_row[OPEN_IDX] def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: - + sell_candle_time = sell_row[DATE_IDX].to_pydatetime() sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore - sell_row[DATE_IDX].to_pydatetime(), sell_row[BUY_IDX], + sell_candle_time, sell_row[BUY_IDX], sell_row[SELL_IDX], low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX]) if sell.sell_flag: - trade.close_date = sell_row[DATE_IDX].to_pydatetime() + trade.close_date = sell_candle_time trade.sell_reason = sell.sell_reason trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) @@ -342,7 +342,7 @@ class Backtesting: rate=closerate, time_in_force=time_in_force, sell_reason=sell.sell_reason, - current_time=sell_row[DATE_IDX].to_pydatetime()): + current_time=sell_candle_time): return None trade.close(closerate, show_msg=False) From cf27968b97cc1491b61923a0a95f2183aacf0592 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Aug 2021 19:38:56 +0200 Subject: [PATCH 120/200] Properly preserve trade's low during backtesting --- freqtrade/persistence/models.py | 4 ++-- freqtrade/strategy/interface.py | 2 +- tests/optimize/test_backtesting.py | 2 +- tests/strategy/test_interface.py | 2 +- tests/test_persistence.py | 13 +++++++++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 43fbec8c0..a45274266 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -354,12 +354,12 @@ class LocalTrade(): LocalTrade.trades_open = [] LocalTrade.total_profit = 0 - def adjust_min_max_rates(self, current_price: float) -> None: + def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None: """ Adjust the max_rate and min_rate. """ self.max_rate = max(current_price, self.max_rate or self.open_rate) - self.min_rate = min(current_price, self.min_rate or self.open_rate) + self.min_rate = min(current_price_low, self.min_rate or self.open_rate) def _set_new_stoploss(self, new_loss: float, stoploss: float): """Assign new stop value""" diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d4f10301a..bb8980a53 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -568,7 +568,7 @@ class IStrategy(ABC, HyperStrategyMixin): current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) - trade.adjust_min_max_rates(high or current_rate) + trade.adjust_min_max_rates(high or current_rate, low or current_rate) stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ff9b81c30..57b2b8733 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -584,7 +584,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'initial_stop_loss_ratio': [-0.1, -0.1], 'stop_loss_abs': [0.0940005, 0.09272236], 'stop_loss_ratio': [-0.1, -0.1], - 'min_rate': [0.1038, 0.10302485], + 'min_rate': [0.10370188, 0.10300000000000001], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], 'buy_tag': [None, None], diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 686a36c96..cb4b8bd63 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -398,7 +398,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili exchange='binance', open_rate=1, ) - trade.adjust_min_max_rates(trade.open_rate) + trade.adjust_min_max_rates(trade.open_rate, trade.open_rate) strategy.trailing_stop = trailing strategy.trailing_stop_positive = -0.05 strategy.use_custom_stoploss = custom diff --git a/tests/test_persistence.py b/tests/test_persistence.py index f7bcad806..105cee23a 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -799,25 +799,30 @@ def test_adjust_min_max_rates(fee): open_rate=1, ) - trade.adjust_min_max_rates(trade.open_rate) + trade.adjust_min_max_rates(trade.open_rate, trade.open_rate) assert trade.max_rate == 1 assert trade.min_rate == 1 # check min adjusted, max remained - trade.adjust_min_max_rates(0.96) + trade.adjust_min_max_rates(0.96, 0.96) assert trade.max_rate == 1 assert trade.min_rate == 0.96 # check max adjusted, min remains - trade.adjust_min_max_rates(1.05) + trade.adjust_min_max_rates(1.05, 1.05) assert trade.max_rate == 1.05 assert trade.min_rate == 0.96 # current rate "in the middle" - no adjustment - trade.adjust_min_max_rates(1.03) + trade.adjust_min_max_rates(1.03, 1.03) assert trade.max_rate == 1.05 assert trade.min_rate == 0.96 + # current rate "in the middle" - no adjustment + trade.adjust_min_max_rates(1.10, 0.91) + assert trade.max_rate == 1.10 + assert trade.min_rate == 0.91 + @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize('use_db', [True, False]) From 3f160c71443281d4118a79ee5c11af3891c384d5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 10 Aug 2021 07:09:38 +0200 Subject: [PATCH 121/200] Cache dataframe before cutting the first candle This allows providing the "current closed" candle in all cases. --- freqtrade/optimize/backtesting.py | 6 +++--- tests/optimize/test_backtesting.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5efb5101f..fce27d39b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -246,11 +246,11 @@ class Backtesting: if has_buy_tag: df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) - df_analyzed.drop(df_analyzed.head(1).index, inplace=True) - # Update dataprovider cache self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) + df_analyzed = df_analyzed.drop(df_analyzed.head(1).index) + # Convert from Pandas to list for performance reasons # (Looping Pandas is slow.) data[pair] = df_analyzed[headers].values.tolist() @@ -478,9 +478,9 @@ class Backtesting: if row[DATE_IDX] > tmp: continue - self.dataprovider._set_dataframe_max_index(row_index) row_index += 1 indexes[pair] = row_index + self.dataprovider._set_dataframe_max_index(row_index) # without positionstacking, we can only have one open trade per pair. # max_open_trades must be respected diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 57b2b8733..998b2d837 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -742,9 +742,9 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): # 100 buys signals results = result['results'] assert len(results) == 100 - # Cached data should be 199 (missing 1 candle at the start) + # Cached data should be 200 analyzed_df = backtesting.dataprovider.get_analyzed_dataframe('UNITTEST/BTC', '1m')[0] - assert len(analyzed_df) == 199 + assert len(analyzed_df) == 200 # Expect last candle to be 1 below end date (as the last candle is assumed as "incomplete" # during backtesting) expected_last_candle_date = backtest_conf['end_date'] - timedelta(minutes=1) @@ -807,11 +807,11 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0 # Cached data correctly removed amounts - offset = 2 if tres == 0 else 1 + offset = 1 if tres == 0 else 0 removed_candles = len(data[pair]) - offset - backtesting.strategy.startup_candle_count assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles assert len(backtesting.dataprovider.get_analyzed_dataframe( - 'NXT/BTC', '5m')[0]) == len(data['NXT/BTC']) - 2 - backtesting.strategy.startup_candle_count + 'NXT/BTC', '5m')[0]) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count backtest_conf = { 'processed': processed, From 039d6384edf79d87556f5fa66b5917d1a77f9a6b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 10 Aug 2021 09:48:26 +0200 Subject: [PATCH 122/200] Stream hyperopt-result in small batches Avoiding memory-exhaustion on huge hyperopt results closes #5305 closes #5149 --- freqtrade/optimize/hyperopt_epoch_filters.py | 12 ++--- freqtrade/optimize/hyperopt_tools.py | 54 +++++++++++--------- tests/commands/test_commands.py | 24 +++++++-- tests/optimize/test_hyperopt_tools.py | 24 +++++++-- 4 files changed, 78 insertions(+), 36 deletions(-) diff --git a/freqtrade/optimize/hyperopt_epoch_filters.py b/freqtrade/optimize/hyperopt_epoch_filters.py index b70db94af..80cc89d4b 100644 --- a/freqtrade/optimize/hyperopt_epoch_filters.py +++ b/freqtrade/optimize/hyperopt_epoch_filters.py @@ -7,7 +7,7 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) -def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List: +def hyperopt_filter_epochs(epochs: List, filteroptions: dict, log: bool = True) -> List: """ Filter our items from the list of hyperopt results """ @@ -24,11 +24,11 @@ def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List: epochs = _hyperopt_filter_epochs_profit(epochs, filteroptions) epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions) - - logger.info(f"{len(epochs)} " + - ("best " if filteroptions['only_best'] else "") + - ("profitable " if filteroptions['only_profitable'] else "") + - "epochs found.") + if log: + logger.info(f"{len(epochs)} " + + ("best " if filteroptions['only_best'] else "") + + ("profitable " if filteroptions['only_profitable'] else "") + + "epochs found.") return epochs diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 0bb6aba15..b2e024f65 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -4,7 +4,7 @@ import logging from copy import deepcopy from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, Iterator, List, Optional, Tuple import numpy as np import rapidjson @@ -90,37 +90,33 @@ class HyperoptTools(): return any(s in config['spaces'] for s in [space, 'all', 'default']) @staticmethod - def _read_results(results_file: Path) -> List: + def _read_results(results_file: Path, batch_size: int = 10) -> Iterator[List[Any]]: """ - Read hyperopt results from file + Stream hyperopt results from file """ import rapidjson logger.info(f"Reading epochs from '{results_file}'") with results_file.open('r') as f: - data = [rapidjson.loads(line) for line in f] - return data + data = [] + for line in f: + data += [rapidjson.loads(line)] + if len(data) >= batch_size: + yield data + data = [] + yield data @staticmethod - def load_previous_results(results_file: Path) -> List: - """ - Load data for epochs from the file if we have one - """ - epochs: List = [] + def _test_hyperopt_results_exist(results_file) -> bool: if results_file.is_file() and results_file.stat().st_size > 0: if results_file.suffix == '.pickle': raise OperationalException( "Legacy hyperopt results are no longer supported." "Please rerun hyperopt or use an older version to load this file." ) - else: - epochs = HyperoptTools._read_results(results_file) - # Detection of some old format, without 'is_best' field saved - if epochs[0].get('is_best') is None: - raise OperationalException( - "The file with HyperoptTools results is incompatible with this version " - "of Freqtrade and cannot be loaded.") - logger.info(f"Loaded {len(epochs)} previous evaluations from disk.") - return epochs + return True + else: + # No file found. + return False @staticmethod def load_filtered_results(results_file: Path, config: Dict[str, Any]) -> Tuple[List, int]: @@ -138,12 +134,24 @@ class HyperoptTools(): 'filter_min_objective': config.get('hyperopt_list_min_objective', None), 'filter_max_objective': config.get('hyperopt_list_max_objective', None), } + if not HyperoptTools._test_hyperopt_results_exist(results_file): + # No file found. + return [], 0 - # Previous evaluations - epochs = HyperoptTools.load_previous_results(results_file) - total_epochs = len(epochs) + epochs = [] + total_epochs = 0 + for epochs_tmp in HyperoptTools._read_results(results_file): + if total_epochs == 0 and epochs_tmp[0].get('is_best') is None: + raise OperationalException( + "The file with HyperoptTools results is incompatible with this version " + "of Freqtrade and cannot be loaded.") + total_epochs += len(epochs_tmp) + epochs += hyperopt_filter_epochs(epochs_tmp, filteroptions, log=False) - epochs = hyperopt_filter_epochs(epochs, filteroptions) + logger.info(f"Loaded {total_epochs} previous evaluations from disk.") + + # Final filter run ... + epochs = hyperopt_filter_epochs(epochs, filteroptions, log=True) return epochs, total_epochs diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 80dd04b27..fc5101979 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -941,8 +941,16 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir): csv_file = Path(tmpdir) / "test.csv" mocker.patch( - 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results', - MagicMock(return_value=saved_hyperopt_results) + 'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist', + return_value=True + ) + + def fake_iterator(*args, **kwargs): + yield from [saved_hyperopt_results] + + mocker.patch( + 'freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results', + side_effect=fake_iterator ) args = [ @@ -1175,8 +1183,16 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir): def test_hyperopt_show(mocker, capsys, saved_hyperopt_results): mocker.patch( - 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results', - MagicMock(return_value=saved_hyperopt_results) + 'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist', + return_value=True + ) + + def fake_iterator(*args, **kwargs): + yield from [saved_hyperopt_results] + + mocker.patch( + 'freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results', + side_effect=fake_iterator ) mocker.patch('freqtrade.commands.hyperopt_commands.show_backtest_result') diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index d59a44da7..cbcb13384 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -20,9 +20,14 @@ def create_results() -> List[Dict]: def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None: + + hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt') + + hyperopt_epochs = HyperoptTools.load_filtered_results(hyperopt.results_file, {}) + assert hyperopt_epochs == ([], 0) + # Test writing to temp dir and reading again epochs = create_results() - hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt') caplog.set_level(logging.DEBUG) @@ -33,15 +38,28 @@ def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None: hyperopt._save_result(epochs[0]) assert log_has(f"2 epochs saved to '{hyperopt.results_file}'.", caplog) - hyperopt_epochs = HyperoptTools.load_previous_results(hyperopt.results_file) + hyperopt_epochs = HyperoptTools.load_filtered_results(hyperopt.results_file, {}) assert len(hyperopt_epochs) == 2 + assert hyperopt_epochs[1] == 2 + assert len(hyperopt_epochs[0]) == 2 + + result_gen = HyperoptTools._read_results(hyperopt.results_file, 1) + epoch = next(result_gen) + assert len(epoch) == 1 + assert epoch[0] == epochs[0] + epoch = next(result_gen) + assert len(epoch) == 1 + epoch = next(result_gen) + assert len(epoch) == 0 + with pytest.raises(StopIteration): + next(result_gen) def test_load_previous_results2(mocker, testdatadir, caplog) -> None: results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle' with pytest.raises(OperationalException, match=r"Legacy hyperopt results are no longer supported.*"): - HyperoptTools.load_previous_results(results_file) + HyperoptTools.load_filtered_results(results_file, {}) @pytest.mark.parametrize("spaces, expected_results", [ From 65d025923d9453b2173de63c898232847e31ad7a Mon Sep 17 00:00:00 2001 From: ipqhjjybj <250657661@qq.com> Date: Wed, 11 Aug 2021 14:35:16 +0800 Subject: [PATCH 123/200] add code --- 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 fce27d39b..a8fd9c04a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -504,7 +504,7 @@ class Backtesting: open_trades[pair].append(trade) LocalTrade.add_bt_trade(trade) - for trade in open_trades[pair]: + for trade in list(open_trades[pair]): # also check the buying candle for sell conditions. trade_entry = self._get_sell_trade_entry(trade, row) # Sell occurred From 61c076563fb7c778b93d9e056f5403423aa9406f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Aug 2021 12:11:29 +0200 Subject: [PATCH 124/200] Add max-slippage limiting for dry-run orders to avoid insane market order fills --- freqtrade/exchange/exchange.py | 13 ++++++++++++- tests/exchange/test_exchange.py | 23 ++++++++++++++--------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index c6f60e08a..cde643cff 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -618,6 +618,8 @@ class Exchange: if self.exchange_has('fetchL2OrderBook'): ob = self.fetch_l2_order_book(pair, 20) ob_type = 'asks' if side == 'buy' else 'bids' + slippage = 0.05 + max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage)) remaining_amount = amount filled_amount = 0 @@ -626,7 +628,9 @@ class Exchange: book_entry_coin_volume = book_entry[1] if remaining_amount > 0: if remaining_amount < book_entry_coin_volume: + # Orderbook at this slot bigger than remaining amount filled_amount += remaining_amount * book_entry_price + break else: filled_amount += book_entry_coin_volume * book_entry_price remaining_amount -= book_entry_coin_volume @@ -635,7 +639,14 @@ class Exchange: else: # If remaining_amount wasn't consumed completely (break was not called) filled_amount += remaining_amount * book_entry_price - forecast_avg_filled_price = filled_amount / amount + forecast_avg_filled_price = max(filled_amount, 0) / amount + # Limit max. slippage to specified value + if side == 'buy': + forecast_avg_filled_price = min(forecast_avg_filled_price, max_slippage_val) + + else: + forecast_avg_filled_price = max(forecast_avg_filled_price, max_slippage_val) + return self.price_to_precision(pair, forecast_avg_filled_price) return rate diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a3ebbe8bd..9ac9f84e5 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -984,16 +984,21 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice, assert order['fee'] -@pytest.mark.parametrize("side,amount,endprice", [ - ("buy", 1, 25.566), - ("buy", 100, 25.5672), # Requires interpolation - ("buy", 1000, 25.575), # More than orderbook return - ("sell", 1, 25.563), - ("sell", 100, 25.5625), # Requires interpolation - ("sell", 1000, 25.5555), # More than orderbook return +@pytest.mark.parametrize("side,rate,amount,endprice", [ + # spread is 25.263-25.266 + ("buy", 25.564, 1, 25.566), + ("buy", 25.564, 100, 25.5672), # Requires interpolation + ("buy", 25.590, 100, 25.5672), # Price above spread ... average is lower + ("buy", 25.564, 1000, 25.575), # More than orderbook return + ("buy", 24.000, 100000, 25.200), # Run into max_slippage of 5% + ("sell", 25.564, 1, 25.563), + ("sell", 25.564, 100, 25.5625), # Requires interpolation + ("sell", 25.510, 100, 25.5625), # price below spread - average is higher + ("sell", 25.564, 1000, 25.5555), # More than orderbook return + ("sell", 27, 10000, 25.65), # max-slippage 5% ]) @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_create_dry_run_order_market_fill(default_conf, mocker, side, amount, endprice, +def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amount, endprice, exchange_name, order_book_l2_usd): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) @@ -1003,7 +1008,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, amount, en ) order = exchange.create_dry_run_order( - pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=25.5) + pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=rate) assert 'id' in order assert f'dry_run_{side}_' in order["id"] assert order["side"] == side From f6267c75143b35c0270428f70d7936f7880fc917 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Wed, 11 Aug 2021 10:18:25 +0300 Subject: [PATCH 125/200] Fix buy_tag not being saved to trade object. Column is mistakenly excluded because advise_buy() creating this column runs after code detecting presence of buy_tag column. --- freqtrade/optimize/backtesting.py | 10 +++------- tests/optimize/__init__.py | 2 ++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index fce27d39b..06464d40b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -218,7 +218,7 @@ class Backtesting: """ # Every change to this headers list must evaluate further usages of the resulting tuple # and eventually change the constants for indexes at the top - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag'] data: Dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) @@ -226,13 +226,10 @@ class Backtesting: for pair, pair_data in processed.items(): self.check_abort() self.progress.increment() - has_buy_tag = 'buy_tag' in pair_data - headers = headers + ['buy_tag'] if has_buy_tag else headers if not pair_data.empty: pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist - if has_buy_tag: - pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist + pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() @@ -243,8 +240,7 @@ class Backtesting: # from the previous candle df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) - if has_buy_tag: - df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) + df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) # Update dataprovider cache self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index f29d8d585..6ad2d300b 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -52,4 +52,6 @@ def _build_backtest_dataframe(data): # Ensure floats are in place for column in ['open', 'high', 'low', 'close', 'volume']: frame[column] = frame[column].astype('float64') + if 'buy_tag' not in columns: + frame['buy_tag'] = None return frame From fad253ad5141978b5d35d878414feeead837fab8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Aug 2021 06:53:55 +0200 Subject: [PATCH 126/200] Version bump ccxt to 1.54.74 closes #5401 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 60175c12f..0e107d8e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.21.1 pandas==1.3.1 -ccxt==1.54.62 +ccxt==1.54.74 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 From e03784d98dc016790cb42eb194adf2c2be752091 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Aug 2021 05:02:36 +0000 Subject: [PATCH 127/200] Fix filled exception closes #5404 --- freqtrade/persistence/models.py | 2 +- tests/test_persistence.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a45274266..5eaca7966 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -161,7 +161,7 @@ class Order(_DECL_BASE): self.ft_is_open = True if self.status in ('closed', 'canceled', 'cancelled'): self.ft_is_open = False - if order.get('filled', 0) > 0: + if (order.get('filled', 0.0) or 0.0) > 0: self.order_filled_date = datetime.now(timezone.utc) self.order_update_date = datetime.now(timezone.utc) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 105cee23a..d036b045e 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1224,6 +1224,11 @@ def test_update_order_from_ccxt(caplog): assert o.ft_is_open assert o.order_filled_date is None + # Order is unfilled, "filled" not set + # https://github.com/freqtrade/freqtrade/issues/5404 + ccxt_order.update({'filled': None, 'remaining': 20.0, 'status': 'canceled'}) + o.update_from_ccxt_object(ccxt_order) + # Order has been closed ccxt_order.update({'filled': 20.0, 'remaining': 0.0, 'status': 'closed'}) o.update_from_ccxt_object(ccxt_order) From ae11be39706b37eb6733b12cee814cdf351b53d9 Mon Sep 17 00:00:00 2001 From: axel Date: Thu, 12 Aug 2021 14:47:01 -0400 Subject: [PATCH 128/200] manage None or string value returned by custom_entry_price and add unit test for those cases --- freqtrade/freqtradebot.py | 12 ++++++++---- tests/test_freqtradebot.py | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 99f5d2894..99fe1c5a3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -479,13 +479,17 @@ class FreqtradeBot(LoggingMixin): buy_limit_requested = price else: # Calculate price - buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy") + proposed_buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy") custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, - default_retval=buy_limit_requested)( + default_retval=proposed_buy_rate)( pair=pair, current_time=datetime.now(timezone.utc), - proposed_rate=buy_limit_requested) + proposed_rate=proposed_buy_rate) - buy_limit_requested = custom_entry_price + if custom_entry_price and (isinstance(custom_entry_price, int) + or isinstance(custom_entry_price, float)): + buy_limit_requested = custom_entry_price + else: + buy_limit_requested = proposed_buy_rate if not buy_limit_requested: raise PricingError('Could not determine buy price.') diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index c73e51dec..69a4fa530 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -913,6 +913,30 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order assert trade assert trade.open_rate_requested == 0.77 + # In case of custom entry price set to None + limit_buy_order['status'] = 'open' + limit_buy_order['id'] = '5567' + freqtrade.strategy.custom_entry_price = lambda **kwargs: None + + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_rate=MagicMock(return_value=10), + ) + + assert freqtrade.execute_buy(pair, stake_amount) + trade = Trade.query.all()[7] + assert trade + assert trade.open_rate_requested == 10 + + # In case of custom entry price not float type + limit_buy_order['status'] = 'open' + limit_buy_order['id'] = '5568' + freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" + assert freqtrade.execute_buy(pair, stake_amount) + trade = Trade.query.all()[8] + assert trade + assert trade.open_rate_requested == 10 + def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From b098ce4e76abe78f27fee9be8860399ebc831f7a Mon Sep 17 00:00:00 2001 From: axel Date: Thu, 12 Aug 2021 15:13:14 -0400 Subject: [PATCH 129/200] add function get_valid_price to validate type of custom entry or exit price and use default proposed price if invalid --- freqtrade/freqtradebot.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 99fe1c5a3..99670d612 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -485,11 +485,7 @@ class FreqtradeBot(LoggingMixin): pair=pair, current_time=datetime.now(timezone.utc), proposed_rate=proposed_buy_rate) - if custom_entry_price and (isinstance(custom_entry_price, int) - or isinstance(custom_entry_price, float)): - buy_limit_requested = custom_entry_price - else: - buy_limit_requested = proposed_buy_rate + buy_limit_requested = self.get_valid_price(custom_entry_price, proposed_buy_rate) if not buy_limit_requested: raise PricingError('Could not determine buy price.') @@ -1087,12 +1083,15 @@ class FreqtradeBot(LoggingMixin): limit = trade.stop_loss # set custom_exit_price if available + proposed_limit_rate = limit current_profit = trade.calc_profit_ratio(limit) - limit = strategy_safe_wrapper(self.strategy.custom_exit_price, - default_retval=limit)( + custom_exit_price = strategy_safe_wrapper(self.strategy.custom_exit_price, + default_retval=proposed_limit_rate)( pair=trade.pair, trade=trade, current_time=datetime.now(timezone.utc), - proposed_rate=limit, current_profit=current_profit) + proposed_rate=proposed_limit_rate, current_profit=current_profit) + + limit = self.get_valid_price(custom_exit_price, proposed_limit_rate) # First cancelling stoploss on exchange ... if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: @@ -1393,3 +1392,17 @@ class FreqtradeBot(LoggingMixin): amount=amount, fee_abs=fee_abs) else: return amount + + def get_valid_price(self, custom_price: float, proposed_price: float) -> float: + """ + Return the valid price. + Check if the custom price is of the good type if not return proposed_price + :return: valid price for the order + """ + if custom_price and (isinstance(custom_price, int) + or isinstance(custom_price, float)): + valid_price = custom_price + else: + valid_price = proposed_price + + return valid_price From dbf7f34ecb180672a79454817725b15d058e4480 Mon Sep 17 00:00:00 2001 From: axel Date: Thu, 12 Aug 2021 15:30:49 -0400 Subject: [PATCH 130/200] add unit test to function get_valid_price --- freqtrade/freqtradebot.py | 10 +++++++--- tests/test_freqtradebot.py | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 99670d612..2225ddd89 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1399,9 +1399,13 @@ class FreqtradeBot(LoggingMixin): Check if the custom price is of the good type if not return proposed_price :return: valid price for the order """ - if custom_price and (isinstance(custom_price, int) - or isinstance(custom_price, float)): - valid_price = custom_price + if custom_price: + if isinstance(custom_price, int): + valid_price = float(custom_price) + elif isinstance(custom_price, float): + valid_price = custom_price + else: + valid_price = proposed_price else: valid_price = proposed_price diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 69a4fa530..a67f5b290 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4588,3 +4588,27 @@ def test_refind_lost_order(mocker, default_conf, fee, caplog): freqtrade.refind_lost_order(trades[4]) assert log_has(f"Error updating {order['id']}.", caplog) + + +def test_get_valid_price(mocker, default_conf) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + freqtrade = FreqtradeBot(default_conf) + + custom_price_string = "10" + custom_price_float = 10.0 + custom_price_int = 10 + + proposed_price = 12.2 + + valid_price_from_string = freqtrade.get_valid_price(custom_price_string, proposed_price) + valid_price_from_int = freqtrade.get_valid_price(custom_price_int, proposed_price) + valid_price_from_float = freqtrade.get_valid_price(custom_price_float, proposed_price) + + assert isinstance(valid_price_from_string, float) + assert isinstance(valid_price_from_int, float) + assert isinstance(valid_price_from_float, float) + + assert valid_price_from_string == proposed_price + assert valid_price_from_int == custom_price_int + assert valid_price_from_float == custom_price_float From 20cc60bfde456dafd94bc59ab797018f62fb20ad Mon Sep 17 00:00:00 2001 From: axel Date: Fri, 13 Aug 2021 11:06:15 -0400 Subject: [PATCH 131/200] update get_valid_price function and test cases to handle inputs with try catch --- freqtrade/freqtradebot.py | 6 ++---- tests/test_freqtradebot.py | 6 +++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2225ddd89..9a1b2ab0c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1400,11 +1400,9 @@ class FreqtradeBot(LoggingMixin): :return: valid price for the order """ if custom_price: - if isinstance(custom_price, int): + try: valid_price = float(custom_price) - elif isinstance(custom_price, float): - valid_price = custom_price - else: + except ValueError: valid_price = proposed_price else: valid_price = proposed_price diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a67f5b290..a475ced48 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4596,19 +4596,23 @@ def test_get_valid_price(mocker, default_conf) -> None: freqtrade = FreqtradeBot(default_conf) custom_price_string = "10" + custom_price_badstring = "10abc" custom_price_float = 10.0 custom_price_int = 10 proposed_price = 12.2 valid_price_from_string = freqtrade.get_valid_price(custom_price_string, proposed_price) + valid_price_from_badstring = freqtrade.get_valid_price(custom_price_badstring, proposed_price) valid_price_from_int = freqtrade.get_valid_price(custom_price_int, proposed_price) valid_price_from_float = freqtrade.get_valid_price(custom_price_float, proposed_price) assert isinstance(valid_price_from_string, float) + assert isinstance(valid_price_from_badstring, float) assert isinstance(valid_price_from_int, float) assert isinstance(valid_price_from_float, float) - assert valid_price_from_string == proposed_price + assert valid_price_from_string == custom_price_float + assert valid_price_from_badstring == proposed_price assert valid_price_from_int == custom_price_int assert valid_price_from_float == custom_price_float From 0a6c0c429ae5fb354a06d8409231216aa387e0f6 Mon Sep 17 00:00:00 2001 From: axel Date: Fri, 13 Aug 2021 11:12:33 -0400 Subject: [PATCH 132/200] add a note concerning default custom entry or exit price in documentation --- docs/strategy-advanced.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index f59cb8ef5..17fdddc37 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -363,6 +363,9 @@ By default, freqtrade use the orderbook to automatically set an order price, you You can use this feature by creating a custom_entry_price function in your strategy file to customize entry prices and custom_exit_price for exits. +!!!Note +If your custom pricing function return None or an invalid value, a default entry or exit price will be chosen based on the current rate. + ### Custom order entry and exit price exemple ``` python from datetime import datetime, timedelta, timezone From c7147311f8d75635cff229ceccc88656903da045 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Aug 2021 17:14:38 +0200 Subject: [PATCH 133/200] Fix json syntax error in config template --- freqtrade/templates/base_config.json.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 03a6c4855..a5782f7cd 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -25,7 +25,7 @@ "ask_strategy": { "price_side": "ask", "use_order_book": true, - "order_book_top": 1, + "order_book_top": 1 }, {{ exchange | indent(4) }}, "pairlists": [ From db5a9443967eae8c0ed97229d5a510a3ffe14715 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Aug 2021 08:46:19 +0200 Subject: [PATCH 134/200] Cleanup GHA node after building images --- build_helpers/publish_docker_arm64.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/build_helpers/publish_docker_arm64.sh b/build_helpers/publish_docker_arm64.sh index 08793d339..e7b69b2dc 100755 --- a/build_helpers/publish_docker_arm64.sh +++ b/build_helpers/publish_docker_arm64.sh @@ -74,7 +74,5 @@ fi docker images -if [ $? -ne 0 ]; then - echo "failed building image" - return 1 -fi +# Cleanup old images from arm64 node. +docker image prune -a --force --filter "until=24h" From bb472ff98b06a8ae4059b742e56b22f1d2aca244 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Aug 2021 08:47:14 +0200 Subject: [PATCH 135/200] Improve new-exchange documentation --- docs/developer.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/developer.md b/docs/developer.md index dd56a367c..bd138212b 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -240,11 +240,18 @@ The `IProtection` parent class provides a helper method for this in `calculate_l !!! Note This section is a Work in Progress and is not a complete guide on how to test a new exchange with Freqtrade. +!!! Note + Make sure to use an up-to-date version of CCXT before running any of the below tests. + You can get the latest version of ccxt by running `pip install -U ccxt` with activated virtual environment. + Native docker is not supported for these tests, however the available dev-container will support all required actions and eventually necessary changes. + Most exchanges supported by CCXT should work out of the box. To quickly test the public endpoints of an exchange, add a configuration for your exchange to `test_ccxt_compat.py` and run these tests with `pytest --longrun tests/exchange/test_ccxt_compat.py`. Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar). +Also try to use `freqtrade download-data` for an extended timerange and verify that the data downloaded correctly (no holes, the specified timerange was actually downloaded). + ### Stoploss On Exchange Check if the new exchange supports Stoploss on Exchange orders through their API. From 0f7ddabec80428e80f036618e65d4e72d7605af7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Aug 2021 09:05:03 +0200 Subject: [PATCH 136/200] Slightly reword documentation --- docs/bot-basics.md | 5 +++-- docs/strategy-advanced.md | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 943af0362..b34594f46 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -36,11 +36,12 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Calls `check_sell_timeout()` strategy callback for open sell orders. * Verifies existing positions and eventually places sell orders. * Considers stoploss, ROI and sell-signal. - * Determine sell-price based on `ask_strategy` configuration setting. + * Determine sell-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback. * Before a sell order is placed, `confirm_trade_exit()` strategy callback is called. * Check if trade-slots are still available (if `max_open_trades` is reached). * Verifies buy signal trying to enter new positions. - * Determine buy-price based on `bid_strategy` configuration setting. + * Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback. + * Determine stake size by calling the `custom_stake_amount()` callback. * Before a buy order is placed, `confirm_trade_entry()` strategy callback is called. This loop will be repeated again and again until the bot is stopped. diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 17fdddc37..e53f20693 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -359,14 +359,15 @@ See [Dataframe access](#dataframe-access) for more information about dataframe u ## Custom order price rules -By default, freqtrade use the orderbook to automatically set an order price, you also have the option to create custom order prices based on your strategy. +By default, freqtrade use the orderbook to automatically set an order price([Relevant documentation](configuration.md#prices-used-for-orders)), you also have the option to create custom order prices based on your strategy. -You can use this feature by creating a custom_entry_price function in your strategy file to customize entry prices and custom_exit_price for exits. +You can use this feature by creating a `custom_entry_price()` function in your strategy file to customize entry prices and custom_exit_price for exits. -!!!Note -If your custom pricing function return None or an invalid value, a default entry or exit price will be chosen based on the current rate. +!!! Note + If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration. + +### Custom order entry and exit price example -### Custom order entry and exit price exemple ``` python from datetime import datetime, timedelta, timezone from freqtrade.persistence import Trade @@ -380,9 +381,9 @@ class AwesomeStrategy(IStrategy): dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - proposed_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] + new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] - return proposed_entryprice + return new_entryprice def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, @@ -390,12 +391,17 @@ class AwesomeStrategy(IStrategy): dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - proposed_exitprice = dataframe['bollinger_10_upperband'].iat[-1] + new_exitprice = dataframe['bollinger_10_upperband'].iat[-1] - return proposed_exitprice + return new_exitprice ``` +!!! Warning + Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. + +!!! Warning "No backtesting support" + Custom entry-prices are currently not supported during backtesting. ## Custom order timeout rules From 123971d2713a65d51421f46958ac481e03d9f518 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Aug 2021 16:02:12 +0200 Subject: [PATCH 137/200] Don't change passed in parameter variable --- freqtrade/rpc/api_server/api_v1.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index f2361fda8..7e613f184 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -223,11 +223,11 @@ def list_strategies(config=Depends(get_config)): @router.get('/strategy/{strategy}', response_model=StrategyResponse, tags=['strategy']) def get_strategy(strategy: str, config=Depends(get_config)): - config = deepcopy(config) + config_ = deepcopy(config) from freqtrade.resolvers.strategy_resolver import StrategyResolver try: - strategy_obj = StrategyResolver._load_strategy(strategy, config, - extra_dir=config.get('strategy_path')) + strategy_obj = StrategyResolver._load_strategy(strategy, config_, + extra_dir=config_.get('strategy_path')) except OperationalException: raise HTTPException(status_code=404, detail='Strategy not found') From 4f10a885290c5703e0f93e6880db8af757e20fca Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Aug 2021 15:14:53 +0200 Subject: [PATCH 138/200] Reduce verbosity when incompatible pairs are detected --- freqtrade/plugins/pairlist/IPairList.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index bfde2ace0..0155f918b 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -150,18 +150,20 @@ class IPairList(LoggingMixin, ABC): for pair in pairlist: # pair is not in the generated dynamic market or has the wrong stake currency if pair not in markets: - logger.warning(f"Pair {pair} is not compatible with exchange " - f"{self._exchange.name}. Removing it from whitelist..") + self.log_once(f"Pair {pair} is not compatible with exchange " + f"{self._exchange.name}. Removing it from whitelist..", + logger.warning) continue if not self._exchange.market_is_tradable(markets[pair]): - logger.warning(f"Pair {pair} is not tradable with Freqtrade." - "Removing it from whitelist..") + self.log_once(f"Pair {pair} is not tradable with Freqtrade." + "Removing it from whitelist..", logger.warning) continue if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']: - logger.warning(f"Pair {pair} is not compatible with your stake currency " - f"{self._config['stake_currency']}. Removing it from whitelist..") + self.log_once(f"Pair {pair} is not compatible with your stake currency " + f"{self._config['stake_currency']}. Removing it from whitelist..", + logger.warning) continue # Check if market is active From ed6776c5cdb8d2472cb0ef3669e3b4b4fe987021 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Aug 2021 17:06:16 +0200 Subject: [PATCH 139/200] Fix disappearing pairlist bug --- freqtrade/plugins/pairlist/VolumePairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 901fde2d0..8d01aeee1 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -126,7 +126,7 @@ class VolumePairList(IPairList): pairlist = [s['symbol'] for s in filtered_tickers] pairlist = self.filter_pairlist(pairlist, tickers) - self._pair_cache['pairlist'] = pairlist + self._pair_cache['pairlist'] = pairlist.copy() return pairlist From 322ea2481e7be80355199d803aab29bf5f52d827 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Aug 2021 19:28:36 +0200 Subject: [PATCH 140/200] Add log-message for edge-case --- freqtrade/plugins/pairlist/rangestabilityfilter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index ef7f2cbcb..3e5a002ff 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -120,5 +120,6 @@ class RangeStabilityFilter(IPairList): logger.info) result = False self._pair_cache[pair] = result - + else: + self.log_once(f"Removed {pair} from whitelist, no candles found.", logger.info) return result From dda82765893d4474e4a90d0b274dfab36939c382 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Aug 2021 20:01:54 +0200 Subject: [PATCH 141/200] Update documentation for sell_profit_offset As highlighed in #5393 --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index fd4806fe6..fab3004a5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -105,7 +105,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `ask_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Asks](#sell-price-with-orderbook-enabled)
*Defaults to `1`.*
**Datatype:** Positive Integer | `use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean | `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0`.*
**Datatype:** Float (as ratio) +| `sell_profit_offset` | Sell-signal is only active above this value. Only active in combination with `sell_profit_only=True`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0`.*
**Datatype:** Float (as ratio) | `ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used.
**Datatype:** Integer | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict From 37d4545123cbfbf5e52cddf703396a38105f9157 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 03:01:06 +0000 Subject: [PATCH 142/200] Bump ccxt from 1.54.74 to 1.55.6 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.54.74 to 1.55.6. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.54.74...1.55.6) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0e107d8e0..3880e3ced 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.21.1 pandas==1.3.1 -ccxt==1.54.74 +ccxt==1.55.6 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 From e7b6a996dffbdffdfd9ab5a9de1ca87fdb5f91e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 03:01:10 +0000 Subject: [PATCH 143/200] Bump mkdocs-material from 7.2.2 to 7.2.4 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 7.2.2 to 7.2.4. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/7.2.2...7.2.4) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 047821f2d..8fa7341c9 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.2 -mkdocs-material==7.2.2 +mkdocs-material==7.2.4 mdx_truly_sane_lists==1.2 pymdown-extensions==8.2 From 7125793249cb1cb6b97b2206fe8580c07150d04d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 03:01:16 +0000 Subject: [PATCH 144/200] Bump uvicorn from 0.14.0 to 0.15.0 Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.14.0 to 0.15.0. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.14.0...0.15.0) --- updated-dependencies: - dependency-name: uvicorn dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0e107d8e0..ae6b24eb5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ sdnotify==0.3.2 # API Server fastapi==0.68.0 -uvicorn==0.14.0 +uvicorn==0.15.0 pyjwt==2.1.0 aiofiles==0.7.0 From d8607b2ce880163027f6736702934c94949257f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 03:01:18 +0000 Subject: [PATCH 145/200] Bump flake8-tidy-imports from 4.3.0 to 4.4.1 Bumps [flake8-tidy-imports](https://github.com/adamchainz/flake8-tidy-imports) from 4.3.0 to 4.4.1. - [Release notes](https://github.com/adamchainz/flake8-tidy-imports/releases) - [Changelog](https://github.com/adamchainz/flake8-tidy-imports/blob/main/HISTORY.rst) - [Commits](https://github.com/adamchainz/flake8-tidy-imports/compare/4.3.0...4.4.1) --- updated-dependencies: - dependency-name: flake8-tidy-imports dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9629bbea1..b20cb6693 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ coveralls==3.2.0 flake8==3.9.2 flake8-type-annotations==0.1.0 -flake8-tidy-imports==4.3.0 +flake8-tidy-imports==4.4.1 mypy==0.910 pytest==6.2.4 pytest-asyncio==0.15.1 From a10fd6690675d481f26df78d0fa3f9147684140e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 03:01:21 +0000 Subject: [PATCH 146/200] Bump plotly from 5.1.0 to 5.2.1 Bumps [plotly](https://github.com/plotly/plotly.py) from 5.1.0 to 5.2.1. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v5.1.0...v5.2.1) --- updated-dependencies: - dependency-name: plotly dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index e03fd4d66..d835ed5d9 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.1.0 +plotly==5.2.1 From 3878e5186e19a3f5fbe3f14c1b6300320891496e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 03:01:34 +0000 Subject: [PATCH 147/200] Bump numpy from 1.21.1 to 1.21.2 Bumps [numpy](https://github.com/numpy/numpy) from 1.21.1 to 1.21.2. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/HOWTO_RELEASE.rst.txt) - [Commits](https://github.com/numpy/numpy/compare/v1.21.1...v1.21.2) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0e107d8e0..1d0951e6d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.21.1 +numpy==1.21.2 pandas==1.3.1 ccxt==1.54.74 From 108a6cb897342e4f436d41f845dc9d0198e58770 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 04:28:42 +0000 Subject: [PATCH 148/200] Bump pandas from 1.3.1 to 1.3.2 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.3.1 to 1.3.2. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md) - [Commits](https://github.com/pandas-dev/pandas/compare/v1.3.1...v1.3.2) --- updated-dependencies: - dependency-name: pandas dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 863475d86..e93e6211f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.21.2 -pandas==1.3.1 +pandas==1.3.2 ccxt==1.55.6 # Pin cryptography for now due to rust build errors with piwheels From abddb3ef257e6046b65595b0da2d19145adebccc Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Aug 2021 06:38:36 +0200 Subject: [PATCH 149/200] Add test for directory traversal --- tests/rpc/test_rpc_apiserver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 1517b6fcc..edf5ce3c5 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -109,6 +109,10 @@ def test_api_ui_fallback(botclient): rc = client_get(client, "/something") assert rc.status_code == 200 + rc = client_get(client, '%2F%2F%2Fetc/passwd') + assert rc.status_code == 200 + assert '`freqtrade install-ui`' in rc.text + def test_api_ui_version(botclient, mocker): ftbot, client = botclient From 6b2ef36a567a4aba47113fb56dfa9377f70797ad Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Aug 2021 06:45:43 +0200 Subject: [PATCH 150/200] Prevent directory traversal in UI Serving Checking for directory base closes #5427 --- freqtrade/rpc/api_server/web_ui.py | 7 +++++-- tests/rpc/test_rpc_apiserver.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/web_ui.py b/freqtrade/rpc/api_server/web_ui.py index 76c8ed8f2..0168930cf 100644 --- a/freqtrade/rpc/api_server/web_ui.py +++ b/freqtrade/rpc/api_server/web_ui.py @@ -37,8 +37,11 @@ async def index_html(rest_of_path: str): if rest_of_path.startswith('api') or rest_of_path.startswith('.'): raise HTTPException(status_code=404, detail="Not Found") uibase = Path(__file__).parent / 'ui/installed/' - if (uibase / rest_of_path).is_file(): - return FileResponse(str(uibase / rest_of_path)) + filename = uibase / rest_of_path + # It's security relevant to check "relative_to". + # Without this, Directory-traversal is possible. + if filename.is_file() and filename.is_relative_to(uibase): + return FileResponse(str(filename)) index_file = uibase / 'index.html' if not index_file.is_file(): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index edf5ce3c5..3d02e8188 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -109,6 +109,7 @@ def test_api_ui_fallback(botclient): rc = client_get(client, "/something") assert rc.status_code == 200 + # Test directory traversal rc = client_get(client, '%2F%2F%2Fetc/passwd') assert rc.status_code == 200 assert '`freqtrade install-ui`' in rc.text From 4b65206e6b8e19ef7696a8f7daaad3a441aa352e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Aug 2021 07:08:50 +0200 Subject: [PATCH 151/200] Add compat code for is_relative_to --- freqtrade/rpc/api_server/web_ui.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/web_ui.py b/freqtrade/rpc/api_server/web_ui.py index 0168930cf..9aae0afae 100644 --- a/freqtrade/rpc/api_server/web_ui.py +++ b/freqtrade/rpc/api_server/web_ui.py @@ -29,6 +29,15 @@ async def ui_version(): } +def is_relative_to(path, base) -> bool: + # Helper function simulating behaviour of is_relative_to, which was only added in python 3.9 + try: + path.relative_to(base) + except ValueError: + pass + return False + + @router_ui.get('/{rest_of_path:path}', include_in_schema=False) async def index_html(rest_of_path: str): """ @@ -40,7 +49,7 @@ async def index_html(rest_of_path: str): filename = uibase / rest_of_path # It's security relevant to check "relative_to". # Without this, Directory-traversal is possible. - if filename.is_file() and filename.is_relative_to(uibase): + if filename.is_file() and is_relative_to(filename, uibase): return FileResponse(str(filename)) index_file = uibase / 'index.html' From 4115121c249077ff123c2d15ac22df62ed375ed4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Aug 2021 07:26:58 +0200 Subject: [PATCH 152/200] Fix missing return statement in is_Relative_to --- freqtrade/rpc/api_server/web_ui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/rpc/api_server/web_ui.py b/freqtrade/rpc/api_server/web_ui.py index 9aae0afae..b04269c61 100644 --- a/freqtrade/rpc/api_server/web_ui.py +++ b/freqtrade/rpc/api_server/web_ui.py @@ -33,6 +33,7 @@ def is_relative_to(path, base) -> bool: # Helper function simulating behaviour of is_relative_to, which was only added in python 3.9 try: path.relative_to(base) + return True except ValueError: pass return False From f24a951ec5cbc5ee3804d5eddfbf692acff07aeb Mon Sep 17 00:00:00 2001 From: slowy07 Date: Mon, 16 Aug 2021 19:16:24 +0700 Subject: [PATCH 153/200] fix: typo spelling grammar --- docs/exchanges.md | 2 +- docs/strategy_analysis_example.md | 2 +- freqtrade/data/btanalysis.py | 2 +- freqtrade/data/converter.py | 4 ++-- freqtrade/exchange/exchange.py | 2 +- freqtrade/freqtradebot.py | 2 +- freqtrade/plot/plotting.py | 2 +- freqtrade/rpc/rpc.py | 2 +- tests/data/test_converter.py | 2 +- tests/data/test_dataprovider.py | 2 +- tests/data/test_history.py | 6 +++--- tests/strategy/test_interface.py | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 29b9bb533..5f54a524e 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -105,7 +105,7 @@ To use subaccounts with FTX, you need to edit the configuration and add the foll ## Kucoin -Kucoin requries a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows: +Kucoin requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows: ```json "exchange": { diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 27192aa2f..dd7e07824 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -228,7 +228,7 @@ graph = generate_candlestick_graph(pair=pair, # Show graph inline # graph.show() -# Render graph in a seperate window +# Render graph in a separate window graph.show(renderer="browser") ``` diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index d62712cbb..7d97661c4 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index", "trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"] -# Mid-term format, crated by BacktestResult Named Tuple +# Mid-term format, created by BacktestResult Named Tuple BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration', 'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open', 'fee_close', 'amount', 'profit_abs', 'profit_ratio'] diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 040f58d62..ca6464965 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -242,7 +242,7 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to: :param config: Config dictionary :param convert_from: Source format :param convert_to: Target format - :param erase: Erase souce data (does not apply if source and target format are identical) + :param erase: Erase source data (does not apply if source and target format are identical) """ from freqtrade.data.history.idatahandler import get_datahandler src = get_datahandler(config['datadir'], convert_from) @@ -267,7 +267,7 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: :param config: Config dictionary :param convert_from: Source format :param convert_to: Target format - :param erase: Erase souce data (does not apply if source and target format are identical) + :param erase: Erase source data (does not apply if source and target format are identical) """ from freqtrade.data.history.idatahandler import get_datahandler src = get_datahandler(config['datadir'], convert_from) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index cde643cff..9aa5b98a8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1497,7 +1497,7 @@ class Exchange: :returns List of trade data """ if not self.exchange_has("fetchTrades"): - raise OperationalException("This exchange does not suport downloading Trades.") + raise OperationalException("This exchange does not support downloading Trades.") return asyncio.get_event_loop().run_until_complete( self._async_get_trade_history(pair=pair, since=since, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 09aa06adf..fb15d6e5c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -977,7 +977,7 @@ class FreqtradeBot(LoggingMixin): # if trade is partially complete, edit the stake details for the trade # and close the order # cancel_order may not contain the full order dict, so we need to fallback - # to the order dict aquired before cancelling. + # to the order dict acquired before cancelling. # we need to fall back to the values from order if corder does not contain these keys. trade.amount = filled_amount trade.stake_amount = trade.amount * trade.open_rate diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 2fbf343ce..509c03e90 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -538,7 +538,7 @@ def load_and_plot_trades(config: Dict[str, Any]): - Initializes plot-script - Get candle (OHLCV) data - Generate Dafaframes populated with indicators and signals based on configured strategy - - Load trades excecuted during the selected period + - Load trades executed during the selected period - Generate Plotly plot objects - Generate plot files :return: None diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 902975fde..0264003a5 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -776,7 +776,7 @@ class RPC: if has_content: dataframe.loc[:, '__date_ts'] = dataframe.loc[:, 'date'].view(int64) // 1000 // 1000 - # Move open to seperate column when signal for easy plotting + # Move open to separate column when signal for easy plotting if 'buy' in dataframe.columns: buy_mask = (dataframe['buy'] == 1) buy_signals = int(buy_mask.sum()) diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index 802fd4b12..6c95a9f18 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -119,7 +119,7 @@ def test_ohlcv_fill_up_missing_data2(caplog): # 3rd candle has been filled row = data2.loc[2, :] assert row['volume'] == 0 - # close shoult match close of previous candle + # close should match close of previous candle assert row['close'] == data.loc[1, 'close'] assert row['open'] == row['close'] assert row['high'] == row['close'] diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index e43309743..0f42068c1 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -66,7 +66,7 @@ def test_historic_ohlcv_dataformat(mocker, default_conf, ohlcv_history): hdf5loadmock.assert_not_called() jsonloadmock.assert_called_once() - # Swiching to dataformat hdf5 + # Switching to dataformat hdf5 hdf5loadmock.reset_mock() jsonloadmock.reset_mock() default_conf["dataformat_ohlcv"] = "hdf5" diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 9cfe861ea..13d22ebb7 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -200,15 +200,15 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: assert start_ts == test_data[0][0] - 1000 # timeframe starts in the center of the cached data - # should return the chached data w/o the last item + # should return the cached data w/o the last item timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0) data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler) assert_frame_equal(data, test_data_df.iloc[:-1]) assert test_data[-2][0] <= start_ts < test_data[-1][0] - # timeframe starts after the chached data - # should return the chached data w/o the last item + # timeframe starts after the cached data + # should return the cached data w/o the last item timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 100, 0) data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler) assert_frame_equal(data, test_data_df.iloc[:-1]) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index cb4b8bd63..eea6a85d2 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -630,7 +630,7 @@ def test_strategy_safe_wrapper_error(caplog, error): assert ret caplog.clear() - # Test supressing error + # Test suppressing error ret = strategy_safe_wrapper(failing_method, message='DeadBeef', supress_error=True)() assert log_has_re(r'DeadBeef.*', caplog) From 0264d77d8670e43304b58ad1ea2d39cff6550596 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Aug 2021 20:02:25 +0200 Subject: [PATCH 154/200] Fix test for fixed typo --- tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 9ac9f84e5..a5099a3ce 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2182,7 +2182,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange pair = 'ETH/BTC' with pytest.raises(OperationalException, - match="This exchange does not suport downloading Trades."): + match="This exchange does not support downloading Trades."): exchange.get_historic_trades(pair, since=trades_history[0][0], until=trades_history[-1][0]) From 3ea4b2ba00a4d3cef42def46dc0b32a7ef7b7603 Mon Sep 17 00:00:00 2001 From: axel Date: Mon, 16 Aug 2021 15:18:57 -0400 Subject: [PATCH 155/200] add custom_price_max_distance_percent security to get_valid_price, update tests --- freqtrade/freqtradebot.py | 17 ++++++++++++++--- tests/test_freqtradebot.py | 14 +++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9a1b2ab0c..13632bad1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1401,10 +1401,21 @@ class FreqtradeBot(LoggingMixin): """ if custom_price: try: - valid_price = float(custom_price) + valid_custom_price = float(custom_price) except ValueError: - valid_price = proposed_price + valid_custom_price = proposed_price else: - valid_price = proposed_price + valid_custom_price = proposed_price + + cust_p_max_dist_pct = self.config.get('custom_price_max_distance_percent', 2.0) + min_custom_price_allowed = proposed_price - ((proposed_price * cust_p_max_dist_pct) / 100) + max_custom_price_allowed = proposed_price + ((proposed_price * cust_p_max_dist_pct) / 100) + + if valid_custom_price > max_custom_price_allowed: + valid_price = max_custom_price_allowed + elif valid_custom_price < min_custom_price_allowed: + valid_price = min_custom_price_allowed + else: + valid_price = valid_custom_price return valid_price diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a475ced48..80bcabdb6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4594,19 +4594,25 @@ def test_get_valid_price(mocker, default_conf) -> None: patch_RPCManager(mocker) patch_exchange(mocker) freqtrade = FreqtradeBot(default_conf) + freqtrade.config['custom_price_max_distance_percent'] = 2.0 custom_price_string = "10" custom_price_badstring = "10abc" custom_price_float = 10.0 custom_price_int = 10 - proposed_price = 12.2 + custom_price_over_max_alwd = 11.0 + custom_price_under_min_alwd = 9.0 + proposed_price = 10.1 valid_price_from_string = freqtrade.get_valid_price(custom_price_string, proposed_price) valid_price_from_badstring = freqtrade.get_valid_price(custom_price_badstring, proposed_price) valid_price_from_int = freqtrade.get_valid_price(custom_price_int, proposed_price) valid_price_from_float = freqtrade.get_valid_price(custom_price_float, proposed_price) + valid_price_at_max_alwd = freqtrade.get_valid_price(custom_price_over_max_alwd, proposed_price) + valid_price_at_min_alwd = freqtrade.get_valid_price(custom_price_under_min_alwd, proposed_price) + assert isinstance(valid_price_from_string, float) assert isinstance(valid_price_from_badstring, float) assert isinstance(valid_price_from_int, float) @@ -4616,3 +4622,9 @@ def test_get_valid_price(mocker, default_conf) -> None: assert valid_price_from_badstring == proposed_price assert valid_price_from_int == custom_price_int assert valid_price_from_float == custom_price_float + + assert valid_price_at_max_alwd != custom_price_over_max_alwd + assert valid_price_at_max_alwd > proposed_price + + assert valid_price_at_min_alwd != custom_price_under_min_alwd + assert valid_price_at_min_alwd < proposed_price From faff40577a48acc161593e685c707664c5d9083d Mon Sep 17 00:00:00 2001 From: axel Date: Mon, 16 Aug 2021 15:33:05 -0400 Subject: [PATCH 156/200] fix test_execute_buy In case of custom entry price --- tests/test_freqtradebot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 80bcabdb6..5b5e3ce28 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -905,13 +905,14 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order freqtrade.execute_buy(pair, stake_amount) # In case of custom entry price + mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50) limit_buy_order['status'] = 'open' limit_buy_order['id'] = '5566' - freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.77 + freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508 assert freqtrade.execute_buy(pair, stake_amount) trade = Trade.query.all()[6] assert trade - assert trade.open_rate_requested == 0.77 + assert trade.open_rate_requested == 0.508 # In case of custom entry price set to None limit_buy_order['status'] = 'open' From 17daba321bfbc755443c5399c9ad3569f11fc38b Mon Sep 17 00:00:00 2001 From: axel Date: Mon, 16 Aug 2021 23:09:30 -0400 Subject: [PATCH 157/200] add custom_price_max_distance_percent config option in constants --- freqtrade/constants.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index de4bc99b4..2f51f45f7 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -191,6 +191,9 @@ CONF_SCHEMA = { }, 'required': ['price_side'] }, + 'custom_price_max_distance_percent': { + 'type': 'number', 'minimum': 0.0 + }, 'order_types': { 'type': 'object', 'properties': { From f08d673a52f4034e367c26ca911911b793d6a734 Mon Sep 17 00:00:00 2001 From: axel Date: Mon, 16 Aug 2021 23:26:08 -0400 Subject: [PATCH 158/200] add details and exemple of custom_price_max_distance_percent usage in doc --- docs/strategy-advanced.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index e53f20693..babcc5e7b 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -398,7 +398,10 @@ class AwesomeStrategy(IStrategy): ``` !!! Warning - Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. + Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_percent` parameter. + +_Exemple_ +If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_percent` is set to 2%, The retained valid custom entry price will be 98. !!! Warning "No backtesting support" Custom entry-prices are currently not supported during backtesting. From f47191582872279de090667cb5fc8f703125569c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Aug 2021 06:41:13 +0200 Subject: [PATCH 159/200] Add test for refresh_latest_ohlcv caching --- tests/exchange/test_exchange.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a5099a3ce..27eeed39b 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1564,13 +1564,16 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: pairs = [('IOTA/ETH', '5m'), ('XRP/ETH', '5m')] # empty dicts assert not exchange._klines - exchange.refresh_latest_ohlcv(pairs, cache=False) + res = exchange.refresh_latest_ohlcv(pairs, cache=False) # No caching assert not exchange._klines + + assert len(res) == len(pairs) assert exchange._api_async.fetch_ohlcv.call_count == 2 exchange._api_async.fetch_ohlcv.reset_mock() - exchange.refresh_latest_ohlcv(pairs) + res = exchange.refresh_latest_ohlcv(pairs) + assert len(res) == len(pairs) assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog) assert exchange._klines @@ -1587,12 +1590,16 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: assert exchange.klines(pair, copy=False) is exchange.klines(pair, copy=False) # test caching - exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]) + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]) + assert len(res) == len(pairs) assert exchange._api_async.fetch_ohlcv.call_count == 2 assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, " f"timeframe {pairs[0][1]} ...", caplog) + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')], + cache=False) + assert len(res) == 3 @pytest.mark.asyncio From 9758bed250450eb54de455987de2c2c5b93ed6be Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Aug 2021 06:44:20 +0200 Subject: [PATCH 160/200] Fix refresh_latest_ohlcv bug --- freqtrade/exchange/exchange.py | 7 ++++++- freqtrade/plugins/pairlist/VolumePairList.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9aa5b98a8..dbd72aca4 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1253,7 +1253,7 @@ class Exchange: logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list)) input_coroutines = [] - + cached_pairs = [] # Gather coroutines to run for pair, timeframe in set(pair_list): if (((pair, timeframe) not in self._klines) @@ -1265,6 +1265,7 @@ class Exchange: "Using cached candle (OHLCV) data for pair %s, timeframe %s ...", pair, timeframe ) + cached_pairs.append((pair, timeframe)) results = asyncio.get_event_loop().run_until_complete( asyncio.gather(*input_coroutines, return_exceptions=True)) @@ -1287,6 +1288,10 @@ class Exchange: results_df[(pair, timeframe)] = ohlcv_df if cache: self._klines[(pair, timeframe)] = ohlcv_df + # Return cached klines + for pair, timeframe in cached_pairs: + results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False) + return results_df def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool: diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 8d01aeee1..23291d39e 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -115,7 +115,7 @@ class VolumePairList(IPairList): pairlist = self._pair_cache.get('pairlist') if pairlist: # Item found - no refresh necessary - return pairlist + return pairlist.copy() else: # Use fresh pairlist # Check if pair quote currency equals to the stake currency. From 37e3d20357943e18e0f0197a11e911aa4d2031e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Aug 2021 06:54:10 +0200 Subject: [PATCH 161/200] Fix no-event-loop available closes #5433 --- freqtrade/rpc/api_server/uvicorn_threaded.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/uvicorn_threaded.py b/freqtrade/rpc/api_server/uvicorn_threaded.py index 2f72cb74c..b63999f51 100644 --- a/freqtrade/rpc/api_server/uvicorn_threaded.py +++ b/freqtrade/rpc/api_server/uvicorn_threaded.py @@ -32,8 +32,11 @@ class UvicornServer(uvicorn.Server): asyncio_setup() else: asyncio.set_event_loop(uvloop.new_event_loop()) - - loop = asyncio.get_event_loop() + try: + loop = asyncio.get_event_loop() + except RuntimeError: + # When running in a thread, we'll not have an eventloop yet. + loop = asyncio.new_event_loop() loop.run_until_complete(self.serve(sockets=sockets)) @contextlib.contextmanager From 4164f9385313f98bce671aaa2fc9182bd5ad72d5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Aug 2021 20:41:08 +0200 Subject: [PATCH 162/200] Simplify fiat_convert and handle multi-mappings --- freqtrade/rpc/fiat_convert.py | 40 ++++++++++++++++++++-------------- tests/rpc/test_fiat_convert.py | 39 +++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index cdc09b437..f4e82261e 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -5,7 +5,7 @@ e.g BTC to USD import datetime import logging -from typing import Dict +from typing import Dict, List from cachetools.ttl import TTLCache from pycoingecko import CoinGeckoAPI @@ -25,8 +25,7 @@ class CryptoToFiatConverter: """ __instance = None _coingekko: CoinGeckoAPI = None - - _cryptomap: Dict = {} + _coinlistings: List[Dict] = [] _backoff: float = 0.0 def __new__(cls): @@ -49,9 +48,8 @@ class CryptoToFiatConverter: def _load_cryptomap(self) -> None: try: - coinlistings = self._coingekko.get_coins_list() - # Create mapping table from symbol to coingekko_id - self._cryptomap = {x['symbol']: x['id'] for x in coinlistings} + # Use list-comprehension to ensure we get a list. + self._coinlistings = [x for x in self._coingekko.get_coins_list()] except RequestException as request_exception: if "429" in str(request_exception): logger.warning( @@ -69,6 +67,24 @@ class CryptoToFiatConverter: logger.error( f"Could not load FIAT Cryptocurrency map for the following problem: {exception}") + def _get_gekko_id(self, crypto_symbol): + if not self._coinlistings: + if self._backoff <= datetime.datetime.now().timestamp(): + self._load_cryptomap() + # Still not loaded. + if not self._coinlistings: + return None + else: + return None + found = [x for x in self._coinlistings if x['symbol'] == crypto_symbol] + if len(found) == 1: + return found[0]['id'] + + if len(found) > 0: + # Wrong! + logger.warning(f"Found multiple mappings in goingekko for {crypto_symbol}.") + return None + def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float: """ Convert an amount of crypto-currency to fiat @@ -143,22 +159,14 @@ class CryptoToFiatConverter: if crypto_symbol == fiat_symbol: return 1.0 - if self._cryptomap == {}: - if self._backoff <= datetime.datetime.now().timestamp(): - self._load_cryptomap() - # return 0.0 if we still don't have data to check, no reason to proceed - if self._cryptomap == {}: - return 0.0 - else: - return 0.0 + _gekko_id = self._get_gekko_id(crypto_symbol) - if crypto_symbol not in self._cryptomap: + if not _gekko_id: # return 0 for unsupported stake currencies (fiat-convert should not break the bot) logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol) return 0.0 try: - _gekko_id = self._cryptomap[crypto_symbol] return float( self._coingekko.get_price( ids=_gekko_id, diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 9fb1122f5..2fe5d4a56 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -22,7 +22,7 @@ def test_fiat_convert_is_supported(mocker): def test_fiat_convert_find_price(mocker): fiat_convert = CryptoToFiatConverter() - fiat_convert._cryptomap = {} + fiat_convert._coinlistings = {} fiat_convert._backoff = 0 mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap', return_value=None) @@ -44,7 +44,7 @@ def test_fiat_convert_find_price(mocker): def test_fiat_convert_unsupported_crypto(mocker, caplog): - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) + mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings', return_value=[]) fiat_convert = CryptoToFiatConverter() assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog) @@ -88,9 +88,9 @@ def test_fiat_convert_two_FIAT(mocker): def test_loadcryptomap(mocker): fiat_convert = CryptoToFiatConverter() - assert len(fiat_convert._cryptomap) == 2 + assert len(fiat_convert._coinlistings) == 2 - assert fiat_convert._cryptomap["btc"] == "bitcoin" + assert fiat_convert._get_gekko_id("btc") == "bitcoin" def test_fiat_init_network_exception(mocker): @@ -102,11 +102,10 @@ def test_fiat_init_network_exception(mocker): ) # with pytest.raises(RequestEsxception): fiat_convert = CryptoToFiatConverter() - fiat_convert._cryptomap = {} + fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() - length_cryptomap = len(fiat_convert._cryptomap) - assert length_cryptomap == 0 + assert len(fiat_convert._coinlistings) == 0 def test_fiat_convert_without_network(mocker): @@ -132,11 +131,10 @@ def test_fiat_too_many_requests_response(mocker, caplog): ) # with pytest.raises(RequestEsxception): fiat_convert = CryptoToFiatConverter() - fiat_convert._cryptomap = {} + fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() - length_cryptomap = len(fiat_convert._cryptomap) - assert length_cryptomap == 0 + assert len(fiat_convert._coinlistings) == 0 assert fiat_convert._backoff > datetime.datetime.now().timestamp() assert log_has( 'Too many requests for Coingecko API, backing off and trying again later.', @@ -144,20 +142,33 @@ def test_fiat_too_many_requests_response(mocker, caplog): ) +def test_fiat_multiple_coins(mocker, caplog): + fiat_convert = CryptoToFiatConverter() + fiat_convert._coinlistings = [ + {'id': 'helium', 'symbol': 'hnt', 'name': 'Helium'}, + {'id': 'hymnode', 'symbol': 'hnt', 'name': 'Hymnode'}, + {'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'}, + ] + + assert fiat_convert._get_gekko_id('btc') == 'bitcoin' + assert fiat_convert._get_gekko_id('hnt') is None + + assert log_has('Found multiple mappings in goingekko for hnt.', caplog) + + def test_fiat_invalid_response(mocker, caplog): # Because CryptoToFiatConverter is a Singleton we reset the listings - listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}") + listmock = MagicMock(return_value=None) mocker.patch.multiple( 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): fiat_convert = CryptoToFiatConverter() - fiat_convert._cryptomap = {} + fiat_convert._coinlistings = [] fiat_convert._load_cryptomap() - length_cryptomap = len(fiat_convert._cryptomap) - assert length_cryptomap == 0 + assert len(fiat_convert._coinlistings) == 0 assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*', caplog) From 6e41add40e3d223f26d9acd9054571e1b7e947b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Aug 2021 06:25:30 +0200 Subject: [PATCH 163/200] Version bump ccxt closes #5437 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3bd34fad8..0e00c289a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.21.2 pandas==1.3.2 -ccxt==1.55.6 +ccxt==1.55.13 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 From acb00cd0728efe1e2bee29fa3ed2ae2b7173935b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Aug 2021 06:25:52 +0200 Subject: [PATCH 164/200] Use realistic threshold for "get_fee" test --- tests/exchange/test_ccxt_compat.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index dce10da84..ffca9ec81 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -142,8 +142,8 @@ class TestCCXTExchange(): def test_ccxt_get_fee(self, exchange): exchange, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] - - assert 0 < exchange.get_fee(pair, 'limit', 'buy') < 1 - assert 0 < exchange.get_fee(pair, 'limit', 'sell') < 1 - assert 0 < exchange.get_fee(pair, 'market', 'buy') < 1 - assert 0 < exchange.get_fee(pair, 'market', 'sell') < 1 + threshold = 0.01 + assert 0 < exchange.get_fee(pair, 'limit', 'buy') < threshold + assert 0 < exchange.get_fee(pair, 'limit', 'sell') < threshold + assert 0 < exchange.get_fee(pair, 'market', 'buy') < threshold + assert 0 < exchange.get_fee(pair, 'market', 'sell') < threshold From 2fb9f6e2f4d4621a9f87c27ee22588344901e29e Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 18 Aug 2021 05:07:37 -0400 Subject: [PATCH 165/200] rename custom price max distance option in config, update formula and test associated --- docs/strategy-advanced.md | 4 ++-- freqtrade/constants.py | 2 +- freqtrade/freqtradebot.py | 6 +++--- tests/test_freqtradebot.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index babcc5e7b..a0ae7201f 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -398,10 +398,10 @@ class AwesomeStrategy(IStrategy): ``` !!! Warning - Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_percent` parameter. + Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter. _Exemple_ -If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_percent` is set to 2%, The retained valid custom entry price will be 98. +If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98. !!! Warning "No backtesting support" Custom entry-prices are currently not supported during backtesting. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 2f51f45f7..cde276ac0 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -191,7 +191,7 @@ CONF_SCHEMA = { }, 'required': ['price_side'] }, - 'custom_price_max_distance_percent': { + 'custom_price_max_distance_ratio': { 'type': 'number', 'minimum': 0.0 }, 'order_types': { diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bd62934c5..caf201451 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1407,9 +1407,9 @@ class FreqtradeBot(LoggingMixin): else: valid_custom_price = proposed_price - cust_p_max_dist_pct = self.config.get('custom_price_max_distance_percent', 2.0) - min_custom_price_allowed = proposed_price - ((proposed_price * cust_p_max_dist_pct) / 100) - max_custom_price_allowed = proposed_price + ((proposed_price * cust_p_max_dist_pct) / 100) + cust_p_max_dist_r = self.config.get('custom_price_max_distance_ratio', 0.02) + min_custom_price_allowed = proposed_price - (proposed_price * cust_p_max_dist_r) + max_custom_price_allowed = proposed_price + (proposed_price * cust_p_max_dist_r) if valid_custom_price > max_custom_price_allowed: valid_price = max_custom_price_allowed diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5b5e3ce28..21bad5c64 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4595,7 +4595,7 @@ def test_get_valid_price(mocker, default_conf) -> None: patch_RPCManager(mocker) patch_exchange(mocker) freqtrade = FreqtradeBot(default_conf) - freqtrade.config['custom_price_max_distance_percent'] = 2.0 + freqtrade.config['custom_price_max_distance_ratio'] = 0.02 custom_price_string = "10" custom_price_badstring = "10abc" From 9469c6dfa9bc78bd6c9ff6ae17f0156ada4d6b39 Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 18 Aug 2021 05:10:29 -0400 Subject: [PATCH 166/200] small cosmetic changes in doc related to custom entry and exit exemple --- docs/strategy-advanced.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index a0ae7201f..f5f2d9197 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -400,8 +400,8 @@ class AwesomeStrategy(IStrategy): !!! Warning Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter. -_Exemple_ -If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98. +!!! Exemple + If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98. !!! Warning "No backtesting support" Custom entry-prices are currently not supported during backtesting. From ffd60f392be7a83cf2bf786314322eba4e27bacf Mon Sep 17 00:00:00 2001 From: axel Date: Wed, 18 Aug 2021 05:22:45 -0400 Subject: [PATCH 167/200] add custom price max distance ratio option in configuration.md --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index fab3004a5..09198e019 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -110,6 +110,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used.
**Datatype:** Integer | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict +| `custom_price_max_distance_ratio` | Configure maximum distance ratio between current and custom entry or exit price.
*Defaults to `0.02` 2%).*
**Datatype:** Positive float | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
**Datatype:** String | `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.
**Datatype:** Boolean | `exchange.key` | API key to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String From d97fc1e484ceb29a93f31e259556e9f74abda717 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Aug 2021 19:55:11 +0200 Subject: [PATCH 168/200] Update docs/strategy-advanced.md --- docs/strategy-advanced.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index f5f2d9197..b039f542f 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -400,7 +400,7 @@ class AwesomeStrategy(IStrategy): !!! Warning Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter. -!!! Exemple +!!! Example If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98. !!! Warning "No backtesting support" From 9951f510795c241f516d6046b12699be885e84b6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Aug 2021 20:20:11 +0200 Subject: [PATCH 169/200] Update test to ensure direction of movement is correct --- freqtrade/freqtradebot.py | 12 ++++-------- tests/test_freqtradebot.py | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index caf201451..e7a2a3784 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1411,11 +1411,7 @@ class FreqtradeBot(LoggingMixin): min_custom_price_allowed = proposed_price - (proposed_price * cust_p_max_dist_r) max_custom_price_allowed = proposed_price + (proposed_price * cust_p_max_dist_r) - if valid_custom_price > max_custom_price_allowed: - valid_price = max_custom_price_allowed - elif valid_custom_price < min_custom_price_allowed: - valid_price = min_custom_price_allowed - else: - valid_price = valid_custom_price - - return valid_price + # Bracket between min_custom_price_allowed and max_custom_price_allowed + return max( + min(valid_custom_price, max_custom_price_allowed), + min_custom_price_allowed) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 21bad5c64..a2bb01a4b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4624,8 +4624,8 @@ def test_get_valid_price(mocker, default_conf) -> None: assert valid_price_from_int == custom_price_int assert valid_price_from_float == custom_price_float - assert valid_price_at_max_alwd != custom_price_over_max_alwd + assert valid_price_at_max_alwd < custom_price_over_max_alwd assert valid_price_at_max_alwd > proposed_price - assert valid_price_at_min_alwd != custom_price_under_min_alwd + assert valid_price_at_min_alwd > custom_price_under_min_alwd assert valid_price_at_min_alwd < proposed_price From dc0b4d07d48b78541701c5939ada74c31304c200 Mon Sep 17 00:00:00 2001 From: topscoder <86197446+topscoder@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:52:11 +0200 Subject: [PATCH 170/200] Fix typo Reseting -> Resetting --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index a85bd3104..feb0241f8 100755 --- a/setup.sh +++ b/setup.sh @@ -163,7 +163,7 @@ function update() { # Reset Develop or Stable branch function reset() { echo "----------------------------" - echo "Reseting branch and virtual env" + echo "Resetting branch and virtual env" echo "----------------------------" if [ "1" == $(git branch -vv |grep -cE "\* develop|\* stable") ] From ba5abb20bd07211022b0db1a4cf47fa5ec7e9609 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Aug 2021 06:30:27 +0200 Subject: [PATCH 171/200] Run compat ci for gate.io --- requirements.txt | 2 +- tests/exchange/test_ccxt_compat.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0e00c289a..09109baaf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.21.2 pandas==1.3.2 -ccxt==1.55.13 +ccxt==1.55.20 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index ffca9ec81..3a32d108b 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -42,6 +42,11 @@ EXCHANGES = { 'hasQuoteVolume': True, 'timeframe': '5m', }, + 'gateio': { + 'pair': 'BTC/USDT', + 'hasQuoteVolume': True, + 'timeframe': '5m', + }, } From 695a1e21bf0b622bffc78dfd56073367cd713cc7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Aug 2021 06:51:04 +0200 Subject: [PATCH 172/200] Set gate.io download limit to 1000 candles --- freqtrade/exchange/__init__.py | 1 + freqtrade/exchange/gateio.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 freqtrade/exchange/gateio.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 015e0c869..b0c88a51a 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -15,6 +15,7 @@ from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges, timeframe_to_seconds, validate_exchange, validate_exchanges) from freqtrade.exchange.ftx import Ftx +from freqtrade.exchange.gateio import Gateio from freqtrade.exchange.hitbtc import Hitbtc from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.kucoin import Kucoin diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py new file mode 100644 index 000000000..9c910a10d --- /dev/null +++ b/freqtrade/exchange/gateio.py @@ -0,0 +1,23 @@ +""" Gate.io exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + + +logger = logging.getLogger(__name__) + + +class Gateio(Exchange): + """ + Gate.io exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + + Please note that this exchange is not included in the list of exchanges + officially supported by the Freqtrade development team. So some features + may still not work as expected. + """ + + _ft_has: Dict = { + "ohlcv_candle_limit": 1000, + } From 40ad4510194e3eba6512fa1f2ca7d071ae38babd Mon Sep 17 00:00:00 2001 From: Masoud Azizi Date: Thu, 19 Aug 2021 18:34:02 +0000 Subject: [PATCH 173/200] Download-data log process added Download-data log process added pytest assert solved --- freqtrade/data/history/history_utils.py | 33 ++++++++++++++----------- tests/data/test_history.py | 10 +++++--- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 1459dfd78..6f125aaa9 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -117,10 +117,11 @@ def refresh_data(datadir: Path, :param timerange: Limit data to be loaded to this timerange """ data_handler = get_datahandler(datadir, data_format) - for pair in pairs: - _download_pair_history(pair=pair, timeframe=timeframe, - datadir=datadir, timerange=timerange, - exchange=exchange, data_handler=data_handler) + for idx, pair in enumerate(pairs): + process = f'{idx}/{len(pairs)}' + _download_pair_history(pair=pair, process=process, + timeframe=timeframe, datadir=datadir, + timerange=timerange, exchange=exchange, data_handler=data_handler) def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optional[TimeRange], @@ -153,13 +154,14 @@ def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optiona return data, start_ms -def _download_pair_history(datadir: Path, +def _download_pair_history(pair: str, *, + datadir: Path, exchange: Exchange, - pair: str, *, - new_pairs_days: int = 30, timeframe: str = '5m', - timerange: Optional[TimeRange] = None, - data_handler: IDataHandler = None) -> bool: + process: str = '', + new_pairs_days: int = 30, + data_handler: IDataHandler = None, + timerange: Optional[TimeRange] = None) -> bool: """ Download latest candles from the exchange for the pair and timeframe passed in parameters The data is downloaded starting from the last correct data that @@ -177,7 +179,7 @@ def _download_pair_history(datadir: Path, try: logger.info( - f'Download history data for pair: "{pair}", timeframe: {timeframe} ' + f'Download history data for pair: "{pair}" ({process}), timeframe: {timeframe} ' f'and store in {datadir}.' ) @@ -234,7 +236,7 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes """ pairs_not_available = [] data_handler = get_datahandler(datadir, data_format) - for pair in pairs: + for idx, pair in enumerate(pairs, start=1): if pair not in exchange.markets: pairs_not_available.append(pair) logger.info(f"Skipping pair {pair}...") @@ -247,10 +249,11 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes f'Deleting existing data for pair {pair}, interval {timeframe}.') logger.info(f'Downloading pair {pair}, interval {timeframe}.') - _download_pair_history(datadir=datadir, exchange=exchange, - pair=pair, timeframe=str(timeframe), - new_pairs_days=new_pairs_days, - timerange=timerange, data_handler=data_handler) + process = f'{idx}/{len(pairs)}' + _download_pair_history(pair=pair, process=process, + datadir=datadir, exchange=exchange, + timerange=timerange, data_handler=data_handler, + timeframe=str(timeframe), new_pairs_days=new_pairs_days) return pairs_not_available diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 13d22ebb7..bcc2bf052 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -133,8 +133,8 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC') assert file.is_file() assert log_has_re( - 'Download history data for pair: "MEME/BTC", timeframe: 1m ' - 'and store in .*', caplog + 'Download history data for pair: "MEME/BTC" (0/1), timeframe: 1m ' + 'and store in', caplog ) @@ -278,8 +278,10 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: return_value=None) mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick) exchange = get_patched_exchange(mocker, default_conf) - _download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='1m') - _download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='3m') + _download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC", + timeframe='1m') + _download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC", + timeframe='3m') assert json_dump_mock.call_count == 2 From 127f470bc3928b052fc5ac00d85f4422ae4e5f1f Mon Sep 17 00:00:00 2001 From: Masoud Azizi Date: Fri, 20 Aug 2021 20:28:10 +0000 Subject: [PATCH 174/200] .* ADDED --- tests/data/test_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index bcc2bf052..e9d2c3638 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -133,8 +133,8 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC') assert file.is_file() assert log_has_re( - 'Download history data for pair: "MEME/BTC" (0/1), timeframe: 1m ' - 'and store in', caplog + r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m ' + r'and store in .*', caplog ) From 56759cea7b8697f3e2f687e8e2f47bdbf001c593 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Aug 2021 14:37:45 +0200 Subject: [PATCH 175/200] Add "high fee rate" blocker to fees from trades part of #5415 (potentially fixing this) --- freqtrade/freqtradebot.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e7a2a3784..41d61f946 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1381,7 +1381,9 @@ class FreqtradeBot(LoggingMixin): if fee_currency: # fee_rate should use mean fee_rate = sum(fee_rate_array) / float(len(fee_rate_array)) if fee_rate_array else None - trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', '')) + if fee_rate is not None and fee_rate < 0.02: + # Only update if fee-rate is < 2% + trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', '')) if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): logger.warning(f"Amount {amount} does not match amount {trade.amount}") From 04b4deab588897d258ec8f44851875f86e99b3fa Mon Sep 17 00:00:00 2001 From: JoeSchr Date: Sun, 22 Aug 2021 20:14:36 +0300 Subject: [PATCH 176/200] Update strategy-advanced.md Add `` for consistency --- docs/strategy-advanced.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index b039f542f..4409af6ea 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -361,7 +361,7 @@ See [Dataframe access](#dataframe-access) for more information about dataframe u By default, freqtrade use the orderbook to automatically set an order price([Relevant documentation](configuration.md#prices-used-for-orders)), you also have the option to create custom order prices based on your strategy. -You can use this feature by creating a `custom_entry_price()` function in your strategy file to customize entry prices and custom_exit_price for exits. +You can use this feature by creating a `custom_entry_price()` function in your strategy file to customize entry prices and `custom_exit_price()` for exits. !!! Note If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration. From 3026583ed480cdaab0be78554fcc0d68cf85ed00 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Aug 2021 19:40:09 +0200 Subject: [PATCH 177/200] Reduce verbosity of "is in blacklist" logging --- freqtrade/plugins/pairlist/VolumePairList.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 23291d39e..c70e4a904 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -4,6 +4,7 @@ Volume PairList provider Provides dynamic pair list based on trade volumes """ import logging +from functools import partial from typing import Any, Dict, List import arrow @@ -203,7 +204,7 @@ class VolumePairList(IPairList): # Validate whitelist to only have active market pairs pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) - pairs = self.verify_blacklist(pairs, logger.info) + pairs = self.verify_blacklist(pairs, partial(self.log_once, logmethod=logger.info)) # Limit pairlist to the requested number of pairs pairs = pairs[:self._number_pairs] From 4ee467f8575ae2d2f3488bad00ba4ee774b4e8bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 03:01:26 +0000 Subject: [PATCH 178/200] Bump ccxt from 1.55.20 to 1.55.28 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.55.20 to 1.55.28. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.55.20...1.55.28) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 09109baaf..f7934c903 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.21.2 pandas==1.3.2 -ccxt==1.55.20 +ccxt==1.55.28 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 From 0f9bfcf8b0699b9935d96d7bf18e3091693d0245 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 03:01:28 +0000 Subject: [PATCH 179/200] Bump types-cachetools from 0.1.10 to 4.2.0 Bumps [types-cachetools](https://github.com/python/typeshed) from 0.1.10 to 4.2.0. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b20cb6693..67ee0035b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,7 +19,7 @@ isort==5.9.3 nbconvert==6.1.0 # mypy types -types-cachetools==0.1.10 +types-cachetools==4.2.0 types-filelock==0.1.5 types-requests==2.25.6 types-tabulate==0.8.2 From 90f1845eafa03f9a6236904bc78a75190bad0065 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 03:01:33 +0000 Subject: [PATCH 180/200] Bump prompt-toolkit from 3.0.19 to 3.0.20 Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.19 to 3.0.20. - [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases) - [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG) - [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.19...3.0.20) --- updated-dependencies: - dependency-name: prompt-toolkit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 09109baaf..11b48cbc3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,4 +40,4 @@ aiofiles==0.7.0 colorama==0.4.4 # Building config files interactively questionary==1.10.0 -prompt-toolkit==3.0.19 +prompt-toolkit==3.0.20 From 345c7ab64b8a7422bdc555605c7c1cb1669c61d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 03:01:42 +0000 Subject: [PATCH 181/200] Bump sqlalchemy from 1.4.22 to 1.4.23 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.22 to 1.4.23. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/master/CHANGES) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 09109baaf..1a46fc828 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ ccxt==1.55.20 # Pin cryptography for now due to rust build errors with piwheels cryptography==3.4.7 aiohttp==3.7.4.post0 -SQLAlchemy==1.4.22 +SQLAlchemy==1.4.23 python-telegram-bot==13.7 arrow==1.1.1 cachetools==4.2.2 From 60b476611cae94d3134c6d6670b9c0c2cbae9474 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Aug 2021 20:51:41 +0200 Subject: [PATCH 182/200] Simplify a number of tests by relying on default arguments --- tests/rpc/test_rpc.py | 34 ++++++++++++------------ tests/rpc/test_rpc_apiserver.py | 24 ++++++++--------- tests/rpc/test_rpc_telegram.py | 46 ++++++++++++++++----------------- tests/test_freqtradebot.py | 4 +-- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 136fa157c..0ba42c4ce 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -35,7 +35,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -192,7 +192,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: ) del default_conf['fiat_display_currency'] freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -239,7 +239,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -371,7 +371,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -459,7 +459,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -526,7 +526,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() with pytest.raises(RPCException, match="Error getting current tickers."): @@ -567,7 +567,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): ) default_conf['dry_run'] = False freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() @@ -612,7 +612,7 @@ def test_rpc_start(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED @@ -633,7 +633,7 @@ def test_rpc_stop(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -655,7 +655,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -687,7 +687,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED @@ -805,7 +805,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) # Create some test data @@ -838,7 +838,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) counts = rpc._rpc_count() @@ -863,7 +863,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) pair = 'ETH/BTC' trade = rpc._rpc_forcebuy(pair, None) @@ -889,7 +889,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> # Test not buying freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot.config['stake_amount'] = 0 - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) pair = 'TKN/BTC' trade = rpc._rpc_forcebuy(pair, None) @@ -902,7 +902,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) pair = 'ETH/BTC' with pytest.raises(RPCException, match=r'trader is not running'): @@ -913,7 +913,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) pair = 'ETH/BTC' with pytest.raises(RPCException, match=r'Forcebuy not enabled.'): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 3d02e8188..68bd01911 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -447,7 +447,7 @@ def test_api_balance(botclient, mocker, rpc_balance): def test_api_count(botclient, mocker, ticker, fee, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -509,7 +509,7 @@ def test_api_locks(botclient): def test_api_show_config(botclient, mocker): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) rc = client_get(client, f"{BASE_URI}/show_config") assert_response(rc) @@ -527,7 +527,7 @@ def test_api_show_config(botclient, mocker): def test_api_daily(botclient, mocker, ticker, fee, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -545,7 +545,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): def test_api_trades(botclient, mocker, fee, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets) @@ -573,7 +573,7 @@ def test_api_trades(botclient, mocker, fee, markets): def test_api_trade_single(botclient, mocker, fee, ticker, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -593,7 +593,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): def test_api_delete_trade(botclient, mocker, fee, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( @@ -667,7 +667,7 @@ def test_api_logs(botclient): def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -683,7 +683,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_profit(botclient, mocker, ticker, fee, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -734,7 +734,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): @pytest.mark.usefixtures("init_persistence") def test_api_stats(botclient, mocker, ticker, fee, markets,): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -762,7 +762,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,): def test_api_performance(botclient, fee): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) trade = Trade( pair='LTC/ETH', @@ -808,7 +808,7 @@ def test_api_performance(botclient, fee): def test_api_status(botclient, mocker, ticker, fee, markets): ftbot, client = botclient - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -1051,7 +1051,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): markets=PropertyMock(return_value=markets), _is_dry_limit_order_filled=MagicMock(return_value=False), ) - patch_get_signal(ftbot, (True, False, None)) + patch_get_signal(ftbot) rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index b678c3363..8596c7c44 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -119,7 +119,7 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None: rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) - patch_get_signal(bot, (True, False, None)) + patch_get_signal(bot) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is True assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) @@ -139,7 +139,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) - patch_get_signal(bot, (True, False, None)) + patch_get_signal(bot) dummy.dummy_handler(update=update, context=MagicMock()) assert dummy.state['called'] is False assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) @@ -155,7 +155,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None bot = FreqtradeBot(default_conf) rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) - patch_get_signal(bot, (True, False, None)) + patch_get_signal(bot) dummy.dummy_exception(update=update, context=MagicMock()) assert dummy.state['called'] is False @@ -229,7 +229,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) freqtradebot.state = State.STOPPED # Status is also enabled when stopped @@ -286,7 +286,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) freqtradebot.state = State.STOPPED # Status table is also enabled when stopped @@ -330,7 +330,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # Create some test data freqtradebot.enter_positions() @@ -401,7 +401,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # Try invalid data msg_mock.reset_mock() @@ -433,7 +433,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -488,7 +488,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -514,7 +514,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick side_effect=lambda a, b: f"{a}/{b}") telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] @@ -537,7 +537,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) freqtradebot.config['dry_run'] = False telegram._balance(update=update, context=MagicMock()) @@ -550,7 +550,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] @@ -579,7 +579,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None }) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) telegram._balance(update=update, context=MagicMock()) assert msg_mock.call_count > 1 @@ -678,7 +678,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # Create some test data freqtradebot.enter_positions() @@ -737,7 +737,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # Create some test data freqtradebot.enter_positions() @@ -798,7 +798,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # Create some test data freqtradebot.enter_positions() @@ -839,7 +839,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # Trader is not running freqtradebot.state = State.STOPPED @@ -877,7 +877,7 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # /forcebuy ETH/BTC context = MagicMock() @@ -906,7 +906,7 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) update.message.text = '/forcebuy ETH/Nonepair' telegram._forcebuy(update=update, context=MagicMock()) @@ -923,7 +923,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) context = MagicMock() context.args = [] @@ -951,7 +951,7 @@ def test_performance_handle(default_conf, update, ticker, fee, get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) # Create some test data freqtradebot.enter_positions() @@ -979,7 +979,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) freqtradebot.state = State.STOPPED telegram._count(update=update, context=MagicMock()) @@ -1008,7 +1008,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None get_fee=fee, ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - patch_get_signal(freqtradebot, (True, False, None)) + patch_get_signal(freqtradebot) telegram._locks(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'No active locks.' in msg_mock.call_args_list[0][0][0] diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a2bb01a4b..a859787cb 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1954,7 +1954,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open, assert nb_trades == 0 # Buy is triggering, so buying ... - patch_get_signal(freqtrade, value=(True, False, None)) + patch_get_signal(freqtrade) freqtrade.enter_positions() trades = Trade.query.all() nb_trades = len(trades) @@ -1999,7 +1999,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, ) freqtrade = get_patched_freqtradebot(mocker, default_conf) - patch_get_signal(freqtrade, value=(True, False, None)) + patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.enter_positions() From 8a9407bac9ff906db1d6391e6ce1177b9af1fa79 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Aug 2021 07:13:09 +0200 Subject: [PATCH 183/200] Don't hard-fail when fetch_ticker doesn't return a value closes #5477 --- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index dbd72aca4..79ee94149 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1038,7 +1038,7 @@ class Exchange: logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price") ticker = self.fetch_ticker(pair) ticker_rate = ticker[conf_strategy['price_side']] - if ticker['last']: + if ticker['last'] and ticker_rate: if side == 'buy' and ticker_rate > ticker['last']: balance = conf_strategy['ask_last_balance'] ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 27eeed39b..f6ac4c459 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1851,6 +1851,31 @@ def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask, assert log_has("Using cached sell rate for ETH/BTC.", caplog) +@pytest.mark.parametrize("entry,side,ask,bid,last,last_ab,expected", [ + ('buy', 'ask', None, 4, 4, 0, 4), # ask not available + ('buy', 'ask', None, None, 4, 0, 4), # ask not available + ('buy', 'bid', 6, None, 4, 0, 5), # bid not available + ('buy', 'bid', None, None, 4, 0, 5), # No rate available + ('sell', 'ask', None, 4, 4, 0, 4), # ask not available + ('sell', 'ask', None, None, 4, 0, 4), # ask not available + ('sell', 'bid', 6, None, 4, 0, 5), # bid not available + ('sell', 'bid', None, None, 4, 0, 5), # bid not available +]) +def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, ask, bid, + last, last_ab, expected) -> None: + caplog.set_level(logging.DEBUG) + default_conf['bid_strategy']['ask_last_balance'] = last_ab + default_conf['bid_strategy']['price_side'] = side + default_conf['ask_strategy']['price_side'] = side + default_conf['ask_strategy']['ask_last_balance'] = last_ab + exchange = get_patched_exchange(mocker, default_conf) + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + return_value={'ask': ask, 'last': last, 'bid': bid}) + + with pytest.raises(PricingError): + exchange.get_rate('ETH/BTC', refresh=True, side=entry) + + @pytest.mark.parametrize('side,expected', [ ('bid', 0.043936), # Value from order_book_l2 fiture - bids side ('ask', 0.043949), # Value from order_book_l2 fiture - asks side From 4b36276e4f0e5b451f2449f9afebc6e90ea143f9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Aug 2021 19:54:26 +0200 Subject: [PATCH 184/200] Improve has_space test --- tests/optimize/test_hyperopt_tools.py | 47 +++++++++++++++++++-------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index cbcb13384..e845b2a77 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -64,34 +64,53 @@ def test_load_previous_results2(mocker, testdatadir, caplog) -> None: @pytest.mark.parametrize("spaces, expected_results", [ (['buy'], - {'buy': True, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False}), + {'buy': True, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False, + 'protection': False}), (['sell'], - {'buy': False, 'sell': True, 'roi': False, 'stoploss': False, 'trailing': False}), + {'buy': False, 'sell': True, 'roi': False, 'stoploss': False, 'trailing': False, + 'protection': False}), (['roi'], - {'buy': False, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False}), + {'buy': False, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False, + 'protection': False}), (['stoploss'], - {'buy': False, 'sell': False, 'roi': False, 'stoploss': True, 'trailing': False}), + {'buy': False, 'sell': False, 'roi': False, 'stoploss': True, 'trailing': False, + 'protection': False}), (['trailing'], - {'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': True}), + {'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': True, + 'protection': False}), (['buy', 'sell', 'roi', 'stoploss'], - {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}), + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False, + 'protection': False}), (['buy', 'sell', 'roi', 'stoploss', 'trailing'], - {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}), + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, + 'protection': False}), (['buy', 'roi'], - {'buy': True, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False}), + {'buy': True, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False, + 'protection': False}), (['all'], - {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}), + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, + 'protection': True}), (['default'], - {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}), + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False, + 'protection': False}), (['default', 'trailing'], - {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}), + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, + 'protection': False}), (['all', 'buy'], - {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}), + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, + 'protection': True}), (['default', 'buy'], - {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}), + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False, + 'protection': False}), + (['all'], + {'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True, + 'protection': True}), + (['protection'], + {'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False, + 'protection': True}), ]) def test_has_space(hyperopt_conf, spaces, expected_results): - for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing']: + for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection']: hyperopt_conf.update({'spaces': spaces}) assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s] From 23d21d8ace2e1e58b52e2c0d601cfc972f0031c4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Aug 2021 19:57:10 +0200 Subject: [PATCH 185/200] Fix wrong message if protection-space is missing closes #5480 --- freqtrade/optimize/hyperopt_auto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_auto.py b/freqtrade/optimize/hyperopt_auto.py index 03f7dd21e..43e92d9c6 100644 --- a/freqtrade/optimize/hyperopt_auto.py +++ b/freqtrade/optimize/hyperopt_auto.py @@ -74,7 +74,7 @@ class HyperOptAuto(IHyperOpt): return self._get_indicator_space('sell', 'sell_indicator_space') def protection_space(self) -> List['Dimension']: - return self._get_indicator_space('protection', 'indicator_space') + return self._get_indicator_space('protection', 'protection_space') def generate_roi_table(self, params: Dict) -> Dict[int, float]: return self._get_func('generate_roi_table')(params) From 3745966c6c0b95b549790a8652c90236079846cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Aug 2021 20:01:00 +0200 Subject: [PATCH 186/200] Update help-docstring for hyperopt --- docs/hyperopt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 96f9ff177..a9f7425f4 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -48,7 +48,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--hyperopt-path PATH] [--eps] [--dmmp] [--enable-protections] [--dry-run-wallet DRY_RUN_WALLET] [-e INT] - [--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]] + [--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]] [--print-all] [--no-color] [--print-json] [-j JOBS] [--random-state INT] [--min-trades INT] [--hyperopt-loss NAME] [--disable-param-export] @@ -92,7 +92,7 @@ optional arguments: Starting balance, used for backtesting / hyperopt and dry-runs. -e INT, --epochs INT Specify number of epochs (default: 100). - --spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...] + --spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...] Specify which parameters to hyperopt. Space-separated list. --print-all Print all results, not only the best ones. From 68dd349094969d48343849134280d9c8ca2920b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Aug 2021 20:04:10 +0200 Subject: [PATCH 187/200] Update hyperopt docs regarding protections --- docs/hyperopt.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index a9f7425f4..c5a52553b 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -576,7 +576,8 @@ Legal values are: * `roi`: just optimize the minimal profit table for your strategy * `stoploss`: search for the best stoploss value * `trailing`: search for the best trailing stop values -* `default`: `all` except `trailing` +* `protection`: search for the best protection parameters (read the [protections section](#optimizing-protections) on how to properly define these) +* `default`: `all` except `trailing` and `protection` * space-separated list of any of the above values for example `--spaces roi stoploss` The default Hyperopt Search Space, used when no `--space` command line option is specified, does not include the `trailing` hyperspace. We recommend you to run optimization for the `trailing` hyperspace separately, when the best parameters for other hyperspaces were found, validated and pasted into your custom strategy. From 7fb570cc58fb8157afde1df0e26cd72eabafcd6a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Aug 2021 20:28:55 +0200 Subject: [PATCH 188/200] hyperopt Fallback methods should not be used. --- freqtrade/optimize/hyperopt.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 901900121..e0b35df32 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -107,13 +107,25 @@ class Hyperopt: # Populate "fallback" functions here # (hasattr is slow so should not be run during "regular" operations) if hasattr(self.custom_hyperopt, 'populate_indicators'): - self.backtesting.strategy.advise_indicators = ( # type: ignore + logger.warning( + "DEPRECATED: Using `populate_indicators()` in the hyperopt file is deprecated. " + "Please move these methods to your strategy." + ) + self.backtesting.strategy.populate_indicators = ( # type: ignore self.custom_hyperopt.populate_indicators) # type: ignore if hasattr(self.custom_hyperopt, 'populate_buy_trend'): - self.backtesting.strategy.advise_buy = ( # type: ignore + logger.warning( + "DEPRECATED: Using `populate_buy_trend()` in the hyperopt file is deprecated. " + "Please move these methods to your strategy." + ) + self.backtesting.strategy.populate_buy_trend = ( # type: ignore self.custom_hyperopt.populate_buy_trend) # type: ignore if hasattr(self.custom_hyperopt, 'populate_sell_trend'): - self.backtesting.strategy.advise_sell = ( # type: ignore + logger.warning( + "DEPRECATED: Using `populate_sell_trend()` in the hyperopt file is deprecated. " + "Please move these methods to your strategy." + ) + self.backtesting.strategy.populate_sell_trend = ( # type: ignore self.custom_hyperopt.populate_sell_trend) # type: ignore # Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set From 63844d39f62957cc0c89ab6d1f9dfdd9c194f9e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 06:48:26 +0200 Subject: [PATCH 189/200] Rename execute_buy to execute_entry --- freqtrade/freqtradebot.py | 8 ++++---- freqtrade/rpc/rpc.py | 2 +- tests/test_freqtradebot.py | 42 +++++++++++++++++++------------------- tests/test_wallets.py | 4 ++-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 41d61f946..d92156ef7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -433,11 +433,11 @@ class FreqtradeBot(LoggingMixin): if ((bid_check_dom.get('enabled', False)) and (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): if self._check_depth_of_market_buy(pair, bid_check_dom): - return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) + return self.execute_entry(pair, stake_amount, buy_tag=buy_tag) else: return False - return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) + return self.execute_entry(pair, stake_amount, buy_tag=buy_tag) else: return False @@ -465,8 +465,8 @@ class FreqtradeBot(LoggingMixin): logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") return False - def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: + def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, + forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 0264003a5..51bf0ab3a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -613,7 +613,7 @@ class RPC: stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) # execute buy - if self._freqtrade.execute_buy(pair, stakeamount, price, forcebuy=True): + if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True): Trade.commit() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() return trade diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a859787cb..9a5a537ef 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -185,7 +185,7 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b limit_buy_order_open['id'] = str(i) result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC') assert pytest.approx(result) == expected[i] - freqtrade.execute_buy('ETH/BTC', result) + freqtrade.execute_entry('ETH/BTC', result) else: with pytest.raises(DependencyException): freqtrade.wallets.get_trade_stake_amount('ETH/BTC') @@ -584,8 +584,8 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde patch_get_signal(freqtrade) # Create 2 existing trades - freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount']) - freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount']) + freqtrade.execute_entry('ETH/BTC', default_conf['stake_amount']) + freqtrade.execute_entry('NEO/BTC', default_conf['stake_amount']) assert len(Trade.get_open_trades()) == 2 # Change order_id for new orders @@ -776,7 +776,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0] -def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: +def test_execute_entry(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None: patch_RPCManager(mocker) patch_exchange(mocker) freqtrade = FreqtradeBot(default_conf) @@ -799,7 +799,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order ) pair = 'ETH/BTC' - assert not freqtrade.execute_buy(pair, stake_amount) + assert not freqtrade.execute_entry(pair, stake_amount) assert buy_rate_mock.call_count == 1 assert buy_mm.call_count == 0 assert freqtrade.strategy.confirm_trade_entry.call_count == 1 @@ -807,7 +807,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order_open['id'] = '22' freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) assert buy_rate_mock.call_count == 1 assert buy_mm.call_count == 1 call_args = buy_mm.call_args_list[0][1] @@ -826,7 +826,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order # Test calling with price limit_buy_order_open['id'] = '33' fix_price = 0.06 - assert freqtrade.execute_buy(pair, stake_amount, fix_price) + assert freqtrade.execute_entry(pair, stake_amount, fix_price) # Make sure get_rate wasn't called again assert buy_rate_mock.call_count == 0 @@ -844,7 +844,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=limit_buy_order)) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[2] assert trade assert trade.open_order_id is None @@ -861,7 +861,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['id'] = '555' mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=limit_buy_order)) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[3] assert trade assert trade.open_order_id == '555' @@ -873,7 +873,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['id'] = '556' freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0 - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[4] assert trade assert trade.stake_amount == 150 @@ -881,7 +881,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order # Exception case limit_buy_order['id'] = '557' freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[5] assert trade assert trade.stake_amount == 2.0 @@ -896,20 +896,20 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['id'] = '66' mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=limit_buy_order)) - assert not freqtrade.execute_buy(pair, stake_amount) + assert not freqtrade.execute_entry(pair, stake_amount) # Fail to get price... mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) with pytest.raises(PricingError, match="Could not determine buy price."): - freqtrade.execute_buy(pair, stake_amount) + freqtrade.execute_entry(pair, stake_amount) # In case of custom entry price mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50) limit_buy_order['status'] = 'open' limit_buy_order['id'] = '5566' freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508 - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[6] assert trade assert trade.open_rate_requested == 0.508 @@ -924,7 +924,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order get_rate=MagicMock(return_value=10), ) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[7] assert trade assert trade.open_rate_requested == 10 @@ -933,13 +933,13 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order limit_buy_order['status'] = 'open' limit_buy_order['id'] = '5568' freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) trade = Trade.query.all()[8] assert trade assert trade.open_rate_requested == 10 -def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: +def test_execute_entry_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -957,18 +957,18 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) - pair = 'ETH/BTC' freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) limit_buy_order['id'] = '222' freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) limit_buy_order['id'] = '2223' freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) - assert freqtrade.execute_buy(pair, stake_amount) + assert freqtrade.execute_entry(pair, stake_amount) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) - assert not freqtrade.execute_buy(pair, stake_amount) + assert not freqtrade.execute_entry(pair, stake_amount) def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None: diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 9f58cb71d..53e3b758e 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -157,13 +157,13 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r assert result == result1 # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' - freqtrade.execute_buy('ETH/USDT', result) + freqtrade.execute_entry('ETH/USDT', result) result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT') assert result == result1 # create 2 trades, order amount should be None - freqtrade.execute_buy('LTC/BTC', result) + freqtrade.execute_entry('LTC/BTC', result) result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT') assert result == 0 From c4be52d1c3100bdb5525510c1a8ff7c5f9bb2c37 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 06:53:42 +0200 Subject: [PATCH 190/200] rename execute_sell to execute_trade_exit --- freqtrade/freqtradebot.py | 8 ++--- freqtrade/rpc/rpc.py | 2 +- tests/test_freqtradebot.py | 72 ++++++++++++++++++++------------------ tests/test_integration.py | 2 +- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d92156ef7..a7451d632 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -745,7 +745,7 @@ class FreqtradeBot(LoggingMixin): trade.stoploss_order_id = None logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.warning('Selling the trade forcefully') - self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple( + self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple( sell_type=SellType.EMERGENCY_SELL)) except ExchangeError: @@ -863,7 +863,7 @@ class FreqtradeBot(LoggingMixin): if should_sell.sell_flag: logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') - self.execute_sell(trade, sell_rate, should_sell) + self.execute_trade_exit(trade, sell_rate, should_sell) return True return False @@ -1064,9 +1064,9 @@ class FreqtradeBot(LoggingMixin): raise DependencyException( f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") - def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: + def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: """ - Executes a limit sell for the given trade and limit + Executes a trade exit for the given trade and limit :param trade: Trade instance :param limit: limit rate for the sell order :param sell_reason: Reason the sell was triggered diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 51bf0ab3a..95a37452b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -557,7 +557,7 @@ class RPC: current_rate = self._freqtrade.exchange.get_rate( trade.pair, refresh=False, side="sell") sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) - self._freqtrade.execute_sell(trade, current_rate, sell_reason) + self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason) # ---- EOF def _exec_forcesell ---- if self._freqtrade.state != State.RUNNING: diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 9a5a537ef..75b67e59c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2007,7 +2007,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open, trade = Trade.query.first() trade.is_open = True - # FIX: sniffing logs, suggest handle_trade should not execute_sell + # FIX: sniffing logs, suggest handle_trade should not execute_trade_exit # instead that responsibility should be moved out of handle_trade(), # we might just want to check if we are in a sell condition without # executing @@ -2633,7 +2633,7 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None: assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order' -def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: +def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -2661,16 +2661,16 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N fetch_ticker=ticker_sell_up ) # Prevented sell ... - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.ROI)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.ROI)) assert rpc_mock.call_count == 0 assert freqtrade.strategy.confirm_trade_exit.call_count == 1 # Repatch with true freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.ROI)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.ROI)) assert freqtrade.strategy.confirm_trade_exit.call_count == 1 assert rpc_mock.call_count == 1 @@ -2697,7 +2697,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N } == last_msg -def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: +def test_execute_trade_exit_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -2722,8 +2722,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) fetch_ticker=ticker_sell_down ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -2749,7 +2749,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) } == last_msg -def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_up, mocker) -> None: +def test_execute_trade_exit_custom_exit_price(default_conf, ticker, fee, ticker_sell_up, + mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -2782,8 +2783,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u # Set a custom exit price freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05 - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)) # Sell price must be different to default bid price @@ -2813,8 +2814,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u } == last_msg -def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, - ticker_sell_down, mocker) -> None: +def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee, + ticker_sell_down, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -2844,8 +2845,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe # Setting trade stoploss to 0.01 trade.stop_loss = 0.00001099 * 0.99 - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -2872,7 +2873,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe } == last_msg -def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None: +def test_execute_trade_exit_sloe_cancel_exception( + mocker, default_conf, ticker, fee, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=InvalidOrderException()) @@ -2899,14 +2901,14 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c freqtrade.config['dry_run'] = False trade.stoploss_order_id = "abcd" - freqtrade.execute_sell(trade=trade, limit=1234, - sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) + freqtrade.execute_trade_exit(trade=trade, limit=1234, + sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) assert create_order_mock.call_count == 2 assert log_has('Could not cancel stoploss order abcd', caplog) -def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up, - mocker) -> None: +def test_execute_trade_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up, + mocker) -> None: default_conf['exchange']['name'] = 'binance' rpc_mock = patch_RPCManager(mocker) @@ -2950,8 +2952,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke fetch_ticker=ticker_sell_up ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) trade = Trade.query.first() assert trade @@ -2959,8 +2961,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke assert rpc_mock.call_count == 3 -def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee, - mocker) -> None: +def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee, + mocker) -> None: default_conf['exchange']['name'] = 'binance' rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) @@ -3031,8 +3033,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL -def test_execute_sell_market_order(default_conf, ticker, fee, - ticker_sell_up, mocker) -> None: +def test_execute_trade_exit_market_order(default_conf, ticker, fee, + ticker_sell_up, mocker) -> None: rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -3058,8 +3060,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee, ) freqtrade.config['order_types']['sell'] = 'market' - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.ROI)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.ROI)) assert not trade.is_open assert trade.close_profit == 0.0620716 @@ -3089,8 +3091,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee, } == last_msg -def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, - ticker_sell_up, mocker) -> None: +def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee, + ticker_sell_up, mocker) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') mocker.patch.multiple( @@ -3117,8 +3119,8 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee, ) sell_reason = SellCheckTuple(sell_type=SellType.ROI) - assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], - sell_reason=sell_reason) + assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'], + sell_reason=sell_reason) assert mock_insuf.call_count == 1 @@ -3374,8 +3376,8 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo fetch_ticker=ticker_sell_down ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], - sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) + freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'], + sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) trade.close(ticker_sell_down()['bid']) assert freqtrade.strategy.is_pair_locked(trade.pair) diff --git a/tests/test_integration.py b/tests/test_integration.py index b12959a03..215927098 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -9,7 +9,7 @@ from freqtrade.strategy.interface import SellCheckTuple from tests.conftest import get_patched_freqtradebot, patch_get_signal -def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, +def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, limit_buy_order, mocker) -> None: """ Tests workflow of selling stoploss_on_exchange. From 1f3ccc2587d8e8c8d4726906a483cb7ad6f456cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 07:00:15 +0200 Subject: [PATCH 191/200] DefaultStrategy does not need to be limited --- config_examples/config_full.example.json | 2 +- docs/backtesting.md | 2 +- freqtrade/commands/cli_options.py | 2 +- freqtrade/commands/deploy_commands.py | 2 -- tests/commands/test_commands.py | 11 ----------- 5 files changed, 3 insertions(+), 16 deletions(-) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 3ca413281..d0f3f0df6 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -174,7 +174,7 @@ "heartbeat_interval": 60 }, "disable_dataframe_checks": false, - "strategy": "DefaultStrategy", + "strategy": "SampleStrategy", "strategy_path": "user_data/strategies/", "dataformat_ohlcv": "json", "dataformat_trades": "jsongz" diff --git a/docs/backtesting.md b/docs/backtesting.md index 89980c670..f750e0c4c 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -62,7 +62,7 @@ optional arguments: this together with `--export trades`, the strategy- name is injected into the filename (so `backtest- data.json` becomes `backtest-data- - DefaultStrategy.json` + SampleStrategy.json` --export {none,trades} Export backtest results (default: trades). --export-filename PATH diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 215ed3f6e..42be5abd6 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -162,7 +162,7 @@ AVAILABLE_CLI_OPTIONS = { 'Please note that ticker-interval needs to be set either in config ' 'or via command line. When using this together with `--export trades`, ' 'the strategy-name is injected into the filename ' - '(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`', + '(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`', nargs='+', ), "export": Arg( diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index eb65579e2..ec558e7b1 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if "strategy" in args and args["strategy"]: - if args["strategy"] == "DefaultStrategy": - raise OperationalException("DefaultStrategy is not allowed as name.") new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index fc5101979..df2eb2c10 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -510,17 +510,6 @@ def test_start_new_strategy(mocker, caplog): start_new_strategy(get_args(args)) -def test_start_new_strategy_DefaultStrat(mocker, caplog): - args = [ - "new-strategy", - "--strategy", - "DefaultStrategy" - ] - with pytest.raises(OperationalException, - match=r"DefaultStrategy is not allowed as name\."): - start_new_strategy(get_args(args)) - - def test_start_new_strategy_no_arg(mocker, caplog): args = [ "new-strategy", From fbf8eb4526601e90f8878985a42a77ba1a46227f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 07:04:33 +0200 Subject: [PATCH 192/200] Rename test-legacy strategy --- tests/commands/test_commands.py | 4 ++-- tests/optimize/test_backtesting.py | 10 +++++----- tests/rpc/test_rpc_apiserver.py | 2 +- tests/strategy/strats/failing_strategy.py | 2 +- tests/strategy/strats/legacy_strategy.py | 2 +- tests/strategy/test_interface.py | 2 +- tests/strategy/test_strategy_loading.py | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index df2eb2c10..0f34f6e10 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -816,7 +816,7 @@ def test_start_list_strategies(mocker, caplog, capsys): # pargs['config'] = None start_list_strategies(pargs) captured = capsys.readouterr() - assert "TestStrategyLegacy" in captured.out + assert "TestStrategyLegacyV1" in captured.out assert "legacy_strategy.py" not in captured.out assert "DefaultStrategy" in captured.out @@ -831,7 +831,7 @@ def test_start_list_strategies(mocker, caplog, capsys): # pargs['config'] = None start_list_strategies(pargs) captured = capsys.readouterr() - assert "TestStrategyLegacy" in captured.out + assert "TestStrategyLegacyV1" in captured.out assert "legacy_strategy.py" in captured.out assert "DefaultStrategy" in captured.out diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 998b2d837..80f7aa2d6 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -473,7 +473,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti Backtesting(default_conf) # Multiple strategies - default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy'] + default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacyV1'] with pytest.raises(OperationalException, match='PrecisionFilter not allowed for backtesting multiple strategies.'): Backtesting(default_conf) @@ -909,7 +909,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): '--disable-max-market-positions', '--strategy-list', 'DefaultStrategy', - 'TestStrategyLegacy', + 'TestStrategyLegacyV1', ] args = get_args(args) start_backtesting(args) @@ -932,7 +932,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', 'Running backtesting for Strategy DefaultStrategy', - 'Running backtesting for Strategy TestStrategyLegacy', + 'Running backtesting for Strategy TestStrategyLegacyV1', ] for line in exists: @@ -1013,7 +1013,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat '--disable-max-market-positions', '--strategy-list', 'DefaultStrategy', - 'TestStrategyLegacy', + 'TestStrategyLegacyV1', ] args = get_args(args) start_backtesting(args) @@ -1030,7 +1030,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', 'Running backtesting for Strategy DefaultStrategy', - 'Running backtesting for Strategy TestStrategyLegacy', + 'Running backtesting for Strategy TestStrategyLegacyV1', ] for line in exists: diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 68bd01911..9a20256cd 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1219,7 +1219,7 @@ def test_api_strategies(botclient): assert rc.json() == {'strategies': [ 'DefaultStrategy', 'HyperoptableStrategy', - 'TestStrategyLegacy' + 'TestStrategyLegacyV1' ]} diff --git a/tests/strategy/strats/failing_strategy.py b/tests/strategy/strats/failing_strategy.py index f8eaac3c3..a65a0ddc2 100644 --- a/tests/strategy/strats/failing_strategy.py +++ b/tests/strategy/strats/failing_strategy.py @@ -5,5 +5,5 @@ import nonexiting_module # noqa from freqtrade.strategy.interface import IStrategy -class TestStrategyLegacy(IStrategy): +class TestStrategyLegacyV1(IStrategy): pass diff --git a/tests/strategy/strats/legacy_strategy.py b/tests/strategy/strats/legacy_strategy.py index 9ef00b110..ebfce632b 100644 --- a/tests/strategy/strats/legacy_strategy.py +++ b/tests/strategy/strats/legacy_strategy.py @@ -10,7 +10,7 @@ from freqtrade.strategy.interface import IStrategy # -------------------------------- # This class is a sample. Feel free to customize it. -class TestStrategyLegacy(IStrategy): +class TestStrategyLegacyV1(IStrategy): """ This is a test strategy using the legacy function headers, which will be removed in a future update. diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index eea6a85d2..d03c7d2c1 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -603,7 +603,7 @@ def test_is_pair_locked(default_conf): def test_is_informative_pairs_callback(default_conf): - default_conf.update({'strategy': 'TestStrategyLegacy'}) + default_conf.update({'strategy': 'TestStrategyLegacyV1'}) strategy = StrategyResolver.load_strategy(default_conf) # Should return empty # Uses fallback to base implementation diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 115a2fbde..b173a562b 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -330,7 +330,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf): @pytest.mark.filterwarnings("ignore:deprecated") def test_deprecate_populate_indicators(result, default_conf): default_location = Path(__file__).parent / "strats" - default_conf.update({'strategy': 'TestStrategyLegacy', + default_conf.update({'strategy': 'TestStrategyLegacyV1', 'strategy_path': default_location}) strategy = StrategyResolver.load_strategy(default_conf) with warnings.catch_warnings(record=True) as w: @@ -365,7 +365,7 @@ def test_deprecate_populate_indicators(result, default_conf): def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): default_location = Path(__file__).parent / "strats" del default_conf['timeframe'] - default_conf.update({'strategy': 'TestStrategyLegacy', + default_conf.update({'strategy': 'TestStrategyLegacyV1', 'strategy_path': default_location}) strategy = StrategyResolver.load_strategy(default_conf) metadata = {'pair': 'ETH/BTC'} From 58ecb34a668528da2ed74c4a799a63a12b6e761b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 07:13:18 +0200 Subject: [PATCH 193/200] Allow DefaultHyperopts as hyperopt name --- freqtrade/commands/deploy_commands.py | 2 -- tests/commands/test_commands.py | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index ec558e7b1..c98335e0b 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -126,8 +126,6 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if 'hyperopt' in args and args['hyperopt']: - if args['hyperopt'] == 'DefaultHyperopt': - raise OperationalException("DefaultHyperopt is not allowed as name.") new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 0f34f6e10..a2ab37e61 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -541,17 +541,6 @@ def test_start_new_hyperopt(mocker, caplog): start_new_hyperopt(get_args(args)) -def test_start_new_hyperopt_DefaultHyperopt(mocker, caplog): - args = [ - "new-hyperopt", - "--hyperopt", - "DefaultHyperopt" - ] - with pytest.raises(OperationalException, - match=r"DefaultHyperopt is not allowed as name\."): - start_new_hyperopt(get_args(args)) - - def test_start_new_hyperopt_no_arg(mocker): args = [ "new-hyperopt", From 0d8e105a339dcd96ac883dbfd23ca210bbd8b3eb Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 07:14:49 +0200 Subject: [PATCH 194/200] Rename legacystrategy file --- tests/commands/test_commands.py | 4 ++-- .../strats/{legacy_strategy.py => legacy_strategy_v1.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename tests/strategy/strats/{legacy_strategy.py => legacy_strategy_v1.py} (100%) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index a2ab37e61..9f21e93d7 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -806,7 +806,7 @@ def test_start_list_strategies(mocker, caplog, capsys): start_list_strategies(pargs) captured = capsys.readouterr() assert "TestStrategyLegacyV1" in captured.out - assert "legacy_strategy.py" not in captured.out + assert "legacy_strategy_v1.py" not in captured.out assert "DefaultStrategy" in captured.out # Test regular output @@ -821,7 +821,7 @@ def test_start_list_strategies(mocker, caplog, capsys): start_list_strategies(pargs) captured = capsys.readouterr() assert "TestStrategyLegacyV1" in captured.out - assert "legacy_strategy.py" in captured.out + assert "legacy_strategy_v1.py" in captured.out assert "DefaultStrategy" in captured.out diff --git a/tests/strategy/strats/legacy_strategy.py b/tests/strategy/strats/legacy_strategy_v1.py similarity index 100% rename from tests/strategy/strats/legacy_strategy.py rename to tests/strategy/strats/legacy_strategy_v1.py From df1c0540abffd71269a13c0dc4cea925f1b95597 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 07:18:26 +0200 Subject: [PATCH 195/200] Rename Hyperopt Test Class --- tests/commands/test_commands.py | 4 ++-- tests/optimize/conftest.py | 2 +- ..._hyperopt.py => hyperopt_test_sep_file.py} | 2 +- tests/optimize/test_hyperopt.py | 20 +++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) rename tests/optimize/hyperopts/{default_hyperopt.py => hyperopt_test_sep_file.py} (99%) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 9f21e93d7..5a177267c 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -839,7 +839,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys): captured = capsys.readouterr() assert "TestHyperoptLegacy" not in captured.out assert "legacy_hyperopt.py" not in captured.out - assert "DefaultHyperOpt" in captured.out + assert "HyperoptTestSepFile" in captured.out assert "test_hyperopt.py" not in captured.out # Test regular output @@ -854,7 +854,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys): captured = capsys.readouterr() assert "TestHyperoptLegacy" not in captured.out assert "legacy_hyperopt.py" not in captured.out - assert "DefaultHyperOpt" in captured.out + assert "HyperoptTestSepFile" in captured.out def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): diff --git a/tests/optimize/conftest.py b/tests/optimize/conftest.py index a7fd238d1..95c9fef97 100644 --- a/tests/optimize/conftest.py +++ b/tests/optimize/conftest.py @@ -16,7 +16,7 @@ def hyperopt_conf(default_conf): hyperconf.update({ 'datadir': Path(default_conf['datadir']), 'runmode': RunMode.HYPEROPT, - 'hyperopt': 'DefaultHyperOpt', + 'hyperopt': 'HyperoptTestSepFile', 'hyperopt_loss': 'ShortTradeDurHyperOptLoss', 'hyperopt_path': str(Path(__file__).parent / 'hyperopts'), 'epochs': 1, diff --git a/tests/optimize/hyperopts/default_hyperopt.py b/tests/optimize/hyperopts/hyperopt_test_sep_file.py similarity index 99% rename from tests/optimize/hyperopts/default_hyperopt.py rename to tests/optimize/hyperopts/hyperopt_test_sep_file.py index 2e2bca3d0..0fa1e1959 100644 --- a/tests/optimize/hyperopts/default_hyperopt.py +++ b/tests/optimize/hyperopts/hyperopt_test_sep_file.py @@ -11,7 +11,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.optimize.hyperopt_interface import IHyperOpt -class DefaultHyperOpt(IHyperOpt): +class HyperoptTestSepFile(IHyperOpt): """ Default hyperopt provided by the Freqtrade bot. You can override it with your own Hyperopt diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 0ca79d268..94f4abc7a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -22,7 +22,7 @@ from freqtrade.strategy.hyper import IntParameter from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) -from .hyperopts.default_hyperopt import DefaultHyperOpt +from .hyperopts.hyperopt_test_sep_file import HyperoptTestSepFile def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -31,7 +31,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpt', + '--hyperopt', 'HyperoptTestSepFile', ] config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT) @@ -63,7 +63,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpt', + '--hyperopt', 'HyperoptTestSepFile', '--datadir', '/foo/bar', '--timeframe', '1m', '--timerange', ':100', @@ -115,7 +115,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpt', + '--hyperopt', 'HyperoptTestSepFile', '--stake-amount', '1', '--starting-balance', '2' ] @@ -136,7 +136,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None def test_hyperoptresolver(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) - hyperopt = DefaultHyperOpt + hyperopt = HyperoptTestSepFile delattr(hyperopt, 'populate_indicators') delattr(hyperopt, 'populate_buy_trend') delattr(hyperopt, 'populate_sell_trend') @@ -144,7 +144,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: 'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object', MagicMock(return_value=hyperopt(default_conf)) ) - default_conf.update({'hyperopt': 'DefaultHyperOpt'}) + default_conf.update({'hyperopt': 'HyperoptTestSepFile'}) x = HyperOptResolver.load_hyperopt(default_conf) assert not hasattr(x, 'populate_indicators') assert not hasattr(x, 'populate_buy_trend') @@ -184,7 +184,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpt', + '--hyperopt', 'HyperoptTestSepFile', '--hyperopt-path', str(Path(__file__).parent / "hyperopts"), '--epochs', '5', @@ -205,7 +205,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpt', + '--hyperopt', 'HyperoptTestSepFile', '--hyperopt-loss', 'SharpeHyperOptLossDaily', '--epochs', '5' ] @@ -229,7 +229,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpt', + '--hyperopt', 'HyperoptTestSepFile', '--hyperopt-loss', 'SharpeHyperOptLossDaily', '--epochs', '5' ] @@ -247,7 +247,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpt', + '--hyperopt', 'HyperoptTestSepFile', '--hyperopt-loss', 'SharpeHyperOptLossDaily', '--epochs', '5' ] From 6d96b1127989fc40a5f0027ec5067d11645bee5b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 07:25:53 +0200 Subject: [PATCH 196/200] Rename DefaultStrategy --- build_helpers/publish_docker_arm64.sh | 2 +- build_helpers/publish_docker_multi.sh | 2 +- tests/commands/test_commands.py | 4 +- tests/conftest.py | 2 +- tests/conftest_trades.py | 8 ++-- tests/data/test_btanalysis.py | 6 +-- tests/data/test_history.py | 6 +-- tests/optimize/test_backtesting.py | 26 ++++++------ tests/optimize/test_edge_cli.py | 6 +-- tests/optimize/test_hyperopt.py | 2 +- tests/optimize/test_hyperopt_tools.py | 16 +++---- tests/optimize/test_optimize_reports.py | 2 +- tests/rpc/test_rpc_apiserver.py | 28 ++++++------- tests/rpc/test_rpc_telegram.py | 4 +- tests/strategy/strats/default_strategy.py | 4 +- tests/strategy/test_default_strategy.py | 16 +++---- tests/strategy/test_interface.py | 26 ++++++------ tests/strategy/test_strategy_loading.py | 42 +++++++++---------- tests/test_arguments.py | 2 +- tests/test_configuration.py | 6 +-- tests/test_plotting.py | 3 -- .../testdata/backtest-result_multistrat.json | 2 +- tests/testdata/backtest-result_new.json | 2 +- 23 files changed, 107 insertions(+), 110 deletions(-) diff --git a/build_helpers/publish_docker_arm64.sh b/build_helpers/publish_docker_arm64.sh index e7b69b2dc..1ad8074d4 100755 --- a/build_helpers/publish_docker_arm64.sh +++ b/build_helpers/publish_docker_arm64.sh @@ -42,7 +42,7 @@ docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_I docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM # Run backtest -docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy +docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 if [ $? -ne 0 ]; then echo "failed running backtest" diff --git a/build_helpers/publish_docker_multi.sh b/build_helpers/publish_docker_multi.sh index 4010eed45..dd6ac841e 100755 --- a/build_helpers/publish_docker_multi.sh +++ b/build_helpers/publish_docker_multi.sh @@ -53,7 +53,7 @@ docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT # Run backtest -docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy +docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 if [ $? -ne 0 ]; then echo "failed running backtest" diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 5a177267c..1da9e5100 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -807,7 +807,7 @@ def test_start_list_strategies(mocker, caplog, capsys): captured = capsys.readouterr() assert "TestStrategyLegacyV1" in captured.out assert "legacy_strategy_v1.py" not in captured.out - assert "DefaultStrategy" in captured.out + assert "StrategyTestV2" in captured.out # Test regular output args = [ @@ -822,7 +822,7 @@ def test_start_list_strategies(mocker, caplog, capsys): captured = capsys.readouterr() assert "TestStrategyLegacyV1" in captured.out assert "legacy_strategy_v1.py" in captured.out - assert "DefaultStrategy" in captured.out + assert "StrategyTestV2" in captured.out def test_start_list_hyperopts(mocker, caplog, capsys): diff --git a/tests/conftest.py b/tests/conftest.py index 0c9a96e2b..5e08e7097 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -323,7 +323,7 @@ def get_default_conf(testdatadir): "user_data_dir": Path("user_data"), "verbosity": 3, "strategy_path": str(Path(__file__).parent / "strategy" / "strats"), - "strategy": "DefaultStrategy", + "strategy": "StrategyTestV2", "disableparamexport": True, "internals": {}, "export": "none", diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index b92b51144..024803be0 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -33,7 +33,7 @@ def mock_trade_1(fee): open_rate=0.123, exchange='binance', open_order_id='dry_run_buy_12345', - strategy='DefaultStrategy', + strategy='StrategyTestV2', timeframe=5, ) o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy') @@ -87,7 +87,7 @@ def mock_trade_2(fee): exchange='binance', is_open=False, open_order_id='dry_run_sell_12345', - strategy='DefaultStrategy', + strategy='StrategyTestV2', timeframe=5, sell_reason='sell_signal', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), @@ -146,7 +146,7 @@ def mock_trade_3(fee): close_profit_abs=0.000155, exchange='binance', is_open=False, - strategy='DefaultStrategy', + strategy='StrategyTestV2', timeframe=5, sell_reason='roi', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), @@ -189,7 +189,7 @@ def mock_trade_4(fee): open_rate=0.123, exchange='binance', open_order_id='prod_buy_12345', - strategy='DefaultStrategy', + strategy='StrategyTestV2', timeframe=5, ) o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy') diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 6bde60926..1dcd04a80 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -93,7 +93,7 @@ def test_load_backtest_data_new_format(testdatadir): def test_load_backtest_data_multi(testdatadir): filename = testdatadir / "backtest-result_multistrat.json" - for strategy in ('DefaultStrategy', 'TestStrategy'): + for strategy in ('StrategyTestV2', 'TestStrategy'): bt_data = load_backtest_data(filename, strategy=strategy) assert isinstance(bt_data, DataFrame) assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID) @@ -128,7 +128,7 @@ def test_load_trades_from_db(default_conf, fee, mocker): for col in BT_DATA_COLUMNS: if col not in ['index', 'open_at_end']: assert col in trades.columns - trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='DefaultStrategy') + trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='StrategyTestV2') assert len(trades) == 4 trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy') assert len(trades) == 0 @@ -186,7 +186,7 @@ def test_load_trades(default_conf, mocker): db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'), no_trades=False, - strategy="DefaultStrategy", + strategy="StrategyTestV2", ) assert db_mock.call_count == 1 diff --git a/tests/data/test_history.py b/tests/data/test_history.py index e9d2c3638..575a590e7 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -380,7 +380,7 @@ def test_file_dump_json_tofile(testdatadir) -> None: def test_get_timerange(default_conf, mocker, testdatadir) -> None: patch_exchange(mocker) - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) data = strategy.advise_all_indicators( @@ -398,7 +398,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None: def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None: patch_exchange(mocker) - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) data = strategy.advise_all_indicators( @@ -422,7 +422,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None: patch_exchange(mocker) - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange('index', 'index', 200, 250) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 80f7aa2d6..6442f6cb7 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -155,7 +155,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--export', 'none' ] @@ -190,7 +190,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--datadir', '/foo/bar', '--timeframe', '1m', '--enable-position-stacking', @@ -240,7 +240,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--stake-amount', '1', '--starting-balance', '2' ] @@ -251,7 +251,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--stake-amount', '1', '--starting-balance', '0.5' ] @@ -269,7 +269,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None: args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', ] pargs = get_args(args) start_backtesting(pargs) @@ -302,7 +302,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: patch_exchange(mocker) del default_conf['timeframe'] - default_conf['strategy_list'] = ['DefaultStrategy', + default_conf['strategy_list'] = ['StrategyTestV2', 'SampleStrategy'] mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) @@ -340,7 +340,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: assert len(processed['UNITTEST/BTC']) == 102 # Load strategy to compare the result between Backtesting function and strategy are the same - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) processed2 = strategy.advise_all_indicators(data) @@ -473,7 +473,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti Backtesting(default_conf) # Multiple strategies - default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacyV1'] + default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1'] with pytest.raises(OperationalException, match='PrecisionFilter not allowed for backtesting multiple strategies.'): Backtesting(default_conf) @@ -837,7 +837,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--datadir', str(testdatadir), '--timeframe', '1m', '--timerange', '1510694220-1510700340', @@ -908,7 +908,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): '--enable-position-stacking', '--disable-max-market-positions', '--strategy-list', - 'DefaultStrategy', + 'StrategyTestV2', 'TestStrategyLegacyV1', ] args = get_args(args) @@ -931,7 +931,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'Backtesting with data from 2017-11-14 21:17:00 ' 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', - 'Running backtesting for Strategy DefaultStrategy', + 'Running backtesting for Strategy StrategyTestV2', 'Running backtesting for Strategy TestStrategyLegacyV1', ] @@ -1012,7 +1012,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat '--enable-position-stacking', '--disable-max-market-positions', '--strategy-list', - 'DefaultStrategy', + 'StrategyTestV2', 'TestStrategyLegacyV1', ] args = get_args(args) @@ -1029,7 +1029,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'Backtesting with data from 2017-11-14 21:17:00 ' 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', - 'Running backtesting for Strategy DefaultStrategy', + 'Running backtesting for Strategy StrategyTestV2', 'Running backtesting for Strategy TestStrategyLegacyV1', ] diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 6818a573b..18d5f1c76 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -16,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca args = [ 'edge', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', ] config = setup_optimize_configuration(get_args(args), RunMode.EDGE) @@ -46,7 +46,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N args = [ 'edge', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--datadir', '/foo/bar', '--timeframe', '1m', '--timerange', ':100', @@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None: args = [ 'edge', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', ] pargs = get_args(args) start_edge(pargs) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 94f4abc7a..565d6077a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -125,7 +125,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None args = [ 'hyperopt', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--stake-amount', '1', '--starting-balance', '0.5' ] diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index e845b2a77..9f917c2a7 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -167,7 +167,7 @@ def test__pprint_dict(): def test_get_strategy_filename(default_conf): - x = HyperoptTools.get_strategy_filename(default_conf, 'DefaultStrategy') + x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2') assert isinstance(x, Path) assert x == Path(__file__).parents[1] / 'strategy/strats/default_strategy.py' @@ -177,7 +177,7 @@ def test_get_strategy_filename(default_conf): def test_export_params(tmpdir): - filename = Path(tmpdir) / "DefaultStrategy.json" + filename = Path(tmpdir) / "StrategyTestV2.json" assert not filename.is_file() params = { "params_details": { @@ -205,12 +205,12 @@ def test_export_params(tmpdir): } } - HyperoptTools.export_params(params, "DefaultStrategy", filename) + HyperoptTools.export_params(params, "StrategyTestV2", filename) assert filename.is_file() content = rapidjson.load(filename.open('r')) - assert content['strategy_name'] == 'DefaultStrategy' + assert content['strategy_name'] == 'StrategyTestV2' assert 'params' in content assert "buy" in content["params"] assert "sell" in content["params"] @@ -223,7 +223,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): default_conf['disableparamexport'] = False export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params") - filename = Path(tmpdir) / "DefaultStrategy.json" + filename = Path(tmpdir) / "StrategyTestV2.json" assert not filename.is_file() params = { "params_details": { @@ -252,16 +252,16 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): FTHYPT_FILEVERSION: 2, } - HyperoptTools.try_export_params(default_conf, "DefaultStrategy22", params) + HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params) assert log_has("Strategy not found, not exporting parameter file.", caplog) assert export_mock.call_count == 0 caplog.clear() - HyperoptTools.try_export_params(default_conf, "DefaultStrategy", params) + HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params) assert export_mock.call_count == 1 - assert export_mock.call_args_list[0][0][1] == 'DefaultStrategy' + assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2' assert export_mock.call_args_list[0][0][2].name == 'default_strategy.json' diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 3f31efb95..83caefd2d 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -52,7 +52,7 @@ def test_text_table_bt_results(): def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) StrategyResolver.load_strategy(default_conf) results = {'DefStrat': { diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 9a20256cd..0256fb632 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -879,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'open_trade_value': 15.1668225, 'sell_reason': None, 'sell_order_status': None, - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', @@ -984,7 +984,7 @@ def test_api_forcebuy(botclient, mocker, fee): close_rate=0.265441, id=22, timeframe=5, - strategy="DefaultStrategy" + strategy="StrategyTestV2" )) mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) @@ -1034,7 +1034,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'open_trade_value': 0.24605460, 'sell_reason': None, 'sell_order_status': None, - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', @@ -1101,7 +1101,7 @@ def test_api_pair_candles(botclient, ohlcv_history): f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") assert_response(rc) assert 'strategy' in rc.json() - assert rc.json()['strategy'] == 'DefaultStrategy' + assert rc.json()['strategy'] == 'StrategyTestV2' assert 'columns' in rc.json() assert 'data_start_ts' in rc.json() assert 'data_start' in rc.json() @@ -1139,19 +1139,19 @@ def test_api_pair_history(botclient, ohlcv_history): # No pair rc = client_get(client, f"{BASE_URI}/pair_history?timeframe={timeframe}" - "&timerange=20180111-20180112&strategy=DefaultStrategy") + "&timerange=20180111-20180112&strategy=StrategyTestV2") assert_response(rc, 422) # No Timeframe rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" - "&timerange=20180111-20180112&strategy=DefaultStrategy") + "&timerange=20180111-20180112&strategy=StrategyTestV2") assert_response(rc, 422) # No timerange rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&strategy=DefaultStrategy") + "&strategy=StrategyTestV2") assert_response(rc, 422) # No strategy @@ -1163,14 +1163,14 @@ def test_api_pair_history(botclient, ohlcv_history): # Working rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&timerange=20180111-20180112&strategy=DefaultStrategy") + "&timerange=20180111-20180112&strategy=StrategyTestV2") assert_response(rc, 200) assert rc.json()['length'] == 289 assert len(rc.json()['data']) == rc.json()['length'] assert 'columns' in rc.json() assert 'data' in rc.json() assert rc.json()['pair'] == 'UNITTEST/BTC' - assert rc.json()['strategy'] == 'DefaultStrategy' + assert rc.json()['strategy'] == 'StrategyTestV2' assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00' assert rc.json()['data_start_ts'] == 1515628800000 assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00' @@ -1179,7 +1179,7 @@ def test_api_pair_history(botclient, ohlcv_history): # No data found rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&timerange=20200111-20200112&strategy=DefaultStrategy") + "&timerange=20200111-20200112&strategy=StrategyTestV2") assert_response(rc, 502) assert rc.json()['error'] == ("Error querying /api/v1/pair_history: " "No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") @@ -1217,7 +1217,7 @@ def test_api_strategies(botclient): assert_response(rc) assert rc.json() == {'strategies': [ - 'DefaultStrategy', + 'StrategyTestV2', 'HyperoptableStrategy', 'TestStrategyLegacyV1' ]} @@ -1226,10 +1226,10 @@ def test_api_strategies(botclient): def test_api_strategy(botclient): ftbot, client = botclient - rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy") + rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2") assert_response(rc) - assert rc.json()['strategy'] == 'DefaultStrategy' + assert rc.json()['strategy'] == 'StrategyTestV2' data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text() assert rc.json()['code'] == data @@ -1288,7 +1288,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog): # start backtesting data = { - "strategy": "DefaultStrategy", + "strategy": "StrategyTestV2", "timeframe": "5m", "timerange": "20180110-20180111", "max_open_trades": 3, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 8596c7c44..2013dad7d 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1236,7 +1236,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None: assert msg_mock.call_count == 1 assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] - assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0] + assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0] assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() @@ -1245,7 +1245,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None: assert msg_mock.call_count == 1 assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] - assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0] + assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0] assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] diff --git a/tests/strategy/strats/default_strategy.py b/tests/strategy/strats/default_strategy.py index 7171b93ae..53e39526f 100644 --- a/tests/strategy/strats/default_strategy.py +++ b/tests/strategy/strats/default_strategy.py @@ -7,9 +7,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.strategy.interface import IStrategy -class DefaultStrategy(IStrategy): +class StrategyTestV2(IStrategy): """ - Default Strategy provided by freqtrade bot. + Strategy used by tests freqtrade bot. Please do not modify this strategy, it's intended for internal use only. Please look at the SampleStrategy in the user_data/strategy directory or strategy repository https://github.com/freqtrade/freqtrade-strategies diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 92ac9f63a..2566c8478 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -4,20 +4,20 @@ from pandas import DataFrame from freqtrade.persistence.models import Trade -from .strats.default_strategy import DefaultStrategy +from .strats.default_strategy import StrategyTestV2 def test_default_strategy_structure(): - assert hasattr(DefaultStrategy, 'minimal_roi') - assert hasattr(DefaultStrategy, 'stoploss') - assert hasattr(DefaultStrategy, 'timeframe') - assert hasattr(DefaultStrategy, 'populate_indicators') - assert hasattr(DefaultStrategy, 'populate_buy_trend') - assert hasattr(DefaultStrategy, 'populate_sell_trend') + assert hasattr(StrategyTestV2, 'minimal_roi') + assert hasattr(StrategyTestV2, 'stoploss') + assert hasattr(StrategyTestV2, 'timeframe') + assert hasattr(StrategyTestV2, 'populate_indicators') + assert hasattr(StrategyTestV2, 'populate_buy_trend') + assert hasattr(StrategyTestV2, 'populate_sell_trend') def test_default_strategy(result, fee): - strategy = DefaultStrategy({}) + strategy = StrategyTestV2({}) metadata = {'pair': 'ETH/BTC'} assert type(strategy.minimal_roi) is dict diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index d03c7d2c1..9023f7981 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -22,11 +22,11 @@ from freqtrade.strategy.interface import SellCheckTuple from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from tests.conftest import log_has, log_has_re -from .strats.default_strategy import DefaultStrategy +from .strats.default_strategy import StrategyTestV2 # Avoid to reinit the same object again and again -_STRATEGY = DefaultStrategy(config={}) +_STRATEGY = StrategyTestV2(config={}) _STRATEGY.dp = DataProvider({}, None, None) @@ -148,7 +148,7 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history): def test_ignore_expired_candle(default_conf): - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) strategy.ignore_buying_expired_candle_after = 60 @@ -229,7 +229,7 @@ def test_assert_df(ohlcv_history, caplog): def test_advise_all_indicators(default_conf, testdatadir) -> None: - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange.parse_timerange('1510694220-1510700340') @@ -240,7 +240,7 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None: def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None: - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators') timerange = TimeRange.parse_timerange('1510694220-1510700340') @@ -258,7 +258,7 @@ def test_min_roi_reached(default_conf, fee) -> None: min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1}, {0: 0.1, 20: 0.05, 55: 0.01}] for roi in min_roi_list: - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) strategy.minimal_roi = roi trade = Trade( @@ -297,7 +297,7 @@ def test_min_roi_reached2(default_conf, fee) -> None: }, ] for roi in min_roi_list: - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) strategy.minimal_roi = roi trade = Trade( @@ -332,7 +332,7 @@ def test_min_roi_reached3(default_conf, fee) -> None: 30: 0.05, 55: 0.30, } - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) strategy.minimal_roi = min_roi trade = Trade( @@ -385,7 +385,7 @@ def test_min_roi_reached3(default_conf, fee) -> None: def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom, profit2, adjusted2, expected2, custom_stop) -> None: - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) trade = Trade( @@ -433,7 +433,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili def test_custom_sell(default_conf, fee, caplog) -> None: - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) trade = Trade( @@ -487,7 +487,7 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: advise_sell=sell_mock, ) - strategy = DefaultStrategy({}) + strategy = StrategyTestV2({}) strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) assert ind_mock.call_count == 1 assert buy_mock.call_count == 1 @@ -518,7 +518,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> advise_sell=sell_mock, ) - strategy = DefaultStrategy({}) + strategy = StrategyTestV2({}) strategy.dp = DataProvider({}, None, None) strategy.process_only_new_candles = True @@ -550,7 +550,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> @pytest.mark.usefixtures("init_persistence") def test_is_pair_locked(default_conf): - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) PairLocks.timeframe = default_conf['timeframe'] PairLocks.use_db = True strategy = StrategyResolver.load_strategy(default_conf) diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index b173a562b..2cbc9d0c6 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -18,7 +18,7 @@ def test_search_strategy(): s, _ = StrategyResolver._search_object( directory=default_location, - object_name='DefaultStrategy', + object_name='StrategyTestV2', add_source=True, ) assert issubclass(s, IStrategy) @@ -74,10 +74,10 @@ def test_load_strategy_base64(result, caplog, default_conf): def test_load_strategy_invalid_directory(result, caplog, default_conf): - default_conf['strategy'] = 'DefaultStrategy' + default_conf['strategy'] = 'StrategyTestV2' extra_dir = Path.cwd() / 'some/path' with pytest.raises(OperationalException): - StrategyResolver._load_strategy('DefaultStrategy', config=default_conf, + StrategyResolver._load_strategy('StrategyTestV2', config=default_conf, extra_dir=extra_dir) assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog) @@ -100,7 +100,7 @@ def test_load_strategy_noname(default_conf): def test_strategy(result, default_conf): - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) metadata = {'pair': 'ETH/BTC'} @@ -127,7 +127,7 @@ def test_strategy(result, default_conf): def test_strategy_override_minimal_roi(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'minimal_roi': { "20": 0.1, "0": 0.5 @@ -144,7 +144,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf): def test_strategy_override_stoploss(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'stoploss': -0.5 }) strategy = StrategyResolver.load_strategy(default_conf) @@ -156,7 +156,7 @@ def test_strategy_override_stoploss(caplog, default_conf): def test_strategy_override_trailing_stop(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'trailing_stop': True }) strategy = StrategyResolver.load_strategy(default_conf) @@ -169,7 +169,7 @@ def test_strategy_override_trailing_stop(caplog, default_conf): def test_strategy_override_trailing_stop_positive(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'trailing_stop_positive': -0.1, 'trailing_stop_positive_offset': -0.2 @@ -189,7 +189,7 @@ def test_strategy_override_timeframe(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'timeframe': 60, 'stake_currency': 'ETH' }) @@ -205,7 +205,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'process_only_new_candles': True }) strategy = StrategyResolver.load_strategy(default_conf) @@ -225,7 +225,7 @@ def test_strategy_override_order_types(caplog, default_conf): 'stoploss_on_exchange': True, } default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'order_types': order_types }) strategy = StrategyResolver.load_strategy(default_conf) @@ -239,12 +239,12 @@ def test_strategy_override_order_types(caplog, default_conf): " 'stoploss_on_exchange': True}.", caplog) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'order_types': {'buy': 'market'} }) # Raise error for invalid configuration with pytest.raises(ImportError, - match=r"Impossible to load Strategy 'DefaultStrategy'. " + match=r"Impossible to load Strategy 'StrategyTestV2'. " r"Order-types mapping is incomplete."): StrategyResolver.load_strategy(default_conf) @@ -258,7 +258,7 @@ def test_strategy_override_order_tif(caplog, default_conf): } default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'order_time_in_force': order_time_in_force }) strategy = StrategyResolver.load_strategy(default_conf) @@ -271,12 +271,12 @@ def test_strategy_override_order_tif(caplog, default_conf): " {'buy': 'fok', 'sell': 'gtc'}.", caplog) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'order_time_in_force': {'buy': 'fok'} }) # Raise error for invalid configuration with pytest.raises(ImportError, - match=r"Impossible to load Strategy 'DefaultStrategy'. " + match=r"Impossible to load Strategy 'StrategyTestV2'. " r"Order-time-in-force mapping is incomplete."): StrategyResolver.load_strategy(default_conf) @@ -284,7 +284,7 @@ def test_strategy_override_order_tif(caplog, default_conf): def test_strategy_override_use_sell_signal(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', }) strategy = StrategyResolver.load_strategy(default_conf) assert strategy.use_sell_signal @@ -294,7 +294,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf): assert default_conf['use_sell_signal'] default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'use_sell_signal': False, }) strategy = StrategyResolver.load_strategy(default_conf) @@ -307,7 +307,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf): def test_strategy_override_use_sell_profit_only(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', }) strategy = StrategyResolver.load_strategy(default_conf) assert not strategy.sell_profit_only @@ -317,7 +317,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf): assert not default_conf['sell_profit_only'] default_conf.update({ - 'strategy': 'DefaultStrategy', + 'strategy': 'StrategyTestV2', 'sell_profit_only': True, }) strategy = StrategyResolver.load_strategy(default_conf) @@ -395,7 +395,7 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): def test_strategy_interface_versioning(result, monkeypatch, default_conf): - default_conf.update({'strategy': 'DefaultStrategy'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) metadata = {'pair': 'ETH/BTC'} diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 5374881fa..fca5c6ab9 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -123,7 +123,7 @@ def test_parse_args_backtesting_custom() -> None: '-c', 'test_conf.json', '--ticker-interval', '1m', '--strategy-list', - 'DefaultStrategy', + 'StrategyTestV2', 'SampleStrategy' ] call_args = Arguments(args).get_parsed_arg() diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7c555a39e..9aea4fa11 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -404,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> arglist = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', ] args = Arguments(arglist).get_parsed_arg() @@ -441,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non arglist = [ 'backtesting', '--config', 'config.json', - '--strategy', 'DefaultStrategy', + '--strategy', 'StrategyTestV2', '--datadir', '/foo/bar', '--userdir', "/tmp/freqtrade", '--ticker-interval', '1m', @@ -498,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non '--ticker-interval', '1m', '--export', 'trades', '--strategy-list', - 'DefaultStrategy', + 'StrategyTestV2', 'TestStrategy' ] diff --git a/tests/test_plotting.py b/tests/test_plotting.py index ecadc3f8b..51301a464 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -70,7 +70,6 @@ def test_add_indicators(default_conf, testdatadir, caplog): indicators1 = {"ema10": {}} indicators2 = {"macd": {"color": "red"}} - default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) # Generate buy/sell signals and indicators @@ -112,7 +111,6 @@ def test_add_areas(default_conf, testdatadir, caplog): "fill_to": "macdhist"}} ind_plain = {"macd": {"fill_to": "macdhist"}} - default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) # Generate buy/sell signals and indicators @@ -239,7 +237,6 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir) data = history.load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) - default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) # Generate buy/sell signals and indicators diff --git a/tests/testdata/backtest-result_multistrat.json b/tests/testdata/backtest-result_multistrat.json index 6999050b6..553783dfa 100644 --- a/tests/testdata/backtest-result_multistrat.json +++ b/tests/testdata/backtest-result_multistrat.json @@ -1 +1 @@ -{"strategy": {"DefaultStrategy": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0, "pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}, "TestStrategy": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0,"pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}}, "strategy_comparison": [{"key": "DefaultStrategy", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}, {"key": "TestStrategy", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}]} +{"strategy": {"StrategyTestV2": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0, "pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}, "TestStrategy": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0,"pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}}, "strategy_comparison": [{"key": "StrategyTestV2", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}, {"key": "TestStrategy", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}]} diff --git a/tests/testdata/backtest-result_new.json b/tests/testdata/backtest-result_new.json index 5334bf80e..84f3806ea 100644 --- a/tests/testdata/backtest-result_new.json +++ b/tests/testdata/backtest-result_new.json @@ -1 +1 @@ -{"strategy": {"DefaultStrategy": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0, "pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}}, "strategy_comparison": [{"key": "DefaultStrategy", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}]} +{"strategy": {"StrategyTestV2": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0, "pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}}, "strategy_comparison": [{"key": "StrategyTestV2", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}]} From 07d71f014f766a419f1264f1bbac3dec3980b7c4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 19:38:41 +0200 Subject: [PATCH 197/200] Rename defaultStrategy file --- tests/optimize/test_backtesting.py | 4 ++-- tests/optimize/test_hyperopt_tools.py | 4 ++-- tests/rpc/test_rpc_apiserver.py | 4 ++-- .../strats/{default_strategy.py => strategy_test_v2.py} | 0 tests/strategy/test_default_strategy.py | 6 +++--- tests/strategy/test_interface.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename tests/strategy/strats/{default_strategy.py => strategy_test_v2.py} (100%) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 6442f6cb7..0bbd110b6 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -694,7 +694,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir, def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): - # Override the default buy trend function in our default_strategy + # Override the default buy trend function in our StrategyTestV2 def fun(dataframe=None, pair=None): buy_value = 1 sell_value = 1 @@ -710,7 +710,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): def test_backtest_only_sell(mocker, default_conf, testdatadir): - # Override the default buy trend function in our default_strategy + # Override the default buy trend function in our StrategyTestV2 def fun(dataframe=None, pair=None): buy_value = 0 sell_value = 1 diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index 9f917c2a7..9c2b2e8fc 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -169,7 +169,7 @@ def test_get_strategy_filename(default_conf): x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2') assert isinstance(x, Path) - assert x == Path(__file__).parents[1] / 'strategy/strats/default_strategy.py' + assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py' x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy') assert x is None @@ -262,7 +262,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): assert export_mock.call_count == 1 assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2' - assert export_mock.call_args_list[0][0][2].name == 'default_strategy.json' + assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json' def test_params_print(capsys): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 0256fb632..2852486ed 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1217,8 +1217,8 @@ def test_api_strategies(botclient): assert_response(rc) assert rc.json() == {'strategies': [ - 'StrategyTestV2', 'HyperoptableStrategy', + 'StrategyTestV2', 'TestStrategyLegacyV1' ]} @@ -1231,7 +1231,7 @@ def test_api_strategy(botclient): assert_response(rc) assert rc.json()['strategy'] == 'StrategyTestV2' - data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text() + data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text() assert rc.json()['code'] == data rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") diff --git a/tests/strategy/strats/default_strategy.py b/tests/strategy/strats/strategy_test_v2.py similarity index 100% rename from tests/strategy/strats/default_strategy.py rename to tests/strategy/strats/strategy_test_v2.py diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 2566c8478..6426ebe5f 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -4,10 +4,10 @@ from pandas import DataFrame from freqtrade.persistence.models import Trade -from .strats.default_strategy import StrategyTestV2 +from .strats.strategy_test_v2 import StrategyTestV2 -def test_default_strategy_structure(): +def test_strategy_test_v2_structure(): assert hasattr(StrategyTestV2, 'minimal_roi') assert hasattr(StrategyTestV2, 'stoploss') assert hasattr(StrategyTestV2, 'timeframe') @@ -16,7 +16,7 @@ def test_default_strategy_structure(): assert hasattr(StrategyTestV2, 'populate_sell_trend') -def test_default_strategy(result, fee): +def test_strategy_test_v2(result, fee): strategy = StrategyTestV2({}) metadata = {'pair': 'ETH/BTC'} diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 9023f7981..128599668 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -22,7 +22,7 @@ from freqtrade.strategy.interface import SellCheckTuple from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from tests.conftest import log_has, log_has_re -from .strats.default_strategy import StrategyTestV2 +from .strats.strategy_test_v2 import StrategyTestV2 # Avoid to reinit the same object again and again From 2ce458810b9764aa3c39845faade7ad9589708aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Aug 2021 19:39:57 +0200 Subject: [PATCH 198/200] rename default_hyperopt_loss file --- ...efault_hyperopt_loss.py => hyperopt_loss_short_trade_dur.py} | 0 tests/optimize/test_hyperoptloss.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename freqtrade/optimize/{default_hyperopt_loss.py => hyperopt_loss_short_trade_dur.py} (100%) diff --git a/freqtrade/optimize/default_hyperopt_loss.py b/freqtrade/optimize/hyperopt_loss_short_trade_dur.py similarity index 100% rename from freqtrade/optimize/default_hyperopt_loss.py rename to freqtrade/optimize/hyperopt_loss_short_trade_dur.py diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index ea0caac04..0082bcc34 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock import pytest from freqtrade.exceptions import OperationalException -from freqtrade.optimize.default_hyperopt_loss import ShortTradeDurHyperOptLoss +from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver From b3a4b0fbde824cbd48e8c2439f3e239de78c0333 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Aug 2021 07:10:13 +0200 Subject: [PATCH 199/200] Version bump to 2021.8 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 34e1bfc5d..8b85ca56e 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2021.7' +__version__ = '2021.8' if __version__ == 'develop': From ef9c1addcfac2700d14012aa5734a66ec35da8e3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Aug 2021 19:54:53 +0200 Subject: [PATCH 200/200] Add expired to list of canceled statuses --- freqtrade/constants.py | 2 ++ freqtrade/exchange/exchange.py | 5 +++-- freqtrade/freqtradebot.py | 6 +++--- freqtrade/persistence/models.py | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index cde276ac0..efcd1aaca 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -49,6 +49,8 @@ USERPATH_NOTEBOOKS = 'notebooks' TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent'] ENV_VAR_PREFIX = 'FREQTRADE__' +NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired') + # Define decimals per coin for outputs # Only used for outputs. diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 79ee94149..26f034e41 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -19,7 +19,8 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU decimal_to_precision) from pandas import DataFrame -from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes +from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, + ListPairsWithTimeframes) from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, @@ -804,7 +805,7 @@ class Exchange: :param order: Order dict as returned from fetch_order() :return: True if order has been cancelled without being filled, False otherwise. """ - return (order.get('status') in ('closed', 'canceled', 'cancelled') + return (order.get('status') in NON_OPEN_EXCHANGE_STATES and order.get('filled') == 0.0) @retrier diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a7451d632..259270483 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -945,7 +945,7 @@ class FreqtradeBot(LoggingMixin): was_trade_fully_canceled = False # Cancelled orders may have the status of 'canceled' or 'closed' - if order['status'] not in ('cancelled', 'canceled', 'closed'): + if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES: filled_val = order.get('filled', 0.0) or 0.0 filled_stake = filled_val * trade.open_rate minstake = self.exchange.get_min_pair_stake_amount( @@ -961,7 +961,7 @@ class FreqtradeBot(LoggingMixin): # Avoid race condition where the order could not be cancelled coz its already filled. # Simply bailing here is the only safe way - as this order will then be # handled in the next iteration. - if corder.get('status') not in ('cancelled', 'canceled', 'closed'): + if corder.get('status') not in constants.NON_OPEN_EXCHANGE_STATES: logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.") return False else: @@ -1142,7 +1142,7 @@ class FreqtradeBot(LoggingMixin): trade.close_rate_requested = limit trade.sell_reason = sell_reason.sell_reason # In case of market sell orders the order can be closed immediately - if order.get('status', 'unknown') == 'closed': + if order.get('status', 'unknown') in ('closed', 'expired'): self.update_trade_state(trade, trade.open_order_id, order) Trade.commit() diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 5eaca7966..8c8c1e0a9 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -13,7 +13,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session from sqlalchemy.pool import StaticPool from sqlalchemy.sql.schema import UniqueConstraint -from freqtrade.constants import DATETIME_PRINT_FORMAT +from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES from freqtrade.enums import SellType from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.misc import safe_value_fallback @@ -159,7 +159,7 @@ class Order(_DECL_BASE): self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc) self.ft_is_open = True - if self.status in ('closed', 'canceled', 'cancelled'): + if self.status in NON_OPEN_EXCHANGE_STATES: self.ft_is_open = False if (order.get('filled', 0.0) or 0.0) > 0: self.order_filled_date = datetime.now(timezone.utc)