1034 lines
42 KiB
Python
1034 lines
42 KiB
Python
# GodStraNew Strategy
|
|
# Author: @Mablue (Masoud Azizi)
|
|
# github: https://github.com/mablue/
|
|
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy roi trailing sell --strategy GodStraNew
|
|
# --- Do not remove these libs ---
|
|
from datetime import datetime
|
|
|
|
from freqtrade import data
|
|
from freqtrade.strategy.parameters import CategoricalParameter, DecimalParameter
|
|
|
|
from numpy.lib import math
|
|
from freqtrade.strategy.interface import IStrategy
|
|
from pandas import DataFrame
|
|
|
|
# --------------------------------
|
|
|
|
# Add your lib to import here
|
|
# TODO: talib is fast but have not more indicators
|
|
import talib.abstract as ta
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
from functools import reduce
|
|
import numpy as np
|
|
from random import shuffle
|
|
# 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
|
|
# Rate of change Percentage: (price-prevPrice)/prevPrice
|
|
'ROCP',
|
|
'ROCR', # Rate of change ratio: (price/prevPrice)
|
|
# Rate of change ratio 100 scale: (price/prevPrice)*100
|
|
'ROCR100',
|
|
'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
|
|
# 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA
|
|
'TRIX',
|
|
'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
|
|
# Takuri (Dragonfly Doji with very long lower shadow)
|
|
'CDLTAKURI',
|
|
'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 ##############################
|
|
|
|
# god_genes = {'SMA'}
|
|
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, smaller than real number
|
|
"/>R", # Normalized indicator devided to cross indicator, bigger than real number
|
|
"/=R", # Normalized indicator devided to cross indicator, equal with real number
|
|
"/<R", # Normalized indicator devided to cross indicator, smaller than real number
|
|
"UT", # Indicator, is in UpTrend status
|
|
"DT", # Indicator, is in DownTrend status
|
|
"OT", # Indicator, is in Off trend status(RANGE)
|
|
"CUT", # Indicator, Entered to UpTrend status
|
|
"CDT", # Indicator, Entered to DownTrend status
|
|
"COT" # Indicator, Entered to Off trend status(RANGE)
|
|
]
|
|
# number of candles to check up,don,off trend.
|
|
TREND_CHECK_CANDLES = 8
|
|
DECIMALS = 1
|
|
########################### END SETTINGS ##########################
|
|
# DATAFRAME = DataFrame()
|
|
|
|
god_genes = list(god_genes)
|
|
# print('selected indicators for optimzatin: \n', god_genes)
|
|
|
|
god_genes_with_timeperiod = list()
|
|
for god_gene in god_genes:
|
|
for timeperiod in timeperiods:
|
|
god_genes_with_timeperiod.append(f'{god_gene}-{timeperiod}')
|
|
|
|
# Let give somethings to CatagoricalParam to Play with them
|
|
# When just one thing is inside catagorical lists
|
|
# TODO: its Not True Way :)
|
|
if len(god_genes) == 1:
|
|
god_genes = god_genes*2
|
|
if len(timeperiods) == 1:
|
|
timeperiods = timeperiods*2
|
|
if len(operators) == 1:
|
|
operators = operators*2
|
|
|
|
|
|
def normalize(df):
|
|
df = (df-df.min())/(df.max()-df.min())
|
|
return df
|
|
|
|
|
|
def gene_calculator(dataframe, indicator):
|
|
# Cuz Timeperiods not effect calculating CDL patterns recognations
|
|
if 'CDL' in indicator:
|
|
splited_indicator = indicator.split('-')
|
|
splited_indicator[1] = "0"
|
|
new_indicator = "-".join(splited_indicator)
|
|
# print(indicator, new_indicator)
|
|
indicator = new_indicator
|
|
|
|
gene = indicator.split("-")
|
|
|
|
gene_name = gene[0]
|
|
gene_len = len(gene)
|
|
|
|
if indicator in dataframe.keys():
|
|
# print(f"{indicator}, calculated befoure")
|
|
# print(len(dataframe.keys()))
|
|
return dataframe[indicator]
|
|
else:
|
|
result = None
|
|
# For Pattern Recognations
|
|
if gene_len == 1:
|
|
# print('gene_len == 1\t', indicator)
|
|
result = getattr(ta, gene_name)(
|
|
dataframe
|
|
)
|
|
return normalize(result)
|
|
elif gene_len == 2:
|
|
# print('gene_len == 2\t', indicator)
|
|
gene_timeperiod = int(gene[1])
|
|
result = getattr(ta, gene_name)(
|
|
dataframe,
|
|
timeperiod=gene_timeperiod,
|
|
)
|
|
return normalize(result)
|
|
# For
|
|
elif gene_len == 3:
|
|
# print('gene_len == 3\t', indicator)
|
|
gene_timeperiod = int(gene[2])
|
|
gene_index = int(gene[1])
|
|
result = getattr(ta, gene_name)(
|
|
dataframe,
|
|
timeperiod=gene_timeperiod,
|
|
).iloc[:, gene_index]
|
|
return normalize(result)
|
|
# For trend operators(MA-5-SMA-4)
|
|
elif gene_len == 4:
|
|
# print('gene_len == 4\t', indicator)
|
|
gene_timeperiod = int(gene[1])
|
|
sharp_indicator = f'{gene_name}-{gene_timeperiod}'
|
|
dataframe[sharp_indicator] = getattr(ta, gene_name)(
|
|
dataframe,
|
|
timeperiod=gene_timeperiod,
|
|
)
|
|
return normalize(ta.SMA(dataframe[sharp_indicator].fillna(0), TREND_CHECK_CANDLES))
|
|
# For trend operators(STOCH-0-4-SMA-4)
|
|
elif gene_len == 5:
|
|
# print('gene_len == 5\t', indicator)
|
|
gene_timeperiod = int(gene[2])
|
|
gene_index = int(gene[1])
|
|
sharp_indicator = f'{gene_name}-{gene_index}-{gene_timeperiod}'
|
|
dataframe[sharp_indicator] = getattr(ta, gene_name)(
|
|
dataframe,
|
|
timeperiod=gene_timeperiod,
|
|
).iloc[:, gene_index]
|
|
return normalize(ta.SMA(dataframe[sharp_indicator].fillna(0), TREND_CHECK_CANDLES))
|
|
|
|
|
|
def condition_generator(dataframe, operator, indicator, crossed_indicator, real_num):
|
|
|
|
condition = (dataframe['volume'] > 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] < 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 == "/<R":
|
|
condition = (
|
|
dataframe[indicator].div(dataframe[crossed_indicator]) < real_num
|
|
)
|
|
elif operator == "UT":
|
|
condition = (
|
|
dataframe[indicator] > 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 GodStraJD3_1(IStrategy):
|
|
# #################### RESULTS PASTE PLACE ####################
|
|
# ROI table:
|
|
minimal_roi = {
|
|
"0": 1,
|
|
"600": 0.166,
|
|
"1200": 0.14,
|
|
"2400": 0.1,
|
|
"3600": 0.05,
|
|
"7289": 0
|
|
}
|
|
|
|
# Stoploss:
|
|
stoploss = -1
|
|
# Buy hypers
|
|
timeframe = '4h'
|
|
|
|
# Trailing stoploss
|
|
trailing_stop = True
|
|
trailing_stop_positive = 0.15
|
|
trailing_stop_positive_offset = 0.20
|
|
trailing_only_offset_is_reached = True
|
|
|
|
|
|
plot_config = {
|
|
# Main plot indicators (Moving averages, ...)
|
|
'main_plot': {
|
|
'bb_lowerband': {'color': 'red'},
|
|
'bb_upperband': {'color': 'green'},
|
|
'sma100': {'color': 'blue'},
|
|
'sma10': {'color': 'yellow'},
|
|
'min': {'color': 'white'},
|
|
'max': {'color': 'white'},
|
|
'sma20': {'color': 'cyan'}
|
|
},
|
|
'subplots': {
|
|
# Subplots - each dict defines one additional plot
|
|
"BB": {
|
|
'bb_width': {'color': 'white'},
|
|
'bb_min': {'color': 'red'},
|
|
},
|
|
# "ADX": {
|
|
# 'adx': {'color': 'white'},
|
|
# 'minus_dm': {'color': 'blue'},
|
|
# 'plus_dm': {'color': 'red'}
|
|
# },
|
|
"Rsi": {
|
|
'rsi': {'color': 'pink'},
|
|
},
|
|
"rolling": {
|
|
'bb_rolling': {'color': '#87e470'},
|
|
"bb_rolling_min": {'color': '#ac3e2a'}
|
|
},
|
|
"percent": {
|
|
"percent": {'color': 'green'},
|
|
"percent5": {'color': 'red'}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
# #################### END OF RESULT PLACE ####################
|
|
|
|
# TODO: Its not dry code!
|
|
# Buy Hyperoptable Parameters/Spaces.
|
|
buy_crossed_indicator0 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="ADD-20", space='buy')
|
|
buy_crossed_indicator1 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="ASIN-6", space='buy')
|
|
buy_crossed_indicator2 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="CDLEVENINGSTAR-50", space='buy')
|
|
|
|
buy_indicator0 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="SMA-100", space='buy')
|
|
buy_indicator1 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="WILLR-50", space='buy')
|
|
buy_indicator2 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="CDLHANGINGMAN-20", space='buy')
|
|
|
|
buy_operator0 = CategoricalParameter(operators, default="/<R", space='buy')
|
|
buy_operator1 = CategoricalParameter(operators, default="<R", space='buy')
|
|
buy_operator2 = CategoricalParameter(operators, default="CB", space='buy')
|
|
|
|
buy_real_num0 = DecimalParameter(
|
|
0, 1, decimals=DECIMALS, default=0.89009, space='buy')
|
|
buy_real_num1 = DecimalParameter(
|
|
0, 1, decimals=DECIMALS, default=0.56953, space='buy')
|
|
buy_real_num2 = DecimalParameter(
|
|
0, 1, decimals=DECIMALS, default=0.38365, space='buy')
|
|
|
|
# Sell Hyperoptable Parameters/Spaces.
|
|
sell_crossed_indicator0 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="CDLSHOOTINGSTAR-150", space='sell')
|
|
sell_crossed_indicator1 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="MAMA-1-100", space='sell')
|
|
sell_crossed_indicator2 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="CDLMATHOLD-6", space='sell')
|
|
|
|
sell_indicator0 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="CDLUPSIDEGAP2CROWS-5", space='sell')
|
|
sell_indicator1 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="CDLHARAMICROSS-150", space='sell')
|
|
sell_indicator2 = CategoricalParameter(
|
|
god_genes_with_timeperiod, default="CDL2CROWS-5", space='sell')
|
|
|
|
sell_operator0 = CategoricalParameter(
|
|
operators, default="<R", space='sell')
|
|
sell_operator1 = CategoricalParameter(operators, default="D", space='sell')
|
|
sell_operator2 = CategoricalParameter(
|
|
operators, default="/>R", space='sell')
|
|
|
|
sell_real_num0 = DecimalParameter(
|
|
0, 1, decimals=DECIMALS, default=0.09731, space='sell')
|
|
sell_real_num1 = DecimalParameter(
|
|
0, 1, decimals=DECIMALS, default=0.81657, space='sell')
|
|
sell_real_num2 = DecimalParameter(
|
|
0, 1, decimals=DECIMALS, default=0.87267, space='sell')
|
|
|
|
# def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
|
# proposed_stake: float, min_stake: float, max_stake: float,
|
|
# **kwargs) -> float:
|
|
#
|
|
# dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
|
# current_candle = dataframe.iloc[-1].squeeze()
|
|
#
|
|
# # print("proposed_stake=", proposed_stake, " max_stake=", max_stake)
|
|
# if current_candle['bb_width'] > 0.065:
|
|
# # print("use more stake", pair, " ", proposed_stake * 2)
|
|
# return min(max_stake, proposed_stake * 2)
|
|
#
|
|
# if current_candle['bb_width'] > 0.045:
|
|
# # print("use more stake", pair, " ", proposed_stake * 1.5)
|
|
# return min(max_stake, proposed_stake * 1.5)
|
|
#
|
|
# # if current_candle['bb_width'] < 0.020:
|
|
# # print("use less stake", pair, " ", proposed_stake / 2)
|
|
# # return min(max_stake, proposed_stake / 2)
|
|
# # if self.config['stake_amount'] == 'unlimited':
|
|
# # # Use entire available wallet during favorable conditions when in compounding mode.
|
|
# # return max_stake
|
|
# # else:
|
|
# # # Compound profits during favorable conditions instead of using a static stake.
|
|
# # return self.wallets.get_total_stake_amount() / self.config['max_open_trades']
|
|
#
|
|
# # Use default stake amount.
|
|
# return proposed_stake
|
|
|
|
def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
|
|
current_profit: float, **kwargs):
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
previous_last_candle = dataframe.iloc[-2].squeeze()
|
|
previous_5_candle = dataframe.iloc[-5].squeeze()
|
|
|
|
# (last_candle['percent5'] < -0.005) \
|
|
# if (0 < current_profit < 0.005) \
|
|
# & ((current_time - trade.open_date_utc).seconds >= 3600 * 2):
|
|
# # & (previous_last_candle['sma10'] > last_candle['sma10']):
|
|
# print("too_small_gain", pair, trade, " profit=", current_profit, " rate=", current_rate, " percent5=",
|
|
# last_candle['percent5'])
|
|
# return 'too_small_gain'
|
|
|
|
# if (current_profit < -0.05) \
|
|
# & ((current_time - trade.open_date_utc).days >= 3):
|
|
# print("lost_half_profit", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
# return 'stop_loss_profit'
|
|
|
|
# if (current_profit > 0.02) \
|
|
# & (last_candle['percent'] < 0.01) \
|
|
# & ((current_time - trade.open_date_utc).seconds >= 3600):
|
|
# print("lost_half_profit", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
# return 'lost_half_profit'
|
|
|
|
if (current_profit > 0) \
|
|
& ((current_time - trade.open_date_utc).seconds >= 3600 * 2) \
|
|
& (previous_5_candle['sma20'] > last_candle['sma20']) \
|
|
& (last_candle['percent'] < 0) \
|
|
& (last_candle['percent5'] < 0):
|
|
# print("over_bb_band_sma20_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'over_bb_band_sma20_desc'
|
|
|
|
if (current_profit > 0) \
|
|
& (last_candle['rsi'] > 75):
|
|
# print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'over_rsi'
|
|
|
|
# description trade
|
|
# Trade(id=0, pair=CAKE/USDT, amount=4.19815281, open_rate=11.91000000, open_since=2021-12-22 17:55:00)
|
|
# print(last_candle)
|
|
if 0.015 < current_profit < 1:
|
|
if (
|
|
(previous_last_candle['sma10'] > last_candle['sma10'] * 1.005) &
|
|
(current_time - trade.open_date_utc).seconds >= 3600 * 3
|
|
# ) | (
|
|
# (current_time - trade.open_date_utc).seconds >= 3600 * 6
|
|
):
|
|
# self.lock_pair(pair, until=current_time + timedelta(hours=3))
|
|
|
|
# print("profit_3h_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'profit_3h_sma10_desc'
|
|
|
|
if (0 < current_profit < 0.1) \
|
|
& (previous_last_candle['sma20'] > last_candle['sma20']) \
|
|
& ((current_time - trade.open_date_utc).seconds >= 3600 * 5):
|
|
# print("profit_5h_sma20_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'profit_5h_sma20_desc'
|
|
|
|
def informative_pairs(self):
|
|
return []
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
# MACD
|
|
# macd = ta.MACD(dataframe)
|
|
# dataframe['macd'] = macd['macd']
|
|
# dataframe['macdsignal'] = macd['macdsignal']
|
|
# dataframe['macdhist'] = macd['macdhist']
|
|
|
|
# # # Plus Directional Indicator / Movement
|
|
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
|
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
|
#
|
|
# # Minus Directional Indicator / Movement
|
|
# dataframe['adx'] = ta.ADX(dataframe)
|
|
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
|
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
|
|
|
# dataframe['min'] = ta.MIN(dataframe)
|
|
# dataframe['max'] = ta.MAX(dataframe)
|
|
|
|
# # Aroon, Aroon Oscillator
|
|
# aroon = ta.AROON(dataframe)
|
|
# dataframe['aroonup'] = aroon['aroonup']
|
|
# dataframe['aroondown'] = aroon['aroondown']
|
|
# dataframe['aroonosc'] = ta.AROONOSC(dataframe)
|
|
|
|
# RSI
|
|
dataframe['rsi'] = ta.RSI(dataframe)
|
|
|
|
# # EMA - Exponential Moving Average
|
|
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
|
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
|
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
|
# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21)
|
|
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
|
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
|
|
|
# # SMA - Simple Moving Average
|
|
# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3)
|
|
# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5)
|
|
dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
|
|
dataframe['sma20'] = ta.SMA(dataframe, timeperiod=20)
|
|
dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
|
|
dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100)
|
|
# dataframe['sma200'] = ta.SMA(dataframe, timeperiod=200)
|
|
# dataframe['sma200_95'] = ta.SMA(dataframe, timeperiod=200) * 0.95
|
|
# dataframe['sma200_98'] = ta.SMA(dataframe, timeperiod=200) * 0.98
|
|
# dataframe['sma500'] = ta.SMA(dataframe, timeperiod=500)
|
|
# dataframe['sma500_90'] = ta.SMA(dataframe, timeperiod=500) * 0.9
|
|
# dataframe['sma500_95'] = ta.SMA(dataframe, timeperiod=500) * 0.95
|
|
# dataframe['sma500_20'] = ta.SMA(dataframe, timeperiod=500) * 0.2
|
|
dataframe["percent"] = (dataframe["close"] - dataframe["open"]) / dataframe["open"]
|
|
dataframe["percent5"] = dataframe["percent"].rolling(5).sum()
|
|
dataframe["percent20"] = dataframe["percent"].rolling(20).sum()
|
|
dataframe['min'] = ta.MIN(dataframe['close'], timeperiod=200)
|
|
dataframe['min20'] = ta.MIN(dataframe['close'], timeperiod=20)
|
|
|
|
dataframe['max'] = ta.MAX(dataframe['close'], timeperiod=200)
|
|
dataframe['max_min'] = dataframe['max'] / dataframe['min']
|
|
# Bollinger Bands
|
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
|
dataframe['bb_lowerband'] = bollinger['lower']
|
|
dataframe['bb_middleband'] = bollinger['mid']
|
|
dataframe['bb_upperband'] = bollinger['upper']
|
|
dataframe["bb_percent"] = (
|
|
(dataframe["close"] - dataframe["bb_lowerband"]) /
|
|
(dataframe["bb_upperband"] - dataframe["bb_lowerband"])
|
|
)
|
|
dataframe["bb_width"] = (
|
|
(dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"]
|
|
)
|
|
dataframe['bb_min'] = ta.MIN(dataframe['bb_lowerband'], timeperiod=36)
|
|
|
|
# Bollinger Bands - Weighted (EMA based instead of SMA)
|
|
weighted_bollinger = qtpylib.weighted_bollinger_bands(
|
|
qtpylib.typical_price(dataframe), window=20, stds=2
|
|
)
|
|
dataframe["wbb_upperband"] = weighted_bollinger["upper"]
|
|
dataframe["wbb_lowerband"] = weighted_bollinger["lower"]
|
|
dataframe["wbb_middleband"] = weighted_bollinger["mid"]
|
|
dataframe["wbb_percent"] = (
|
|
(dataframe["close"] - dataframe["wbb_lowerband"]) /
|
|
(dataframe["wbb_upperband"] - dataframe["wbb_lowerband"])
|
|
)
|
|
dataframe["wbb_width"] = (
|
|
(dataframe["wbb_upperband"] - dataframe["wbb_lowerband"]) / dataframe["wbb_middleband"]
|
|
)
|
|
|
|
# # EMA - Exponential Moving Average
|
|
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
|
return dataframe
|
|
|
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
|
|
conditions = list()
|
|
|
|
# TODO: Its not dry code!
|
|
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)
|
|
# backup
|
|
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_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[
|
|
(
|
|
(dataframe['close'] < dataframe['bb_lowerband'])
|
|
& (dataframe['bb_width'] >= 0.12)
|
|
& (dataframe['volume'] * dataframe['close'] / 1000 > 100)
|
|
) |
|
|
reduce(lambda x, y: x & y, conditions),
|
|
'buy']=1
|
|
# print(len(dataframe.keys()))
|
|
|
|
return dataframe
|
|
|
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
|
|
conditions = list()
|
|
# TODO: Its not dry code!
|
|
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_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_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)
|
|
|
|
#dataframe.loc[(dataframe['close'] < dataframe['bb_lowerband']),'sell']=1
|
|
|
|
if conditions:
|
|
dataframe.loc[
|
|
(
|
|
reduce(lambda x, y: x & y, conditions)
|
|
),
|
|
'sell']=1
|
|
return dataframe
|
|
|
|
|
|
class StrategyHelperLocal:
|
|
"""
|
|
simple helper class to predefine a couple of patterns for our
|
|
strategy
|
|
"""
|
|
|
|
@staticmethod
|
|
def two_green_candles(dataframe):
|
|
"""
|
|
evaluates if we are having 7 green candles in a row
|
|
:param self:
|
|
:param dataframe:
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'] < dataframe['close']) &
|
|
(dataframe['open'].shift(1) < dataframe['close'].shift(1))
|
|
# (dataframe['open'].shift(2) < dataframe['close'].shift(2))
|
|
)
|
|
|
|
@staticmethod
|
|
def no_more_three_green_candles(dataframe):
|
|
"""
|
|
evaluates if we are having not more than 3 green candles in a row
|
|
:param self:
|
|
:param dataframe:
|
|
:return:
|
|
"""
|
|
return not (
|
|
(dataframe['open'] < dataframe['close']) &
|
|
(dataframe['open'].shift(1) < dataframe['close'].shift(1)) &
|
|
(dataframe['open'].shift(2) < dataframe['close'].shift(2)) &
|
|
(dataframe['open'].shift(3) < dataframe['close'].shift(3))
|
|
)
|
|
|
|
@staticmethod
|
|
def seven_green_candles(dataframe):
|
|
"""
|
|
evaluates if we are having 7 green candles in a row
|
|
:param self:
|
|
:param dataframe:
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'] < dataframe['close']) &
|
|
(dataframe['open'].shift(1) < dataframe['close'].shift(1)) &
|
|
(dataframe['open'].shift(2) < dataframe['close'].shift(2)) &
|
|
(dataframe['open'].shift(3) < dataframe['close'].shift(3)) &
|
|
(dataframe['open'].shift(4) < dataframe['close'].shift(4)) &
|
|
(dataframe['open'].shift(5) < dataframe['close'].shift(5)) &
|
|
(dataframe['open'].shift(6) < dataframe['close'].shift(6)) &
|
|
(dataframe['open'].shift(7) < dataframe['close'].shift(7))
|
|
)
|
|
|
|
@staticmethod
|
|
def eight_green_candles(dataframe):
|
|
"""
|
|
evaluates if we are having 8 green candles in a row
|
|
:param self:
|
|
:param dataframe:
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'] < dataframe['close']) &
|
|
(dataframe['open'].shift(1) < dataframe['close'].shift(1)) &
|
|
(dataframe['open'].shift(2) < dataframe['close'].shift(2)) &
|
|
(dataframe['open'].shift(3) < dataframe['close'].shift(3)) &
|
|
(dataframe['open'].shift(4) < dataframe['close'].shift(4)) &
|
|
(dataframe['open'].shift(5) < dataframe['close'].shift(5)) &
|
|
(dataframe['open'].shift(6) < dataframe['close'].shift(6)) &
|
|
(dataframe['open'].shift(7) < dataframe['close'].shift(7)) &
|
|
(dataframe['open'].shift(8) < dataframe['close'].shift(8))
|
|
)
|
|
|
|
@staticmethod
|
|
def two_red_candles(dataframe, shift=0):
|
|
"""
|
|
evaluates if we are having 8 red candles in a row
|
|
:param self:
|
|
:param dataframe:
|
|
:param shift: shift the pattern by n
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'].shift(shift) > dataframe['close'].shift(shift)) &
|
|
(dataframe['open'].shift(1 + shift) > dataframe['close'].shift(1 + shift))
|
|
# (dataframe['open'].shift(2 + shift) > dataframe['close'].shift(2 + shift)) &
|
|
# (dataframe['open'].shift(5 + shift) > dataframe['close'].shift(5 + shift)) &
|
|
# (dataframe['open'].shift(6 + shift) > dataframe['close'].shift(6 + shift)) &
|
|
# (dataframe['open'].shift(7 + shift) > dataframe['close'].shift(7 + shift)) &
|
|
# (dataframe['open'].shift(8 + shift) > dataframe['close'].shift(8 + shift))
|
|
)
|
|
|
|
@staticmethod
|
|
def four_red_candles(dataframe, shift=0):
|
|
"""
|
|
evaluates if we are having 8 red candles in a row
|
|
:param self:
|
|
:param dataframe:
|
|
:param shift: shift the pattern by n
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'].shift(shift) > dataframe['close'].shift(shift)) &
|
|
(dataframe['open'].shift(1 + shift) > dataframe['close'].shift(1 + shift)) &
|
|
(dataframe['open'].shift(2 + shift) > dataframe['close'].shift(2 + shift)) &
|
|
(dataframe['open'].shift(3 + shift) > dataframe['close'].shift(3 + shift)) &
|
|
(dataframe['open'].shift(4 + shift) > dataframe['close'].shift(4 + shift))
|
|
# (dataframe['open'].shift(5 + shift) > dataframe['close'].shift(5 + shift)) &
|
|
# (dataframe['open'].shift(6 + shift) > dataframe['close'].shift(6 + shift)) &
|
|
# (dataframe['open'].shift(7 + shift) > dataframe['close'].shift(7 + shift)) &
|
|
# (dataframe['open'].shift(8 + shift) > dataframe['close'].shift(8 + shift))
|
|
)
|
|
|
|
@staticmethod
|
|
def two_green_one_red_candle(dataframe):
|
|
"""
|
|
evaluates if we are having a red candle and 4 previous green
|
|
:param self:
|
|
:param dataframe:
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'] < dataframe['close']) &
|
|
(dataframe['open'].shift(1) < dataframe['close'].shift(1)) &
|
|
(dataframe['open'].shift(2) > dataframe['close'].shift(2))
|
|
)
|
|
|
|
@staticmethod
|
|
def four_green_one_red_candle(dataframe):
|
|
"""
|
|
evaluates if we are having a red candle and 4 previous green
|
|
:param self:
|
|
:param dataframe:
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'] > dataframe['close']) &
|
|
(dataframe['open'].shift(1) < dataframe['close'].shift(1)) &
|
|
(dataframe['open'].shift(2) < dataframe['close'].shift(2)) &
|
|
(dataframe['open'].shift(3) < dataframe['close'].shift(3)) &
|
|
(dataframe['open'].shift(4) < dataframe['close'].shift(4))
|
|
)
|
|
|
|
@staticmethod
|
|
def four_red_one_green_candle(dataframe):
|
|
"""
|
|
evaluates if we are having a green candle and 4 previous red
|
|
:param self:
|
|
:param dataframe:
|
|
:return:
|
|
"""
|
|
return (
|
|
(dataframe['open'] < dataframe['close']) &
|
|
(dataframe['open'].shift(1) > dataframe['close'].shift(1)) &
|
|
(dataframe['open'].shift(2) > dataframe['close'].shift(2)) &
|
|
(dataframe['open'].shift(3) > dataframe['close'].shift(3)) &
|
|
(dataframe['open'].shift(4) > dataframe['close'].shift(4))
|
|
)
|