From 638bed3dac45206eb001e0512aeb938ae063c20d Mon Sep 17 00:00:00 2001 From: user Date: Wed, 7 Jul 2021 06:46:51 +0000 Subject: [PATCH 01/70] 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 02/70] 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 03/70] 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 04/70] 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 05/70] 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 06/70] 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 07/70] 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 08/70] 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 09/70] 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 10/70] 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 11/70] 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 12/70] 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 13/70] 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 14/70] 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 15/70] 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 16/70] 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 17/70] 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 19/70] 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 20/70] 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 21/70] 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 22/70] 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 23/70] 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 24/70] 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 25/70] 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 26/70] 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 27/70] 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 28/70] 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 29/70] 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 30/70] 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 31/70] 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 32/70] 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 33/70] 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 34/70] 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 35/70] 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 36/70] 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 37/70] 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 38/70] 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 06e7f379b39087134ed8c756eb28a700668b23a0 Mon Sep 17 00:00:00 2001 From: raphael Date: Fri, 23 Jul 2021 16:32:30 -0400 Subject: [PATCH 40/70] Fix code to get Bittrex US-restricted markets Old code was no longer working --- docs/exchanges.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index e54f97714..72f5e4ba4 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -77,8 +77,8 @@ You can get a list of restricted markets by using the following snippet: ``` python import ccxt ct = ccxt.bittrex() -_ = ct.load_markets() -res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarkets()['result'] if x['IsRestricted']] +ct.load_markets() +res = [f"{x['quoteCurrencySymbol']}/{x['baseCurrencySymbol']}" for x in ct.publicGetMarkets() if 'US' in x['prohibitedIn']] print(res) ``` From 34c8a5afaff2c06fc0a09df0d4eba6ad2464bcc6 Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Sun, 25 Jul 2021 07:24:55 +0000 Subject: [PATCH 41/70] 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 42/70] 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 cf4d1875dd2f34c74f64fa416b23aa4804cc1a6c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Jul 2021 06:56:37 +0200 Subject: [PATCH 43/70] Use prohibitedIn instead of isRestricted --- docs/exchanges.md | 5 +++-- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 72f5e4ba4..29b9bb533 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -77,8 +77,9 @@ You can get a list of restricted markets by using the following snippet: ``` python import ccxt ct = ccxt.bittrex() -ct.load_markets() -res = [f"{x['quoteCurrencySymbol']}/{x['baseCurrencySymbol']}" for x in ct.publicGetMarkets() if 'US' in x['prohibitedIn']] +lm = ct.load_markets() + +res = [p for p, x in lm.items() if 'US' in x['info']['prohibitedIn']] print(res) ``` diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 91b278077..47ce8b4ba 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -387,7 +387,7 @@ class Exchange: # its contents depend on the exchange. # It can also be a string or similar ... so we need to verify that first. elif (isinstance(self.markets[pair].get('info', None), dict) - and self.markets[pair].get('info', {}).get('IsRestricted', False)): + and self.markets[pair].get('info', {}).get('prohibitedIn', False)): # Warn users about restricted pairs in whitelist. # We cannot determine reliably if Users are affected. logger.warning(f"Pair {pair} is restricted for some users on this exchange." diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 02adf01c4..bf77a9460 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -673,7 +673,7 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): api_mock = MagicMock() type(api_mock).load_markets = MagicMock(return_value={ 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, - 'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}}, + 'XRP/BTC': {'quote': 'BTC', 'info': {'prohibitedIn': ['US']}}, 'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ... }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) From 6490b82ad61d4ec0796b62fe68ecdcf606dfc970 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Jul 2021 19:48:36 +0200 Subject: [PATCH 44/70] Update docker-documentation for multiarch builds --- docs/docker_quickstart.md | 91 ++++++++------------------------------- 1 file changed, 17 insertions(+), 74 deletions(-) diff --git a/docs/docker_quickstart.md b/docs/docker_quickstart.md index cb66fc7e2..1fa229225 100644 --- a/docs/docker_quickstart.md +++ b/docs/docker_quickstart.md @@ -24,82 +24,21 @@ Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.co Create a new directory and place the [docker-compose file](https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml) in this directory. -=== "PC/MAC/Linux" - ``` bash - mkdir ft_userdata - cd ft_userdata/ - # Download the docker-compose file from the repository - curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml +``` bash +mkdir ft_userdata +cd ft_userdata/ +# Download the docker-compose file from the repository +curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml - # Pull the freqtrade image - docker-compose pull +# Pull the freqtrade image +docker-compose pull - # Create user directory structure - docker-compose run --rm freqtrade create-userdir --userdir user_data +# Create user directory structure +docker-compose run --rm freqtrade create-userdir --userdir user_data - # Create configuration - Requires answering interactive questions - docker-compose run --rm freqtrade new-config --config user_data/config.json - ``` - -=== "RaspberryPi" - ``` bash - mkdir ft_userdata - cd ft_userdata/ - # Download the docker-compose file from the repository - curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml - - # Edit the compose file to use an image named `*_pi` (stable_pi or develop_pi) - - # Pull the freqtrade image - docker-compose pull - - # Create user directory structure - docker-compose run --rm freqtrade create-userdir --userdir user_data - - # Create configuration - Requires answering interactive questions - docker-compose run --rm freqtrade new-config --config user_data/config.json - ``` - - !!! Note "Change your docker Image" - You have to change the docker image in the docker-compose file for your Raspberry build to work properly. - ``` yml - image: freqtradeorg/freqtrade:stable_pi - # image: freqtradeorg/freqtrade:develop_pi - ``` - -=== "ARM 64 Systenms (Mac M1, Raspberry Pi 4, Jetson Nano)" - In case of a Mac M1, make sure that your docker installation is running in native mode - Arm64 images are not yet provided via Docker Hub and need to be build locally first. - Depending on the device, this may take a few minutes (Apple M1) or multiple hours (Raspberry Pi) - - ``` bash - # Clone Freqtrade repository - git clone https://github.com/freqtrade/freqtrade.git - cd freqtrade - # Optionally switch to the stable version - git checkout stable - - # Modify your docker-compose file to enable building and change the image name - # (see the Note Box below for necessary changes) - - # Build image - docker-compose build - - # Create user directory structure - docker-compose run --rm freqtrade create-userdir --userdir user_data - - # Create configuration - Requires answering interactive questions - docker-compose run --rm freqtrade new-config --config user_data/config.json - ``` - - !!! Note "Change your docker Image" - You have to change the docker image in the docker-compose file for your arm64 build to work properly. - ``` yml - image: freqtradeorg/freqtrade:custom_arm64 - build: - context: . - dockerfile: "Dockerfile" - ``` +# Create configuration - Requires answering interactive questions +docker-compose run --rm freqtrade new-config --config user_data/config.json +``` The above snippet creates a new directory called `ft_userdata`, downloads the latest compose file and pulls the freqtrade image. The last 2 steps in the snippet create the directory with `user_data`, as well as (interactively) the default configuration based on your selections. @@ -117,7 +56,7 @@ The last 2 steps in the snippet create the directory with `user_data`, as well a The `SampleStrategy` is run by default. -!!! Warning "`SampleStrategy` is just a demo!" +!!! Danger "`SampleStrategy` is just a demo!" The `SampleStrategy` is there for your reference and give you ideas for your own strategy. Please always backtest your strategy and use dry-run for some time before risking real money! You will find more information about Strategy development in the [Strategy documentation](strategy-customization.md). @@ -167,6 +106,10 @@ Advanced users may edit the docker-compose file further to include all possible All freqtrade arguments will be available by running `docker-compose run --rm freqtrade `. +!!! Warning "`docker-compose` for trade commands" + Trade commands (`freqtrade trade <...>`) should not be ran via `docker-compose run` - but should use `docker-compose up -d` instead. + This makes sure that the container is properly started (including port forwardings) and will make sure that the container will restart after a system reboot. + !!! Note "`docker-compose run --rm`" Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command). From aa34889c047e13607a1b60ccca01cf63d6f57bab Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Jul 2021 07:14:35 +0200 Subject: [PATCH 45/70] 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 46/70] 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 47/70] 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 48/70] 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 49/70] 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 50/70] 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 51/70] 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 52/70] 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 53/70] 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 54/70] 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 55/70] 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 56/70] 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 57/70] 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 58/70] 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 59/70] 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 60/70] 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 61/70] 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 62/70] 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 63/70] 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 64/70] 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 65/70] 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 66/70] 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 67/70] 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 68/70] 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 69/70] 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 70/70] 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