From bca116acf6b247c45c6ceed468ae1e3aa7b53eb9 Mon Sep 17 00:00:00 2001 From: raftersvk Date: Mon, 10 Jan 2022 22:50:17 +0100 Subject: [PATCH 01/17] separation of signals into triggers and guards --- .../MasterMoniGoManiHyperStrategy.py | 198 ++++++++++-------- .../strategies/MoniGoManiHyperStrategy.py | 80 +++---- 2 files changed, 157 insertions(+), 121 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index bc9ad5302..5f2ac1bc2 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -202,8 +202,9 @@ class MasterMoniGoManiHyperStrategy(IStrategy, ABC): total_triggers_possible = {} for trend in mgm_trends: for space in ['buy', 'sell']: - total_signals_possible[f'{space}_{trend}'] = 0 - total_triggers_possible[f'{space}_{trend}'] = 0 + for signal_type in ['triggers', 'guards']: + total_signals_possible[f'{space}_{signal_type}_{trend}'] = 0 + total_triggers_possible[f'{space}_{signal_type}_{trend}'] = 0 class HyperOpt: @staticmethod @@ -399,12 +400,16 @@ def populate_frequi_plots(weighted_signal_plots: dict) -> dict: 'sell': {'color': '#d19e28'} }, 'Total Buy + Sell Signal Strength': { - 'total_buy_signal_strength': {'color': '#09d528'}, - 'total_sell_signal_strength': {'color': '#d19e28'} + 'total_buy_triggers_strength': {'color': '#09d528'}, + 'total_buy_guards_strength': {'color': '#6f1a7b'}, + 'total_sell_triggers_strength': {'color': '#d19e28'}, + 'total_sell_guards_strength': {'color': '#7fba3c'}, }, 'Weighted Buy + Sell Signals Firing': { - 'buy_signals_triggered': {'color': '#09d528'}, - 'sell_signals_triggered': {'color': '#d19e28'} + 'buy_triggers_triggered': {'color': '#09d528'}, + 'buy_guards_triggered': {'color': '#6f1a7b'}, + 'sell_triggers_triggered': {'color': '#d19e28'}, + 'sell_guards_triggered': {'color': '#7fba3c'}, } } } @@ -1009,36 +1014,43 @@ def _generate_weight_condition(self, dataframe: DataFrame, space: str) -> DataFr :return: Lambda conditions """ - number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_signals')) + number_of_weighted_triggers = int(getattr(self, f'number_of_weighted_{space}_triggers')) + number_of_weighted_guards = int(getattr(self, f'number_of_weighted_{space}_guards')) conditions_weight = [] # If TimeFrame-Zooming => Only use 'informative_timeframe' data for trend in self.mgm_trends: if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: - corrected_totals = self.get_corrected_totals_needed( - space=space, trend=trend, number_of_weighted_signals=number_of_weighted_signals) + corrected_triggers_totals = self.get_corrected_totals_needed( + space=space, trend=trend, signal_type='triggers', number_of_weighted_signals=number_of_weighted_triggers) + corrected_guards_totals = self.get_corrected_totals_needed( + space=space, trend=trend, signal_type='guards', number_of_weighted_signals=number_of_weighted_guards) conditions_weight.append( (dataframe['trend'] == trend) & - (dataframe[f'total_{space}_signal_strength'] >= corrected_totals['signal_needed']) & - (dataframe[f'{space}_signals_triggered'] >= corrected_totals['triggers_needed'])) + (dataframe[f'total_{space}_triggers_strength'] >= corrected_triggers_totals['signal_needed']) & + (dataframe[f'{space}_triggers_triggered'] >= corrected_triggers_totals['triggers_needed']) & + (dataframe[f'total_{space}_guards_strength'] >= corrected_guards_totals['signal_needed']) & + (dataframe[f'{space}_guards_triggered'] >= corrected_guards_totals['triggers_needed']) + ) return reduce(lambda x, y: x | y, conditions_weight) - def get_corrected_totals_needed(self, space: str, trend: str, number_of_weighted_signals: int) -> dict: + def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, number_of_weighted_signals: int) -> dict: """ Fetches a dictionary containing - Total Signal Weight Needed - Total Signal Triggers Needed - for a given space and trend, these are the results of weak/strong overrides & dividing by precision + for a given space / signal type / trend, these are the results of weak/strong overrides & dividing by precision :param space: (str) The 'buy' or 'sell' space :param trend: (str) 'upwards', 'sideways', 'downwards' + :param signal_type: (str) 'triggers' or 'guards' :param number_of_weighted_signals: Number of signals for the given space :return: (dict) {'signal_needed', 'triggers_needed'} """ - total_signal_needed = getattr(self, f'{space}__{trend}_trend_total_signal_needed') - total_triggers_needed = getattr(self, f'{space}__{trend}_trend_signal_triggers_needed') + total_signal_needed = getattr(self, f'{space}__{signal_type}_{trend}_trend_total_signal_needed') + total_triggers_needed = getattr(self, f'{space}__{signal_type}_{trend}_trend_signal_triggers_needed') corrected_total_signal_needed = self.apply_weak_strong_overrides( parameter_value=total_signal_needed.value, @@ -1077,13 +1089,14 @@ def apply_weak_strong_overrides(self, parameter_value, else: return parameter_value - def _add_signal(self, signal_name: str, signal_min_value: int, signal_max_value: int, signal_threshold: int, + def _add_signal(self, signal_name: str, signal_type: str, signal_min_value: int, signal_max_value: int, signal_threshold: int, space: str, dataframe: DataFrame, condition: Any): """ # Weighted Variables # ------------------ Calculates the weight of each signal, also adds the signal to the dataframe if debugging is enabled. :param signal_name: Name of the signal to be added + :param signal_type: Type of the signal to be added ('trigger' / 'guard') :param signal_min_value: Minimal search space value to use during the 1st HyperOpt Run and override value for weak signals :param signal_max_value: Maximum search space value to use during @@ -1101,14 +1114,14 @@ def _add_signal(self, signal_name: str, signal_min_value: int, signal_max_value: (self.informative_timeframe != self.backtest_timeframe)) for trend in self.mgm_trends: if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: - parameter_name = f'{space}_{trend}_trend_{signal_name}_weight' + parameter_name = f'{space}_{signal_type}_{trend}_trend_{signal_name}_weight' signal_weight = getattr(self, parameter_name) - + # Apply signal overrides to weak and strong signals where needed signal_weight_value = self.apply_weak_strong_overrides( signal_weight.value, signal_min_value, signal_max_value, signal_threshold) - rolling_needed = getattr(self, f'{space}__{trend}_trend_total_signal_needed_candles_lookback_window') + rolling_needed = getattr(self, f'{space}__{signal_type}_{trend}_trend_total_signal_needed_candles_lookback_window') rolling_needed_value = (rolling_needed.value * self.timeframe_multiplier if has_multiplier else rolling_needed.value) @@ -1122,18 +1135,18 @@ def _add_signal(self, signal_name: str, signal_min_value: int, signal_max_value: # If the weighted signal triggered => Add the weight to the totals needed in the dataframe dataframe.loc[((dataframe['trend'] == trend) & (condition.rolling(rolling_needed_value).sum() > 0)), - f'total_{space}_signal_strength'] += signal_weight_value / self.precision + f'total_{space}_{signal_type}_strength'] += signal_weight_value / self.precision # If the weighted signal is bigger then 0 and triggered => Add up the amount of signals that triggered if signal_weight_value > 0: dataframe.loc[((dataframe['trend'] == trend) & (condition.rolling(rolling_needed_value).sum() > 0)), - f'{space}_signals_triggered'] += 1 + f'{space}_{signal_type}_triggered'] += 1 # Add found weights to comparison values to check if total signals utilized by HyperOpt are possible - self.total_signals_possible[f'{space}_{trend}'] += signal_weight_value + self.total_signals_possible[f'{space}_{signal_type}_{trend}'] += signal_weight_value # Add a signal trigger if it is possible to compare if total triggers needed by HyperOpt are possible if signal_weight_value > 0: - self.total_triggers_possible[f'{space}_{trend}'] += 1 + self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] += 1 # Override Signals: When configured sell/buy signals can be completely turned off for each kind of trend else: @@ -1142,18 +1155,19 @@ def _add_signal(self, signal_name: str, signal_min_value: int, signal_max_value: return dataframe @classmethod - def register_signal_attr(cls, base_cls, name: str, space: str = 'buy') -> None: + def register_signal_attr(cls, base_cls, name: str, signal_type: str, space: str = 'buy') -> None: """ Defines the optimizable parameters of each signal :param base_cls: The inheritor class of the MGM where the attributes will be added - :param space: buy or sell :param name: Signal name + :param signal_type: Signal type (trigger / guard) + :param space: buy or sell :return: None """ # Generating the attributes for each signal trend for trend in cls.mgm_trends: - parameter_name = f'{trend}_trend_{name}_weight' + parameter_name = f'{signal_type}_{trend}_trend_{name}_weight' if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: cls._init_vars(base_cls, space=space, parameter_name=parameter_name, parameter_min_value=cls.min_weighted_signal_value, @@ -1285,29 +1299,30 @@ def init_util_params(cls, base_cls): # Generate the utility attributes for the logic of the weighted_signal_spaces for trend in cls.mgm_trends: for space in ['buy', 'sell']: - if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: - param_total_signal_needed = f'_{trend}_trend_total_signal_needed' - number_of_weighted_signals = int(getattr(cls, f'number_of_weighted_{space}_signals')) - cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_total_signal_needed, - parameter_min_value=cls.min_trend_total_signal_needed_value, - parameter_max_value=int(cls.max_weighted_signal_value * number_of_weighted_signals), - parameter_threshold=cls.search_threshold_weighted_signal_values, - precision=cls.precision, overrideable=True) - - param_needed_candles_lookback_window = f'_{trend}_trend_total_signal_needed_candles_lookback_window' - cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_needed_candles_lookback_window, - parameter_min_value=cls.min_trend_total_signal_needed_candles_lookback_window_value, - parameter_max_value=cls.max_trend_total_signal_needed_candles_lookback_window_value, - parameter_threshold= - cls.search_threshold_trend_total_signal_needed_candles_lookback_window_value, - precision=cls.precision, overrideable=False) - - param_signal_triggers_needed = f'_{trend}_trend_signal_triggers_needed' - cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_signal_triggers_needed, - parameter_min_value=cls.min_trend_signal_triggers_needed_value, - parameter_max_value=number_of_weighted_signals, - parameter_threshold=cls.search_threshold_trend_signal_triggers_needed, - precision=cls.precision, overrideable=True) + for signal_type in ['triggers', 'guards']: + if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: + param_total_signal_needed = f'_{signal_type}_{trend}_trend_total_signal_needed' + number_of_weighted_signals = int(getattr(cls, f'number_of_weighted_{space}_{signal_type}')) + cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_total_signal_needed, + parameter_min_value=cls.min_trend_total_signal_needed_value, + parameter_max_value=int(cls.max_weighted_signal_value * number_of_weighted_signals), + parameter_threshold=cls.search_threshold_weighted_signal_values, + precision=cls.precision, overrideable=True) + + param_needed_candles_lookback_window = f'_{signal_type}_{trend}_trend_total_signal_needed_candles_lookback_window' + cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_needed_candles_lookback_window, + parameter_min_value=cls.min_trend_total_signal_needed_candles_lookback_window_value, + parameter_max_value=cls.max_trend_total_signal_needed_candles_lookback_window_value, + parameter_threshold= + cls.search_threshold_trend_total_signal_needed_candles_lookback_window_value, + precision=cls.precision, overrideable=False) + + param_signal_triggers_needed = f'_{signal_type}_{trend}_trend_signal_triggers_needed' + cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_signal_triggers_needed, + parameter_min_value=cls.min_trend_signal_triggers_needed_value, + parameter_max_value=number_of_weighted_signals, + parameter_threshold=cls.search_threshold_trend_signal_triggers_needed, + precision=cls.precision, overrideable=True) @staticmethod def generate_mgm_attributes(buy_signals, sell_signals): @@ -1327,22 +1342,32 @@ def generate_mgm_attributes(buy_signals, sell_signals): def apply_attributes(base_cls): # Set all signs in the class for later use. - setattr(base_cls, 'buy_signals', buy_signals) - setattr(base_cls, 'sell_signals', sell_signals) + setattr(base_cls, 'buy_triggers', buy_signals['triggers']) + setattr(base_cls, 'buy_guards', buy_signals['guards']) + setattr(base_cls, 'sell_triggers', sell_signals['triggers']) + setattr(base_cls, 'sell_guards', sell_signals['guards']) - # Set number of weighted buy/sell signals for later use. - setattr(base_cls, 'number_of_weighted_buy_signals', len(buy_signals)) - setattr(base_cls, 'number_of_weighted_sell_signals', len(sell_signals)) + # Set number of weighted buy/sell triggers and guards for later use. + setattr(base_cls, 'number_of_weighted_buy_triggers', len(buy_signals['triggers'])) + setattr(base_cls, 'number_of_weighted_buy_guards', len(buy_signals['guards'])) + setattr(base_cls, 'number_of_weighted_sell_triggers', len(sell_signals['triggers'])) + setattr(base_cls, 'number_of_weighted_sell_guards', len(sell_signals['guards'])) # Sets the useful parameters of the MGM, such as unclogger and etc base_cls.init_util_params(base_cls) # Registering signals attributes on class - for name in buy_signals: - base_cls.register_signal_attr(base_cls, name, 'buy') + for name in buy_signals['triggers']: + base_cls.register_signal_attr(base_cls, name, 'triggers', 'buy') + + for name in buy_signals['guards']: + base_cls.register_signal_attr(base_cls, name, 'guards', 'buy') + + for name in sell_signals['triggers']: + base_cls.register_signal_attr(base_cls, name, 'triggers', 'sell') - for name in sell_signals: - base_cls.register_signal_attr(base_cls, name, 'sell') + for name in sell_signals['guards']: + base_cls.register_signal_attr(base_cls, name, 'guards', 'sell') return base_cls @@ -1362,38 +1387,40 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D self.init_hyperopt_epoch() # Initialize total signal and signals triggered columns (should be 0 = false by default) - if 'total_buy_signal_strength' not in dataframe.columns: - dataframe['total_buy_signal_strength'] = dataframe['total_sell_signal_strength'] = 0 - if f'{space}_signals_triggered' not in dataframe.columns: - dataframe[f'{space}_signals_triggered'] = 0 - - # Fetch the weighted signals used + their min/max search space values and threshold used - signals = getattr(self, f'{space}_signals') - signal_min_value = self.mgm_config['weighted_signal_spaces']['min_weighted_signal_value'] - signal_max_value = self.mgm_config['weighted_signal_spaces']['max_weighted_signal_value'] - signal_threshold = self.mgm_config['weighted_signal_spaces']['search_threshold_weighted_signal_values'] + if f'total_{space}_triggers_strength' not in dataframe.columns: + dataframe[f'total_{space}_triggers_strength'] = dataframe[f'total_{space}_guards_strength'] = 0 + if f'{space}_triggers_triggered' not in dataframe.columns: + dataframe[f'{space}_triggers_triggered'] = dataframe[f'{space}_guards_triggered'] = 0 # Calculates the weight and/or generates the debug column for each signal - for signal_name, condition_func in signals.items(): - self._add_signal(signal_name=signal_name, signal_min_value=signal_min_value, - signal_max_value=signal_max_value, signal_threshold=signal_threshold, - space=space, dataframe=dataframe, condition=condition_func(dataframe)) + for signal_type in ['triggers','guards']: + # Fetch the weighted signals used + their min/max search space values and threshold used + signals = getattr(self, f'{space}_{signal_type}') + signal_min_value = self.mgm_config['weighted_signal_spaces']['min_weighted_signal_value'] + signal_max_value = self.mgm_config['weighted_signal_spaces']['max_weighted_signal_value'] + signal_threshold = self.mgm_config['weighted_signal_spaces']['search_threshold_weighted_signal_values'] + + for signal_name, condition_func in signals.items(): + self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_min_value, + signal_max_value=signal_max_value, signal_threshold=signal_threshold, + space=space, dataframe=dataframe, condition=condition_func(dataframe)) # Generates the conditions responsible for searching and comparing the weights needed to activate a buy or sell dataframe.loc[self._generate_weight_condition(dataframe=dataframe, space=space), space] = 1 # Check if total signals needed & triggers needed are possible, if not force the bot to do nothing - number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_signals')) - if self.is_dry_live_run_detected is False: - for trend in self.mgm_trends: - if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: - corrected_totals = self.get_corrected_totals_needed( - space=space, trend=trend, number_of_weighted_signals=number_of_weighted_signals) - - if ((self.total_signals_possible[f'{space}_{trend}'] < corrected_totals['signal_needed']) or - (self.total_triggers_possible[f'{space}_{trend}'] < corrected_totals['triggers_needed'])): - dataframe['buy'] = dataframe['sell'] = 0 - + for signal_type in ['triggers','guards']: + number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_{signal_type}')) + if self.is_dry_live_run_detected is False: + for trend in self.mgm_trends: + if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: + corrected_totals = self.get_corrected_totals_needed( + space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals) + + if ((self.total_signals_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['signal_needed']) or + (self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['triggers_needed'])): + dataframe['buy'] = dataframe['sell'] = 0 + return dataframe def init_hyperopt_epoch(self) -> None: @@ -1407,8 +1434,9 @@ def init_hyperopt_epoch(self) -> None: # Reset the total signals and triggers possible for trend in self.mgm_trends: for space in ['buy', 'sell']: - self.total_signals_possible[f'{space}_{trend}'] = 0 - self.total_triggers_possible[f'{space}_{trend}'] = 0 + for signal_type in ['triggers', 'guards']: + self.total_signals_possible[f'{space}_{signal_type}_{trend}'] = 0 + self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] = 0 # Reset the custom_info dictionary when a new BackTest starts (during HyperOpting) if needed if self.custom_info != self.initial_custom_info: diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index 8ad3aac21..04ba8b3fe 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -22,42 +22,50 @@ # Define the Weighted Buy Signals to be used by MGM buy_signals = { - # Weighted Buy Signal: MACD above Signal - 'macd': lambda df: (df['macd'] > df['macdsignal']), - # Weighted Buy Signal: MFI crosses above 20 (Under-bought / low-price and rising indication) - 'mfi': lambda df: (qtpylib.crossed_above(df['mfi'], 20)), - # Weighted Buy Signal: VWAP crosses above current price - 'vwap_cross': lambda df: (qtpylib.crossed_above(df['vwap'], df['close'])), - # Weighted Buy Signal: Price crosses above Parabolic SAR - 'sar_cross': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), - # Weighted Buy Signal: Stochastic Slow below 20 (Under-bought, indication of starting to move up) - 'stoch': lambda df: (df['slowk'] < 20), - # Weighted Buy Signal: SMA long term Golden Cross (Medium term SMA crosses above Long term SMA) - 'sma_long_golden_cross': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), - # Weighted Buy Signal: SMA short term Golden Cross (Short term SMA crosses above Medium term SMA) - 'sma_short_golden_cross': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), - # Weighted Buy Signal: TEMA - 'tema': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)) + 'triggers' : { + # Weighted Buy Signal: Rolling VWAP crosses above current price + 'rolling_vwap_cross': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), + # Weighted Buy Signal: Price crosses above Parabolic SAR + 'sar_cross': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), + # Weighted Buy Signal: SMA long term Golden Cross (Medium term SMA crosses above Long term SMA) + 'sma_long_golden_cross': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), + # Weighted Buy Signal: SMA short term Golden Cross (Short term SMA crosses above Medium term SMA) + 'sma_short_golden_cross': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), + }, + 'guards' : { + # Weighted Buy Signal: MACD above Signal + 'macd': lambda df: (df['macd'] > df['macdsignal']), + # Weighted Buy Signal: MFI under 20 (Under-bought / low-price and rising indication) + 'mfi': lambda df: (df['mfi'] <= 20), + # Weighted Buy Signal: Stochastic Slow below 20 (Under-bought, indication of starting to move up) + 'stoch': lambda df: (df['slowk'] < 20), + # Weighted Buy Signal: TEMA + 'tema': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), + } } # Define the Weighted Sell Signals to be used by MGM sell_signals = { - # Weighted Sell Signal: MACD below Signal - 'macd': lambda df: (df['macd'] < df['macdsignal']), - # Weighted Sell Signal: MFI crosses below 80 (Over-bought / high-price and dropping indication) - 'mfi': lambda df: (qtpylib.crossed_below(df['mfi'], 80)), - # Weighted Sell Signal: VWAP crosses below current price - 'vwap_cross': lambda df: (qtpylib.crossed_below(df['vwap'], df['close'])), - # Weighted Sell Signal: Price crosses below Parabolic SAR - 'sar_cross': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), - # Weighted Sell Signal: Stochastic Slow above 80 (Over-bought, indication of starting to move down) - 'stoch': lambda df: (df['slowk'] > 80), - # Weighted Sell Signal: SMA long term Death Cross (Medium term SMA crosses below Long term SMA) - 'sma_long_death_cross': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), - # Weighted Sell Signal: SMA short term Death Cross (Short term SMA crosses below Medium term SMA) - 'sma_short_death_cross': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), - # Weighted Buy Signal: TEMA - 'tema': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)) + 'triggers' : { + # Weighted Sell Signal: Rolling VWAP crosses below current price + 'rolling_vwap_cross': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), + # Weighted Sell Signal: Price crosses below Parabolic SAR + 'sar_cross': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), + # Weighted Sell Signal: SMA long term Death Cross (Medium term SMA crosses below Long term SMA) + 'sma_long_death_cross': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), + # Weighted Sell Signal: SMA short term Death Cross (Short term SMA crosses below Medium term SMA) + 'sma_short_death_cross': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), + }, + 'guards' : { + # Weighted Sell Signal: MACD below Signal + 'macd': lambda df: (df['macd'] < df['macdsignal']), + # Weighted Sell Signal: MFI above 80 (Over-bought / high-price and dropping indication) + 'mfi': lambda df: (df['mfi'] >= 80), + # Weighted Sell Signal: Stochastic Slow above 80 (Over-bought, indication of starting to move down) + 'stoch': lambda df: (df['slowk'] > 80), + # Weighted Buy Signal: TEMA + 'tema': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), + } } @@ -100,7 +108,7 @@ class MoniGoManiHyperStrategy(MasterMoniGoManiHyperStrategy): # Plot configuration to show all Weighted Signals/Indicators used by MoniGoMani in FreqUI. # Also loads in MGM Framework Plots for Buy/Sell Signals/Indicators and Trend Detection. plot_config = MasterMoniGoManiHyperStrategy.populate_frequi_plots({ - # Main Plots Signals/Indicators (SMAs, EMAs, Bollinger Bands, VWAP, TEMA) + # Main Plots Signals/Indicators (SMAs, EMAs, Bollinger Bands, Rolling VWAP, TEMA) 'main_plot': { 'sma9': {'color': '#2c05f6'}, 'sma50': {'color': '#19038a'}, @@ -109,7 +117,7 @@ class MoniGoManiHyperStrategy(MasterMoniGoManiHyperStrategy): 'ema50': {'color': '#0a8963'}, 'ema200': {'color': '#074b36'}, 'bb_middleband': {'color': '#6f1a7b'}, - 'vwap': {'color': '#727272'}, + 'rolling_vwap': {'color': '#727272'}, 'tema': {'color': '#9345ee'} }, # Sub Plots - Each dict defines one additional plot @@ -209,8 +217,8 @@ def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFr # Volume Indicators # ----------------- - # VWAP - Volume Weighted Average Price - dataframe['vwap'] = qtpylib.vwap(dataframe) + # Rolling VWAP - Volume Weighted Average Price + dataframe['rolling_vwap'] = qtpylib.rolling_vwap(dataframe) return dataframe From c8ae749503595a539450b67236a3c60f94bee297 Mon Sep 17 00:00:00 2001 From: raftersvk <48151933+raftersvk@users.noreply.github.com> Date: Tue, 11 Jan 2022 19:49:57 +0100 Subject: [PATCH 02/17] update installer.sh to specific branch / easy test setup --- installer.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/installer.sh b/installer.sh index 164b9b8e9..78ed87e86 100755 --- a/installer.sh +++ b/installer.sh @@ -22,9 +22,9 @@ # /usr/bin/env sh <(curl -s "https://raw.githubusercontent.com/Rikj000/MoniGoMani/development/installer.sh") # # === Settings ========================================================================================================= -INSTALL_FOLDER_NAME="Freqtrade-MGM" # By default the folder will be created under the current working directory -MGM_REPO_URL="https://github.com/Rikj000/MoniGoMani.git" -MGM_BRANCH="development" +INSTALL_FOLDER_NAME="Freqtrade-MGM-Raftersvk" # By default the folder will be created under the current working directory +MGM_REPO_URL="https://github.com/raftersvk/MoniGoMani_raftersvk.git" +MGM_BRANCH="separation-of-signals-into-triggers-and-guards" MGM_COMMIT="" SHELL_CONFIGS=( ~/.bashrc From 67205f28e9b0adaaa3dfc84ff5e3eaf0c93d23ac Mon Sep 17 00:00:00 2001 From: raftersvk Date: Tue, 11 Jan 2022 22:28:33 +0100 Subject: [PATCH 03/17] code improvment and new default min/max value for 1st HO run --- .../MasterMoniGoManiHyperStrategy.py | 73 +++++++++---------- .../strategies/MoniGoManiHyperStrategy.py | 38 ---------- 2 files changed, 36 insertions(+), 75 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index dc7a730f4..df27efb88 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -62,6 +62,8 @@ class MasterMoniGoManiHyperStrategy(IStrategy, ABC): # MGM trend names mgm_trends = ['downwards', 'sideways', 'upwards'] + list_of_space = ['buy', 'sell'] + list_of_signal_type = ['triggers', 'guards'] # Initialize empty buy/sell_params dictionaries buy_params = {} @@ -170,7 +172,7 @@ class MasterMoniGoManiHyperStrategy(IStrategy, ABC): # Default stub values from 'mgm-config' are used otherwise. if mgm_config_hyperopt != {}: for space in mgm_config_hyperopt['params']: - if space in ['buy', 'sell']: + if space in list_of_space: for param, param_value in mgm_config_hyperopt['params'][space].items(): if param.startswith('buy'): buy_params[param] = param_value @@ -209,8 +211,8 @@ class MasterMoniGoManiHyperStrategy(IStrategy, ABC): total_signals_possible = {} total_triggers_possible = {} for trend in mgm_trends: - for space in ['buy', 'sell']: - for signal_type in ['triggers', 'guards']: + for space in list_of_space: + for signal_type in list_of_signal_type: total_signals_possible[f'{space}_{signal_type}_{trend}'] = 0 total_triggers_possible[f'{space}_{signal_type}_{trend}'] = 0 @@ -919,7 +921,7 @@ def convert_candle_time(self, current_time: datetime, current_candle: int = 1) - return candle_time - def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, +def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, **kwargs) -> bool: """ Open Trade Unclogger Buy Cooldown Window @@ -1325,14 +1327,23 @@ def init_util_params(cls, base_cls): # Generate the utility attributes for the logic of the weighted_signal_spaces for trend in cls.mgm_trends: - for space in ['buy', 'sell']: - for signal_type in ['triggers', 'guards']: + for space in cls.list_of_space: + for signal_type in cls.list_of_signal_type: if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: param_total_signal_needed = f'_{signal_type}_{trend}_trend_total_signal_needed' number_of_weighted_signals = int(getattr(cls, f'number_of_weighted_{space}_{signal_type}')) + + # 1st HyperOpt Run: No need to search up to max value, (max value - search_threshold) is enough, 2nd Hyperopt Run will be able to go to max value + parameter_dictionary = getattr(cls, f'{space}_params') + parameter_value = parameter_dictionary.get(f'{space}_{param_total_signal_needed}') + if parameter_value is None: + search_threshold = 0 + else: + search_threshold = cls.search_threshold_trend_signal_triggers_needed + cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_total_signal_needed, parameter_min_value=cls.min_trend_total_signal_needed_value, - parameter_max_value=int(cls.max_weighted_signal_value * number_of_weighted_signals), + parameter_max_value=int(cls.max_weighted_signal_value * (number_of_weighted_signals - search_threshold)), parameter_threshold=cls.search_threshold_weighted_signal_values, precision=cls.precision, overrideable=True) @@ -1347,7 +1358,7 @@ def init_util_params(cls, base_cls): param_signal_triggers_needed = f'_{signal_type}_{trend}_trend_signal_triggers_needed' cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_signal_triggers_needed, parameter_min_value=cls.min_trend_signal_triggers_needed_value, - parameter_max_value=number_of_weighted_signals, + parameter_max_value=int(number_of_weighted_signals - search_threshold), parameter_threshold=cls.search_threshold_trend_signal_triggers_needed, precision=cls.precision, overrideable=True) @@ -1368,34 +1379,22 @@ def generate_mgm_attributes(buy_signals, sell_signals): # It will be set as the decorator of the base class def apply_attributes(base_cls): - # Set all signs in the class for later use. - setattr(base_cls, 'buy_triggers', buy_signals['triggers']) - setattr(base_cls, 'buy_guards', buy_signals['guards']) - setattr(base_cls, 'sell_triggers', sell_signals['triggers']) - setattr(base_cls, 'sell_guards', sell_signals['guards']) - - # Set number of weighted buy/sell triggers and guards for later use. - setattr(base_cls, 'number_of_weighted_buy_triggers', len(buy_signals['triggers'])) - setattr(base_cls, 'number_of_weighted_buy_guards', len(buy_signals['guards'])) - setattr(base_cls, 'number_of_weighted_sell_triggers', len(sell_signals['triggers'])) - setattr(base_cls, 'number_of_weighted_sell_guards', len(sell_signals['guards'])) + for signal_type in base_cls.list_of_signal_type: + # Set all signs in the class for later use. + setattr(base_cls, f'buy_{signal_type}', buy_signals[f'{signal_type}']) + setattr(base_cls, f'sell_{signal_type}', sell_signals[f'{signal_type}']) + # Set number of weighted buy/sell triggers and guards for later use. + setattr(base_cls, f'number_of_weighted_buy_{signal_type}', len(buy_signals[f'{signal_type}'])) + setattr(base_cls, f'number_of_weighted_sell_{signal_type}', len(sell_signals[f'{signal_type}'])) + # Registering signals attributes on class + for name in buy_signals[f'{signal_type}']: + base_cls.register_signal_attr(base_cls, name, f'{signal_type}', 'buy') + for name in sell_signals[f'{signal_type}']: + base_cls.register_signal_attr(base_cls, name, f'{signal_type}', 'sell') # Sets the useful parameters of the MGM, such as unclogger and etc base_cls.init_util_params(base_cls) - # Registering signals attributes on class - for name in buy_signals['triggers']: - base_cls.register_signal_attr(base_cls, name, 'triggers', 'buy') - - for name in buy_signals['guards']: - base_cls.register_signal_attr(base_cls, name, 'guards', 'buy') - - for name in sell_signals['triggers']: - base_cls.register_signal_attr(base_cls, name, 'triggers', 'sell') - - for name in sell_signals['guards']: - base_cls.register_signal_attr(base_cls, name, 'guards', 'sell') - return base_cls return apply_attributes @@ -1420,7 +1419,7 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D dataframe[f'{space}_triggers_triggered'] = dataframe[f'{space}_guards_triggered'] = 0 # Calculates the weight and/or generates the debug column for each signal - for signal_type in ['triggers','guards']: + for signal_type in self.list_of_signal_type: # Fetch the weighted signals used + their min/max search space values and threshold used signals = getattr(self, f'{space}_{signal_type}') signal_min_value = self.mgm_config['weighted_signal_spaces']['min_weighted_signal_value'] @@ -1436,14 +1435,14 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D dataframe.loc[self._generate_weight_condition(dataframe=dataframe, space=space), space] = 1 # Check if total signals needed & triggers needed are possible, if not force the bot to do nothing - for signal_type in ['triggers','guards']: + for signal_type in self.list_of_signal_type: number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_{signal_type}')) if self.is_dry_live_run_detected is False: for trend in self.mgm_trends: if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: corrected_totals = self.get_corrected_totals_needed( space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals) - + if ((self.total_signals_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['signal_needed']) or (self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['triggers_needed'])): dataframe['buy'] = dataframe['sell'] = 0 @@ -1460,8 +1459,8 @@ def init_hyperopt_epoch(self) -> None: # Reset the total signals and triggers possible for trend in self.mgm_trends: - for space in ['buy', 'sell']: - for signal_type in ['triggers', 'guards']: + for space in self.list_of_space: + for signal_type in self.list_of_signal_type: self.total_signals_possible[f'{space}_{signal_type}_{trend}'] = 0 self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] = 0 diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index dd841a473..04ba8b3fe 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -22,7 +22,6 @@ # Define the Weighted Buy Signals to be used by MGM buy_signals = { -<<<<<<< HEAD 'triggers' : { # Weighted Buy Signal: Rolling VWAP crosses above current price 'rolling_vwap_cross': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), @@ -43,29 +42,10 @@ # Weighted Buy Signal: TEMA 'tema': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), } -======= - # Weighted Buy Signal: MACD above Signal - 'macd': lambda df: (df['macd'] > df['macdsignal']), - # Weighted Buy Signal: MFI crosses above 20 (Under-bought / low-price and rising indication) - 'mfi': lambda df: (qtpylib.crossed_above(df['mfi'], 20)), - # Weighted Buy Signal: Rolling VWAP crosses above current price - 'rolling_vwap_cross': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), - # Weighted Buy Signal: Price crosses above Parabolic SAR - 'sar_cross': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), - # Weighted Buy Signal: Stochastic Slow below 20 (Under-bought, indication of starting to move up) - 'stoch': lambda df: (df['slowk'] < 20), - # Weighted Buy Signal: SMA long term Golden Cross (Medium term SMA crosses above Long term SMA) - 'sma_long_golden_cross': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), - # Weighted Buy Signal: SMA short term Golden Cross (Short term SMA crosses above Medium term SMA) - 'sma_short_golden_cross': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), - # Weighted Buy Signal: TEMA - 'tema': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)) ->>>>>>> 527b1061cd00f7eab2c6c1744fcabdb24d26f3c0 } # Define the Weighted Sell Signals to be used by MGM sell_signals = { -<<<<<<< HEAD 'triggers' : { # Weighted Sell Signal: Rolling VWAP crosses below current price 'rolling_vwap_cross': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), @@ -86,24 +66,6 @@ # Weighted Buy Signal: TEMA 'tema': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), } -======= - # Weighted Sell Signal: MACD below Signal - 'macd': lambda df: (df['macd'] < df['macdsignal']), - # Weighted Sell Signal: MFI crosses below 80 (Over-bought / high-price and dropping indication) - 'mfi': lambda df: (qtpylib.crossed_below(df['mfi'], 80)), - # Weighted Sell Signal: Rolling VWAP crosses below current price - 'rolling_vwap_cross': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), - # Weighted Sell Signal: Price crosses below Parabolic SAR - 'sar_cross': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), - # Weighted Sell Signal: Stochastic Slow above 80 (Over-bought, indication of starting to move down) - 'stoch': lambda df: (df['slowk'] > 80), - # Weighted Sell Signal: SMA long term Death Cross (Medium term SMA crosses below Long term SMA) - 'sma_long_death_cross': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), - # Weighted Sell Signal: SMA short term Death Cross (Short term SMA crosses below Medium term SMA) - 'sma_short_death_cross': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), - # Weighted Buy Signal: TEMA - 'tema': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)) ->>>>>>> 527b1061cd00f7eab2c6c1744fcabdb24d26f3c0 } From 2f8a95b2fa0a866e85874bc22945efd5ac71a35d Mon Sep 17 00:00:00 2001 From: raftersvk Date: Wed, 12 Jan 2022 21:07:53 +0100 Subject: [PATCH 04/17] hyperopt parameter : in case min == max value no need to optimize --- user_data/strategies/MasterMoniGoManiHyperStrategy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index df27efb88..a8786a87c 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -1288,6 +1288,11 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val else: optimize = True + # In case min = max value no need to optimize, force default value to min/max + if min_value == max_value: + default_value = min_value + optimize = False + parameter_config = { 'min_value': int(min_value * precision), 'max_value': int(max_value * precision), From 014c23824133c4534628357a26bb59772c5627a6 Mon Sep 17 00:00:00 2001 From: raftersvk Date: Wed, 12 Jan 2022 21:19:09 +0100 Subject: [PATCH 05/17] indent correction --- user_data/strategies/MasterMoniGoManiHyperStrategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index a8786a87c..c694e6692 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -921,7 +921,7 @@ def convert_candle_time(self, current_time: datetime, current_candle: int = 1) - return candle_time -def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, **kwargs) -> bool: """ Open Trade Unclogger Buy Cooldown Window From faeea551342cc0d6fa2bde2ebeaf5e1a7b417f58 Mon Sep 17 00:00:00 2001 From: raftersvk Date: Sat, 15 Jan 2022 15:45:43 +0100 Subject: [PATCH 06/17] new configurable signal weight search space and generic rework of the search spaces mechanism --- .../MasterMoniGoManiHyperStrategy.py | 129 ++++++++++++------ .../strategies/MoniGoManiHyperStrategy.py | 86 +++++++++--- 2 files changed, 159 insertions(+), 56 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index c694e6692..05f479298 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -1051,9 +1051,9 @@ def _generate_weight_condition(self, dataframe: DataFrame, space: str) -> DataFr for trend in self.mgm_trends: if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: corrected_triggers_totals = self.get_corrected_totals_needed( - space=space, trend=trend, signal_type='triggers', number_of_weighted_signals=number_of_weighted_triggers) + space=space, trend=trend, signal_type='triggers', number_of_weighted_signals=number_of_weighted_triggers,metadata={}) corrected_guards_totals = self.get_corrected_totals_needed( - space=space, trend=trend, signal_type='guards', number_of_weighted_signals=number_of_weighted_guards) + space=space, trend=trend, signal_type='guards', number_of_weighted_signals=number_of_weighted_guards,metadata={}) conditions_weight.append( (dataframe['trend'] == trend) & @@ -1065,7 +1065,7 @@ def _generate_weight_condition(self, dataframe: DataFrame, space: str) -> DataFr return reduce(lambda x, y: x | y, conditions_weight) - def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, number_of_weighted_signals: int) -> dict: + def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, number_of_weighted_signals: int, metadata: dict) -> dict: """ Fetches a dictionary containing - Total Signal Weight Needed @@ -1085,7 +1085,7 @@ def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, parameter_value=total_signal_needed.value, parameter_min_value=self.min_trend_total_signal_needed_value, parameter_max_value=self.max_weighted_signal_value * number_of_weighted_signals, - parameter_threshold=self.search_threshold_weighted_signal_values + parameter_threshold=self.search_threshold_weighted_signal_values * number_of_weighted_signals ) / self.precision corrected_total_triggers_needed = self.apply_weak_strong_overrides( @@ -1095,6 +1095,8 @@ def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, parameter_threshold=self.search_threshold_trend_signal_triggers_needed ) / self.precision + print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_signal_needed:{corrected_total_signal_needed}') + print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_triggers_needed:{corrected_total_triggers_needed}') return {'signal_needed': corrected_total_signal_needed, 'triggers_needed': corrected_total_triggers_needed} def apply_weak_strong_overrides(self, parameter_value, @@ -1198,10 +1200,12 @@ def register_signal_attr(cls, base_cls, name: str, signal_type: str, space: str for trend in cls.mgm_trends: parameter_name = f'{signal_type}_{trend}_trend_{name}_weight' if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: + signals = getattr(cls, f'{space}_{signal_type}') + signal_attributes = cls.calculate_signal_attr(cls, name, signals[name]['min_weight'], signals[name]['max_weight']) cls._init_vars(base_cls, space=space, parameter_name=parameter_name, - parameter_min_value=cls.min_weighted_signal_value, - parameter_max_value=cls.max_weighted_signal_value, - parameter_threshold=cls.search_threshold_weighted_signal_values, + parameter_min_value=signal_attributes['min_value'], + parameter_max_value=signal_attributes['max_value'], + parameter_threshold=signal_attributes['threshold'], precision=cls.precision, overrideable=True) @classmethod @@ -1223,12 +1227,17 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val :return: None """ + print(f'DEBUG --- _init_vars -- START space : {space} parameter_name : {parameter_name} min : {parameter_min_value}, max : {parameter_max_value}, parameter_threshold : {parameter_threshold}, overrideable : {overrideable}') + # Narrow the search spaces for overrideable parameters by default override_parameter_min_value = parameter_min_value override_parameter_max_value = parameter_max_value if overrideable is True: parameter_min_value = parameter_min_value + parameter_threshold parameter_max_value = parameter_max_value - parameter_threshold + # Keep the original min value if search space is not big enough to restrict it + if parameter_min_value > parameter_max_value : + parameter_min_value = override_parameter_min_value parameter_dictionary = getattr(cls, f'{space}_params') parameter_key = f'{space}_{parameter_name}' @@ -1250,8 +1259,8 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val if parameter_value <= parameter_min_value: min_value = override_parameter_min_value # Limit search space min_value to not go too low - elif (parameter_value - parameter_threshold) < parameter_min_value: - min_value = parameter_min_value + #elif (parameter_value - parameter_threshold) < parameter_min_value: + # min_value = parameter_min_value # Otherwise just refine the search space else: min_value = parameter_value - parameter_threshold @@ -1260,8 +1269,8 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val if parameter_value >= parameter_max_value: max_value = override_parameter_max_value # Limit search space max_value to not go too high - elif (parameter_value + parameter_threshold) > parameter_max_value: - max_value = parameter_max_value + #elif (parameter_value + parameter_threshold) > parameter_max_value: + # max_value = parameter_max_value # Otherwise just refine the search space else: max_value = parameter_value + parameter_threshold @@ -1280,7 +1289,7 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val # 2nd HyperOpt Run: Apply Overrides where needed if (parameter_value is not None) and (overrideable is True): - if default_value == override_parameter_min_value or default_value == override_parameter_max_value: + if (default_value == override_parameter_min_value or default_value == override_parameter_max_value) and ((override_parameter_max_value - override_parameter_min_value) > parameter_threshold): optimize = False else: optimize = True @@ -1288,11 +1297,14 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val else: optimize = True - # In case min = max value no need to optimize, force default value to min/max - if min_value == max_value: - default_value = min_value + # In case max <= min value no need to optimize, force default/max value to min + print(f'DEBUG --- _init_vars -- MIDDLE parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}') + if max_value <= min_value: + default_value = max_value = min_value optimize = False + print(f'DEBUG --- _init_vars -- END parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}\n') + parameter_config = { 'min_value': int(min_value * precision), 'max_value': int(max_value * precision), @@ -1324,7 +1336,6 @@ def init_util_params(cls, base_cls): if isinstance(param_config, dict) is True: param_config['threshold'] = (param_config['threshold'] if 'threshold' in param_config else cls.search_threshold_weighted_signal_values) - cls._init_vars(base_cls=base_cls, space='sell', parameter_name=parameter_name, parameter_min_value=param_config['min'], parameter_max_value=param_config['max'], parameter_threshold=param_config['threshold'], @@ -1338,18 +1349,27 @@ def init_util_params(cls, base_cls): param_total_signal_needed = f'_{signal_type}_{trend}_trend_total_signal_needed' number_of_weighted_signals = int(getattr(cls, f'number_of_weighted_{space}_{signal_type}')) - # 1st HyperOpt Run: No need to search up to max value, (max value - search_threshold) is enough, 2nd Hyperopt Run will be able to go to max value + # 1st HyperOpt Run: hack to start with the real min to avoid starting with too high expectations parameter_dictionary = getattr(cls, f'{space}_params') parameter_value = parameter_dictionary.get(f'{space}_{param_total_signal_needed}') if parameter_value is None: - search_threshold = 0 + #1st Hyperopt + if cls.min_trend_total_signal_needed_value < (cls.search_threshold_weighted_signal_values*number_of_weighted_signals): + min_total_signal_needed = 0 + else: + min_total_signal_needed = (cls.min_trend_total_signal_needed_value - + (cls.search_threshold_weighted_signal_values* number_of_weighted_signals)) + min_triggers_needed = (cls.min_trend_signal_triggers_needed_value - + cls.search_threshold_trend_signal_triggers_needed) else: - search_threshold = cls.search_threshold_trend_signal_triggers_needed - + #2nd Hyperopt + min_total_signal_needed = cls.min_trend_total_signal_needed_value + min_triggers_needed = cls.min_trend_signal_triggers_needed_value + cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_total_signal_needed, - parameter_min_value=cls.min_trend_total_signal_needed_value, - parameter_max_value=int(cls.max_weighted_signal_value * (number_of_weighted_signals - search_threshold)), - parameter_threshold=cls.search_threshold_weighted_signal_values, + parameter_min_value=min_total_signal_needed, + parameter_max_value=int(cls.max_weighted_signal_value * number_of_weighted_signals), + parameter_threshold=int(cls.search_threshold_weighted_signal_values* number_of_weighted_signals), precision=cls.precision, overrideable=True) param_needed_candles_lookback_window = f'_{signal_type}_{trend}_trend_total_signal_needed_candles_lookback_window' @@ -1362,8 +1382,8 @@ def init_util_params(cls, base_cls): param_signal_triggers_needed = f'_{signal_type}_{trend}_trend_signal_triggers_needed' cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_signal_triggers_needed, - parameter_min_value=cls.min_trend_signal_triggers_needed_value, - parameter_max_value=int(number_of_weighted_signals - search_threshold), + parameter_min_value=min_triggers_needed, + parameter_max_value=number_of_weighted_signals, parameter_threshold=cls.search_threshold_trend_signal_triggers_needed, precision=cls.precision, overrideable=True) @@ -1425,20 +1445,16 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D # Calculates the weight and/or generates the debug column for each signal for signal_type in self.list_of_signal_type: - # Fetch the weighted signals used + their min/max search space values and threshold used + # Fetch the weighted signals used signals = getattr(self, f'{space}_{signal_type}') - signal_min_value = self.mgm_config['weighted_signal_spaces']['min_weighted_signal_value'] - signal_max_value = self.mgm_config['weighted_signal_spaces']['max_weighted_signal_value'] - signal_threshold = self.mgm_config['weighted_signal_spaces']['search_threshold_weighted_signal_values'] - - for signal_name, condition_func in signals.items(): - self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_min_value, - signal_max_value=signal_max_value, signal_threshold=signal_threshold, + for signal_name, signal_params in signals.items(): + condition_func = signal_params['condition'] + signal_attributes = self.calculate_signal_attr(signal_name, signal_params['min_weight'], signal_params["max_weight"]) + # Populate signal + self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_attributes['min_value'], + signal_max_value=signal_attributes['max_value'], signal_threshold=signal_attributes['threshold'], space=space, dataframe=dataframe, condition=condition_func(dataframe)) - # Generates the conditions responsible for searching and comparing the weights needed to activate a buy or sell - dataframe.loc[self._generate_weight_condition(dataframe=dataframe, space=space), space] = 1 - # Check if total signals needed & triggers needed are possible, if not force the bot to do nothing for signal_type in self.list_of_signal_type: number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_{signal_type}')) @@ -1446,11 +1462,23 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D for trend in self.mgm_trends: if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: corrected_totals = self.get_corrected_totals_needed( - space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals) + space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals, metadata=metadata) if ((self.total_signals_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['signal_needed']) or (self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['triggers_needed'])): - dataframe['buy'] = dataframe['sell'] = 0 + self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] = False + print(f'DEBUG --- _populate_trend -- {metadata}_{space}_{signal_type}_{trend}_trading_during_trends = False') + + is_valid_epoch = False + # if at leat one trend of the current space is tradable + for trend in self.mgm_trends: + is_valid_epoch = (self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True or is_valid_epoch) + + if is_valid_epoch: + # Generates the conditions responsible for searching and comparing the weights needed to activate a buy or sell + dataframe.loc[self._generate_weight_condition(dataframe=dataframe, space=space), space] = 1 + else: + dataframe['buy'] = dataframe['sell'] = 0 return dataframe @@ -1480,3 +1508,28 @@ def init_hyperopt_epoch(self) -> None: separator_window = (self.separator / 1) - (1 / self.separator) self.separator_candle_weight_reducer = separator_window / self.get_param_value( 'sell___unclogger_trend_lookback_candles_window') + + def calculate_signal_attr(self, signal_name: str, min_weight: int=None, max_weight: int=None) -> dict: + """ + Calculates the min / max / threshold values for a signal given some forced values. + If none then apply default config values + :param signal_name: Signal name + :param min_weight: forced min weight value for the signal + :param max_weight : forced min weight value for the signal + :return: (dict) {min_value, max_value, threshold} + """ + + signal_min_value = self.min_weighted_signal_value + signal_max_value = self.max_weighted_signal_value + signal_threshold = self.search_threshold_weighted_signal_values + + if min_weight is not None: + signal_min_value = min_weight + if max_weight is not None: + signal_max_value = max_weight + + signal_threshold = min(signal_threshold,int((signal_max_value - signal_min_value) / 2)) + + #print(f'DEBUG --- calculate_signal_attr -- signal_name : {signal_name} min : {signal_min_value} max : {signal_max_value} threshold : {signal_threshold}') + + return {'min_value' : signal_min_value, 'max_value' : signal_max_value, 'threshold' : signal_threshold} diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index 04ba8b3fe..88021d24a 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -24,23 +24,48 @@ buy_signals = { 'triggers' : { # Weighted Buy Signal: Rolling VWAP crosses above current price - 'rolling_vwap_cross': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), + 'rolling_vwap_cross': { + 'condition': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Buy Signal: Price crosses above Parabolic SAR - 'sar_cross': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), + 'sar_cross': { + 'condition': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Buy Signal: SMA long term Golden Cross (Medium term SMA crosses above Long term SMA) - 'sma_long_golden_cross': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), + 'sma_long_golden_cross': { + 'condition': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Buy Signal: SMA short term Golden Cross (Short term SMA crosses above Medium term SMA) - 'sma_short_golden_cross': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), + 'sma_short_golden_cross': { + 'condition': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), + 'min_weight' : None, 'max_weight' : None, + }, + }, 'guards' : { # Weighted Buy Signal: MACD above Signal - 'macd': lambda df: (df['macd'] > df['macdsignal']), + 'macd': { + 'condition': lambda df: (df['macd'] > df['macdsignal']), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Buy Signal: MFI under 20 (Under-bought / low-price and rising indication) - 'mfi': lambda df: (df['mfi'] <= 20), + 'mfi': { + 'condition': lambda df: (df['mfi'] <= 20), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Buy Signal: Stochastic Slow below 20 (Under-bought, indication of starting to move up) - 'stoch': lambda df: (df['slowk'] < 20), - # Weighted Buy Signal: TEMA - 'tema': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), + 'stoch': { + 'condition': lambda df: (df['slowk'] < 20), + 'min_weight' : None, 'max_weight' : None, + }, + # Weighted Buy Signal: TEMA increasing under BB middleband + 'tema_bb': { + 'condition': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), + 'min_weight' : None, 'max_weight' : None, + }, } } @@ -48,23 +73,48 @@ sell_signals = { 'triggers' : { # Weighted Sell Signal: Rolling VWAP crosses below current price - 'rolling_vwap_cross': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), + 'rolling_vwap_cross': { + 'condition': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Sell Signal: Price crosses below Parabolic SAR - 'sar_cross': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), + 'sar_cross': { + 'condition': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Sell Signal: SMA long term Death Cross (Medium term SMA crosses below Long term SMA) - 'sma_long_death_cross': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), + 'sma_long_death_cross': { + 'condition': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Sell Signal: SMA short term Death Cross (Short term SMA crosses below Medium term SMA) - 'sma_short_death_cross': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), + 'sma_short_death_cross': { + 'condition': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), + 'min_weight' : None, 'max_weight' : None, + }, + }, 'guards' : { # Weighted Sell Signal: MACD below Signal - 'macd': lambda df: (df['macd'] < df['macdsignal']), + 'macd': { + 'condition': lambda df: (df['macd'] < df['macdsignal']), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Sell Signal: MFI above 80 (Over-bought / high-price and dropping indication) - 'mfi': lambda df: (df['mfi'] >= 80), + 'mfi': { + 'condition': lambda df: (df['mfi'] >= 80), + 'min_weight' : None, 'max_weight' : None, + }, # Weighted Sell Signal: Stochastic Slow above 80 (Over-bought, indication of starting to move down) - 'stoch': lambda df: (df['slowk'] > 80), - # Weighted Buy Signal: TEMA - 'tema': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), + 'stoch': { + 'condition': lambda df: (df['slowk'] > 80), + 'min_weight' : None, 'max_weight' : None, + }, + # Weighted Buy Signal: TEMA decreasing over BB middleband + 'tema_bb': { + 'condition': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), + 'min_weight' : None, 'max_weight' : None, + }, } } From 060dff2419432387ad373e4fabe9140b07564d4b Mon Sep 17 00:00:00 2001 From: raftersvk Date: Wed, 19 Jan 2022 23:00:32 +0100 Subject: [PATCH 07/17] individual hyperoptable weighted signal space --- .../MasterMoniGoManiHyperStrategy.py | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index 05f479298..8ec66cbac 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -1095,8 +1095,8 @@ def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, parameter_threshold=self.search_threshold_trend_signal_triggers_needed ) / self.precision - print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_signal_needed:{corrected_total_signal_needed}') - print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_triggers_needed:{corrected_total_triggers_needed}') + #print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_signal_needed:{corrected_total_signal_needed}') + #print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_triggers_needed:{corrected_total_triggers_needed}') return {'signal_needed': corrected_total_signal_needed, 'triggers_needed': corrected_total_triggers_needed} def apply_weak_strong_overrides(self, parameter_value, @@ -1201,7 +1201,7 @@ def register_signal_attr(cls, base_cls, name: str, signal_type: str, space: str parameter_name = f'{signal_type}_{trend}_trend_{name}_weight' if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: signals = getattr(cls, f'{space}_{signal_type}') - signal_attributes = cls.calculate_signal_attr(cls, name, signals[name]['min_weight'], signals[name]['max_weight']) + signal_attributes = cls.calculate_signal_attr(cls, name, signals[name]['min'], signals[name]['max']) cls._init_vars(base_cls, space=space, parameter_name=parameter_name, parameter_min_value=signal_attributes['min_value'], parameter_max_value=signal_attributes['max_value'], @@ -1227,7 +1227,7 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val :return: None """ - print(f'DEBUG --- _init_vars -- START space : {space} parameter_name : {parameter_name} min : {parameter_min_value}, max : {parameter_max_value}, parameter_threshold : {parameter_threshold}, overrideable : {overrideable}') + #print(f'DEBUG --- _init_vars -- START space : {space} parameter_name : {parameter_name} min : {parameter_min_value}, max : {parameter_max_value}, parameter_threshold : {parameter_threshold}, overrideable : {overrideable}') # Narrow the search spaces for overrideable parameters by default override_parameter_min_value = parameter_min_value @@ -1259,8 +1259,8 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val if parameter_value <= parameter_min_value: min_value = override_parameter_min_value # Limit search space min_value to not go too low - #elif (parameter_value - parameter_threshold) < parameter_min_value: - # min_value = parameter_min_value + elif (parameter_value - parameter_threshold) < parameter_min_value: + min_value = parameter_min_value # Otherwise just refine the search space else: min_value = parameter_value - parameter_threshold @@ -1269,8 +1269,8 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val if parameter_value >= parameter_max_value: max_value = override_parameter_max_value # Limit search space max_value to not go too high - #elif (parameter_value + parameter_threshold) > parameter_max_value: - # max_value = parameter_max_value + elif (parameter_value + parameter_threshold) > parameter_max_value: + max_value = parameter_max_value # Otherwise just refine the search space else: max_value = parameter_value + parameter_threshold @@ -1298,12 +1298,12 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val optimize = True # In case max <= min value no need to optimize, force default/max value to min - print(f'DEBUG --- _init_vars -- MIDDLE parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}') + #print(f'DEBUG --- _init_vars -- MIDDLE parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}') if max_value <= min_value: default_value = max_value = min_value optimize = False - print(f'DEBUG --- _init_vars -- END parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}\n') + #print(f'DEBUG --- _init_vars -- END parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}\n') parameter_config = { 'min_value': int(min_value * precision), @@ -1404,18 +1404,29 @@ def generate_mgm_attributes(buy_signals, sell_signals): # It will be set as the decorator of the base class def apply_attributes(base_cls): - for signal_type in base_cls.list_of_signal_type: - # Set all signs in the class for later use. - setattr(base_cls, f'buy_{signal_type}', buy_signals[f'{signal_type}']) - setattr(base_cls, f'sell_{signal_type}', sell_signals[f'{signal_type}']) - # Set number of weighted buy/sell triggers and guards for later use. - setattr(base_cls, f'number_of_weighted_buy_{signal_type}', len(buy_signals[f'{signal_type}'])) - setattr(base_cls, f'number_of_weighted_sell_{signal_type}', len(sell_signals[f'{signal_type}'])) - # Registering signals attributes on class - for name in buy_signals[f'{signal_type}']: - base_cls.register_signal_attr(base_cls, name, f'{signal_type}', 'buy') - for name in sell_signals[f'{signal_type}']: - base_cls.register_signal_attr(base_cls, name, f'{signal_type}', 'sell') + signals = {} + signals.update({'buy':buy_signals}) + signals.update({'sell':sell_signals}) + for space in base_cls.list_of_space: + for signal_type in base_cls.list_of_signal_type: + valid_signals = {} + for signal_name, signal_params in signals[space][signal_type].items(): + if signal_params['max'] is None or signal_params['max'] > 0 : + if signal_params['min'] is None: + signal_params['min'] = base_cls.min_weighted_signal_value + if signal_params['max'] is None: + signal_params['max'] = base_cls.max_weighted_signal_value + if signal_params['threshold'] is None: + signal_params['threshold'] = base_cls.search_threshold_weighted_signal_values + valid_signals.update({signal_name:signal_params}) + + # Set number of weighted buy/sell triggers and guards for later use. + setattr(base_cls, f'number_of_weighted_{space}_{signal_type}', len(valid_signals)) + + # Registering signals attributes on class + setattr(base_cls, f'{space}_{signal_type}', valid_signals) + for name in valid_signals: + base_cls.register_signal_attr(base_cls, name, f'{signal_type}', f'{space}') # Sets the useful parameters of the MGM, such as unclogger and etc base_cls.init_util_params(base_cls) @@ -1449,7 +1460,7 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D signals = getattr(self, f'{space}_{signal_type}') for signal_name, signal_params in signals.items(): condition_func = signal_params['condition'] - signal_attributes = self.calculate_signal_attr(signal_name, signal_params['min_weight'], signal_params["max_weight"]) + signal_attributes = self.calculate_signal_attr(signal_name, signal_params['min'], signal_params["max"]) # Populate signal self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_attributes['min_value'], signal_max_value=signal_attributes['max_value'], signal_threshold=signal_attributes['threshold'], @@ -1467,7 +1478,7 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D if ((self.total_signals_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['signal_needed']) or (self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['triggers_needed'])): self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] = False - print(f'DEBUG --- _populate_trend -- {metadata}_{space}_{signal_type}_{trend}_trading_during_trends = False') + #print(f'DEBUG --- _populate_trend -- {metadata}_{space}_{signal_type}_{trend}_trading_during_trends = False') is_valid_epoch = False # if at leat one trend of the current space is tradable From d50a87d28c46bc6dac836bd97bc6257ea4b492a2 Mon Sep 17 00:00:00 2001 From: raftersvk Date: Fri, 21 Jan 2022 23:22:41 +0100 Subject: [PATCH 08/17] further changes for triggers/guards and individual weighted signals search space parameters --- .../MasterMoniGoManiHyperStrategy.py | 93 +++++-------- .../strategies/MoniGoManiHyperStrategy.py | 129 +++++++++++------- 2 files changed, 113 insertions(+), 109 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index 28d517677..2227814d4 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -62,7 +62,7 @@ class MasterMoniGoManiHyperStrategy(IStrategy, ABC): # MGM trend names mgm_trends = ['downwards', 'sideways', 'upwards'] - list_of_signals_space = ['buy', 'sell'] + list_of_signal_space = ['buy', 'sell'] list_of_signal_type = ['triggers', 'guards'] # Initialize empty buy/sell/protection_params dictionaries @@ -212,11 +212,15 @@ class MasterMoniGoManiHyperStrategy(IStrategy, ABC): separator_candle_weight_reducer = 0.03 # Gets set automatically # Initialize comparison values to check if total signals utilized by HyperOpt are possible + max_total_weight_possible = {} + max_total_threshold_possible = {} total_signals_possible = {} total_triggers_possible = {} - for trend in mgm_trends: - for space in list_of_signals_space: - for signal_type in list_of_signal_type: + for space in list_of_signal_space: + for signal_type in list_of_signal_type: + max_total_weight_possible[f'{space}_{signal_type}'] = 0 + max_total_threshold_possible[f'{space}_{signal_type}'] = 0 + for trend in mgm_trends: total_signals_possible[f'{space}_{signal_type}_{trend}'] = 0 total_triggers_possible[f'{space}_{signal_type}_{trend}'] = 0 @@ -1084,9 +1088,9 @@ def _generate_weight_condition(self, dataframe: DataFrame, space: str) -> DataFr for trend in self.mgm_trends: if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: corrected_triggers_totals = self.get_corrected_totals_needed( - space=space, trend=trend, signal_type='triggers', number_of_weighted_signals=number_of_weighted_triggers,metadata={}) + space=space, trend=trend, signal_type='triggers', number_of_weighted_signals=number_of_weighted_triggers) corrected_guards_totals = self.get_corrected_totals_needed( - space=space, trend=trend, signal_type='guards', number_of_weighted_signals=number_of_weighted_guards,metadata={}) + space=space, trend=trend, signal_type='guards', number_of_weighted_signals=number_of_weighted_guards) conditions_weight.append( (dataframe['trend'] == trend) & @@ -1098,7 +1102,7 @@ def _generate_weight_condition(self, dataframe: DataFrame, space: str) -> DataFr return reduce(lambda x, y: x | y, conditions_weight) - def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, number_of_weighted_signals: int, metadata: dict) -> dict: + def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, number_of_weighted_signals: int) -> dict: """ Fetches a dictionary containing - Total Signal Weight Needed @@ -1117,8 +1121,8 @@ def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, corrected_total_signal_needed = self.apply_weak_strong_overrides( parameter_value=total_signal_needed.value, parameter_min_value=self.min_trend_total_signal_needed_value, - parameter_max_value=self.max_weighted_signal_value * number_of_weighted_signals, - parameter_threshold=self.search_threshold_weighted_signal_values * number_of_weighted_signals + parameter_max_value=self.max_total_weight_possible[f'{space}_{signal_type}'], + parameter_threshold=self.max_total_threshold_possible[f'{space}_{signal_type}'] ) / self.precision corrected_total_triggers_needed = self.apply_weak_strong_overrides( @@ -1128,8 +1132,6 @@ def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, parameter_threshold=self.search_threshold_trend_signal_triggers_needed ) / self.precision - #print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_signal_needed:{corrected_total_signal_needed}') - #print(f'DEBUG --- {metadata}_{space}_{signal_type}_{trend}_corrected_total_triggers_needed:{corrected_total_triggers_needed}') return {'signal_needed': corrected_total_signal_needed, 'triggers_needed': corrected_total_triggers_needed} def apply_weak_strong_overrides(self, parameter_value, @@ -1234,11 +1236,10 @@ def register_signal_attr(cls, base_cls, name: str, signal_type: str, space: str parameter_name = f'{signal_type}_{trend}_trend_{name}_weight' if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: signals = getattr(cls, f'{space}_{signal_type}') - signal_attributes = cls.calculate_signal_attr(cls, name, signals[name]['min'], signals[name]['max']) cls._init_vars(base_cls, space=space, parameter_name=parameter_name, - parameter_min_value=signal_attributes['min_value'], - parameter_max_value=signal_attributes['max_value'], - parameter_threshold=signal_attributes['threshold'], + parameter_min_value=signals[name]['min'], + parameter_max_value=signals[name]['max'], + parameter_threshold=signals[name]['threshold'], precision=cls.precision, overrideable=True) @classmethod @@ -1262,8 +1263,6 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val :return: None """ - #print(f'DEBUG --- _init_vars -- START space : {space} parameter_name : {parameter_name} min : {parameter_min_value}, max : {parameter_max_value}, parameter_threshold : {parameter_threshold}, overrideable : {overrideable}') - # Narrow the search spaces for overrideable parameters by default override_parameter_min_value = parameter_min_value override_parameter_max_value = parameter_max_value @@ -1333,13 +1332,10 @@ def _init_vars(cls, base_cls, space: str, parameter_name: str, parameter_min_val optimize = True # In case max <= min value no need to optimize, force default/max value to min - #print(f'DEBUG --- _init_vars -- MIDDLE parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}') if max_value <= min_value: default_value = max_value = min_value optimize = False - #print(f'DEBUG --- _init_vars -- END parameter_name : {parameter_name} min : {min_value}, max : {max_value}, default : {default_value}, optimize : {optimize}\n') - parameter_config = { 'min_value': min_value * precision, 'max_value': max_value * precision, @@ -1416,7 +1412,7 @@ def init_util_params(cls, base_cls): # Generate the utility attributes for the logic of the weighted_signal_spaces for trend in cls.mgm_trends: - for space in cls.list_of_signals_space: + for space in cls.list_of_signal_space: for signal_type in cls.list_of_signal_type: if cls.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: param_total_signal_needed = f'_{signal_type}_{trend}_trend_total_signal_needed' @@ -1441,8 +1437,8 @@ def init_util_params(cls, base_cls): cls._init_vars(base_cls=base_cls, space=space, parameter_name=param_total_signal_needed, parameter_min_value=min_total_signal_needed, - parameter_max_value=int(cls.max_weighted_signal_value * number_of_weighted_signals), - parameter_threshold=int(cls.search_threshold_weighted_signal_values* number_of_weighted_signals), + parameter_max_value=cls.max_total_weight_possible[f'{space}_{signal_type}'], + parameter_threshold=cls.max_total_threshold_possible[f'{space}_{signal_type}'], precision=cls.precision, overrideable=True) param_needed_candles_lookback_window = f'_{signal_type}_{trend}_trend_total_signal_needed_candles_lookback_window' @@ -1480,7 +1476,7 @@ def apply_attributes(base_cls): signals = {} signals.update({'buy':buy_signals}) signals.update({'sell':sell_signals}) - for space in base_cls.list_of_signals_space: + for space in base_cls.list_of_signal_space: for signal_type in base_cls.list_of_signal_type: valid_signals = {} for signal_name, signal_params in signals[space][signal_type].items(): @@ -1491,6 +1487,9 @@ def apply_attributes(base_cls): signal_params['max'] = base_cls.max_weighted_signal_value if signal_params['threshold'] is None: signal_params['threshold'] = base_cls.search_threshold_weighted_signal_values + + base_cls.max_total_weight_possible[f'{space}_{signal_type}'] += signal_params['max'] + base_cls.max_total_threshold_possible[f'{space}_{signal_type}'] += signal_params['threshold'] valid_signals.update({signal_name:signal_params}) # Set number of weighted buy/sell triggers and guards for later use. @@ -1533,30 +1532,27 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D signals = getattr(self, f'{space}_{signal_type}') for signal_name, signal_params in signals.items(): condition_func = signal_params['condition'] - signal_attributes = self.calculate_signal_attr(signal_name, signal_params['min'], signal_params["max"]) # Populate signal - self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_attributes['min_value'], - signal_max_value=signal_attributes['max_value'], signal_threshold=signal_attributes['threshold'], + self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_params['min'], + signal_max_value=signal_params["max"], signal_threshold=signal_params['threshold'], space=space, dataframe=dataframe, condition=condition_func(dataframe)) # Check if total signals needed & triggers needed are possible, if not force the bot to do nothing + is_valid_epoch = True for signal_type in self.list_of_signal_type: number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_{signal_type}')) if self.is_dry_live_run_detected is False: for trend in self.mgm_trends: if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: corrected_totals = self.get_corrected_totals_needed( - space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals, metadata=metadata) - + space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals) + + is_valid_trend = True if ((self.total_signals_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['signal_needed']) or (self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['triggers_needed'])): - self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] = False - #print(f'DEBUG --- _populate_trend -- {metadata}_{space}_{signal_type}_{trend}_trading_during_trends = False') + is_valid_trend = False - is_valid_epoch = False - # if at leat one trend of the current space is tradable - for trend in self.mgm_trends: - is_valid_epoch = (self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True or is_valid_epoch) + is_valid_epoch = (is_valid_epoch and is_valid_trend) if is_valid_epoch: # Generates the conditions responsible for searching and comparing the weights needed to activate a buy or sell @@ -1576,7 +1572,7 @@ def init_hyperopt_epoch(self) -> None: # Reset the total signals and triggers possible for trend in self.mgm_trends: - for space in self.list_of_signals_space: + for space in self.list_of_signal_space: for signal_type in self.list_of_signal_type: self.total_signals_possible[f'{space}_{signal_type}_{trend}'] = 0 self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] = 0 @@ -1592,28 +1588,3 @@ def init_hyperopt_epoch(self) -> None: separator_window = (self.separator / 1) - (1 / self.separator) self.separator_candle_weight_reducer = separator_window / self.get_param_value( 'sell___unclogger_trend_lookback_candles_window') - - def calculate_signal_attr(self, signal_name: str, min_weight: int=None, max_weight: int=None) -> dict: - """ - Calculates the min / max / threshold values for a signal given some forced values. - If none then apply default config values - :param signal_name: Signal name - :param min_weight: forced min weight value for the signal - :param max_weight : forced min weight value for the signal - :return: (dict) {min_value, max_value, threshold} - """ - - signal_min_value = self.min_weighted_signal_value - signal_max_value = self.max_weighted_signal_value - signal_threshold = self.search_threshold_weighted_signal_values - - if min_weight is not None: - signal_min_value = min_weight - if max_weight is not None: - signal_max_value = max_weight - - signal_threshold = min(signal_threshold,int((signal_max_value - signal_min_value) / 2)) - - #print(f'DEBUG --- calculate_signal_attr -- signal_name : {signal_name} min : {signal_min_value} max : {signal_max_value} threshold : {signal_threshold}') - - return {'min_value' : signal_min_value, 'max_value' : signal_max_value, 'threshold' : signal_threshold} diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index 79eb192f3..c5974ee3b 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -13,7 +13,10 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.constants import ListPairsWithTimeframes +from freqtrade.strategy import IntParameter,DecimalParameter +from finta import TA as fta +import pandas_ta as pta # Master Framework file must reside in same folder as Strategy file sys.path.append(str(Path(__file__).parent)) @@ -22,98 +25,96 @@ # Define the Weighted Buy Signals to be used by MGM buy_signals = { - 'triggers' : { + 'triggers': { # Weighted Buy Signal: Rolling VWAP crosses above current price 'rolling_vwap_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: Price crosses above Parabolic SAR 'sar_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: SMA long term Golden Cross (Medium term SMA crosses above Long term SMA) - 'sma_long_golden_cross': { + 'sma_long_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: SMA short term Golden Cross (Short term SMA crosses above Medium term SMA) - 'sma_short_golden_cross': { + 'sma_short_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, - }, - 'guards' : { + 'guards': { # Weighted Buy Signal: MACD above Signal 'macd': { 'condition': lambda df: (df['macd'] > df['macdsignal']), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: MFI under 20 (Under-bought / low-price and rising indication) 'mfi': { 'condition': lambda df: (df['mfi'] <= 20), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: Stochastic Slow below 20 (Under-bought, indication of starting to move up) 'stoch': { 'condition': lambda df: (df['slowk'] < 20), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: TEMA increasing under BB middleband 'tema_bb': { 'condition': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, } } # Define the Weighted Sell Signals to be used by MGM sell_signals = { - 'triggers' : { + 'triggers': { # Weighted Sell Signal: Rolling VWAP crosses below current price 'rolling_vwap_cross': { 'condition': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: Price crosses below Parabolic SAR 'sar_cross': { 'condition': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: SMA long term Death Cross (Medium term SMA crosses below Long term SMA) - 'sma_long_death_cross': { + 'sma_long_cross': { 'condition': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: SMA short term Death Cross (Short term SMA crosses below Medium term SMA) - 'sma_short_death_cross': { + 'sma_short_cross': { 'condition': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, - }, - 'guards' : { + 'guards': { # Weighted Sell Signal: MACD below Signal 'macd': { 'condition': lambda df: (df['macd'] < df['macdsignal']), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: MFI above 80 (Over-bought / high-price and dropping indication) 'mfi': { 'condition': lambda df: (df['mfi'] >= 80), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: Stochastic Slow above 80 (Over-bought, indication of starting to move down) 'stoch': { 'condition': lambda df: (df['slowk'] > 80), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: TEMA decreasing over BB middleband 'tema_bb': { 'condition': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), - 'min_weight' : None, 'max_weight' : None, + 'min': 0, 'max': 20, 'threshold': 2 }, } } @@ -152,7 +153,7 @@ class MoniGoManiHyperStrategy(MasterMoniGoManiHyperStrategy): """ # Strategy interface version - allow new iterations of the strategy interface. - # Check the Freqtrade documentation, or it's Sample strategy to get the latest version. + # Check the Freqtrade documentation or it's Sample strategy to get the latest version. INTERFACE_VERSION = 2 # Plot configuration to show all Weighted Signals/Indicators used by MoniGoMani in FreqUI. @@ -218,7 +219,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Adds several TA indicators to MoniGoMani's DataFrame, per pair. + Adds several different TA indicators to MoniGoMani's DataFrame per pair. Should be called with 'informative_pair' (1h candles) during backtesting/hyperopting with TimeFrame-Zoom! Performance Note: For the best performance be frugal on the number of indicators you are using. @@ -234,41 +235,73 @@ def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFr # ------------------- # Parabolic SAR - dataframe['sar'] = ta.SAR(dataframe) + if (buy_signals['triggers']['sar_cross']['max'] is None + or buy_signals['triggers']['sar_cross']['max'] > 0 + or sell_signals['triggers']['sar_cross']['max'] is None + or sell_signals['triggers']['sar_cross']['max'] > 0): + dataframe['sar'] = ta.SAR(dataframe) # Stochastic Slow - stoch = ta.STOCH(dataframe) - dataframe['slowk'] = stoch['slowk'] + if (buy_signals['guards']['stoch']['max'] is None + or buy_signals['guards']['stoch']['max'] > 0 + or sell_signals['guards']['stoch']['max'] is None + or sell_signals['guards']['stoch']['max'] > 0): + stoch = ta.STOCH(dataframe) + dataframe['slowk'] = stoch['slowk'] # MACD - Moving Average Convergence Divergence - macd = ta.MACD(dataframe) - dataframe['macd'] = macd['macd'] # MACD - Blue TradingView Line (Bullish if on top) - dataframe['macdsignal'] = macd['macdsignal'] # Signal - Orange TradingView Line (Bearish if on top) + if (buy_signals['guards']['macd']['max'] is None + or buy_signals['guards']['macd']['max'] > 0 + or sell_signals['guards']['macd']['max'] is None + or sell_signals['guards']['macd']['max'] > 0): + macd = ta.MACD(dataframe) + dataframe['macd'] = macd['macd'] # MACD - Blue TradingView Line (Bullish if on top) + dataframe['macdsignal'] = macd['macdsignal'] # Signal - Orange TradingView Line (Bearish if on top) # MFI - Money Flow Index (Under bought / Over sold & Over bought / Under sold / volume Indicator) - dataframe['mfi'] = ta.MFI(dataframe) + if (buy_signals['guards']['mfi']['max'] is None + or buy_signals['guards']['mfi']['max'] > 0 + or sell_signals['guards']['mfi']['max'] is None + or sell_signals['guards']['mfi']['max'] > 0): + dataframe['mfi'] = ta.MFI(dataframe) # Overlap Studies # --------------- - # Bollinger Bands - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) - dataframe['bb_middleband'] = bollinger['mid'] - - # SMA's & EMA's are trend following tools (Should not be used when line goes sideways) - # SMA - Simple Moving Average (Moves slower compared to EMA, price trend over X periods) - dataframe['sma9'] = ta.SMA(dataframe, timeperiod=9) - dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50) - dataframe['sma200'] = ta.SMA(dataframe, timeperiod=200) + if (buy_signals['guards']['tema_bb']['max'] is None + or buy_signals['guards']['tema_bb']['max'] > 0 + or sell_signals['guards']['tema_bb']['max'] is None + or sell_signals['guards']['tema_bb']['max'] > 0): + # Bollinger Bands + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_middleband'] = bollinger['mid'] + # TEMA - Triple Exponential Moving Average + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + if (buy_signals['triggers']['sma_short_cross']['max'] is None + or buy_signals['triggers']['sma_short_cross']['max'] > 0 + or buy_signals['triggers']['sma_long_cross']['max'] is None + or buy_signals['triggers']['sma_long_cross']['max'] > 0 + or sell_signals['triggers']['sma_short_cross']['max'] is None + or sell_signals['triggers']['sma_short_cross']['max'] > 0 + or sell_signals['triggers']['sma_long_cross']['max'] is None + or sell_signals['triggers']['sma_long_cross']['max'] > 0): + # SMA's & EMA's are trend following tools (Should not be used when line goes sideways) + # SMA - Simple Moving Average (Moves slower compared to EMA, price trend over X periods) + dataframe['sma9'] = ta.SMA(dataframe, timeperiod=9) + dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50) + dataframe['sma200'] = ta.SMA(dataframe, timeperiod=200) - # TEMA - Triple Exponential Moving Average - dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) # Volume Indicators # ----------------- - # Rolling VWAP - Volume Weighted Average Price - dataframe['rolling_vwap'] = qtpylib.rolling_vwap(dataframe) + if (buy_signals['triggers']['rolling_vwap_cross']['max'] is None + or buy_signals['triggers']['rolling_vwap_cross']['max'] > 0 + or sell_signals['triggers']['rolling_vwap_cross']['max'] is None + or sell_signals['triggers']['rolling_vwap_cross']['max'] > 0): + # Rolling VWAP - Volume Weighted Average Price + dataframe['rolling_vwap'] = qtpylib.rolling_vwap(dataframe) return dataframe From 9ad503ac7f17ae780c2e69d0aae15a9d9b277581 Mon Sep 17 00:00:00 2001 From: raftersvk Date: Sat, 22 Jan 2022 14:33:19 +0100 Subject: [PATCH 09/17] sample mgm-config for better HO results --- user_data/mgm-config.example.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/user_data/mgm-config.example.json b/user_data/mgm-config.example.json index 777efbc6e..37435b833 100644 --- a/user_data/mgm-config.example.json +++ b/user_data/mgm-config.example.json @@ -17,15 +17,15 @@ "sell_trades_when_upwards": true }, "weighted_signal_spaces": { - "sell_profit_only": true, + "sell_profit_only": false, "min_weighted_signal_value": 0, - "max_weighted_signal_value": 100, - "min_trend_total_signal_needed_value": 60, + "max_weighted_signal_value": 20, + "min_trend_total_signal_needed_value": 10, "min_trend_total_signal_needed_candles_lookback_window_value": 1, "max_trend_total_signal_needed_candles_lookback_window_value": 8, - "min_trend_signal_triggers_needed": 2, - "search_threshold_weighted_signal_values": 22, - "search_threshold_trend_total_signal_needed_candles_lookback_window_value": 1, + "min_trend_signal_triggers_needed": 1, + "search_threshold_weighted_signal_values": 2, + "search_threshold_trend_total_signal_needed_candles_lookback_window_value": 3, "search_threshold_trend_signal_triggers_needed": 1 }, "stoploss_spaces": { @@ -161,13 +161,13 @@ } }, "dry_run": true, - "dry_run_wallet": 500, + "dry_run_wallet": 1000, "max_open_trades": -1, "stake_currency": "USDT", - "stake_amount": 45, + "stake_amount": 300, "tradable_balance_ratio": 0.99, "amount_reserve_percent": 0.05, - "amend_last_stake_amount": false, + "amend_last_stake_amount": true, "last_stake_amount_min_ratio": 0.5, "cancel_open_orders_on_exit": false, "use_sell_signal": true, From e8e95275ad679fdcf9723edd14b7c4f885597a65 Mon Sep 17 00:00:00 2001 From: raftersvk <48151933+raftersvk@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:58:18 +0100 Subject: [PATCH 10/17] Remove unneeded finta import --- user_data/strategies/MoniGoManiHyperStrategy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index c5974ee3b..935e8aaec 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -15,7 +15,6 @@ from freqtrade.constants import ListPairsWithTimeframes from freqtrade.strategy import IntParameter,DecimalParameter -from finta import TA as fta import pandas_ta as pta # Master Framework file must reside in same folder as Strategy file From 0e0bcba31d38c1a0aa505e94cf22571cc94c4659 Mon Sep 17 00:00:00 2001 From: Rikj000 Date: Sun, 30 Jan 2022 13:38:42 +0000 Subject: [PATCH 11/17] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Optimized=20imports?= =?UTF-8?q?=20&=20fixed=20indentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategies/MoniGoManiHyperStrategy.py | 67 +++++++++---------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index 935e8aaec..fcbea4da2 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -13,9 +13,6 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.constants import ListPairsWithTimeframes -from freqtrade.strategy import IntParameter,DecimalParameter - -import pandas_ta as pta # Master Framework file must reside in same folder as Strategy file sys.path.append(str(Path(__file__).parent)) @@ -27,45 +24,45 @@ 'triggers': { # Weighted Buy Signal: Rolling VWAP crosses above current price 'rolling_vwap_cross': { - 'condition': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: Price crosses above Parabolic SAR 'sar_cross': { - 'condition': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: SMA long term Golden Cross (Medium term SMA crosses above Long term SMA) 'sma_long_cross': { - 'condition': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: SMA short term Golden Cross (Short term SMA crosses above Medium term SMA) 'sma_short_cross': { - 'condition': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), + 'min': 0, 'max': 20, 'threshold': 2 }, }, 'guards': { # Weighted Buy Signal: MACD above Signal 'macd': { - 'condition': lambda df: (df['macd'] > df['macdsignal']), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['macd'] > df['macdsignal']), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: MFI under 20 (Under-bought / low-price and rising indication) 'mfi': { - 'condition': lambda df: (df['mfi'] <= 20), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['mfi'] <= 20), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: Stochastic Slow below 20 (Under-bought, indication of starting to move up) 'stoch': { - 'condition': lambda df: (df['slowk'] < 20), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['slowk'] < 20), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: TEMA increasing under BB middleband 'tema_bb': { - 'condition': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), + 'min': 0, 'max': 20, 'threshold': 2 }, } } @@ -75,45 +72,45 @@ 'triggers': { # Weighted Sell Signal: Rolling VWAP crosses below current price 'rolling_vwap_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: Price crosses below Parabolic SAR 'sar_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: SMA long term Death Cross (Medium term SMA crosses below Long term SMA) 'sma_long_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: SMA short term Death Cross (Short term SMA crosses below Medium term SMA) 'sma_short_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), + 'min': 0, 'max': 20, 'threshold': 2 }, }, 'guards': { # Weighted Sell Signal: MACD below Signal 'macd': { - 'condition': lambda df: (df['macd'] < df['macdsignal']), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['macd'] < df['macdsignal']), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: MFI above 80 (Over-bought / high-price and dropping indication) 'mfi': { - 'condition': lambda df: (df['mfi'] >= 80), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['mfi'] >= 80), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: Stochastic Slow above 80 (Over-bought, indication of starting to move down) 'stoch': { - 'condition': lambda df: (df['slowk'] > 80), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['slowk'] > 80), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: TEMA decreasing over BB middleband 'tema_bb': { - 'condition': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), + 'min': 0, 'max': 20, 'threshold': 2 }, } } From 6439b51f22a3444b641265b60925494e94b47b20 Mon Sep 17 00:00:00 2001 From: Rikj000 Date: Sun, 30 Jan 2022 14:21:02 +0000 Subject: [PATCH 12/17] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Improved=20nested=20?= =?UTF-8?q?if-or=20block=20notation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategies/MoniGoManiHyperStrategy.py | 80 +++++++++++-------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index fcbea4da2..5236e4c3a 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -215,7 +215,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Adds several different TA indicators to MoniGoMani's DataFrame per pair. + Adds multiple TA indicators to MoniGoMani's DataFrame per pair. Should be called with 'informative_pair' (1h candles) during backtesting/hyperopting with TimeFrame-Zoom! Performance Note: For the best performance be frugal on the number of indicators you are using. @@ -231,57 +231,69 @@ def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFr # ------------------- # Parabolic SAR - if (buy_signals['triggers']['sar_cross']['max'] is None - or buy_signals['triggers']['sar_cross']['max'] > 0 - or sell_signals['triggers']['sar_cross']['max'] is None - or sell_signals['triggers']['sar_cross']['max'] > 0): + if ( + buy_signals['triggers']['sar_cross']['max'] is None or + buy_signals['triggers']['sar_cross']['max'] > 0 or + sell_signals['triggers']['sar_cross']['max'] is None or + sell_signals['triggers']['sar_cross']['max'] > 0 + ): dataframe['sar'] = ta.SAR(dataframe) # Stochastic Slow - if (buy_signals['guards']['stoch']['max'] is None - or buy_signals['guards']['stoch']['max'] > 0 - or sell_signals['guards']['stoch']['max'] is None - or sell_signals['guards']['stoch']['max'] > 0): + if ( + buy_signals['guards']['stoch']['max'] is None or + buy_signals['guards']['stoch']['max'] > 0 or + sell_signals['guards']['stoch']['max'] is None or + sell_signals['guards']['stoch']['max'] > 0 + ): stoch = ta.STOCH(dataframe) dataframe['slowk'] = stoch['slowk'] # MACD - Moving Average Convergence Divergence - if (buy_signals['guards']['macd']['max'] is None - or buy_signals['guards']['macd']['max'] > 0 - or sell_signals['guards']['macd']['max'] is None - or sell_signals['guards']['macd']['max'] > 0): + if ( + buy_signals['guards']['macd']['max'] is None or + buy_signals['guards']['macd']['max'] > 0 or + sell_signals['guards']['macd']['max'] is None or + sell_signals['guards']['macd']['max'] > 0 + ): macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] # MACD - Blue TradingView Line (Bullish if on top) dataframe['macdsignal'] = macd['macdsignal'] # Signal - Orange TradingView Line (Bearish if on top) # MFI - Money Flow Index (Under bought / Over sold & Over bought / Under sold / volume Indicator) - if (buy_signals['guards']['mfi']['max'] is None - or buy_signals['guards']['mfi']['max'] > 0 - or sell_signals['guards']['mfi']['max'] is None - or sell_signals['guards']['mfi']['max'] > 0): + if ( + buy_signals['guards']['mfi']['max'] is None or + buy_signals['guards']['mfi']['max'] > 0 or + sell_signals['guards']['mfi']['max'] is None or + sell_signals['guards']['mfi']['max'] > 0 + ): dataframe['mfi'] = ta.MFI(dataframe) # Overlap Studies # --------------- - if (buy_signals['guards']['tema_bb']['max'] is None - or buy_signals['guards']['tema_bb']['max'] > 0 - or sell_signals['guards']['tema_bb']['max'] is None - or sell_signals['guards']['tema_bb']['max'] > 0): + if ( + buy_signals['guards']['tema_bb']['max'] is None or + buy_signals['guards']['tema_bb']['max'] > 0 or + sell_signals['guards']['tema_bb']['max'] is None or + sell_signals['guards']['tema_bb']['max'] > 0 + ): # Bollinger Bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_middleband'] = bollinger['mid'] # TEMA - Triple Exponential Moving Average dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) - if (buy_signals['triggers']['sma_short_cross']['max'] is None - or buy_signals['triggers']['sma_short_cross']['max'] > 0 - or buy_signals['triggers']['sma_long_cross']['max'] is None - or buy_signals['triggers']['sma_long_cross']['max'] > 0 - or sell_signals['triggers']['sma_short_cross']['max'] is None - or sell_signals['triggers']['sma_short_cross']['max'] > 0 - or sell_signals['triggers']['sma_long_cross']['max'] is None - or sell_signals['triggers']['sma_long_cross']['max'] > 0): + if ( + buy_signals['triggers']['sma_short_cross']['max'] is None or + buy_signals['triggers']['sma_short_cross']['max'] > 0 or + buy_signals['triggers']['sma_long_cross']['max'] is None or + buy_signals['triggers']['sma_long_cross']['max'] > 0 or + sell_signals['triggers']['sma_short_cross']['max'] is None or + sell_signals['triggers']['sma_short_cross']['max'] > 0 or + sell_signals['triggers']['sma_long_cross']['max'] is None or + sell_signals['triggers']['sma_long_cross']['max'] > 0 + ): # SMA's & EMA's are trend following tools (Should not be used when line goes sideways) # SMA - Simple Moving Average (Moves slower compared to EMA, price trend over X periods) dataframe['sma9'] = ta.SMA(dataframe, timeperiod=9) @@ -292,10 +304,12 @@ def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFr # Volume Indicators # ----------------- - if (buy_signals['triggers']['rolling_vwap_cross']['max'] is None - or buy_signals['triggers']['rolling_vwap_cross']['max'] > 0 - or sell_signals['triggers']['rolling_vwap_cross']['max'] is None - or sell_signals['triggers']['rolling_vwap_cross']['max'] > 0): + if ( + buy_signals['triggers']['rolling_vwap_cross']['max'] is None or + buy_signals['triggers']['rolling_vwap_cross']['max'] > 0 or + sell_signals['triggers']['rolling_vwap_cross']['max'] is None or + sell_signals['triggers']['rolling_vwap_cross']['max'] > 0 + ): # Rolling VWAP - Volume Weighted Average Price dataframe['rolling_vwap'] = qtpylib.rolling_vwap(dataframe) From 3d51d813739951111c6cf7808a079b8fa9014ec4 Mon Sep 17 00:00:00 2001 From: Rikj000 Date: Sun, 30 Jan 2022 14:22:42 +0000 Subject: [PATCH 13/17] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Fixed=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- user_data/strategies/MoniGoManiHyperStrategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index 5236e4c3a..29ffe818d 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -149,7 +149,7 @@ class MoniGoManiHyperStrategy(MasterMoniGoManiHyperStrategy): """ # Strategy interface version - allow new iterations of the strategy interface. - # Check the Freqtrade documentation or it's Sample strategy to get the latest version. + # Check the Freqtrade documentation, or it's Sample strategy to get the latest version. INTERFACE_VERSION = 2 # Plot configuration to show all Weighted Signals/Indicators used by MoniGoMani in FreqUI. From cd66d38b9bf0103e842167707aab117905ad3675 Mon Sep 17 00:00:00 2001 From: Rikj000 Date: Sun, 30 Jan 2022 16:16:07 +0000 Subject: [PATCH 14/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Some=20minor=20refac?= =?UTF-8?q?tors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategies/MasterMoniGoManiHyperStrategy.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index 2227814d4..9c212bb3a 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -1182,7 +1182,7 @@ def _add_signal(self, signal_name: str, signal_type: str, signal_min_value: int, if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: parameter_name = f'{space}_{signal_type}_{trend}_trend_{signal_name}_weight' signal_weight = getattr(self, parameter_name) - + # Apply signal overrides to weak and strong signals where needed signal_weight_value = self.apply_weak_strong_overrides( signal_weight.value, signal_min_value, signal_max_value, signal_threshold) @@ -1422,16 +1422,16 @@ def init_util_params(cls, base_cls): parameter_dictionary = getattr(cls, f'{space}_params') parameter_value = parameter_dictionary.get(f'{space}_{param_total_signal_needed}') if parameter_value is None: - #1st Hyperopt + # 1st Hyperopt if cls.min_trend_total_signal_needed_value < (cls.search_threshold_weighted_signal_values*number_of_weighted_signals): min_total_signal_needed = 0 else: min_total_signal_needed = (cls.min_trend_total_signal_needed_value - - (cls.search_threshold_weighted_signal_values* number_of_weighted_signals)) + (cls.search_threshold_weighted_signal_values* number_of_weighted_signals)) min_triggers_needed = (cls.min_trend_signal_triggers_needed_value - - cls.search_threshold_trend_signal_triggers_needed) + cls.search_threshold_trend_signal_triggers_needed) else: - #2nd Hyperopt + # 2nd Hyperopt min_total_signal_needed = cls.min_trend_total_signal_needed_value min_triggers_needed = cls.min_trend_signal_triggers_needed_value @@ -1534,8 +1534,8 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D condition_func = signal_params['condition'] # Populate signal self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_params['min'], - signal_max_value=signal_params["max"], signal_threshold=signal_params['threshold'], - space=space, dataframe=dataframe, condition=condition_func(dataframe)) + signal_max_value=signal_params['max'], signal_threshold=signal_params['threshold'], + space=space, dataframe=dataframe, condition=condition_func(dataframe)) # Check if total signals needed & triggers needed are possible, if not force the bot to do nothing is_valid_epoch = True From 314eb595ed9b3feab3579866a756c201542c2bb9 Mon Sep 17 00:00:00 2001 From: raftersvk Date: Wed, 2 Feb 2022 21:50:34 +0100 Subject: [PATCH 15/17] bug correction + improvments on populating dataframe --- .../MasterMoniGoManiHyperStrategy.py | 124 ++++++++++-------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index 2227814d4..455482eaf 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -29,7 +29,6 @@ logger = logging.getLogger(__name__) - # --- ↑ Do not remove these libs ↑ ------------------------------------------------------------------------------------- @@ -212,14 +211,13 @@ class MasterMoniGoManiHyperStrategy(IStrategy, ABC): separator_candle_weight_reducer = 0.03 # Gets set automatically # Initialize comparison values to check if total signals utilized by HyperOpt are possible + min_total_weight_possible = {} max_total_weight_possible = {} max_total_threshold_possible = {} total_signals_possible = {} total_triggers_possible = {} for space in list_of_signal_space: for signal_type in list_of_signal_type: - max_total_weight_possible[f'{space}_{signal_type}'] = 0 - max_total_threshold_possible[f'{space}_{signal_type}'] = 0 for trend in mgm_trends: total_signals_possible[f'{space}_{signal_type}_{trend}'] = 0 total_triggers_possible[f'{space}_{signal_type}_{trend}'] = 0 @@ -1120,7 +1118,7 @@ def get_corrected_totals_needed(self, space: str, trend: str, signal_type: str, corrected_total_signal_needed = self.apply_weak_strong_overrides( parameter_value=total_signal_needed.value, - parameter_min_value=self.min_trend_total_signal_needed_value, + parameter_min_value=self.min_total_weight_possible[f'{space}_{signal_type}'], parameter_max_value=self.max_total_weight_possible[f'{space}_{signal_type}'], parameter_threshold=self.max_total_threshold_possible[f'{space}_{signal_type}'] ) / self.precision @@ -1182,7 +1180,7 @@ def _add_signal(self, signal_name: str, signal_type: str, signal_min_value: int, if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: parameter_name = f'{space}_{signal_type}_{trend}_trend_{signal_name}_weight' signal_weight = getattr(self, parameter_name) - + # Apply signal overrides to weak and strong signals where needed signal_weight_value = self.apply_weak_strong_overrides( signal_weight.value, signal_min_value, signal_max_value, signal_threshold) @@ -1191,29 +1189,31 @@ def _add_signal(self, signal_name: str, signal_type: str, signal_min_value: int, rolling_needed_value = (rolling_needed.value * self.timeframe_multiplier if has_multiplier else rolling_needed.value) - # If debuggable weighted signal dataframe => Add individual per signal rows in the dataframe - if self.debuggable_weighted_signal_dataframe: - if parameter_name not in dataframe.columns: - dataframe[parameter_name] = 0 - - dataframe.loc[((dataframe['trend'] == trend) & (condition.rolling(rolling_needed_value).sum() > 0)), - parameter_name] = signal_weight_value / self.precision + # Add individual signal column in the dataframe + if parameter_name not in dataframe.columns: + dataframe[parameter_name] = 0 + # Check that signal condition is met (without checking the trend) and apply the signal weight for the given trend + dataframe.loc[condition == True,parameter_name] = signal_weight_value / self.precision # If the weighted signal triggered => Add the weight to the totals needed in the dataframe - dataframe.loc[((dataframe['trend'] == trend) & (condition.rolling(rolling_needed_value).sum() > 0)), + dataframe.loc[((dataframe['trend'] == trend) & dataframe[parameter_name].rolling(rolling_needed_value).sum() > 0), f'total_{space}_{signal_type}_strength'] += signal_weight_value / self.precision # If the weighted signal is bigger than 0 and triggered => Add up the amount of signals that triggered if signal_weight_value > 0: - dataframe.loc[((dataframe['trend'] == trend) & (condition.rolling(rolling_needed_value).sum() > 0)), + dataframe.loc[((dataframe['trend'] == trend) & dataframe[parameter_name].rolling(rolling_needed_value).sum() > 0), f'{space}_{signal_type}_triggered'] += 1 - # Add found weights to comparison values to check if total signals utilized by HyperOpt are possible - self.total_signals_possible[f'{space}_{signal_type}_{trend}'] += signal_weight_value - # Add a signal trigger if it is possible to compare if total triggers needed by HyperOpt are possible if signal_weight_value > 0: + # Add found weights to comparison values to check if total signals utilized by HyperOpt are possible + self.total_signals_possible[f'{space}_{signal_type}_{trend}'] += signal_weight_value + # Add a signal trigger if it is possible to compare if total triggers needed by HyperOpt are possible self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] += 1 + # If not debuggable weighted signal dataframe : remove individual signal columns + if not self.debuggable_weighted_signal_dataframe: + dataframe.drop(parameter_name, axis = 1) + # Override Signals: When configured sell/buy signals can be completely turned off for each kind of trend else: dataframe.loc[dataframe['trend'] == trend, space] = 0 @@ -1476,9 +1476,16 @@ def apply_attributes(base_cls): signals = {} signals.update({'buy':buy_signals}) signals.update({'sell':sell_signals}) + + min_total_weight = {} + max_total_weight = {} + max_total_threshold = {} for space in base_cls.list_of_signal_space: for signal_type in base_cls.list_of_signal_type: valid_signals = {} + min_total_weight[f'{space}_{signal_type}'] = 0 + max_total_weight[f'{space}_{signal_type}'] = 0 + max_total_threshold[f'{space}_{signal_type}'] = 0 for signal_name, signal_params in signals[space][signal_type].items(): if signal_params['max'] is None or signal_params['max'] > 0 : if signal_params['min'] is None: @@ -1488,13 +1495,19 @@ def apply_attributes(base_cls): if signal_params['threshold'] is None: signal_params['threshold'] = base_cls.search_threshold_weighted_signal_values - base_cls.max_total_weight_possible[f'{space}_{signal_type}'] += signal_params['max'] - base_cls.max_total_threshold_possible[f'{space}_{signal_type}'] += signal_params['threshold'] + min_total_weight[f'{space}_{signal_type}'] += signal_params['min'] + max_total_weight[f'{space}_{signal_type}'] += signal_params['max'] + max_total_threshold[f'{space}_{signal_type}'] += signal_params['threshold'] valid_signals.update({signal_name:signal_params}) # Set number of weighted buy/sell triggers and guards for later use. setattr(base_cls, f'number_of_weighted_{space}_{signal_type}', len(valid_signals)) + # Set min/max/threshold totals for later use + setattr(base_cls, f'min_total_weight_possible', min_total_weight) + setattr(base_cls, f'max_total_weight_possible', max_total_weight) + setattr(base_cls, f'max_total_threshold_possible', max_total_threshold) + # Registering signals attributes on class setattr(base_cls, f'{space}_{signal_type}', valid_signals) for name in valid_signals: @@ -1520,46 +1533,51 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D if (self.is_dry_live_run_detected is False) and (space == 'buy'): self.init_hyperopt_epoch() - # Initialize total signal and signals triggered columns (should be 0 = false by default) - if f'total_{space}_triggers_strength' not in dataframe.columns: - dataframe[f'total_{space}_triggers_strength'] = dataframe[f'total_{space}_guards_strength'] = 0 - if f'{space}_triggers_triggered' not in dataframe.columns: - dataframe[f'{space}_triggers_triggered'] = dataframe[f'{space}_guards_triggered'] = 0 - # Calculates the weight and/or generates the debug column for each signal - for signal_type in self.list_of_signal_type: - # Fetch the weighted signals used - signals = getattr(self, f'{space}_{signal_type}') - for signal_name, signal_params in signals.items(): - condition_func = signal_params['condition'] - # Populate signal - self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_params['min'], - signal_max_value=signal_params["max"], signal_threshold=signal_params['threshold'], - space=space, dataframe=dataframe, condition=condition_func(dataframe)) - - # Check if total signals needed & triggers needed are possible, if not force the bot to do nothing - is_valid_epoch = True - for signal_type in self.list_of_signal_type: - number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_{signal_type}')) + is_valid_epoch = False + if space == "buy" or (space == "sell" and sum(dataframe['buy'] > 0) > 0): + for signal_type in self.list_of_signal_type: + for trend in self.mgm_trends: + if f'total_{space}_{signal_type}_strength' not in dataframe.columns: + dataframe[f'total_{space}_{signal_type}_strength'] = 0 + if f'{space}_{signal_type}_triggered' not in dataframe.columns: + dataframe[f'{space}_{signal_type}_triggered'] = 0 + + # Fetch the weighted signals used + signals = getattr(self, f'{space}_{signal_type}') + for signal_name, signal_params in signals.items(): + condition_func = signal_params['condition'] + # Populate signal + self._add_signal(signal_name=signal_name, signal_type=signal_type, signal_min_value=signal_params['min'], + signal_max_value=signal_params["max"], signal_threshold=signal_params['threshold'], + space=space, dataframe=dataframe, condition=condition_func(dataframe)) + + # Check if total signals needed & triggers needed are possible, if not force the bot to do nothing if self.is_dry_live_run_detected is False: + is_valid_epoch = True for trend in self.mgm_trends: - if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: - corrected_totals = self.get_corrected_totals_needed( - space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals) - - is_valid_trend = True - if ((self.total_signals_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['signal_needed']) or - (self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['triggers_needed'])): - is_valid_trend = False - - is_valid_epoch = (is_valid_epoch and is_valid_trend) - + for signal_type in self.list_of_signal_type: + number_of_weighted_signals = int(getattr(self, f'number_of_weighted_{space}_{signal_type}')) + if self.mgm_config['trading_during_trends'][f'{space}_trades_when_{trend}'] is True: + corrected_totals = self.get_corrected_totals_needed( + space=space, trend=trend, signal_type=signal_type, number_of_weighted_signals=number_of_weighted_signals) + + if ((self.total_signals_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['signal_needed']) or + (self.total_triggers_possible[f'{space}_{signal_type}_{trend}'] < corrected_totals['triggers_needed'])): + is_valid_trend = False + is_valid_epoch = (is_valid_epoch or is_valid_trend) + else: + is_valid_epoch = True + + # At least 1 buy trend (trigger+guard) and 1 sell trend (trigger+guard) should be correct for the buy&sell conditions to be calculated if is_valid_epoch: # Generates the conditions responsible for searching and comparing the weights needed to activate a buy or sell dataframe.loc[self._generate_weight_condition(dataframe=dataframe, space=space), space] = 1 - else: - dataframe['buy'] = dataframe['sell'] = 0 - + + # Check that the current epoch actually finds at leat 1 buy and 1 sell, if not punish the epoch + if space == "sell" and (sum(dataframe['buy'] > 0) == 0 or sum(dataframe['sell'] > 0) == 0): + dataframe["buy"] = dataframe["sell"] = 0 + return dataframe def init_hyperopt_epoch(self) -> None: From 2c76774f93f07be1d62a494f2c709a8af42d57db Mon Sep 17 00:00:00 2001 From: raftersvk Date: Thu, 3 Feb 2022 21:08:16 +0100 Subject: [PATCH 16/17] Add buy and sell triggers' names into buy_tag / exit_tag --- .../MasterMoniGoManiHyperStrategy.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index 66915ba0e..2c9582f5b 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -1204,6 +1204,13 @@ def _add_signal(self, signal_name: str, signal_type: str, signal_min_value: int, dataframe.loc[((dataframe['trend'] == trend) & dataframe[parameter_name].rolling(rolling_needed_value).sum() > 0), f'{space}_{signal_type}_triggered'] += 1 + # If the weighted signal is a 'trigger' and it actually triggered => Add the signal name to the 'reason' (used for info in 'buy_tag' and 'exit_tag') + if signal_type == "triggers": + if f'{space}_reason' not in dataframe.columns: + dataframe[f'{space}_reason'] = "" + dataframe.loc[((dataframe['trend'] == trend) & dataframe[parameter_name].rolling(rolling_needed_value).sum() > 0), + f'{space}_reason'] += f'__{signal_name}' + if signal_weight_value > 0: # Add found weights to comparison values to check if total signals utilized by HyperOpt are possible self.total_signals_possible[f'{space}_{signal_type}_{trend}'] += signal_weight_value @@ -1576,7 +1583,18 @@ def _populate_trend(self, space: str, dataframe: DataFrame, metadata: dict) -> D # Check that the current epoch actually finds at leat 1 buy and 1 sell, if not punish the epoch if space == "sell" and (sum(dataframe['buy'] > 0) == 0 or sum(dataframe['sell'] > 0) == 0): - dataframe["buy"] = dataframe["sell"] = 0 + dataframe['buy'] = 0 + dataframe['sell'] = 0 + else: + # Epoch is valid : Register buy and exit tag + if space == "sell": + tag = "exit_tag" + else : + tag = "buy_tag" + + # Fill the buy/exit tag and remove the temporary reason column + dataframe.loc[dataframe[space] == 1,tag] = f'{space}__' + dataframe['trend'] + dataframe[f'{space}_reason'] + dataframe.drop(f'{space}_reason', axis = 1) return dataframe From 0d94374d069ab4250c96e45449603e3c98a051b1 Mon Sep 17 00:00:00 2001 From: raftersvk Date: Tue, 15 Mar 2022 21:02:02 +0100 Subject: [PATCH 17/17] code beautifuling and small sell reason correction --- .../MasterMoniGoManiHyperStrategy.py | 2 +- .../strategies/MoniGoManiHyperStrategy.py | 136 ++++++++---------- 2 files changed, 62 insertions(+), 76 deletions(-) diff --git a/user_data/strategies/MasterMoniGoManiHyperStrategy.py b/user_data/strategies/MasterMoniGoManiHyperStrategy.py index 2c9582f5b..152122758 100644 --- a/user_data/strategies/MasterMoniGoManiHyperStrategy.py +++ b/user_data/strategies/MasterMoniGoManiHyperStrategy.py @@ -1024,7 +1024,7 @@ def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: f # Check if weighted signal is profitable if sell_profit_only is enabled in the weighted_signal_spaces if ((self.mgm_config['weighted_signal_spaces']['sell_profit_only'] is - True) and (sell_reason == 'sell_signal') and (trade.calc_profit_ratio(rate) < 0)): + True) and sell_reason.startswith('sell_') and (trade.calc_profit_ratio(rate) < 0)): return False # Check if ROI is enabled for the currently detected trend elif sell_reason == 'roi': diff --git a/user_data/strategies/MoniGoManiHyperStrategy.py b/user_data/strategies/MoniGoManiHyperStrategy.py index 29ffe818d..cad2cdcbd 100644 --- a/user_data/strategies/MoniGoManiHyperStrategy.py +++ b/user_data/strategies/MoniGoManiHyperStrategy.py @@ -25,44 +25,44 @@ # Weighted Buy Signal: Rolling VWAP crosses above current price 'rolling_vwap_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['rolling_vwap'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: Price crosses above Parabolic SAR 'sar_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['sar'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: SMA long term Golden Cross (Medium term SMA crosses above Long term SMA) 'sma_long_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['sma50'], df['sma200'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: SMA short term Golden Cross (Short term SMA crosses above Medium term SMA) 'sma_short_cross': { 'condition': lambda df: (qtpylib.crossed_above(df['sma9'], df['sma50'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'min': 0, 'max': 20, 'threshold': 2 }, }, 'guards': { # Weighted Buy Signal: MACD above Signal 'macd': { - 'condition': lambda df: (df['macd'] > df['macdsignal']), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['macd'] > df['macdsignal']), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: MFI under 20 (Under-bought / low-price and rising indication) 'mfi': { - 'condition': lambda df: (df['mfi'] <= 20), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['mfi'] <= 20), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: Stochastic Slow below 20 (Under-bought, indication of starting to move up) 'stoch': { - 'condition': lambda df: (df['slowk'] < 20), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['slowk'] < 20), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: TEMA increasing under BB middleband 'tema_bb': { - 'condition': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['tema'] <= df['bb_middleband']) & (df['tema'] > df['tema'].shift(1)), + 'min': 0, 'max': 20, 'threshold': 2 }, } } @@ -72,45 +72,45 @@ 'triggers': { # Weighted Sell Signal: Rolling VWAP crosses below current price 'rolling_vwap_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['rolling_vwap'], df['close'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: Price crosses below Parabolic SAR 'sar_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['sar'], df['close'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: SMA long term Death Cross (Medium term SMA crosses below Long term SMA) 'sma_long_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['sma50'], df['sma200'])), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: SMA short term Death Cross (Short term SMA crosses below Medium term SMA) 'sma_short_cross': { - 'condition': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (qtpylib.crossed_below(df['sma9'], df['sma50'])), + 'min': 0, 'max': 20, 'threshold': 2 }, }, 'guards': { # Weighted Sell Signal: MACD below Signal 'macd': { - 'condition': lambda df: (df['macd'] < df['macdsignal']), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['macd'] < df['macdsignal']), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: MFI above 80 (Over-bought / high-price and dropping indication) 'mfi': { - 'condition': lambda df: (df['mfi'] >= 80), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['mfi'] >= 80), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Sell Signal: Stochastic Slow above 80 (Over-bought, indication of starting to move down) 'stoch': { - 'condition': lambda df: (df['slowk'] > 80), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['slowk'] > 80), + 'min': 0, 'max': 20, 'threshold': 2 }, # Weighted Buy Signal: TEMA decreasing over BB middleband 'tema_bb': { - 'condition': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), - 'min': 0, 'max': 20, 'threshold': 2 + 'condition': lambda df: (df['tema'] > df['bb_middleband']) & (df['tema'] < df['tema'].shift(1)), + 'min': 0, 'max': 20, 'threshold': 2 }, } } @@ -149,7 +149,7 @@ class MoniGoManiHyperStrategy(MasterMoniGoManiHyperStrategy): """ # Strategy interface version - allow new iterations of the strategy interface. - # Check the Freqtrade documentation, or it's Sample strategy to get the latest version. + # Check the Freqtrade documentation or it's Sample strategy to get the latest version. INTERFACE_VERSION = 2 # Plot configuration to show all Weighted Signals/Indicators used by MoniGoMani in FreqUI. @@ -231,69 +231,57 @@ def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFr # ------------------- # Parabolic SAR - if ( - buy_signals['triggers']['sar_cross']['max'] is None or - buy_signals['triggers']['sar_cross']['max'] > 0 or - sell_signals['triggers']['sar_cross']['max'] is None or - sell_signals['triggers']['sar_cross']['max'] > 0 - ): + if (buy_signals['triggers']['sar_cross']['max'] is None + or buy_signals['triggers']['sar_cross']['max'] > 0 + or sell_signals['triggers']['sar_cross']['max'] is None + or sell_signals['triggers']['sar_cross']['max'] > 0): dataframe['sar'] = ta.SAR(dataframe) # Stochastic Slow - if ( - buy_signals['guards']['stoch']['max'] is None or - buy_signals['guards']['stoch']['max'] > 0 or - sell_signals['guards']['stoch']['max'] is None or - sell_signals['guards']['stoch']['max'] > 0 - ): + if (buy_signals['guards']['stoch']['max'] is None + or buy_signals['guards']['stoch']['max'] > 0 + or sell_signals['guards']['stoch']['max'] is None + or sell_signals['guards']['stoch']['max'] > 0): stoch = ta.STOCH(dataframe) dataframe['slowk'] = stoch['slowk'] # MACD - Moving Average Convergence Divergence - if ( - buy_signals['guards']['macd']['max'] is None or - buy_signals['guards']['macd']['max'] > 0 or - sell_signals['guards']['macd']['max'] is None or - sell_signals['guards']['macd']['max'] > 0 - ): + if (buy_signals['guards']['macd']['max'] is None + or buy_signals['guards']['macd']['max'] > 0 + or sell_signals['guards']['macd']['max'] is None + or sell_signals['guards']['macd']['max'] > 0): macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] # MACD - Blue TradingView Line (Bullish if on top) dataframe['macdsignal'] = macd['macdsignal'] # Signal - Orange TradingView Line (Bearish if on top) # MFI - Money Flow Index (Under bought / Over sold & Over bought / Under sold / volume Indicator) - if ( - buy_signals['guards']['mfi']['max'] is None or - buy_signals['guards']['mfi']['max'] > 0 or - sell_signals['guards']['mfi']['max'] is None or - sell_signals['guards']['mfi']['max'] > 0 - ): + if (buy_signals['guards']['mfi']['max'] is None + or buy_signals['guards']['mfi']['max'] > 0 + or sell_signals['guards']['mfi']['max'] is None + or sell_signals['guards']['mfi']['max'] > 0): dataframe['mfi'] = ta.MFI(dataframe) # Overlap Studies # --------------- - if ( - buy_signals['guards']['tema_bb']['max'] is None or - buy_signals['guards']['tema_bb']['max'] > 0 or - sell_signals['guards']['tema_bb']['max'] is None or - sell_signals['guards']['tema_bb']['max'] > 0 - ): + if (buy_signals['guards']['tema_bb']['max'] is None + or buy_signals['guards']['tema_bb']['max'] > 0 + or sell_signals['guards']['tema_bb']['max'] is None + or sell_signals['guards']['tema_bb']['max'] > 0): # Bollinger Bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_middleband'] = bollinger['mid'] # TEMA - Triple Exponential Moving Average dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) - if ( - buy_signals['triggers']['sma_short_cross']['max'] is None or - buy_signals['triggers']['sma_short_cross']['max'] > 0 or - buy_signals['triggers']['sma_long_cross']['max'] is None or - buy_signals['triggers']['sma_long_cross']['max'] > 0 or - sell_signals['triggers']['sma_short_cross']['max'] is None or - sell_signals['triggers']['sma_short_cross']['max'] > 0 or - sell_signals['triggers']['sma_long_cross']['max'] is None or - sell_signals['triggers']['sma_long_cross']['max'] > 0 - ): + if (buy_signals['triggers']['sma_short_cross']['max'] is None + or buy_signals['triggers']['sma_short_cross']['max'] > 0 + or buy_signals['triggers']['sma_long_cross']['max'] is None + or buy_signals['triggers']['sma_long_cross']['max'] > 0 + or sell_signals['triggers']['sma_short_cross']['max'] is None + or sell_signals['triggers']['sma_short_cross']['max'] > 0 + or sell_signals['triggers']['sma_long_cross']['max'] is None + or sell_signals['triggers']['sma_long_cross']['max'] > 0): # SMA's & EMA's are trend following tools (Should not be used when line goes sideways) # SMA - Simple Moving Average (Moves slower compared to EMA, price trend over X periods) dataframe['sma9'] = ta.SMA(dataframe, timeperiod=9) @@ -304,12 +292,10 @@ def do_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFr # Volume Indicators # ----------------- - if ( - buy_signals['triggers']['rolling_vwap_cross']['max'] is None or - buy_signals['triggers']['rolling_vwap_cross']['max'] > 0 or - sell_signals['triggers']['rolling_vwap_cross']['max'] is None or - sell_signals['triggers']['rolling_vwap_cross']['max'] > 0 - ): + if (buy_signals['triggers']['rolling_vwap_cross']['max'] is None + or buy_signals['triggers']['rolling_vwap_cross']['max'] > 0 + or sell_signals['triggers']['rolling_vwap_cross']['max'] is None + or sell_signals['triggers']['rolling_vwap_cross']['max'] > 0): # Rolling VWAP - Volume Weighted Average Price dataframe['rolling_vwap'] = qtpylib.rolling_vwap(dataframe)