Merge pull request #1119 from creslinux/ta_on_candle
ta_on_candle (not loop, with optional flag in config.json) Resubmitting - because GIT.
This commit is contained in:
commit
6b74fb0893
@ -22,7 +22,8 @@ The table below will list all configuration parameters.
|
|||||||
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to 'unlimited' to allow the bot to use all avaliable balance.
|
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to 'unlimited' to allow the bot to use all avaliable balance.
|
||||||
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes
|
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes
|
||||||
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
||||||
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
||||||
|
| `process_only_new_candles` | false | No | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. Can be set either in Configuration or in the strategy.
|
||||||
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
|
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
|
||||||
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
||||||
| `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
|
| `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
|
||||||
|
@ -53,6 +53,7 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
||||||
'dry_run': {'type': 'boolean'},
|
'dry_run': {'type': 'boolean'},
|
||||||
|
'process_only_new_candles': {'type': 'boolean'},
|
||||||
'minimal_roi': {
|
'minimal_roi': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'patternProperties': {
|
'patternProperties': {
|
||||||
|
@ -70,8 +70,15 @@ class IStrategy(ABC):
|
|||||||
# associated ticker interval
|
# associated ticker interval
|
||||||
ticker_interval: str
|
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:
|
def __init__(self, config: dict) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self._last_candle_seen_per_pair = {}
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
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
|
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 = parse_ticker_dataframe(ticker_history)
|
||||||
dataframe = self.advise_indicators(dataframe, metadata)
|
|
||||||
dataframe = self.advise_buy(dataframe, metadata)
|
pair = str(metadata.get('pair'))
|
||||||
dataframe = self.advise_sell(dataframe, metadata)
|
|
||||||
|
# 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
|
return dataframe
|
||||||
|
|
||||||
def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
|
def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
|
||||||
|
@ -66,6 +66,15 @@ class StrategyResolver(object):
|
|||||||
else:
|
else:
|
||||||
config['ticker_interval'] = self.strategy.ticker_interval
|
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
|
# Sort and apply type conversions
|
||||||
self.strategy.minimal_roi = OrderedDict(sorted(
|
self.strategy.minimal_roi = OrderedDict(sorted(
|
||||||
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
|
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
|
||||||
|
@ -129,3 +129,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 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)
|
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
|
) 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):
|
def test_deprecate_populate_indicators(result):
|
||||||
default_location = path.join(path.dirname(path.realpath(__file__)))
|
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||||
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||||
|
@ -45,6 +45,9 @@ class TestStrategy(IStrategy):
|
|||||||
# Optimal ticker interval for the strategy
|
# Optimal ticker interval for the strategy
|
||||||
ticker_interval = '5m'
|
ticker_interval = '5m'
|
||||||
|
|
||||||
|
# run "populate_indicators" only for new candle
|
||||||
|
ta_on_candle = False
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Adds several different TA indicators to the given DataFrame
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
Loading…
Reference in New Issue
Block a user