Merge pull request #2092 from freqtrade/split_analyze_ticker
Split analyze_ticker
This commit is contained in:
commit
eeecdd4e5a
@ -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}")
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
||||||
|
@ -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():
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user