from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter, stoploss_from_open import logging from pandas import DataFrame from freqtrade.resolvers import StrategyResolver from itertools import combinations from functools import reduce from freqtrade.persistence import Trade from datetime import datetime logger = logging.getLogger(__name__) # "AwesomeMacd", STRATEGIES = ['CombinedBinHAndCluc', 'CombinedBinHAndClucV2', 'CombinedBinHAndClucV5', 'CombinedBinHAndClucV7', 'CombinedBinHAndClucV8', 'SMAOffset', 'SMAOffsetV2', 'SMAOffsetProtectOptV0', 'SMAOffsetProtectOptV1', 'NostalgiaForInfinityV1', 'NostalgiaForInfinityV2', 'NostalgiaForInfinityV3', 'NostalgiaForInfinityV4', 'NostalgiaForInfinityV5', 'NostalgiaForInfinityV7', 'Obelisk_Ichimoku_ZEMA_v1'] STRAT_COMBINATIONS = reduce(lambda x, y: list(combinations(STRATEGIES, y)) + x, range(len(STRATEGIES) + 1), []) MAX_COMBINATIONS = len(STRAT_COMBINATIONS) - 2 class EnsembleStrategy(IStrategy): INTERFACE_VERSION = 3 loaded_strategies = {} informative_timeframe = '1h' entry_action_diff_threshold = DecimalParameter(0, 1, default=0, decimals=2, optimize=True, load=True) entry_strategies = IntParameter(0, MAX_COMBINATIONS, default=0, optimize=True, load=True) # trailing stoploss hyperopt parameters # hard stoploss profit exit_HSL = DecimalParameter(-0.2, -0.04, default=-0.08, decimals=3, optimize=True, load=True) # profit threshold 1, trigger point, SL_1 is used exit_PF_1 = DecimalParameter(0.008, 0.02, default=0.016, decimals=3, optimize=True, load=True) exit_SL_1 = DecimalParameter(0.008, 0.02, default=0.011, decimals=3, optimize=True, load=True) # profit threshold 2, SL_2 is used exit_PF_2 = DecimalParameter(0.04, 0.1, default=0.08, decimals=3, optimize=True, load=True) exit_SL_2 = DecimalParameter(0.02, 0.07, default=0.04, decimals=3, optimize=True, load=True) stoploss = -0.99 # effectively disabled. exit_profit_offset = 0.001 # it doesn't meant anything, just to guarantee there is a minimal profit. use_exit_signal = False ignore_roi_if_entry_signal = False exit_profit_only = False # Trailing stoploss trailing_stop = False trailing_only_offset_is_reached = False trailing_stop_positive = 0.01 trailing_stop_positive_offset = 0.025 # Custom stoploss use_custom_stoploss = True # Run "populate_indicators()" only for new candle. process_only_new_candles = True # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 200 minimal_roi = {'0': 100.0} entry_params = {} exit_params = {} protections = [{'method': 'CooldownPeriod', 'stop_duration_candles': 2}, {'method': 'StoplossGuard', 'lookback_period_candles': 100, 'trade_limit': 4, 'stop_duration_candles': 10, 'only_per_pair': True}] def __init__(self, config: dict) -> None: super().__init__(config) logger.info(f'Buy stratrategies: {STRAT_COMBINATIONS[self.entry_strategies.value]}') def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.informative_timeframe) for pair in pairs] return informative_pairs def get_strategy(self, strategy_name): strategy = self.loaded_strategies.get(strategy_name) if not strategy: config = self.config config['strategy'] = strategy_name strategy = StrategyResolver.load_strategy(config) strategy.dp = self.dp strategy.wallets = self.wallets self.loaded_strategies[strategy_name] = strategy return strategy def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: strategies = STRAT_COMBINATIONS[self.entry_strategies.value] for strategy_name in strategies: strategy = self.get_strategy(strategy_name) strategy_indicators = strategy.advise_indicators(dataframe, metadata) dataframe[f'strat_entry_signal_{strategy_name}'] = strategy.advise_entry(strategy_indicators, metadata)['enter_long'] dataframe['enter_long'] = (dataframe.filter(like='strat_entry_signal_').mean(axis=1) > self.entry_action_diff_threshold.value).astype(int) return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['exit_long'] = 0 return dataframe def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: """ new custom stoploss, both hard and trailing functions. Trailing stoploss first rises at a slower rate than the current rate until a profit threshold is reached, after which it rises at a constant percentage as per a normal trailing stoploss. This allows more margin for pull-backs during a rise. """ # hard stoploss profit HSL = self.exit_HSL.value PF_1 = self.exit_PF_1.value SL_1 = self.exit_SL_1.value PF_2 = self.exit_PF_2.value SL_2 = self.exit_SL_2.value # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used. if current_profit > PF_2: sl_profit = SL_2 + (current_profit - PF_2) elif current_profit > PF_1: sl_profit = SL_1 + (current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1) else: sl_profit = HSL if current_profit > PF_1: stoploss = stoploss_from_open(sl_profit, current_profit) else: stoploss = stoploss_from_open(HSL, current_profit) return stoploss or stoploss_from_open(HSL, current_profit) or 1