merging develop into async. requirement.txt conflict resolved
This commit is contained in:
@@ -53,6 +53,7 @@ CONF_SCHEMA = {
|
||||
},
|
||||
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
||||
'dry_run': {'type': 'boolean'},
|
||||
'process_only_new_candles': {'type': 'boolean'},
|
||||
'minimal_roi': {
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
|
@@ -70,8 +70,15 @@ class IStrategy(ABC):
|
||||
# associated ticker interval
|
||||
ticker_interval: str
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles: bool = False
|
||||
|
||||
# Dict to determine if analysis is necessary
|
||||
_last_candle_seen_per_pair: Dict[str, datetime] = {}
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
self.config = config
|
||||
self._last_candle_seen_per_pair = {}
|
||||
|
||||
@abstractmethod
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
@@ -112,10 +119,30 @@ class IStrategy(ABC):
|
||||
add several TA indicators and buy signal to it
|
||||
:return DataFrame with ticker data and indicator data
|
||||
"""
|
||||
|
||||
dataframe = parse_ticker_dataframe(ticker_history)
|
||||
dataframe = self.advise_indicators(dataframe, metadata)
|
||||
dataframe = self.advise_buy(dataframe, metadata)
|
||||
dataframe = self.advise_sell(dataframe, metadata)
|
||||
|
||||
pair = str(metadata.get('pair'))
|
||||
|
||||
# Test if seen this pair and last candle before.
|
||||
# always run if process_only_new_candles is set to true
|
||||
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.
|
||||
logging.debug("TA Analysis Launched")
|
||||
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']
|
||||
else:
|
||||
logging.debug("Skippinig TA Analysis for already analyzed candle")
|
||||
dataframe['buy'] = 0
|
||||
dataframe['sell'] = 0
|
||||
|
||||
# Other Defs in strategy that want to be called every loop here
|
||||
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
|
||||
logging.debug("Loop Analysis Launched")
|
||||
|
||||
return dataframe
|
||||
|
||||
def get_signal(self, pair: str, interval: str,
|
||||
|
@@ -66,6 +66,15 @@ class StrategyResolver(object):
|
||||
else:
|
||||
config['ticker_interval'] = self.strategy.ticker_interval
|
||||
|
||||
if 'process_only_new_candles' in config:
|
||||
self.strategy.process_only_new_candles = config['process_only_new_candles']
|
||||
logger.info(
|
||||
"Override process_only_new_candles 'process_only_new_candles' "
|
||||
"with value in config file: %s.", config['process_only_new_candles']
|
||||
)
|
||||
else:
|
||||
config['process_only_new_candles'] = self.strategy.process_only_new_candles
|
||||
|
||||
# Sort and apply type conversions
|
||||
self.strategy.minimal_roi = OrderedDict(sorted(
|
||||
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
|
||||
|
@@ -128,3 +128,75 @@ def test_min_roi_reached(default_conf, fee) -> None:
|
||||
|
||||
assert not strategy.min_roi_reached(trade, -0.01, arrow.utcnow().shift(minutes=-1).datetime)
|
||||
assert strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-1).datetime)
|
||||
|
||||
|
||||
def test_analyze_ticker_default(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)
|
||||
sell_mock = MagicMock(side_effect=lambda x, meta: x)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.strategy.interface.IStrategy',
|
||||
advise_indicators=ind_mock,
|
||||
advise_buy=buy_mock,
|
||||
advise_sell=sell_mock,
|
||||
|
||||
)
|
||||
strategy = DefaultStrategy({})
|
||||
strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'})
|
||||
assert ind_mock.call_count == 1
|
||||
assert buy_mock.call_count == 1
|
||||
assert buy_mock.call_count == 1
|
||||
|
||||
assert log_has('TA Analysis Launched', caplog.record_tuples)
|
||||
assert not log_has('Skippinig TA Analysis for already analyzed candle',
|
||||
caplog.record_tuples)
|
||||
caplog.clear()
|
||||
|
||||
strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'})
|
||||
# No analysis happens as process_only_new_candles is true
|
||||
assert ind_mock.call_count == 2
|
||||
assert buy_mock.call_count == 2
|
||||
assert buy_mock.call_count == 2
|
||||
assert log_has('TA Analysis Launched', caplog.record_tuples)
|
||||
assert not log_has('Skippinig TA Analysis for already analyzed candle',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_analyze_ticker_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)
|
||||
sell_mock = MagicMock(side_effect=lambda x, meta: x)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.strategy.interface.IStrategy',
|
||||
advise_indicators=ind_mock,
|
||||
advise_buy=buy_mock,
|
||||
advise_sell=sell_mock,
|
||||
|
||||
)
|
||||
strategy = DefaultStrategy({})
|
||||
strategy.process_only_new_candles = True
|
||||
|
||||
ret = strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'})
|
||||
assert ind_mock.call_count == 1
|
||||
assert buy_mock.call_count == 1
|
||||
assert buy_mock.call_count == 1
|
||||
assert log_has('TA Analysis Launched', caplog.record_tuples)
|
||||
assert not log_has('Skippinig TA Analysis for already analyzed candle',
|
||||
caplog.record_tuples)
|
||||
caplog.clear()
|
||||
|
||||
ret = strategy.analyze_ticker(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
|
||||
assert buy_mock.call_count == 1
|
||||
# only skipped analyze adds buy and sell columns, otherwise it's all mocked
|
||||
assert 'buy' in ret
|
||||
assert 'sell' in ret
|
||||
assert ret['buy'].sum() == 0
|
||||
assert ret['sell'].sum() == 0
|
||||
assert not log_has('TA Analysis Launched', caplog.record_tuples)
|
||||
assert log_has('Skippinig TA Analysis for already analyzed candle',
|
||||
caplog.record_tuples)
|
||||
|
@@ -165,6 +165,23 @@ def test_strategy_override_ticker_interval(caplog):
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_strategy_override_process_only_new_candles(caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
config = {
|
||||
'strategy': 'DefaultStrategy',
|
||||
'process_only_new_candles': True
|
||||
}
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert resolver.strategy.process_only_new_candles
|
||||
assert ('freqtrade.strategy.resolver',
|
||||
logging.INFO,
|
||||
"Override process_only_new_candles 'process_only_new_candles' "
|
||||
"with value in config file: True."
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_deprecate_populate_indicators(result):
|
||||
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||
|
Reference in New Issue
Block a user