Merge branch 'develop' into rate_caching

This commit is contained in:
hroff-1902 2020-02-26 04:04:20 +03:00 committed by GitHub
commit 5a900858d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 268 additions and 151 deletions

View File

@ -71,6 +71,8 @@ class JsonDataHandler(IDataHandler):
return DataFrame(columns=self._columns) return DataFrame(columns=self._columns)
pairdata = read_json(filename, orient='values') pairdata = read_json(filename, orient='values')
pairdata.columns = self._columns pairdata.columns = self._columns
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'})
pairdata['date'] = to_datetime(pairdata['date'], pairdata['date'] = to_datetime(pairdata['date'],
unit='ms', unit='ms',
utc=True, utc=True,

View File

@ -6,7 +6,6 @@ import logging
import traceback import traceback
from datetime import datetime from datetime import datetime
from math import isclose from math import isclose
from os import getpid
from threading import Lock from threading import Lock
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
@ -53,13 +52,9 @@ class FreqtradeBot:
# Init objects # Init objects
self.config = config self.config = config
self._heartbeat_msg = 0
self._sell_rate_cache = TTLCache(maxsize=100, ttl=5) self._sell_rate_cache = TTLCache(maxsize=100, ttl=5)
self._buy_rate_cache = TTLCache(maxsize=100, ttl=5) self._buy_rate_cache = TTLCache(maxsize=100, ttl=5)
self.heartbeat_interval = self.config.get('internals', {}).get('heartbeat_interval', 60)
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config) self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
# Check config consistency here since strategies can set certain options # Check config consistency here since strategies can set certain options
@ -163,11 +158,6 @@ class FreqtradeBot:
self.check_handle_timedout() self.check_handle_timedout()
Trade.session.flush() Trade.session.flush()
if (self.heartbeat_interval
and (arrow.utcnow().timestamp - self._heartbeat_msg > self.heartbeat_interval)):
logger.info(f"Bot heartbeat. PID={getpid()}")
self._heartbeat_msg = arrow.utcnow().timestamp
def _refresh_whitelist(self, trades: List[Trade] = []) -> List[str]: def _refresh_whitelist(self, trades: List[Trade] = []) -> List[str]:
""" """
Refresh whitelist from pairlist or edge and extend it with trades. Refresh whitelist from pairlist or edge and extend it with trades.

View File

@ -124,24 +124,70 @@ class SampleStrategy(IStrategy):
# Momentum Indicators # Momentum Indicators
# ------------------------------------ # ------------------------------------
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# ADX # ADX
dataframe['adx'] = ta.ADX(dataframe) dataframe['adx'] = ta.ADX(dataframe)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Aroon, Aroon Oscillator # # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe) # aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup'] # dataframe['aroonup'] = aroon['aroonup']
# dataframe['aroondown'] = aroon['aroondown'] # dataframe['aroondown'] = aroon['aroondown']
# dataframe['aroonosc'] = ta.AROONOSC(dataframe) # dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# # Awesome oscillator # # Awesome Oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) # dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# # Commodity Channel Index: values Oversold:<-100, Overbought:>100 # # Keltner Channel
# keltner = qtpylib.keltner_channel(dataframe)
# dataframe["kc_upperband"] = keltner["upper"]
# dataframe["kc_lowerband"] = keltner["lower"]
# dataframe["kc_middleband"] = keltner["mid"]
# dataframe["kc_percent"] = (
# (dataframe["close"] - dataframe["kc_lowerband"]) /
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"])
# )
# dataframe["kc_width"] = (
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"]) / dataframe["kc_middleband"]
# )
# # Ultimate Oscillator
# dataframe['uo'] = ta.ULTOSC(dataframe)
# # Commodity Channel Index: values [Oversold:-100, Overbought:100]
# dataframe['cci'] = ta.CCI(dataframe) # dataframe['cci'] = ta.CCI(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stochastic Slow
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# # Stochastic RSI
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# MACD # MACD
macd = ta.MACD(dataframe) macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd'] dataframe['macd'] = macd['macd']
@ -151,60 +197,58 @@ class SampleStrategy(IStrategy):
# MFI # MFI
dataframe['mfi'] = ta.MFI(dataframe) dataframe['mfi'] = ta.MFI(dataframe)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # ROC # # ROC
# dataframe['roc'] = ta.ROC(dataframe) # dataframe['roc'] = ta.ROC(dataframe)
# # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stoch
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# # Stoch RSI
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# Overlap Studies # Overlap Studies
# ------------------------------------ # ------------------------------------
# Bollinger bands # Bollinger Bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper'] dataframe['bb_upperband'] = bollinger['upper']
dataframe["bb_percent"] = (
(dataframe["close"] - dataframe["bb_lowerband"]) /
(dataframe["bb_upperband"] - dataframe["bb_lowerband"])
)
dataframe["bb_width"] = (
(dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"]
)
# Bollinger Bands - Weighted (EMA based instead of SMA)
# weighted_bollinger = qtpylib.weighted_bollinger_bands(
# qtpylib.typical_price(dataframe), window=20, stds=2
# )
# dataframe["wbb_upperband"] = weighted_bollinger["upper"]
# dataframe["wbb_lowerband"] = weighted_bollinger["lower"]
# dataframe["wbb_middleband"] = weighted_bollinger["mid"]
# dataframe["wbb_percent"] = (
# (dataframe["close"] - dataframe["wbb_lowerband"]) /
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"])
# )
# dataframe["wbb_width"] = (
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"]) /
# dataframe["wbb_middleband"]
# )
# # EMA - Exponential Moving Average # # EMA - Exponential Moving Average
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) # dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) # dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) # dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21)
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) # dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) # dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average # # SMA - Simple Moving Average
# dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) # dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3)
# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5)
# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21)
# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100)
# SAR Parabol # Parabolic SAR
dataframe['sar'] = ta.SAR(dataframe) dataframe['sar'] = ta.SAR(dataframe)
# TEMA - Triple Exponential Moving Average # TEMA - Triple Exponential Moving Average
@ -264,7 +308,7 @@ class SampleStrategy(IStrategy):
# # Chart type # # Chart type
# # ------------------------------------ # # ------------------------------------
# # Heikinashi stategy # # Heikin Ashi Strategy
# heikinashi = qtpylib.heikinashi(dataframe) # heikinashi = qtpylib.heikinashi(dataframe)
# dataframe['ha_open'] = heikinashi['open'] # dataframe['ha_open'] = heikinashi['open']
# dataframe['ha_close'] = heikinashi['close'] # dataframe['ha_close'] = heikinashi['close']

View File

@ -2,24 +2,70 @@
# Momentum Indicators # Momentum Indicators
# ------------------------------------ # ------------------------------------
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# ADX # ADX
dataframe['adx'] = ta.ADX(dataframe) dataframe['adx'] = ta.ADX(dataframe)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Aroon, Aroon Oscillator # # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe) # aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup'] # dataframe['aroonup'] = aroon['aroonup']
# dataframe['aroondown'] = aroon['aroondown'] # dataframe['aroondown'] = aroon['aroondown']
# dataframe['aroonosc'] = ta.AROONOSC(dataframe) # dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# # Awesome oscillator # # Awesome Oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) # dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# # Commodity Channel Index: values Oversold:<-100, Overbought:>100 # # Keltner Channel
# keltner = qtpylib.keltner_channel(dataframe)
# dataframe["kc_upperband"] = keltner["upper"]
# dataframe["kc_lowerband"] = keltner["lower"]
# dataframe["kc_middleband"] = keltner["mid"]
# dataframe["kc_percent"] = (
# (dataframe["close"] - dataframe["kc_lowerband"]) /
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"])
# )
# dataframe["kc_width"] = (
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"]) / dataframe["kc_middleband"]
# )
# # Ultimate Oscillator
# dataframe['uo'] = ta.ULTOSC(dataframe)
# # Commodity Channel Index: values [Oversold:-100, Overbought:100]
# dataframe['cci'] = ta.CCI(dataframe) # dataframe['cci'] = ta.CCI(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stochastic Slow
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# # Stochastic RSI
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# MACD # MACD
macd = ta.MACD(dataframe) macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd'] dataframe['macd'] = macd['macd']
@ -29,60 +75,57 @@ dataframe['macdhist'] = macd['macdhist']
# MFI # MFI
dataframe['mfi'] = ta.MFI(dataframe) dataframe['mfi'] = ta.MFI(dataframe)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # ROC # # ROC
# dataframe['roc'] = ta.ROC(dataframe) # dataframe['roc'] = ta.ROC(dataframe)
# # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stoch
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# # Stoch RSI
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# Overlap Studies # Overlap Studies
# ------------------------------------ # ------------------------------------
# Bollinger bands # Bollinger Bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper'] dataframe['bb_upperband'] = bollinger['upper']
dataframe["bb_percent"] = (
(dataframe["close"] - dataframe["bb_lowerband"]) /
(dataframe["bb_upperband"] - dataframe["bb_lowerband"])
)
dataframe["bb_width"] = (
(dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"]
)
# Bollinger Bands - Weighted (EMA based instead of SMA)
# weighted_bollinger = qtpylib.weighted_bollinger_bands(
# qtpylib.typical_price(dataframe), window=20, stds=2
# )
# dataframe["wbb_upperband"] = weighted_bollinger["upper"]
# dataframe["wbb_lowerband"] = weighted_bollinger["lower"]
# dataframe["wbb_middleband"] = weighted_bollinger["mid"]
# dataframe["wbb_percent"] = (
# (dataframe["close"] - dataframe["wbb_lowerband"]) /
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"])
# )
# dataframe["wbb_width"] = (
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"]) / dataframe["wbb_middleband"]
# )
# # EMA - Exponential Moving Average # # EMA - Exponential Moving Average
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) # dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) # dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) # dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21)
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) # dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) # dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average # # SMA - Simple Moving Average
# dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) # dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3)
# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5)
# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21)
# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100)
# SAR Parabol # Parabolic SAR
dataframe['sar'] = ta.SAR(dataframe) dataframe['sar'] = ta.SAR(dataframe)
# TEMA - Triple Exponential Moving Average # TEMA - Triple Exponential Moving Average
@ -142,7 +185,7 @@ dataframe['htleadsine'] = hilbert['leadsine']
# # Chart type # # Chart type
# # ------------------------------------ # # ------------------------------------
# # Heikinashi stategy # # Heikin Ashi Strategy
# heikinashi = qtpylib.heikinashi(dataframe) # heikinashi = qtpylib.heikinashi(dataframe)
# dataframe['ha_open'] = heikinashi['open'] # dataframe['ha_open'] = heikinashi['open']
# dataframe['ha_close'] = heikinashi['close'] # dataframe['ha_close'] = heikinashi['close']

View File

@ -4,6 +4,7 @@ Main Freqtrade worker class.
import logging import logging
import time import time
import traceback import traceback
from os import getpid
from typing import Any, Callable, Dict, Optional from typing import Any, Callable, Dict, Optional
import sdnotify import sdnotify
@ -26,12 +27,15 @@ class Worker:
""" """
Init all variables and objects the bot needs to work Init all variables and objects the bot needs to work
""" """
logger.info('Starting worker %s', __version__) logger.info(f"Starting worker {__version__}")
self._args = args self._args = args
self._config = config self._config = config
self._init(False) self._init(False)
self.last_throttle_start_time: float = 0
self._heartbeat_msg: float = 0
# Tell systemd that we completed initialization phase # Tell systemd that we completed initialization phase
if self._sd_notify: if self._sd_notify:
logger.debug("sd_notify: READY=1") logger.debug("sd_notify: READY=1")
@ -48,10 +52,10 @@ class Worker:
# Init the instance of the bot # Init the instance of the bot
self.freqtrade = FreqtradeBot(self._config) self.freqtrade = FreqtradeBot(self._config)
self._throttle_secs = self._config.get('internals', {}).get( internals_config = self._config.get('internals', {})
'process_throttle_secs', self._throttle_secs = internals_config.get('process_throttle_secs',
constants.PROCESS_THROTTLE_SECS constants.PROCESS_THROTTLE_SECS)
) self._heartbeat_interval = internals_config.get('heartbeat_interval', 60)
self._sd_notify = sdnotify.SystemdNotifier() if \ self._sd_notify = sdnotify.SystemdNotifier() if \
self._config.get('internals', {}).get('sd_notify', False) else None self._config.get('internals', {}).get('sd_notify', False) else None
@ -63,31 +67,33 @@ class Worker:
if state == State.RELOAD_CONF: if state == State.RELOAD_CONF:
self._reconfigure() self._reconfigure()
def _worker(self, old_state: Optional[State], throttle_secs: Optional[float] = None) -> State: def _worker(self, old_state: Optional[State]) -> State:
""" """
Trading routine that must be run at each loop The main routine that runs each throttling iteration and handles the states.
:param old_state: the previous service state from the previous call :param old_state: the previous service state from the previous call
:return: current service state :return: current service state
""" """
state = self.freqtrade.state state = self.freqtrade.state
if throttle_secs is None:
throttle_secs = self._throttle_secs
# Log state transition # Log state transition
if state != old_state: if state != old_state:
self.freqtrade.notify_status(f'{state.name.lower()}') self.freqtrade.notify_status(f'{state.name.lower()}')
logger.info('Changing state to: %s', state.name) logger.info(f"Changing state to: {state.name}")
if state == State.RUNNING: if state == State.RUNNING:
self.freqtrade.startup() self.freqtrade.startup()
# Reset heartbeat timestamp to log the heartbeat message at
# first throttling iteration when the state changes
self._heartbeat_msg = 0
if state == State.STOPPED: if state == State.STOPPED:
# Ping systemd watchdog before sleeping in the stopped state # Ping systemd watchdog before sleeping in the stopped state
if self._sd_notify: if self._sd_notify:
logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.") logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.")
self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.") self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.")
time.sleep(throttle_secs) self._throttle(func=self._process_stopped, throttle_secs=self._throttle_secs)
elif state == State.RUNNING: elif state == State.RUNNING:
# Ping systemd watchdog before throttling # Ping systemd watchdog before throttling
@ -95,28 +101,40 @@ class Worker:
logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.") logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.")
self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.") self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.")
self._throttle(func=self._process, min_secs=throttle_secs) self._throttle(func=self._process_running, throttle_secs=self._throttle_secs)
if self._heartbeat_interval:
now = time.time()
if (now - self._heartbeat_msg) > self._heartbeat_interval:
logger.info(f"Bot heartbeat. PID={getpid()}, "
f"version='{__version__}', state='{state.name}'")
self._heartbeat_msg = now
return state return state
def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any: def _throttle(self, func: Callable[..., Any], throttle_secs: float, *args, **kwargs) -> Any:
""" """
Throttles the given callable that it Throttles the given callable that it
takes at least `min_secs` to finish execution. takes at least `min_secs` to finish execution.
:param func: Any callable :param func: Any callable
:param min_secs: minimum execution time in seconds :param throttle_secs: throttling interation execution time limit in seconds
:return: Any :return: Any (result of execution of func)
""" """
start = time.time() self.last_throttle_start_time = time.time()
logger.debug("========================================")
result = func(*args, **kwargs) result = func(*args, **kwargs)
end = time.time() time_passed = time.time() - self.last_throttle_start_time
duration = max(min_secs - (end - start), 0.0) sleep_duration = max(throttle_secs - time_passed, 0.0)
logger.debug('Throttling %s for %.2f seconds', func.__name__, duration) logger.debug(f"Throttling with '{func.__name__}()': sleep for {sleep_duration:.2f} s, "
time.sleep(duration) f"last iteration took {time_passed:.2f} s.")
time.sleep(sleep_duration)
return result return result
def _process(self) -> None: def _process_stopped(self) -> None:
logger.debug("========================================") # Maybe do here something in the future...
pass
def _process_running(self) -> None:
try: try:
self.freqtrade.process() self.freqtrade.process()
except TemporaryError as error: except TemporaryError as error:

View File

@ -1,11 +1,11 @@
# requirements without requirements installable via conda # requirements without requirements installable via conda
# mainly used for Raspberry pi installs # mainly used for Raspberry pi installs
ccxt==1.22.61 ccxt==1.22.95
SQLAlchemy==1.3.13 SQLAlchemy==1.3.13
python-telegram-bot==12.4.2 python-telegram-bot==12.4.2
arrow==0.15.5 arrow==0.15.5
cachetools==4.0.0 cachetools==4.0.0
requests==2.22.0 requests==2.23.0
urllib3==1.25.8 urllib3==1.25.8
wrapt==1.12.0 wrapt==1.12.0
jsonschema==3.2.0 jsonschema==3.2.0

View File

@ -4,6 +4,6 @@
# Required for hyperopt # Required for hyperopt
scipy==1.4.1 scipy==1.4.1
scikit-learn==0.22.1 scikit-learn==0.22.1
scikit-optimize==0.7.2 scikit-optimize==0.7.4
filelock==3.0.12 filelock==3.0.12
joblib==0.14.1 joblib==0.14.1

View File

@ -1,5 +1,5 @@
# Include all requirements to run the bot. # Include all requirements to run the bot.
-r requirements.txt -r requirements.txt
plotly==4.5.0 plotly==4.5.1

View File

@ -782,7 +782,7 @@ def test_process_exchange_failures(default_conf, ticker, mocker) -> None:
worker = Worker(args=None, config=default_conf) worker = Worker(args=None, config=default_conf)
patch_get_signal(worker.freqtrade) patch_get_signal(worker.freqtrade)
worker._process() worker._process_running()
assert sleep_mock.has_calls() assert sleep_mock.has_calls()
@ -799,7 +799,7 @@ def test_process_operational_exception(default_conf, ticker, mocker) -> None:
assert worker.freqtrade.state == State.RUNNING assert worker.freqtrade.state == State.RUNNING
worker._process() worker._process_running()
assert worker.freqtrade.state == State.STOPPED assert worker.freqtrade.state == State.STOPPED
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status'] assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status']
@ -3684,30 +3684,6 @@ def test_startup_trade_reinit(default_conf, edge_conf, mocker):
assert reinit_mock.call_count == 0 assert reinit_mock.call_count == 0
def test_process_i_am_alive(default_conf, mocker, caplog):
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
ftbot = get_patched_freqtradebot(mocker, default_conf)
message = r"Bot heartbeat\. PID=.*"
ftbot.process()
assert log_has_re(message, caplog)
assert ftbot._heartbeat_msg != 0
caplog.clear()
# Message is not shown before interval is up
ftbot.process()
assert not log_has_re(message, caplog)
caplog.clear()
# Set clock - 70 seconds
ftbot._heartbeat_msg -= 70
ftbot.process()
assert log_has_re(message, caplog)
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order, caplog): def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order, caplog):
default_conf['dry_run'] = True default_conf['dry_run'] = True

View File

@ -5,7 +5,7 @@ from unittest.mock import MagicMock, PropertyMock
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.state import State from freqtrade.state import State
from freqtrade.worker import Worker from freqtrade.worker import Worker
from tests.conftest import get_patched_worker, log_has from tests.conftest import get_patched_worker, log_has, log_has_re
def test_worker_state(mocker, default_conf, markets) -> None: def test_worker_state(mocker, default_conf, markets) -> None:
@ -38,15 +38,13 @@ def test_worker_running(mocker, default_conf, caplog) -> None:
def test_worker_stopped(mocker, default_conf, caplog) -> None: def test_worker_stopped(mocker, default_conf, caplog) -> None:
mock_throttle = MagicMock() mock_throttle = MagicMock()
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle) mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
mock_sleep = mocker.patch('time.sleep', return_value=None)
worker = get_patched_worker(mocker, default_conf) worker = get_patched_worker(mocker, default_conf)
worker.freqtrade.state = State.STOPPED worker.freqtrade.state = State.STOPPED
state = worker._worker(old_state=State.RUNNING) state = worker._worker(old_state=State.RUNNING)
assert state is State.STOPPED assert state is State.STOPPED
assert log_has('Changing state to: STOPPED', caplog) assert log_has('Changing state to: STOPPED', caplog)
assert mock_throttle.call_count == 0 assert mock_throttle.call_count == 1
assert mock_sleep.call_count == 1
def test_throttle(mocker, default_conf, caplog) -> None: def test_throttle(mocker, default_conf, caplog) -> None:
@ -57,14 +55,14 @@ def test_throttle(mocker, default_conf, caplog) -> None:
worker = get_patched_worker(mocker, default_conf) worker = get_patched_worker(mocker, default_conf)
start = time.time() start = time.time()
result = worker._throttle(throttled_func, min_secs=0.1) result = worker._throttle(throttled_func, throttle_secs=0.1)
end = time.time() end = time.time()
assert result == 42 assert result == 42
assert end - start > 0.1 assert end - start > 0.1
assert log_has('Throttling throttled_func for 0.10 seconds', caplog) assert log_has_re(r"Throttling with 'throttled_func\(\)': sleep for 0\.10 s.*", caplog)
result = worker._throttle(throttled_func, min_secs=-1) result = worker._throttle(throttled_func, throttle_secs=-1)
assert result == 42 assert result == 42
@ -74,8 +72,54 @@ def test_throttle_with_assets(mocker, default_conf) -> None:
worker = get_patched_worker(mocker, default_conf) worker = get_patched_worker(mocker, default_conf)
result = worker._throttle(throttled_func, min_secs=0.1, nb_assets=666) result = worker._throttle(throttled_func, throttle_secs=0.1, nb_assets=666)
assert result == 666 assert result == 666
result = worker._throttle(throttled_func, min_secs=0.1) result = worker._throttle(throttled_func, throttle_secs=0.1)
assert result == -1 assert result == -1
def test_worker_heartbeat_running(default_conf, mocker, caplog):
message = r"Bot heartbeat\. PID=.*state='RUNNING'"
mock_throttle = MagicMock()
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
worker = get_patched_worker(mocker, default_conf)
worker.freqtrade.state = State.RUNNING
worker._worker(old_state=State.STOPPED)
assert log_has_re(message, caplog)
caplog.clear()
# Message is not shown before interval is up
worker._worker(old_state=State.RUNNING)
assert not log_has_re(message, caplog)
caplog.clear()
# Set clock - 70 seconds
worker._heartbeat_msg -= 70
worker._worker(old_state=State.RUNNING)
assert log_has_re(message, caplog)
def test_worker_heartbeat_stopped(default_conf, mocker, caplog):
message = r"Bot heartbeat\. PID=.*state='STOPPED'"
mock_throttle = MagicMock()
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
worker = get_patched_worker(mocker, default_conf)
worker.freqtrade.state = State.STOPPED
worker._worker(old_state=State.RUNNING)
assert log_has_re(message, caplog)
caplog.clear()
# Message is not shown before interval is up
worker._worker(old_state=State.STOPPED)
assert not log_has_re(message, caplog)
caplog.clear()
# Set clock - 70 seconds
worker._heartbeat_msg -= 70
worker._worker(old_state=State.STOPPED)
assert log_has_re(message, caplog)