Move Analyze to a class
This commit is contained in:
parent
e025dc0dba
commit
a8b8ab20b7
@ -1,26 +1,42 @@
|
|||||||
"""
|
"""
|
||||||
Functions to analyze ticker data with indicators and produce buy and sell signals
|
Functions to analyze ticker data with indicators and produce buy and sell signals
|
||||||
"""
|
"""
|
||||||
import logging
|
|
||||||
from datetime import timedelta
|
|
||||||
from enum import Enum
|
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from enum import Enum
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
from typing import Dict, List
|
||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
|
from freqtrade.logger import Logger
|
||||||
from freqtrade.strategy.strategy import Strategy
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class SignalType(Enum):
|
class SignalType(Enum):
|
||||||
""" Enum to distinguish between buy and sell signals """
|
"""
|
||||||
|
Enum to distinguish between buy and sell signals
|
||||||
|
"""
|
||||||
BUY = "buy"
|
BUY = "buy"
|
||||||
SELL = "sell"
|
SELL = "sell"
|
||||||
|
|
||||||
|
|
||||||
|
class Analyze(object):
|
||||||
|
"""
|
||||||
|
Analyze class contains everything the bot need to determine if the situation is good for
|
||||||
|
buying or selling.
|
||||||
|
"""
|
||||||
|
def __init__(self, config: dict) -> None:
|
||||||
|
"""
|
||||||
|
Init Analyze
|
||||||
|
:param config: Bot configuration (use the one from Configuration())
|
||||||
|
"""
|
||||||
|
self.logger = Logger(name=__name__).get_logger()
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
self.strategy = Strategy()
|
||||||
|
self.strategy.init(self.config)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse_ticker_dataframe(ticker: list) -> DataFrame:
|
def parse_ticker_dataframe(ticker: list) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Analyses the trend for the given ticker history
|
Analyses the trend for the given ticker history
|
||||||
@ -36,8 +52,7 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
|
|||||||
frame.sort_values('date', inplace=True)
|
frame.sort_values('date', inplace=True)
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|
||||||
"""
|
"""
|
||||||
Adds several different TA indicators to the given DataFrame
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
@ -45,46 +60,40 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|||||||
you are using. Let uncomment only the indicator you are using in your strategies
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
"""
|
"""
|
||||||
strategy = Strategy()
|
|
||||||
return strategy.populate_indicators(dataframe=dataframe)
|
|
||||||
|
|
||||||
|
return self.strategy.populate_indicators(dataframe=dataframe)
|
||||||
|
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
strategy = Strategy()
|
return self.strategy.populate_buy_trend(dataframe=dataframe)
|
||||||
return strategy.populate_buy_trend(dataframe=dataframe)
|
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
strategy = Strategy()
|
return self.strategy.populate_sell_trend(dataframe=dataframe)
|
||||||
return strategy.populate_sell_trend(dataframe=dataframe)
|
|
||||||
|
|
||||||
|
def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame:
|
||||||
def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
|
|
||||||
"""
|
"""
|
||||||
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
|
||||||
:return DataFrame with ticker data and indicator data
|
:return DataFrame with ticker data and indicator data
|
||||||
"""
|
"""
|
||||||
dataframe = parse_ticker_dataframe(ticker_history)
|
dataframe = self.parse_ticker_dataframe(ticker_history)
|
||||||
dataframe = populate_indicators(dataframe)
|
dataframe = self.populate_indicators(dataframe)
|
||||||
dataframe = populate_buy_trend(dataframe)
|
dataframe = self.populate_buy_trend(dataframe)
|
||||||
dataframe = populate_sell_trend(dataframe)
|
dataframe = self.populate_sell_trend(dataframe)
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
|
||||||
# FIX: Maybe return False, if an error has occured,
|
# FIX: Maybe return False, if an error has occured,
|
||||||
# Otherwise we might mask an error as an non-signal-scenario
|
# Otherwise we might mask an error as an non-signal-scenario
|
||||||
def get_signal(pair: str, interval: int) -> (bool, bool):
|
def get_signal(self, pair: str, interval: int) -> (bool, bool):
|
||||||
"""
|
"""
|
||||||
Calculates current signal based several technical analysis indicators
|
Calculates current signal based several technical analysis indicators
|
||||||
:param pair: pair in format BTC_ANT or BTC-ANT
|
:param pair: pair in format BTC_ANT or BTC-ANT
|
||||||
@ -92,20 +101,28 @@ def get_signal(pair: str, interval: int) -> (bool, bool):
|
|||||||
"""
|
"""
|
||||||
ticker_hist = get_ticker_history(pair, interval)
|
ticker_hist = get_ticker_history(pair, interval)
|
||||||
if not ticker_hist:
|
if not ticker_hist:
|
||||||
logger.warning('Empty ticker history for pair %s', pair)
|
self.logger.warning('Empty ticker history for pair %s', pair)
|
||||||
return (False, False) # return False ?
|
return (False, False) # return False ?
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dataframe = analyze_ticker(ticker_hist)
|
dataframe = self.analyze_ticker(ticker_hist)
|
||||||
except ValueError as ex:
|
except ValueError as error:
|
||||||
logger.warning('Unable to analyze ticker for pair %s: %s', pair, str(ex))
|
self.logger.warning(
|
||||||
|
'Unable to analyze ticker for pair %s: %s',
|
||||||
|
pair,
|
||||||
|
str(error)
|
||||||
|
)
|
||||||
return (False, False) # return False ?
|
return (False, False) # return False ?
|
||||||
except Exception as ex:
|
except Exception as error:
|
||||||
logger.exception('Unexpected error when analyzing ticker for pair %s: %s', pair, str(ex))
|
self.logger.exception(
|
||||||
|
'Unexpected error when analyzing ticker for pair %s: %s',
|
||||||
|
pair,
|
||||||
|
str(error)
|
||||||
|
)
|
||||||
return (False, False) # return False ?
|
return (False, False) # return False ?
|
||||||
|
|
||||||
if dataframe.empty:
|
if dataframe.empty:
|
||||||
logger.warning('Empty dataframe for pair %s', pair)
|
self.logger.warning('Empty dataframe for pair %s', pair)
|
||||||
return (False, False) # return False ?
|
return (False, False) # return False ?
|
||||||
|
|
||||||
latest = dataframe.iloc[-1]
|
latest = dataframe.iloc[-1]
|
||||||
@ -113,9 +130,61 @@ def get_signal(pair: str, interval: int) -> (bool, bool):
|
|||||||
# Check if dataframe is out of date
|
# Check if dataframe is out of date
|
||||||
signal_date = arrow.get(latest['date'])
|
signal_date = arrow.get(latest['date'])
|
||||||
if signal_date < arrow.now() - timedelta(minutes=(interval + 5)):
|
if signal_date < arrow.now() - timedelta(minutes=(interval + 5)):
|
||||||
logger.warning('Too old dataframe for pair %s', pair)
|
self.logger.warning('Too old dataframe for pair %s', pair)
|
||||||
return (False, False) # return False ?
|
return (False, False) # return False ?
|
||||||
|
|
||||||
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
|
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
|
||||||
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell))
|
self.logger.debug(
|
||||||
|
'trigger: %s (pair=%s) buy=%s sell=%s',
|
||||||
|
latest['date'],
|
||||||
|
pair,
|
||||||
|
str(buy),
|
||||||
|
str(sell)
|
||||||
|
)
|
||||||
return (buy, sell)
|
return (buy, sell)
|
||||||
|
|
||||||
|
def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool) -> bool:
|
||||||
|
"""
|
||||||
|
This function evaluate if on the condition required to trigger a sell has been reached
|
||||||
|
if the threshold is reached and updates the trade record.
|
||||||
|
:return: True if trade should be sold, False otherwise
|
||||||
|
"""
|
||||||
|
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
|
||||||
|
if self.min_roi_reached(trade=trade, current_rate=rate, current_time=date):
|
||||||
|
self.logger.debug('Executing sell due to ROI ...')
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
|
||||||
|
if self.config.get('experimental', {}).get('sell_profit_only', False):
|
||||||
|
self.logger.debug('Checking if trade is profitable ...')
|
||||||
|
if trade.calc_profit(rate=rate) <= 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if sell and not buy and self.config.get('experimental', {}).get('use_sell_signal', False):
|
||||||
|
self.logger.debug('Executing sell due to sell signal ...')
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def min_roi_reached(self, trade: Trade, current_rate: float, current_time: datetime) -> bool:
|
||||||
|
"""
|
||||||
|
Based an earlier trade and current price and ROI configuration, decides whether bot should
|
||||||
|
sell
|
||||||
|
:return True if bot should sell at current rate
|
||||||
|
"""
|
||||||
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
|
if self.strategy.stoploss is not None and current_profit < float(self.strategy.stoploss):
|
||||||
|
self.logger.debug('Stop loss hit.')
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if time matches and current rate is above threshold
|
||||||
|
time_diff = (current_time - trade.open_date).total_seconds() / 60
|
||||||
|
for duration, threshold in sorted(self.strategy.minimal_roi.items()):
|
||||||
|
if time_diff > float(duration) and current_profit > threshold:
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Threshold not reached. (cur_profit: %1.2f%%)',
|
||||||
|
float(current_profit) * 100.0
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
@ -1,16 +1,45 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
|
"""
|
||||||
|
Unit test file for analyse.py
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import arrow
|
|
||||||
import logging
|
import logging
|
||||||
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
import freqtrade.tests.conftest as tt # test tools
|
import freqtrade.tests.conftest as tt # test tools
|
||||||
from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
|
from freqtrade.analyze import Analyze, SignalType
|
||||||
populate_buy_trend, populate_indicators,
|
|
||||||
populate_sell_trend)
|
|
||||||
from freqtrade.strategy.strategy import Strategy
|
# Avoid to reinit the same object again and again
|
||||||
|
_ANALYZE = Analyze({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
|
|
||||||
|
def test_signaltype_object() -> None:
|
||||||
|
"""
|
||||||
|
Test the SignalType object has the mandatory Constants
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
assert hasattr(SignalType, 'BUY')
|
||||||
|
assert hasattr(SignalType, 'SELL')
|
||||||
|
|
||||||
|
|
||||||
|
def test_analyze_object() -> None:
|
||||||
|
"""
|
||||||
|
Test the Analyze object has the mandatory methods
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
assert hasattr(Analyze, 'parse_ticker_dataframe')
|
||||||
|
assert hasattr(Analyze, 'populate_indicators')
|
||||||
|
assert hasattr(Analyze, 'populate_buy_trend')
|
||||||
|
assert hasattr(Analyze, 'populate_sell_trend')
|
||||||
|
assert hasattr(Analyze, 'analyze_ticker')
|
||||||
|
assert hasattr(Analyze, 'get_signal')
|
||||||
|
assert hasattr(Analyze, 'should_sell')
|
||||||
|
assert hasattr(Analyze, 'min_roi_reached')
|
||||||
|
|
||||||
|
|
||||||
def test_dataframe_correct_columns(result):
|
def test_dataframe_correct_columns(result):
|
||||||
@ -18,71 +47,75 @@ def test_dataframe_correct_columns(result):
|
|||||||
['close', 'high', 'low', 'open', 'date', 'volume']
|
['close', 'high', 'low', 'open', 'date', 'volume']
|
||||||
|
|
||||||
|
|
||||||
def test_dataframe_correct_length(result):
|
|
||||||
# no idea what this check truly does - should we just remove it?
|
|
||||||
assert len(result.index) == 14397
|
|
||||||
|
|
||||||
|
|
||||||
def test_populates_buy_trend(result):
|
def test_populates_buy_trend(result):
|
||||||
# Load the default strategy for the unit test, because this logic is done in main.py
|
# Load the default strategy for the unit test, because this logic is done in main.py
|
||||||
Strategy().init({'strategy': 'default_strategy'})
|
dataframe = _ANALYZE.populate_buy_trend(_ANALYZE.populate_indicators(result))
|
||||||
|
|
||||||
dataframe = populate_buy_trend(populate_indicators(result))
|
|
||||||
assert 'buy' in dataframe.columns
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
|
|
||||||
def test_populates_sell_trend(result):
|
def test_populates_sell_trend(result):
|
||||||
# Load the default strategy for the unit test, because this logic is done in main.py
|
# Load the default strategy for the unit test, because this logic is done in main.py
|
||||||
Strategy().init({'strategy': 'default_strategy'})
|
dataframe = _ANALYZE.populate_sell_trend(_ANALYZE.populate_indicators(result))
|
||||||
|
|
||||||
dataframe = populate_sell_trend(populate_indicators(result))
|
|
||||||
assert 'sell' in dataframe.columns
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
|
||||||
def test_returns_latest_buy_signal(mocker):
|
def test_returns_latest_buy_signal(mocker):
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
|
||||||
mocker.patch(
|
|
||||||
'freqtrade.analyze.analyze_ticker',
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.analyze.Analyze',
|
||||||
|
analyze_ticker=MagicMock(
|
||||||
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (True, False)
|
)
|
||||||
|
assert _ANALYZE.get_signal('BTC-ETH', 5) == (True, False)
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch.multiple(
|
||||||
'freqtrade.analyze.analyze_ticker',
|
'freqtrade.analyze.Analyze',
|
||||||
|
analyze_ticker=MagicMock(
|
||||||
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (False, True)
|
)
|
||||||
|
assert _ANALYZE.get_signal('BTC-ETH', 5) == (False, True)
|
||||||
|
|
||||||
|
|
||||||
def test_returns_latest_sell_signal(mocker):
|
def test_returns_latest_sell_signal(mocker):
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
|
||||||
mocker.patch(
|
mocker.patch.multiple(
|
||||||
'freqtrade.analyze.analyze_ticker',
|
'freqtrade.analyze.Analyze',
|
||||||
|
analyze_ticker=MagicMock(
|
||||||
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (False, True)
|
)
|
||||||
|
|
||||||
mocker.patch(
|
assert _ANALYZE.get_signal('BTC-ETH', 5) == (False, True)
|
||||||
'freqtrade.analyze.analyze_ticker',
|
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.analyze.Analyze',
|
||||||
|
analyze_ticker=MagicMock(
|
||||||
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (True, False)
|
)
|
||||||
|
assert _ANALYZE.get_signal('BTC-ETH', 5) == (True, False)
|
||||||
|
|
||||||
|
|
||||||
def test_get_signal_empty(default_conf, mocker, caplog):
|
def test_get_signal_empty(default_conf, mocker, caplog):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
||||||
assert (False, False) == get_signal('foo', int(default_conf['ticker_interval']))
|
assert (False, False) == _ANALYZE.get_signal('foo', int(default_conf['ticker_interval']))
|
||||||
assert tt.log_has('Empty ticker history for pair foo',
|
assert tt.log_has('Empty ticker history for pair foo', caplog.record_tuples)
|
||||||
caplog.record_tuples)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker',
|
mocker.patch.multiple(
|
||||||
side_effect=ValueError('xyz'))
|
'freqtrade.analyze.Analyze',
|
||||||
assert (False, False) == get_signal('foo', int(default_conf['ticker_interval']))
|
analyze_ticker=MagicMock(
|
||||||
|
side_effect=ValueError('xyz')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert (False, False) == _ANALYZE.get_signal('foo', int(default_conf['ticker_interval']))
|
||||||
assert tt.log_has('Unable to analyze ticker for pair foo: xyz',
|
assert tt.log_has('Unable to analyze ticker for pair foo: xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
@ -90,8 +123,13 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
|||||||
def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
|
def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([]))
|
mocker.patch.multiple(
|
||||||
assert (False, False) == get_signal('xyz', int(default_conf['ticker_interval']))
|
'freqtrade.analyze.Analyze',
|
||||||
|
analyze_ticker=MagicMock(
|
||||||
|
return_value=DataFrame([])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert (False, False) == _ANALYZE.get_signal('xyz', int(default_conf['ticker_interval']))
|
||||||
assert tt.log_has('Empty dataframe for pair xyz',
|
assert tt.log_has('Empty dataframe for pair xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
@ -102,27 +140,36 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
|
|||||||
# FIX: The get_signal function has hardcoded 10, which we must inturn hardcode
|
# FIX: The get_signal function has hardcoded 10, which we must inturn hardcode
|
||||||
oldtime = arrow.utcnow() - datetime.timedelta(minutes=11)
|
oldtime = arrow.utcnow() - datetime.timedelta(minutes=11)
|
||||||
ticks = DataFrame([{'buy': 1, 'date': oldtime}])
|
ticks = DataFrame([{'buy': 1, 'date': oldtime}])
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks))
|
mocker.patch.multiple(
|
||||||
assert (False, False) == get_signal('xyz', int(default_conf['ticker_interval']))
|
'freqtrade.analyze.Analyze',
|
||||||
|
analyze_ticker=MagicMock(
|
||||||
|
return_value=DataFrame(ticks)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert (False, False) == _ANALYZE.get_signal('xyz', int(default_conf['ticker_interval']))
|
||||||
assert tt.log_has('Too old dataframe for pair xyz',
|
assert tt.log_has('Too old dataframe for pair xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_get_signal_handles_exceptions(mocker):
|
def test_get_signal_handles_exceptions(mocker):
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker',
|
mocker.patch.multiple(
|
||||||
side_effect=Exception('invalid ticker history '))
|
'freqtrade.analyze.Analyze',
|
||||||
|
analyze_ticker=MagicMock(
|
||||||
|
side_effect=Exception('invalid ticker history ')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
assert get_signal('BTC-ETH', 5) == (False, False)
|
assert _ANALYZE.get_signal('BTC-ETH', 5) == (False, False)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
||||||
columns = ['close', 'high', 'low', 'open', 'date', 'volume']
|
columns = ['close', 'high', 'low', 'open', 'date', 'volume']
|
||||||
|
|
||||||
# Test file with BV data
|
# Test file with BV data
|
||||||
dataframe = parse_ticker_dataframe(ticker_history)
|
dataframe = Analyze.parse_ticker_dataframe(ticker_history)
|
||||||
assert dataframe.columns.tolist() == columns
|
assert dataframe.columns.tolist() == columns
|
||||||
|
|
||||||
# Test file without BV data
|
# Test file without BV data
|
||||||
dataframe = parse_ticker_dataframe(ticker_history_without_bv)
|
dataframe = Analyze.parse_ticker_dataframe(ticker_history_without_bv)
|
||||||
assert dataframe.columns.tolist() == columns
|
assert dataframe.columns.tolist() == columns
|
||||||
|
Loading…
Reference in New Issue
Block a user