# Guru Skippyasurmuni Strategy # Author: AZcoigreach # github: https://github.com/azcoigreach/ # ___ _ _ ___ _ _ # / __|| | | || _ \| | | | # | (_ || |_| || /| |_| | # \___| \___/ |_|_\ \___/ # ___ _ __ ___ ___ ___ __ __ _ ___ _ _ ___ __ __ _ _ _ _ ___ # / __|| |/ /|_ _|| _ \| _ \\ \ / //_\ / __|| | | || _ \| \/ || | | || \| ||_ _| # \__ \| ' < | | | _/| _/ \ V // _ \ \__ \| |_| || /| |\/| || |_| || .` | | | # |___/|_|\_\|___||_| |_| |_|/_/ \_\|___/ \___/ |_|_\|_| |_| \___/ |_|\_||___| # ''' Guru Skippyasurmuni - Even though Skippy is designed to be easily modified and hyperopted. However, Skippy has alread spent a lot of time thinking about the best way to trade crypto. He has come to the conclusing - simple is better. A couple basic indicators and some simple rules will yield the best results in every condition. ''' # Hyperopting ''' docker-compose run --rm freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell --strategy GuruSkippyasurmuni -e 2000 --timerange=20220314- --eps ''' # Backtesting - Powershell ''' $1 = 20220314 ; $2 = "day" ; docker-compose run --rm freqtrade backtesting --datadir user_data/data/binanceus --config /freqtrade/user_data/SkippyGod_config.json --export trades -s SkippyGod --fee 0.00075 --timerange=$1- --breakdown $2 --eps ; docker-compose run --rm freqtrade plot-dataframe -s SkippyGod --config /freqtrade/user_data/SkippyGod_config.json -i 5m --timerange=$1- --indicators2 AROONOSC-5 RSI-5 ; docker-compose run --rm freqtrade plot-profit -s SkippyGod --config /freqtrade/user_data/SkippyGod_config.json -i 5m --timerange=$1- ''' # --- Do not remove these libs --- from freqtrade.strategy.interface import IStrategy from typing import Dict, List from functools import reduce from pandas import DataFrame # -------------------------------- import talib.abstract as ta import numpy as np import freqtrade.vendor.qtpylib.indicators as qtpylib import datetime from technical.util import resample_to_interval, resampled_merge from datetime import datetime, timedelta from freqtrade.persistence import Trade from freqtrade.strategy import stoploss_from_open, merge_informative_pair, DecimalParameter, IntParameter, CategoricalParameter import technical.indicators as ftt from freqtrade.exchange import timeframe_to_prev_date import warnings from pandas.core.common import SettingWithCopyWarning warnings.simplefilter(action="ignore", category=SettingWithCopyWarning) from random import shuffle # Initiate genes splicing --> Source: MaBlue GodStrNew --> Target: Guru Skippyasurmuni strategy. # TODO: this gene is removed 'MAVP' cuz or error on periods all_god_genes = { 'Overlap Studies': { 'BBANDS-0', # Bollinger Bands 'BBANDS-1', # Bollinger Bands 'BBANDS-2', # Bollinger Bands 'DEMA', # Double Exponential Moving Average 'EMA', # Exponential Moving Average 'HT_TRENDLINE', # Hilbert Transform - Instantaneous Trendline 'KAMA', # Kaufman Adaptive Moving Average 'MA', # Moving average 'MAMA-0', # MESA Adaptive Moving Average 'MAMA-1', # MESA Adaptive Moving Average # TODO: Fix this # 'MAVP', # Moving average with variable period 'MIDPOINT', # MidPoint over period 'MIDPRICE', # Midpoint Price over period 'SAR', # Parabolic SAR 'SAREXT', # Parabolic SAR - Extended 'SMA', # Simple Moving Average 'T3', # Triple Exponential Moving Average (T3) 'TEMA', # Triple Exponential Moving Average 'TRIMA', # Triangular Moving Average 'WMA', # Weighted Moving Average }, 'Momentum Indicators': { 'ADX', # Average Directional Movement Index 'ADXR', # Average Directional Movement Index Rating 'APO', # Absolute Price Oscillator 'AROON-0', # Aroon 'AROON-1', # Aroon 'AROONOSC', # Aroon Oscillator 'BOP', # Balance Of Power 'CCI', # Commodity Channel Index 'CMO', # Chande Momentum Oscillator 'DX', # Directional Movement Index 'MACD-0', # Moving Average Convergence/Divergence 'MACD-1', # Moving Average Convergence/Divergence 'MACD-2', # Moving Average Convergence/Divergence 'MACDEXT-0', # MACD with controllable MA type 'MACDEXT-1', # MACD with controllable MA type 'MACDEXT-2', # MACD with controllable MA type 'MACDFIX-0', # Moving Average Convergence/Divergence Fix 12/26 'MACDFIX-1', # Moving Average Convergence/Divergence Fix 12/26 'MACDFIX-2', # Moving Average Convergence/Divergence Fix 12/26 'MFI', # Money Flow Index 'MINUS_DI', # Minus Directional Indicator 'MINUS_DM', # Minus Directional Movement 'MOM', # Momentum 'PLUS_DI', # Plus Directional Indicator 'PLUS_DM', # Plus Directional Movement 'PPO', # Percentage Price Oscillator 'ROC', # Rate of change : ((price/prevPrice)-1)*100 'ROCP', # Rate of change Percentage: (price-prevPrice)/prevPrice 'ROCR', # Rate of change ratio: (price/prevPrice) 'ROCR100', # Rate of change ratio 100 scale: (price/prevPrice)*100 'RSI', # Relative Strength Index 'STOCH-0', # Stochastic 'STOCH-1', # Stochastic 'STOCHF-0', # Stochastic Fast 'STOCHF-1', # Stochastic Fast 'STOCHRSI-0', # Stochastic Relative Strength Index 'STOCHRSI-1', # Stochastic Relative Strength Index 'TRIX', # 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA 'ULTOSC', # Ultimate Oscillator 'WILLR', # Williams' %R }, 'Volume Indicators': { 'AD', # Chaikin A/D Line 'ADOSC', # Chaikin A/D Oscillator 'OBV', # On Balance Volume }, 'Volatility Indicators': { 'ATR', # Average True Range 'NATR', # Normalized Average True Range 'TRANGE', # True Range }, 'Price Transform': { 'AVGPRICE', # Average Price 'MEDPRICE', # Median Price 'TYPPRICE', # Typical Price 'WCLPRICE', # Weighted Close Price }, 'Cycle Indicators': { 'HT_DCPERIOD', # Hilbert Transform - Dominant Cycle Period 'HT_DCPHASE', # Hilbert Transform - Dominant Cycle Phase 'HT_PHASOR-0', # Hilbert Transform - Phasor Components 'HT_PHASOR-1', # Hilbert Transform - Phasor Components 'HT_SINE-0', # Hilbert Transform - SineWave 'HT_SINE-1', # Hilbert Transform - SineWave 'HT_TRENDMODE', # Hilbert Transform - Trend vs Cycle Mode }, 'Pattern Recognition': { 'CDL2CROWS', # Two Crows 'CDL3BLACKCROWS', # Three Black Crows 'CDL3INSIDE', # Three Inside Up/Down 'CDL3LINESTRIKE', # Three-Line Strike 'CDL3OUTSIDE', # Three Outside Up/Down 'CDL3STARSINSOUTH', # Three Stars In The South 'CDL3WHITESOLDIERS', # Three Advancing White Soldiers 'CDLABANDONEDBABY', # Abandoned Baby 'CDLADVANCEBLOCK', # Advance Block 'CDLBELTHOLD', # Belt-hold 'CDLBREAKAWAY', # Breakaway 'CDLCLOSINGMARUBOZU', # Closing Marubozu 'CDLCONCEALBABYSWALL', # Concealing Baby Swallow 'CDLCOUNTERATTACK', # Counterattack 'CDLDARKCLOUDCOVER', # Dark Cloud Cover 'CDLDOJI', # Doji 'CDLDOJISTAR', # Doji Star 'CDLDRAGONFLYDOJI', # Dragonfly Doji 'CDLENGULFING', # Engulfing Pattern 'CDLEVENINGDOJISTAR', # Evening Doji Star 'CDLEVENINGSTAR', # Evening Star 'CDLGAPSIDESIDEWHITE', # Up/Down-gap side-by-side white lines 'CDLGRAVESTONEDOJI', # Gravestone Doji 'CDLHAMMER', # Hammer 'CDLHANGINGMAN', # Hanging Man 'CDLHARAMI', # Harami Pattern 'CDLHARAMICROSS', # Harami Cross Pattern 'CDLHIGHWAVE', # High-Wave Candle 'CDLHIKKAKE', # Hikkake Pattern 'CDLHIKKAKEMOD', # Modified Hikkake Pattern 'CDLHOMINGPIGEON', # Homing Pigeon 'CDLIDENTICAL3CROWS', # Identical Three Crows 'CDLINNECK', # In-Neck Pattern 'CDLINVERTEDHAMMER', # Inverted Hammer 'CDLKICKING', # Kicking 'CDLKICKINGBYLENGTH', # Kicking - bull/bear determined by the longer marubozu 'CDLLADDERBOTTOM', # Ladder Bottom 'CDLLONGLEGGEDDOJI', # Long Legged Doji 'CDLLONGLINE', # Long Line Candle 'CDLMARUBOZU', # Marubozu 'CDLMATCHINGLOW', # Matching Low 'CDLMATHOLD', # Mat Hold 'CDLMORNINGDOJISTAR', # Morning Doji Star 'CDLMORNINGSTAR', # Morning Star 'CDLONNECK', # On-Neck Pattern 'CDLPIERCING', # Piercing Pattern 'CDLRICKSHAWMAN', # Rickshaw Man 'CDLRISEFALL3METHODS', # Rising/Falling Three Methods 'CDLSEPARATINGLINES', # Separating Lines 'CDLSHOOTINGSTAR', # Shooting Star 'CDLSHORTLINE', # Short Line Candle 'CDLSPINNINGTOP', # Spinning Top 'CDLSTALLEDPATTERN', # Stalled Pattern 'CDLSTICKSANDWICH', # Stick Sandwich 'CDLTAKURI', # Takuri (Dragonfly Doji with very long lower shadow) 'CDLTASUKIGAP', # Tasuki Gap 'CDLTHRUSTING', # Thrusting Pattern 'CDLTRISTAR', # Tristar Pattern 'CDLUNIQUE3RIVER', # Unique 3 River 'CDLUPSIDEGAP2CROWS', # Upside Gap Two Crows 'CDLXSIDEGAP3METHODS', # Upside/Downside Gap Three Methods }, 'Statistic Functions': { 'BETA', # Beta 'CORREL', # Pearson's Correlation Coefficient (r) 'LINEARREG', # Linear Regression 'LINEARREG_ANGLE', # Linear Regression Angle 'LINEARREG_INTERCEPT', # Linear Regression Intercept 'LINEARREG_SLOPE', # Linear Regression Slope 'STDDEV', # Standard Deviation 'TSF', # Time Series Forecast 'VAR', # Variance } } god_genes = set() ########################### SETTINGS ############################## # RSI and an Aroon Oscillator are the only two metrics you need to catch the highs and lows god_genes = { 'RSI', # Relative Strength Index 'AROONOSC', # Aroon Oscillator } # god_genes |= all_god_genes['Overlap Studies'] # god_genes |= all_god_genes['Momentum Indicators'] # god_genes |= all_god_genes['Volume Indicators'] # god_genes |= all_god_genes['Volatility Indicators'] # god_genes |= all_god_genes['Price Transform'] # god_genes |= all_god_genes['Cycle Indicators'] # god_genes |= all_god_genes['Pattern Recognition'] # god_genes |= all_god_genes['Statistic Functions'] timeperiods = [5, 6, 12, 15, 50, 55, 100, 110] operators = [ "D", # Disabled gene ">", # Indicator, bigger than cross indicator "<", # Indicator, smaller than cross indicator "=", # Indicator, equal with cross indicator "C", # Indicator, crossed the cross indicator "CA", # Indicator, crossed above the cross indicator "CB", # Indicator, crossed below the cross indicator ">R", # Normalized indicator, bigger than real number "=R", # Normalized indicator, equal with real number "R", # Normalized indicator devided to cross indicator, bigger than real number "/=R", # Normalized indicator devided to cross indicator, equal with real number "/ 10) # TODO : it ill callculated in populate indicators. dataframe[indicator] = gene_calculator(dataframe, indicator) dataframe[crossed_indicator] = gene_calculator(dataframe, crossed_indicator) indicator_trend_sma = f"{indicator}-SMA-{TREND_CHECK_CANDLES}" if operator in ["UT", "DT", "OT", "CUT", "CDT", "COT"]: dataframe[indicator_trend_sma] = gene_calculator(dataframe, indicator_trend_sma) if operator == ">": condition = ( dataframe[indicator] > dataframe[crossed_indicator] ) elif operator == "=": condition = ( np.isclose(dataframe[indicator], dataframe[crossed_indicator]) ) elif operator == "<": condition = ( dataframe[indicator] < dataframe[crossed_indicator] ) elif operator == "C": condition = ( (qtpylib.crossed_below(dataframe[indicator], dataframe[crossed_indicator])) | (qtpylib.crossed_above(dataframe[indicator], dataframe[crossed_indicator])) ) elif operator == "CA": condition = ( qtpylib.crossed_above(dataframe[indicator], dataframe[crossed_indicator]) ) elif operator == "CB": condition = ( qtpylib.crossed_below( dataframe[indicator], dataframe[crossed_indicator]) ) elif operator == ">R": condition = ( dataframe[indicator] > real_num ) elif operator == "=R": condition = ( np.isclose(dataframe[indicator], real_num) ) elif operator == "R": condition = ( dataframe[indicator].div(dataframe[crossed_indicator]) > real_num ) elif operator == "/=R": condition = ( np.isclose(dataframe[indicator].div(dataframe[crossed_indicator]), real_num) ) elif operator == "/ dataframe[indicator_trend_sma] ) elif operator == "DT": condition = ( dataframe[indicator] < dataframe[indicator_trend_sma] ) elif operator == "OT": condition = ( np.isclose(dataframe[indicator], dataframe[indicator_trend_sma]) ) elif operator == "CUT": condition = ( ( qtpylib.crossed_above( dataframe[indicator], dataframe[indicator_trend_sma] ) ) & ( dataframe[indicator] > dataframe[indicator_trend_sma] ) ) elif operator == "CDT": condition = ( ( qtpylib.crossed_below( dataframe[indicator], dataframe[indicator_trend_sma] ) ) & ( dataframe[indicator] < dataframe[indicator_trend_sma] ) ) elif operator == "COT": condition = ( ( ( qtpylib.crossed_below( dataframe[indicator], dataframe[indicator_trend_sma] ) ) | ( qtpylib.crossed_above( dataframe[indicator], dataframe[indicator_trend_sma] ) ) ) & ( np.isclose( dataframe[indicator], dataframe[indicator_trend_sma] ) ) ) return condition, dataframe class GuruSkippyasurmuni(IStrategy): INTERFACE_VERSION = 2 # Variables DATESTAMP = 0 COUNT = 0 custom_info = { } # Skippy's BUY and SELL conditions # Buy hyperspace params: buy_params = { "buy_crossed_indicator0": "STOCHRSI-0-15", "buy_crossed_indicator1": "MACDFIX-0-15", "buy_crossed_indicator2": "STOCHRSI-0-55", "buy_indicator0": "MACDFIX-0-15", "buy_indicator1": "RSI-5", "buy_indicator2": "AROONOSC-5", "buy_operator0": "D", "buy_operator1": "R", "sell_operator2": "=R", "sell_real_num0": 0.65, "sell_real_num1": 0.9, "sell_real_num2": 1.0, } # ROI table: minimal_roi = { "0": 1000.0 } # Stoploss: stoploss = -1.0 # Trailing stop: trailing_stop = False trailing_stop_positive = 0.001 trailing_stop_positive_offset = 0.01 trailing_only_offset_is_reached = True # Sell signal use_sell_signal = True sell_profit_only = True # sell_profit_offset = 0.01 ignore_roi_if_buy_signal = False # Optimal timeframe for the strategy timeframe = '5m' # timeframe = '1m' process_only_new_candles = True plot_config = { 'subplots': { "buy_1": { 'buy_1': {'color': 'blue'} }, "sell_1": { 'sell_1': {'color': 'orange'} }, } } # DCA config position_adjustment_enable = True max_entry_position_adjustment = 24 # Protections @property def protections(self): return [ { "method": "CooldownPeriod", "stop_duration_candles": 3 } ] # TODO: Its not dry code! # Buy Hyperoptable Parameters/Spaces. buy_crossed_indicator0 = CategoricalParameter( god_genes_with_timeperiod, default="STOCHRSI-0-15", space='buy') buy_crossed_indicator1 = CategoricalParameter( god_genes_with_timeperiod, default="MACDFIX-0-15", space='buy') buy_crossed_indicator2 = CategoricalParameter( god_genes_with_timeperiod, default="STOCHRSI-0-55", space='buy') buy_indicator0 = CategoricalParameter( god_genes_with_timeperiod, default="MACDFIX-0-15", space='buy') buy_indicator1 = CategoricalParameter( god_genes_with_timeperiod, default="RSI-5", space='buy') buy_indicator2 = CategoricalParameter( god_genes_with_timeperiod, default="AROONOSC-5", space='buy') buy_operator0 = CategoricalParameter(operators, default="D", space='buy') buy_operator1 = CategoricalParameter(operators, default=" DataFrame: # Check if the entry already exists pair = metadata['pair'] if not pair in self.custom_info: # Create empty entry for this pair {DATESTAMP} self.custom_info[pair] = ['', 0] # count = self.custom_info[pair][self.COUNT] # dataframe['buy_1'] = 0 # # # iterate through dataframe, create buys and sells # row = 0 # last_row = dataframe.tail(1).index.item() # while (row <= last_row): # if(count == 0): # dataframe['buy'].iloc[row] = 1 # if(count > 0): # dataframe['buy_1'].iloc[row] = 1 # else: # count += 1 # if(row == 0): # self.custom_info[pair][self.COUNT] = count # row += 1 return dataframe def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, sell_reason: str, current_profit: float, **kwargs) -> bool: # Minimum 1% profit before Sell trigger if current_profit < 0.01: return False return True def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, **kwargs) -> float: return proposed_stake # Skippy's has finally figured out how to stake positions def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float, max_stake: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) if(len(dataframe) < 1): return None last_candle = dataframe.iloc[-1].squeeze() if(self.custom_info[trade.pair][self.DATESTAMP] != last_candle['date']): # Trigger once per cnadle self.custom_info[trade.pair][self.DATESTAMP] = last_candle['date'] # If current total profit is greater than value don't adjust. if current_profit > -0.01: return None # If last candle had 'buy' indicator adjust stake by original stake_amount if last_candle['buy'] > 0: filled_buys = trade.select_filled_orders('buy') try: stake_amount = filled_buys[0].cost return stake_amount except Exception as exception: return None return None def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = list() # Buy Condition 0 buy_indicator = self.buy_indicator0.value buy_crossed_indicator = self.buy_crossed_indicator0.value buy_operator = self.buy_operator0.value buy_real_num = self.buy_real_num0.value condition, dataframe = condition_generator( dataframe, buy_operator, buy_indicator, buy_crossed_indicator, buy_real_num ) conditions.append(condition) # Buy Condition 1 buy_indicator = self.buy_indicator1.value buy_crossed_indicator = self.buy_crossed_indicator1.value buy_operator = self.buy_operator1.value buy_real_num = self.buy_real_num1.value condition, dataframe = condition_generator( dataframe, buy_operator, buy_indicator, buy_crossed_indicator, buy_real_num ) conditions.append(condition) # Buy Condition 2 buy_indicator = self.buy_indicator2.value buy_crossed_indicator = self.buy_crossed_indicator2.value buy_operator = self.buy_operator2.value buy_real_num = self.buy_real_num2.value condition, dataframe = condition_generator( dataframe, buy_operator, buy_indicator, buy_crossed_indicator, buy_real_num ) conditions.append(condition) if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), 'buy']=1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = list() # Sell Condition 0 sell_indicator = self.sell_indicator0.value sell_crossed_indicator = self.sell_crossed_indicator0.value sell_operator = self.sell_operator0.value sell_real_num = self.sell_real_num0.value condition, dataframe = condition_generator( dataframe, sell_operator, sell_indicator, sell_crossed_indicator, sell_real_num ) conditions.append(condition) # Sell Condition 1 sell_indicator = self.sell_indicator1.value sell_crossed_indicator = self.sell_crossed_indicator1.value sell_operator = self.sell_operator1.value sell_real_num = self.sell_real_num1.value condition, dataframe = condition_generator( dataframe, sell_operator, sell_indicator, sell_crossed_indicator, sell_real_num ) conditions.append(condition) # Sell Condition 2 sell_indicator = self.sell_indicator2.value sell_crossed_indicator = self.sell_crossed_indicator2.value sell_operator = self.sell_operator2.value sell_real_num = self.sell_real_num2.value condition, dataframe = condition_generator( dataframe, sell_operator, sell_indicator, sell_crossed_indicator, sell_real_num ) conditions.append(condition) if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), 'sell']=1 return dataframe