diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 5c9c6e457..d03d3ae53 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -318,6 +318,7 @@ def store_plot_file(fig, filename: str, auto_open: bool = False) -> None: """ Path("user_data/plots").mkdir(parents=True, exist_ok=True) - - plot(fig, filename=str(Path('user_data/plots').joinpath(filename)), + _filename = Path('user_data/plots').joinpath(filename) + plot(fig, filename=str(_filename), auto_open=auto_open) + logger.info(f"Stored plot as {_filename}") diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 2a28bcd22..37aa97bb1 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -158,6 +158,23 @@ class IStrategy(ABC): """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it + :param dataframe: Dataframe containing ticker data + :param metadata: Metadata dictionary with additional data (e.g. 'pair') + :return: DataFrame with ticker data and indicator data + """ + logger.debug("TA Analysis Launched") + dataframe = self.advise_indicators(dataframe, metadata) + dataframe = self.advise_buy(dataframe, metadata) + dataframe = self.advise_sell(dataframe, metadata) + return dataframe + + def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Parses the given ticker history and returns a populated DataFrame + add several TA indicators and buy signal to it + WARNING: Used internally only, may skip analysis if `process_only_new_candles` is set. + :param dataframe: Dataframe containing ticker data + :param metadata: Metadata dictionary with additional data (e.g. 'pair') :return: DataFrame with ticker data and indicator data """ @@ -168,10 +185,7 @@ class IStrategy(ABC): if (not self.process_only_new_candles or self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']): # Defs that only make change on new candle data. - logger.debug("TA Analysis Launched") - dataframe = self.advise_indicators(dataframe, metadata) - dataframe = self.advise_buy(dataframe, metadata) - dataframe = self.advise_sell(dataframe, metadata) + dataframe = self.analyze_ticker(dataframe, metadata) self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date'] else: logger.debug("Skipping TA Analysis for already analyzed candle") @@ -198,7 +212,7 @@ class IStrategy(ABC): return False, False try: - dataframe = self.analyze_ticker(dataframe, {'pair': pair}) + dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair}) except ValueError as error: logger.warning( 'Unable to analyze ticker for pair %s: %s', diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index ee8c8ddd4..9f5ab70e3 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -19,13 +19,13 @@ _STRATEGY = DefaultStrategy(config={}) def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) @@ -33,14 +33,14 @@ def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): def test_returns_latest_sell_signal(mocker, default_conf, ticker_history): mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) @@ -60,7 +60,7 @@ def test_get_signal_empty(default_conf, mocker, caplog): def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_history): caplog.set_level(logging.INFO) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_internal', side_effect=ValueError('xyz') ) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], @@ -71,7 +71,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_hi def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history): caplog.set_level(logging.INFO) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([]) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], @@ -86,7 +86,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history): oldtime = arrow.utcnow().shift(minutes=-16) ticks = DataFrame([{'buy': 1, 'date': oldtime}]) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame(ticks) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], @@ -252,7 +252,7 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: caplog.record_tuples) -def test_analyze_ticker_skip_analyze(ticker_history, mocker, caplog) -> None: +def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) buy_mock = MagicMock(side_effect=lambda x, meta: x) @@ -267,7 +267,7 @@ def test_analyze_ticker_skip_analyze(ticker_history, mocker, caplog) -> None: strategy = DefaultStrategy({}) strategy.process_only_new_candles = True - ret = strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_internal(ticker_history, {'pair': 'ETH/BTC'}) assert 'high' in ret.columns assert 'low' in ret.columns assert 'close' in ret.columns @@ -280,7 +280,7 @@ def test_analyze_ticker_skip_analyze(ticker_history, mocker, caplog) -> None: caplog.record_tuples) caplog.clear() - ret = strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_internal(ticker_history, {'pair': 'ETH/BTC'}) # No analysis happens as process_only_new_candles is true assert ind_mock.call_count == 1 assert buy_mock.call_count == 1 diff --git a/freqtrade/tests/test_plotting.py b/freqtrade/tests/test_plotting.py index 509bf7880..f9da773fe 100644 --- a/freqtrade/tests/test_plotting.py +++ b/freqtrade/tests/test_plotting.py @@ -215,6 +215,8 @@ def test_generate_plot_file(mocker, caplog): assert plot_mock.call_args[0][0] == fig assert (plot_mock.call_args_list[0][1]['filename'] == "user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html") + assert log_has("Stored plot as user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html", + caplog.record_tuples) def test_add_profit(): diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 034a6f448..6412c45a4 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -16,8 +16,6 @@ import logging import sys from typing import Any, Dict, List -import pandas as pd - from freqtrade.configuration import Arguments from freqtrade.configuration.arguments import ARGS_PLOT_DATAFRAME from freqtrade.data.btanalysis import extract_trades_of_period @@ -30,20 +28,6 @@ from freqtrade.state import RunMode logger = logging.getLogger(__name__) -def generate_dataframe(strategy, tickers, pair) -> pd.DataFrame: - """ - Get tickers then Populate strategy indicators and signals, then return the full dataframe - :return: the DataFrame of a pair - """ - - dataframes = strategy.tickerdata_to_dataframe(tickers) - dataframe = dataframes[pair] - dataframe = strategy.advise_buy(dataframe, {'pair': pair}) - dataframe = strategy.advise_sell(dataframe, {'pair': pair}) - - return dataframe - - def analyse_and_plot_pairs(config: Dict[str, Any]): """ From arguments provided in cli: @@ -57,6 +41,7 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): """ plot_elements = init_plotscript(config) trades = plot_elements['trades'] + strategy = plot_elements["strategy"] pair_counter = 0 for pair, data in plot_elements["tickers"].items(): @@ -64,7 +49,8 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): logger.info("analyse pair %s", pair) tickers = {} tickers[pair] = data - dataframe = generate_dataframe(plot_elements["strategy"], tickers, pair) + + dataframe = strategy.analyze_ticker(tickers[pair], {'pair': pair}) trades_pair = trades.loc[trades['pair'] == pair] trades_pair = extract_trades_of_period(dataframe, trades_pair)