Merge pull request #2 from xmatthias/ta_on_candle_xmatt

Ta on candle xmatt
This commit is contained in:
creslin 2018-08-12 10:07:58 +00:00 committed by GitHub
commit bd61478367
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 126 additions and 34 deletions

View File

@ -22,8 +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.
| `ta_on_candle` | false | No | if set to true indicators are processed each new candle. If false each bot loop, 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. | `ta_on_candle` | 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_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).

View File

@ -19,22 +19,6 @@ from freqtrade.persistence import Trade
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CandleAnalyzed:
'''
Maintains dictionary of the last candle date a pair was processed with
This allows analyze_ticker to test if analysed the candle row in dataframe prior.
To not keep testing the same candle data, which is wasteful in CPU and time
'''
def __init__(self, last_seen={}):
self.last_seen = last_seen
def get_last_seen(self, pair):
return self.last_seen.get(pair)
def set_last_seen(self, pair, candle_date):
self.last_seen[pair] = candle_date
class SignalType(Enum): class SignalType(Enum):
""" """
Enum to distinguish between buy and sell signals Enum to distinguish between buy and sell signals
@ -86,9 +70,15 @@ class IStrategy(ABC):
# associated ticker interval # associated ticker interval
ticker_interval: str ticker_interval: str
# run "populate_indicators" only for new candle
ta_on_candle: bool = False
# Dict to determine if analysis is necessary
_candle_seen: Dict[str, datetime] = {}
def __init__(self, config: dict) -> None: def __init__(self, config: dict) -> None:
self.config = config self.config = config
self.candleSeen = CandleAnalyzed() self._candle_seen = {}
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@ -133,23 +123,25 @@ class IStrategy(ABC):
# Test if seen this pair and last candle before. # Test if seen this pair and last candle before.
dataframe = parse_ticker_dataframe(ticker_history) dataframe = parse_ticker_dataframe(ticker_history)
pair = metadata.get('pair') pair = str(metadata.get('pair'))
last_seen = self.candleSeen.get_last_seen(pair)
if last_seen != dataframe.iloc[-1]['date'] or self.config.get('ta_on_candle') is False: # always run if ta_on_candle is set to true
if (not self.ta_on_candle or
self._candle_seen.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.
logging.info("TA Analysis Launched") logging.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata) dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata) dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata) dataframe = self.advise_sell(dataframe, metadata)
self.candleSeen.set_last_seen(pair=pair, candle_date=dataframe.iloc[-1]['date']) self._candle_seen[pair] = dataframe.iloc[-1]['date']
else: else:
dataframe.loc['buy'] = 0 logging.debug("Skippinig TA Analysis for already analyzed candle")
dataframe.loc['sell'] = 0 dataframe['buy'] = 0
dataframe['sell'] = 0
# Other Defs in strategy that want to be called every loop here # Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata) # twitter_sell = self.watch_twitter_feed(dataframe, metadata)
logging.info("Loop Analysis Launched") logging.debug("Loop Analysis Launched")
return dataframe return dataframe

View File

@ -44,14 +44,14 @@ class StrategyResolver(object):
# Check if we need to override configuration # Check if we need to override configuration
if 'minimal_roi' in config: if 'minimal_roi' in config:
self.strategy.minimal_roi = config['minimal_roi'] self.strategy.minimal_roi = config['minimal_roi']
logger.info("Override strategy \'minimal_roi\' with value in config file.") logger.info("Override strategy 'minimal_roi' with value in config file.")
else: else:
config['minimal_roi'] = self.strategy.minimal_roi config['minimal_roi'] = self.strategy.minimal_roi
if 'stoploss' in config: if 'stoploss' in config:
self.strategy.stoploss = config['stoploss'] self.strategy.stoploss = config['stoploss']
logger.info( logger.info(
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss'] "Override strategy 'stoploss' with value in config file: %s.", config['stoploss']
) )
else: else:
config['stoploss'] = self.strategy.stoploss config['stoploss'] = self.strategy.stoploss
@ -59,12 +59,21 @@ class StrategyResolver(object):
if 'ticker_interval' in config: if 'ticker_interval' in config:
self.strategy.ticker_interval = config['ticker_interval'] self.strategy.ticker_interval = config['ticker_interval']
logger.info( logger.info(
"Override strategy \'ticker_interval\' with value in config file: %s.", "Override strategy 'ticker_interval' with value in config file: %s.",
config['ticker_interval'] config['ticker_interval']
) )
else: else:
config['ticker_interval'] = self.strategy.ticker_interval config['ticker_interval'] = self.strategy.ticker_interval
if 'ta_on_candle' in config:
self.strategy.ta_on_candle = config['ta_on_candle']
logger.info(
"Override ta_on_candle 'ta_on_candle' with value in config file: %s.",
config['ta_on_candle']
)
else:
config['ta_on_candle'] = self.strategy.ta_on_candle
# 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(),

View File

@ -105,3 +105,75 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
data = strategy.tickerdata_to_dataframe(tickerlist) data = strategy.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 99 # partial candle was removed assert len(data['UNITTEST/BTC']) == 99 # partial candle was removed
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 ta_on_candle 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.ta_on_candle = 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 ta_on_candle 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)

View File

@ -130,7 +130,7 @@ def test_strategy_override_minimal_roi(caplog):
assert resolver.strategy.minimal_roi[0] == 0.5 assert resolver.strategy.minimal_roi[0] == 0.5
assert ('freqtrade.strategy.resolver', assert ('freqtrade.strategy.resolver',
logging.INFO, logging.INFO,
'Override strategy \'minimal_roi\' with value in config file.' "Override strategy 'minimal_roi' with value in config file."
) in caplog.record_tuples ) in caplog.record_tuples
@ -145,7 +145,7 @@ def test_strategy_override_stoploss(caplog):
assert resolver.strategy.stoploss == -0.5 assert resolver.strategy.stoploss == -0.5
assert ('freqtrade.strategy.resolver', assert ('freqtrade.strategy.resolver',
logging.INFO, logging.INFO,
'Override strategy \'stoploss\' with value in config file: -0.5.' "Override strategy 'stoploss' with value in config file: -0.5."
) in caplog.record_tuples ) in caplog.record_tuples
@ -161,7 +161,23 @@ def test_strategy_override_ticker_interval(caplog):
assert resolver.strategy.ticker_interval == 60 assert resolver.strategy.ticker_interval == 60
assert ('freqtrade.strategy.resolver', assert ('freqtrade.strategy.resolver',
logging.INFO, logging.INFO,
'Override strategy \'ticker_interval\' with value in config file: 60.' "Override strategy 'ticker_interval' with value in config file: 60."
) in caplog.record_tuples
def test_strategy_override_ta_on_candle(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
'ta_on_candle': True
}
resolver = StrategyResolver(config)
assert resolver.strategy.ta_on_candle
assert ('freqtrade.strategy.resolver',
logging.INFO,
"Override ta_on_candle 'ta_on_candle' with value in config file: True."
) in caplog.record_tuples ) in caplog.record_tuples

View File

@ -14,7 +14,7 @@ def load_dataframe_pair(pairs, strategy):
assert isinstance(pairs[0], str) assert isinstance(pairs[0], str)
dataframe = ld[pairs[0]] dataframe = ld[pairs[0]]
dataframe = strategy.analyze_ticker(dataframe, pairs[0]) dataframe = strategy.analyze_ticker(dataframe, {'pair': pairs[0]})
return dataframe return dataframe

View File

@ -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