fc4894e5a5
Added pandas import and implemented fillna to handle NaN values in the Supertrend strategy.
197 lines
7.7 KiB
Python
197 lines
7.7 KiB
Python
"""
|
|
Supertrend strategy:
|
|
* Description: Generate a 3 supertrend indicators for 'buy' strategies & 3 supertrend indicators for 'sell' strategies
|
|
Buys if the 3 'buy' indicators are 'up'
|
|
Sells if the 3 'sell' indicators are 'down'
|
|
* Author: @juankysoriano (Juan Carlos Soriano)
|
|
* github: https://github.com/juankysoriano/
|
|
|
|
*** NOTE: This Supertrend strategy is just one of many possible strategies using `Supertrend` as indicator. It should on any case used at your own risk.
|
|
It comes with at least a couple of caveats:
|
|
1. The implementation for the `supertrend` indicator is based on the following discussion: https://github.com/freqtrade/freqtrade-strategies/issues/30 . Concretelly https://github.com/freqtrade/freqtrade-strategies/issues/30#issuecomment-853042401
|
|
2. The implementation for `supertrend` on this strategy is not validated; meaning this that is not proven to match the results by the paper where it was originally introduced or any other trusted academic resources
|
|
"""
|
|
|
|
import logging
|
|
from numpy.lib import math
|
|
from freqtrade.strategy import IStrategy, IntParameter
|
|
from pandas import DataFrame
|
|
import talib.abstract as ta
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
class Supertrend(IStrategy):
|
|
# Buy params, Sell params, ROI, Stoploss and Trailing Stop are values generated by 'freqtrade hyperopt --strategy Supertrend --hyperopt-loss ShortTradeDurHyperOptLoss --timerange=20210101- --timeframe=1h --spaces all'
|
|
# It's encourage you find the values that better suites your needs and risk management strategies
|
|
|
|
INTERFACE_VERSION: int = 3
|
|
# Buy hyperspace params:
|
|
buy_params = {
|
|
"buy_m1": 4,
|
|
"buy_m2": 7,
|
|
"buy_m3": 1,
|
|
"buy_p1": 8,
|
|
"buy_p2": 9,
|
|
"buy_p3": 8,
|
|
}
|
|
|
|
# Sell hyperspace params:
|
|
sell_params = {
|
|
"sell_m1": 1,
|
|
"sell_m2": 3,
|
|
"sell_m3": 6,
|
|
"sell_p1": 16,
|
|
"sell_p2": 18,
|
|
"sell_p3": 18,
|
|
}
|
|
|
|
# ROI table:
|
|
minimal_roi = {
|
|
"0": 0.087,
|
|
"372": 0.058,
|
|
"861": 0.029,
|
|
"2221": 0
|
|
}
|
|
|
|
# Stoploss:
|
|
stoploss = -0.265
|
|
|
|
# Trailing stop:
|
|
trailing_stop = True
|
|
trailing_stop_positive = 0.05
|
|
trailing_stop_positive_offset = 0.144
|
|
trailing_only_offset_is_reached = False
|
|
|
|
timeframe = '1h'
|
|
|
|
startup_candle_count = 199
|
|
|
|
buy_m1 = IntParameter(1, 7, default=4)
|
|
buy_m2 = IntParameter(1, 7, default=4)
|
|
buy_m3 = IntParameter(1, 7, default=4)
|
|
buy_p1 = IntParameter(7, 21, default=14)
|
|
buy_p2 = IntParameter(7, 21, default=14)
|
|
buy_p3 = IntParameter(7, 21, default=14)
|
|
|
|
sell_m1 = IntParameter(1, 7, default=4)
|
|
sell_m2 = IntParameter(1, 7, default=4)
|
|
sell_m3 = IntParameter(1, 7, default=4)
|
|
sell_p1 = IntParameter(7, 21, default=14)
|
|
sell_p2 = IntParameter(7, 21, default=14)
|
|
sell_p3 = IntParameter(7, 21, default=14)
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
new_cols = []
|
|
|
|
for multiplier in self.buy_m1.range:
|
|
for period in self.buy_p1.range:
|
|
st = self.supertrend(dataframe, multiplier, period)[['STX']].rename(
|
|
columns={'STX': f'supertrend_1_buy_{multiplier}_{period}'})
|
|
new_cols.append(st)
|
|
|
|
for multiplier in self.buy_m2.range:
|
|
for period in self.buy_p2.range:
|
|
st = self.supertrend(dataframe, multiplier, period)[['STX']].rename(
|
|
columns={'STX': f'supertrend_2_buy_{multiplier}_{period}'})
|
|
new_cols.append(st)
|
|
|
|
for multiplier in self.buy_m3.range:
|
|
for period in self.buy_p3.range:
|
|
st = self.supertrend(dataframe, multiplier, period)[['STX']].rename(
|
|
columns={'STX': f'supertrend_3_buy_{multiplier}_{period}'})
|
|
new_cols.append(st)
|
|
|
|
for multiplier in self.sell_m1.range:
|
|
for period in self.sell_p1.range:
|
|
st = self.supertrend(dataframe, multiplier, period)[['STX']].rename(
|
|
columns={'STX': f'supertrend_1_sell_{multiplier}_{period}'})
|
|
new_cols.append(st)
|
|
|
|
for multiplier in self.sell_m2.range:
|
|
for period in self.sell_p2.range:
|
|
st = self.supertrend(dataframe, multiplier, period)[['STX']].rename(
|
|
columns={'STX': f'supertrend_2_sell_{multiplier}_{period}'})
|
|
new_cols.append(st)
|
|
|
|
for multiplier in self.sell_m3.range:
|
|
for period in self.sell_p3.range:
|
|
st = self.supertrend(dataframe, multiplier, period)[['STX']].rename(
|
|
columns={'STX': f'supertrend_3_sell_{multiplier}_{period}'})
|
|
new_cols.append(st)
|
|
|
|
if new_cols:
|
|
dataframe = pd.concat([dataframe] + new_cols, axis=1)
|
|
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
dataframe.loc[
|
|
(
|
|
(dataframe[f'supertrend_1_buy_{self.buy_m1.value}_{self.buy_p1.value}'] == 'up') &
|
|
(dataframe[f'supertrend_2_buy_{self.buy_m2.value}_{self.buy_p2.value}'] == 'up') &
|
|
(dataframe[f'supertrend_3_buy_{self.buy_m3.value}_{self.buy_p3.value}'] == 'up') & # The three indicators are 'up' for the current candle
|
|
(dataframe['volume'] > 0) # There is at least some trading volume
|
|
),
|
|
'enter_long'] = 1
|
|
|
|
return dataframe
|
|
|
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
dataframe.loc[
|
|
(
|
|
(dataframe[f'supertrend_1_sell_{self.sell_m1.value}_{self.sell_p1.value}'] == 'down') &
|
|
(dataframe[f'supertrend_2_sell_{self.sell_m2.value}_{self.sell_p2.value}'] == 'down') &
|
|
(dataframe[f'supertrend_3_sell_{self.sell_m3.value}_{self.sell_p3.value}'] == 'down') & # The three indicators are 'down' for the current candle
|
|
(dataframe['volume'] > 0) # There is at least some trading volume
|
|
),
|
|
'exit_long'] = 1
|
|
|
|
return dataframe
|
|
|
|
|
|
|
|
"""
|
|
Supertrend Indicator; adapted for freqtrade
|
|
from: https://github.com/freqtrade/freqtrade-strategies/issues/30
|
|
"""
|
|
def supertrend(self, dataframe: pd.DataFrame, multiplier, period):
|
|
df = dataframe.copy()
|
|
high = df['high'].values
|
|
low = df['low'].values
|
|
close = df['close'].values
|
|
length = len(df)
|
|
|
|
# 1. TR and ATR
|
|
tr = ta.TRANGE(df['high'], df['low'], df['close'])
|
|
atr = pd.Series(tr).rolling(period).mean().to_numpy()
|
|
|
|
# 2. basic upper / lower bands
|
|
basic_ub = (high + low) / 2 + multiplier * atr
|
|
basic_lb = (high + low) / 2 - multiplier * atr
|
|
|
|
# 3. final upper / lower bands
|
|
final_ub = np.zeros(length)
|
|
final_lb = np.zeros(length)
|
|
|
|
for i in range(period, length):
|
|
final_ub[i] = basic_ub[i] if basic_ub[i] < final_ub[i-1] or close[i-1] > final_ub[i-1] else final_ub[i-1]
|
|
final_lb[i] = basic_lb[i] if basic_lb[i] > final_lb[i-1] or close[i-1] < final_lb[i-1] else final_lb[i-1]
|
|
|
|
# 4. ST calculation
|
|
st = np.zeros(length)
|
|
for i in range(period, length):
|
|
if st[i-1] == final_ub[i-1]:
|
|
st[i] = final_ub[i] if close[i] <= final_ub[i] else final_lb[i]
|
|
elif st[i-1] == final_lb[i-1]:
|
|
st[i] = final_lb[i] if close[i] >= final_lb[i] else final_ub[i]
|
|
|
|
# 5. STX direction
|
|
stx = np.where(st > 0, np.where(close < st, 'down', 'up'), None)
|
|
|
|
# 6. fillna
|
|
result = pd.DataFrame({'ST': st, 'STX': stx}, index=df.index)
|
|
result.fillna(0, inplace=True)
|
|
|
|
return result
|
|
|