# Strategy created by Shane Jones https://twitter.com/shanejones # # Assited by a number of contributors https://github.com/shanejones/goddard/graphs/contributors # # Original repo hosted at https://github.com/shanejones/goddard from datetime import datetime from datetime import timedelta from functools import reduce from freqtrade.strategy.parameters import DecimalParameter, IntParameter import freqtrade.vendor.qtpylib.indicators as qtpylib import talib.abstract as ta from freqtrade.persistence import Trade from freqtrade.strategy import IStrategy from pandas import DataFrame def to_minutes(**timdelta_kwargs): return int(timedelta(**timdelta_kwargs).total_seconds() / 60) class Apollo11(IStrategy): timeframe = "1h" # Stoploss stoploss = -0.16 startup_candle_count: int = 480 trailing_stop = False use_custom_stoploss = True use_exit_signal = False # signal controls buy_signal_1 = True buy_signal_2 = True buy_signal_3 = True # ROI table: minimal_roi = { "0": 10, # This is 10000%, which basically disables ROI } # Indicator values: # Signal 1 s1_ema_xs = IntParameter(low=1, high=20, default=3, space='buy', optimize=True, load=True) s1_ema_sm = IntParameter(low=1, high=20, default=5, space='buy', optimize=True, load=True) s1_ema_md = IntParameter(low=1, high=20, default=10, space='buy', optimize=True, load=True) s1_ema_xl = IntParameter(low=40, high=80, default=50, space='buy', optimize=True, load=True) s1_ema_xxl = IntParameter(low=200, high=300, default=240, space='buy', optimize=True, load=True) # Signal 2 s2_ema_input = IntParameter(low=40, high=80, default=50, space='buy', optimize=True, load=True) s2_ema_offset_input = IntParameter(low=-20, high=20, default=-1, space='buy', optimize=True, load=True) s2_bb_sma_length = IntParameter(low=35, high=70, default=49, space='buy', optimize=True, load=True) s2_bb_std_dev_length = IntParameter(low=50, high=80, default=64, space='buy', optimize=True, load=True) s2_bb_lower_offset = IntParameter(low=1, high=20, default=3, space='buy', optimize=True, load=True) s2_fib_sma_len = IntParameter(low=35, high=70, default=50, space='buy', optimize=True, load=True) s2_fib_atr_len = IntParameter(low=10, high=30, default=14, space='buy', optimize=True, load=True) s2_fib_lower_value = DecimalParameter(4, 6, default=4.236, space='buy', optimize=True) @property def protections(self): return [ { # Don't enter a trade right after selling a trade. "method": "CooldownPeriod", "stop_duration": to_minutes(minutes=0), }, { # Stop trading if max-drawdown is reached. "method": "MaxDrawdown", "lookback_period": to_minutes(hours=12), "trade_limit": 20, # Considering all pairs that have a minimum of 20 trades "stop_duration": to_minutes(hours=1), "max_allowed_drawdown": 0.2, # If max-drawdown is > 20% this will activate }, { # Stop trading if a certain amount of stoploss occurred within a certain time window. "method": "StoplossGuard", "lookback_period": to_minutes(hours=6), "trade_limit": 4, # Considering all pairs that have a minimum of 4 trades "stop_duration": to_minutes(minutes=30), "only_per_pair": False, # Looks at all pairs }, { # Lock pairs with low profits "method": "LowProfitPairs", "lookback_period": to_minutes(hours=1, minutes=30), "trade_limit": 2, # Considering all pairs that have a minimum of 2 trades "stop_duration": to_minutes(hours=15), "required_profit": 0.02, # If profit < 2% this will activate for a pair }, { # Lock pairs with low profits "method": "LowProfitPairs", "lookback_period": to_minutes(hours=6), "trade_limit": 4, # Considering all pairs that have a minimum of 4 trades "stop_duration": to_minutes(minutes=30), "required_profit": 0.01, # If profit < 1% this will activate for a pair }, ] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Adding EMA's into the dataframe dataframe["s1_ema_xs"] = ta.EMA(dataframe, timeperiod=self.s1_ema_xs.value) dataframe["s1_ema_sm"] = ta.EMA(dataframe, timeperiod=self.s1_ema_sm.value) dataframe["s1_ema_md"] = ta.EMA(dataframe, timeperiod=self.s1_ema_md.value) dataframe["s1_ema_xl"] = ta.EMA(dataframe, timeperiod=self.s1_ema_xl.value) dataframe["s1_ema_xxl"] = ta.EMA(dataframe, timeperiod=self.s1_ema_xxl.value) s2_ema_value = ta.EMA(dataframe, timeperiod=self.s2_ema_input.value) s2_ema_xxl_value = ta.EMA(dataframe, timeperiod=200) dataframe["s2_ema"] = s2_ema_value - s2_ema_value * self.s2_ema_offset_input.value dataframe["s2_ema_xxl_off"] = s2_ema_xxl_value - s2_ema_xxl_value * self.s2_fib_lower_value.value dataframe["s2_ema_xxl"] = ta.EMA(dataframe, timeperiod=200) s2_bb_sma_value = ta.SMA(dataframe, timeperiod=self.s2_bb_sma_length.value) s2_bb_std_dev_value = ta.STDDEV(dataframe, self.s2_bb_std_dev_length.value) dataframe["s2_bb_std_dev_value"] = s2_bb_std_dev_value dataframe["s2_bb_lower_band"] = s2_bb_sma_value - (s2_bb_std_dev_value * self.s2_bb_lower_offset.value) s2_fib_atr_value = ta.ATR(dataframe, timeframe=self.s2_fib_atr_len.value) s2_fib_sma_value = ta.SMA(dataframe, timeperiod=self.s2_fib_sma_len.value) dataframe["s2_fib_lower_band"] = s2_fib_sma_value - s2_fib_atr_value * self.s2_fib_lower_value.value s3_bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=3) dataframe["s3_bb_lowerband"] = s3_bollinger["lower"] # Volume weighted MACD dataframe["fastMA"] = ta.EMA(dataframe["volume"] * dataframe["close"], 12) / ta.EMA(dataframe["volume"], 12) dataframe["slowMA"] = ta.EMA(dataframe["volume"] * dataframe["close"], 26) / ta.EMA(dataframe["volume"], 26) dataframe["vwmacd"] = dataframe["fastMA"] - dataframe["slowMA"] dataframe["signal"] = ta.EMA(dataframe["vwmacd"], 9) dataframe["hist"] = dataframe["vwmacd"] - dataframe["signal"] return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # basic buy methods to keep the strategy simple if self.buy_signal_1: conditions = [ dataframe["vwmacd"] < dataframe["signal"], dataframe["low"] < dataframe["s1_ema_xxl"], dataframe["close"] > dataframe["s1_ema_xxl"], qtpylib.crossed_above(dataframe["s1_ema_sm"], dataframe["s1_ema_md"]), dataframe["s1_ema_xs"] < dataframe["s1_ema_xl"], dataframe["volume"] > 0, ] dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_1") if self.buy_signal_2: conditions = [ qtpylib.crossed_above(dataframe["s2_fib_lower_band"], dataframe["s2_bb_lower_band"]), dataframe["close"] < dataframe["s2_ema"], dataframe["volume"] > 0, ] dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_2") if self.buy_signal_3: conditions = [ dataframe["low"] < dataframe["s3_bb_lowerband"], dataframe["low"] > dataframe["s1_ema_xxl"], dataframe["volume"] > 0, ] dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_3") if not all([self.buy_signal_1, self.buy_signal_2, self.buy_signal_3]): dataframe.loc[(), "buy"] = 0 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # This is essentailly ignored as we're using strict ROI / Stoploss / TTP sale scenarios dataframe.loc[(), "sell"] = 0 return dataframe def custom_stoploss( self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs ) -> float: if current_profit > 0.2: return 0.04 if current_profit > 0.1: return 0.03 if current_profit > 0.06: return 0.02 if current_profit > 0.03: return 0.01 # Let's try to minimize the loss if current_profit <= -0.10: if trade.open_date_utc + timedelta(hours=60) < current_time: # After 60H since buy return current_profit / 1.75 if current_profit <= -0.08: if trade.open_date_utc + timedelta(hours=120) < current_time: # After 120H since buy return current_profit / 1.70 return -1