Merge pull request #2092 from freqtrade/split_analyze_ticker

Split analyze_ticker
This commit is contained in:
Matthias 2019-08-04 19:37:52 +02:00 committed by GitHub
commit eeecdd4e5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 37 additions and 34 deletions

View File

@ -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) Path("user_data/plots").mkdir(parents=True, exist_ok=True)
_filename = Path('user_data/plots').joinpath(filename)
plot(fig, filename=str(Path('user_data/plots').joinpath(filename)), plot(fig, filename=str(_filename),
auto_open=auto_open) auto_open=auto_open)
logger.info(f"Stored plot as {_filename}")

View File

@ -158,6 +158,23 @@ class IStrategy(ABC):
""" """
Parses the given ticker history and returns a populated DataFrame Parses the given ticker history and returns a populated DataFrame
add several TA indicators and buy signal to it 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 :return: DataFrame with ticker data and indicator data
""" """
@ -168,10 +185,7 @@ class IStrategy(ABC):
if (not self.process_only_new_candles or if (not self.process_only_new_candles or
self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']): self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']):
# Defs that only make change on new candle data. # Defs that only make change on new candle data.
logger.debug("TA Analysis Launched") dataframe = self.analyze_ticker(dataframe, metadata)
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date'] self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
else: else:
logger.debug("Skipping TA Analysis for already analyzed candle") logger.debug("Skipping TA Analysis for already analyzed candle")
@ -198,7 +212,7 @@ class IStrategy(ABC):
return False, False return False, False
try: try:
dataframe = self.analyze_ticker(dataframe, {'pair': pair}) dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair})
except ValueError as error: except ValueError as error:
logger.warning( logger.warning(
'Unable to analyze ticker for pair %s: %s', 'Unable to analyze ticker for pair %s: %s',

View File

@ -19,13 +19,13 @@ _STRATEGY = DefaultStrategy(config={})
def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): def test_returns_latest_buy_signal(mocker, default_conf, ticker_history):
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, '_analyze_ticker_internal',
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, '_analyze_ticker_internal',
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) 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): def test_returns_latest_sell_signal(mocker, default_conf, ticker_history):
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, '_analyze_ticker_internal',
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, '_analyze_ticker_internal',
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
) )
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) 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): def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_history):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, '_analyze_ticker_internal',
side_effect=ValueError('xyz') side_effect=ValueError('xyz')
) )
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], 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): def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, '_analyze_ticker_internal',
return_value=DataFrame([]) return_value=DataFrame([])
) )
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 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) oldtime = arrow.utcnow().shift(minutes=-16)
ticks = DataFrame([{'buy': 1, 'date': oldtime}]) ticks = DataFrame([{'buy': 1, 'date': oldtime}])
mocker.patch.object( mocker.patch.object(
_STRATEGY, 'analyze_ticker', _STRATEGY, '_analyze_ticker_internal',
return_value=DataFrame(ticks) return_value=DataFrame(ticks)
) )
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 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) 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) caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x) ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_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 = DefaultStrategy({})
strategy.process_only_new_candles = True 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 'high' in ret.columns
assert 'low' in ret.columns assert 'low' in ret.columns
assert 'close' 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.record_tuples)
caplog.clear() 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 # No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 1 assert ind_mock.call_count == 1
assert buy_mock.call_count == 1 assert buy_mock.call_count == 1

View File

@ -215,6 +215,8 @@ def test_generate_plot_file(mocker, caplog):
assert plot_mock.call_args[0][0] == fig assert plot_mock.call_args[0][0] == fig
assert (plot_mock.call_args_list[0][1]['filename'] assert (plot_mock.call_args_list[0][1]['filename']
== "user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html") == "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(): def test_add_profit():

View File

@ -16,8 +16,6 @@ import logging
import sys import sys
from typing import Any, Dict, List from typing import Any, Dict, List
import pandas as pd
from freqtrade.configuration import Arguments from freqtrade.configuration import Arguments
from freqtrade.configuration.arguments import ARGS_PLOT_DATAFRAME from freqtrade.configuration.arguments import ARGS_PLOT_DATAFRAME
from freqtrade.data.btanalysis import extract_trades_of_period from freqtrade.data.btanalysis import extract_trades_of_period
@ -30,20 +28,6 @@ from freqtrade.state import RunMode
logger = logging.getLogger(__name__) 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]): def analyse_and_plot_pairs(config: Dict[str, Any]):
""" """
From arguments provided in cli: From arguments provided in cli:
@ -57,6 +41,7 @@ def analyse_and_plot_pairs(config: Dict[str, Any]):
""" """
plot_elements = init_plotscript(config) plot_elements = init_plotscript(config)
trades = plot_elements['trades'] trades = plot_elements['trades']
strategy = plot_elements["strategy"]
pair_counter = 0 pair_counter = 0
for pair, data in plot_elements["tickers"].items(): 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) logger.info("analyse pair %s", pair)
tickers = {} tickers = {}
tickers[pair] = data 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 = trades.loc[trades['pair'] == pair]
trades_pair = extract_trades_of_period(dataframe, trades_pair) trades_pair = extract_trades_of_period(dataframe, trades_pair)