589 lines
24 KiB
Python
589 lines
24 KiB
Python
import numpy as np
|
|
import talib.abstract as ta
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
import arrow
|
|
|
|
from freqtrade.strategy import (IStrategy, merge_informative_pair, stoploss_from_open,
|
|
IntParameter, DecimalParameter, CategoricalParameter)
|
|
|
|
from typing import Dict, List, Optional, Tuple, Union
|
|
from pandas import DataFrame, Series
|
|
from functools import reduce
|
|
from datetime import datetime, timedelta
|
|
from freqtrade.persistence import Trade
|
|
|
|
# Get rid of pandas warnings during backtesting
|
|
import pandas as pd
|
|
pd.options.mode.chained_assignment = None # default='warn'
|
|
|
|
# Strategy specific imports, files must reside in same folder as strategy
|
|
import sys
|
|
from pathlib import Path
|
|
sys.path.append(str(Path(__file__).parent))
|
|
|
|
# import custom_indicators as cta
|
|
|
|
"""
|
|
Solipsis - By @werkkrew
|
|
|
|
Credits -
|
|
@JimmyNixx for many of the ideas used throughout as well as helping me stay motivated throughout development!
|
|
@rk for submitting many PR's that have made this strategy possible!
|
|
|
|
I ask for nothing in return except that if you make changes which bring you greater success than what has been provided, you share those ideas back to
|
|
the community. Also, please don't nag me with a million questions and especially don't blame me if you lose a ton of money using this.
|
|
|
|
I take no responsibility for any success or failure you have using this strategy.
|
|
|
|
VERSION: 5.2.1
|
|
"""
|
|
|
|
|
|
"""
|
|
Misc. Helper Functions
|
|
"""
|
|
def same_length(bigger, shorter):
|
|
return np.concatenate((np.full((bigger.shape[0] - shorter.shape[0]), np.nan), shorter))
|
|
|
|
"""
|
|
Maths
|
|
"""
|
|
def linear_growth(start: float, end: float, start_time: int, end_time: int, trade_time: int) -> float:
|
|
"""
|
|
Simple linear growth function. Grows from start to end after end_time minutes (starts after start_time minutes)
|
|
"""
|
|
time = max(0, trade_time - start_time)
|
|
rate = (end - start) / (end_time - start_time)
|
|
|
|
return min(end, start + (rate * time))
|
|
|
|
def linear_decay(start: float, end: float, start_time: int, end_time: int, trade_time: int) -> float:
|
|
"""
|
|
Simple linear decay function. Decays from start to end after end_time minutes (starts after start_time minutes)
|
|
"""
|
|
time = max(0, trade_time - start_time)
|
|
rate = (start - end) / (end_time - start_time)
|
|
|
|
return max(end, start - (rate * time))
|
|
|
|
"""
|
|
TA Indicators
|
|
"""
|
|
|
|
def zema(dataframe, period, field='close'):
|
|
"""
|
|
Source: https://github.com/freqtrade/technical/blob/master/technical/indicators/overlap_studies.py#L79
|
|
Modified slightly to use ta.EMA instead of technical ema
|
|
"""
|
|
df = dataframe.copy()
|
|
|
|
df['ema1'] = ta.EMA(df[field], timeperiod=period)
|
|
df['ema2'] = ta.EMA(df['ema1'], timeperiod=period)
|
|
df['d'] = df['ema1'] - df['ema2']
|
|
df['zema'] = df['ema1'] + df['d']
|
|
|
|
return df['zema']
|
|
|
|
def RMI(dataframe, *, length=20, mom=5):
|
|
"""
|
|
Source: https://github.com/freqtrade/technical/blob/master/technical/indicators/indicators.py#L912
|
|
"""
|
|
df = dataframe.copy()
|
|
|
|
df['maxup'] = (df['close'] - df['close'].shift(mom)).clip(lower=0)
|
|
df['maxdown'] = (df['close'].shift(mom) - df['close']).clip(lower=0)
|
|
|
|
df.fillna(0, inplace=True)
|
|
|
|
df["emaInc"] = ta.EMA(df, price='maxup', timeperiod=length)
|
|
df["emaDec"] = ta.EMA(df, price='maxdown', timeperiod=length)
|
|
|
|
df['RMI'] = np.where(df['emaDec'] == 0, 0, 100 - 100 / (1 + df["emaInc"] / df["emaDec"]))
|
|
|
|
return df["RMI"]
|
|
|
|
def mastreak(dataframe: DataFrame, period: int = 4, field='close') -> Series:
|
|
"""
|
|
MA Streak
|
|
Port of: https://www.tradingview.com/script/Yq1z7cIv-MA-Streak-Can-Show-When-a-Run-Is-Getting-Long-in-the-Tooth/
|
|
"""
|
|
df = dataframe.copy()
|
|
|
|
avgval = zema(df, period, field)
|
|
|
|
arr = np.diff(avgval)
|
|
pos = np.clip(arr, 0, 1).astype(bool).cumsum()
|
|
neg = np.clip(arr, -1, 0).astype(bool).cumsum()
|
|
streak = np.where(arr >= 0, pos - np.maximum.accumulate(np.where(arr <= 0, pos, 0)),
|
|
-neg + np.maximum.accumulate(np.where(arr >= 0, neg, 0)))
|
|
|
|
res = same_length(df['close'], streak)
|
|
|
|
return res
|
|
|
|
def pcc(dataframe: DataFrame, period: int = 20, mult: int = 2):
|
|
"""
|
|
Percent Change Channel
|
|
PCC is like KC unless it uses percentage changes in price to set channel distance.
|
|
https://www.tradingview.com/script/6wwAWXA1-MA-Streak-Change-Channel/
|
|
"""
|
|
df = dataframe.copy()
|
|
|
|
df['previous_close'] = df['close'].shift()
|
|
|
|
df['close_change'] = (df['close'] - df['previous_close']) / df['previous_close'] * 100
|
|
df['high_change'] = (df['high'] - df['close']) / df['close'] * 100
|
|
df['low_change'] = (df['low'] - df['close']) / df['close'] * 100
|
|
|
|
df['delta'] = df['high_change'] - df['low_change']
|
|
|
|
mid = zema(df, period, 'close_change')
|
|
rangema = zema(df, period, 'delta')
|
|
|
|
upper = mid + rangema * mult
|
|
lower = mid - rangema * mult
|
|
|
|
return upper, rangema, lower
|
|
|
|
def SSLChannels(dataframe, length=10, mode='sma'):
|
|
"""
|
|
Source: https://www.tradingview.com/script/xzIoaIJC-SSL-channel/
|
|
Source: https://github.com/freqtrade/technical/blob/master/technical/indicators/indicators.py#L1025
|
|
Usage:
|
|
dataframe['sslDown'], dataframe['sslUp'] = SSLChannels(dataframe, 10)
|
|
"""
|
|
if mode not in ('sma'):
|
|
raise ValueError(f"Mode {mode} not supported yet")
|
|
|
|
df = dataframe.copy()
|
|
|
|
if mode == 'sma':
|
|
df['smaHigh'] = df['high'].rolling(length).mean()
|
|
df['smaLow'] = df['low'].rolling(length).mean()
|
|
|
|
df['hlv'] = np.where(df['close'] > df['smaHigh'], 1,
|
|
np.where(df['close'] < df['smaLow'], -1, np.NAN))
|
|
df['hlv'] = df['hlv'].ffill()
|
|
|
|
df['sslDown'] = np.where(df['hlv'] < 0, df['smaHigh'], df['smaLow'])
|
|
df['sslUp'] = np.where(df['hlv'] < 0, df['smaLow'], df['smaHigh'])
|
|
|
|
return df['sslDown'], df['sslUp']
|
|
|
|
def SSLChannels_ATR(dataframe, length=7):
|
|
"""
|
|
SSL Channels with ATR: https://www.tradingview.com/script/SKHqWzql-SSL-ATR-channel/
|
|
Credit to @JimmyNixx for python
|
|
"""
|
|
df = dataframe.copy()
|
|
|
|
df['ATR'] = ta.ATR(df, timeperiod=14)
|
|
df['smaHigh'] = df['high'].rolling(length).mean() + df['ATR']
|
|
df['smaLow'] = df['low'].rolling(length).mean() - df['ATR']
|
|
df['hlv'] = np.where(df['close'] > df['smaHigh'], 1, np.where(df['close'] < df['smaLow'], -1, np.NAN))
|
|
df['hlv'] = df['hlv'].ffill()
|
|
df['sslDown'] = np.where(df['hlv'] < 0, df['smaHigh'], df['smaLow'])
|
|
df['sslUp'] = np.where(df['hlv'] < 0, df['smaLow'], df['smaHigh'])
|
|
|
|
return df['sslDown'], df['sslUp']
|
|
|
|
def WaveTrend(dataframe, chlen=10, avg=21, smalen=4):
|
|
"""
|
|
WaveTrend Ocillator by LazyBear
|
|
https://www.tradingview.com/script/2KE8wTuF-Indicator-WaveTrend-Oscillator-WT/
|
|
"""
|
|
df = dataframe.copy()
|
|
|
|
df['hlc3'] = (df['high'] + df['low'] + df['close']) / 3
|
|
df['esa'] = ta.EMA(df['hlc3'], timeperiod=chlen)
|
|
df['d'] = ta.EMA((df['hlc3'] - df['esa']).abs(), timeperiod=chlen)
|
|
df['ci'] = (df['hlc3'] - df['esa']) / (0.015 * df['d'])
|
|
df['tci'] = ta.EMA(df['ci'], timeperiod=avg)
|
|
|
|
df['wt1'] = df['tci']
|
|
df['wt2'] = ta.SMA(df['wt1'], timeperiod=smalen)
|
|
df['wt1-wt2'] = df['wt1'] - df['wt2']
|
|
|
|
return df['wt1'], df['wt2']
|
|
|
|
def T3(dataframe, length=5):
|
|
"""
|
|
T3 Average by HPotter on Tradingview
|
|
https://www.tradingview.com/script/qzoC9H1I-T3-Average/
|
|
"""
|
|
df = dataframe.copy()
|
|
|
|
df['xe1'] = ta.EMA(df['close'], timeperiod=length)
|
|
df['xe2'] = ta.EMA(df['xe1'], timeperiod=length)
|
|
df['xe3'] = ta.EMA(df['xe2'], timeperiod=length)
|
|
df['xe4'] = ta.EMA(df['xe3'], timeperiod=length)
|
|
df['xe5'] = ta.EMA(df['xe4'], timeperiod=length)
|
|
df['xe6'] = ta.EMA(df['xe5'], timeperiod=length)
|
|
b = 0.7
|
|
c1 = -b*b*b
|
|
c2 = 3*b*b+3*b*b*b
|
|
c3 = -6*b*b-3*b-3*b*b*b
|
|
c4 = 1+3*b+b*b*b+3*b*b
|
|
df['T3Average'] = c1 * df['xe6'] + c2 * df['xe5'] + c3 * df['xe4'] + c4 * df['xe3']
|
|
|
|
return df['T3Average']
|
|
|
|
|
|
def SROC(dataframe, roclen=21, emalen=13, smooth=21):
|
|
df = dataframe.copy()
|
|
|
|
roc = ta.ROC(df, timeperiod=roclen)
|
|
ema = ta.EMA(df, timeperiod=emalen)
|
|
sroc = ta.ROC(ema, timeperiod=smooth)
|
|
|
|
return sroc
|
|
|
|
|
|
class Solipsis5(IStrategy):
|
|
|
|
## Buy Space Hyperopt Variables
|
|
|
|
# Base Pair Params
|
|
base_mp = IntParameter(10, 50, default=30, space='buy', load=True, optimize=True)
|
|
base_rmi_max = IntParameter(30, 60, default=50, space='buy', load=True, optimize=True)
|
|
base_rmi_min = IntParameter(0, 30, default=20, space='buy', load=True, optimize=True)
|
|
base_ma_streak = IntParameter(1, 4, default=1, space='buy', load=True, optimize=True)
|
|
base_rmi_streak = IntParameter(3, 8, default=3, space='buy', load=True, optimize=True)
|
|
base_trigger = CategoricalParameter(['pcc', 'rmi', 'none'], default='rmi', space='buy', load=True, optimize=True)
|
|
inf_pct_adr = DecimalParameter(0.70, 0.99, default=0.80, space='buy', load=True, optimize=True)
|
|
# BTC Informative
|
|
xbtc_guard = CategoricalParameter(['strict', 'lazy', 'none'], default='lazy', space='buy', optimize=True)
|
|
xbtc_base_rmi = IntParameter(20, 70, default=40, space='buy', load=True, optimize=True)
|
|
# BTC / ETH Stake Parameters
|
|
xtra_base_stake_rmi = IntParameter(10, 50, default=50, space='buy', load=True, optimize=True)
|
|
xtra_base_fiat_rmi = IntParameter(30, 70, default=50, space='buy', load=True, optimize=True)
|
|
|
|
## Sell Space Params are being used for both custom_stoploss and custom_sell
|
|
|
|
# Custom Sell Profit (formerly Dynamic ROI)
|
|
csell_roi_type = CategoricalParameter(['static', 'decay', 'step'], default='step', space='sell', load=True, optimize=True)
|
|
csell_roi_time = IntParameter(720, 1440, default=720, space='sell', load=True, optimize=True)
|
|
csell_roi_start = DecimalParameter(0.01, 0.05, default=0.01, space='sell', load=True, optimize=True)
|
|
csell_roi_end = DecimalParameter(0.0, 0.01, default=0, space='sell', load=True, optimize=True)
|
|
csell_trend_type = CategoricalParameter(['rmi', 'ssl', 'candle', 'any', 'none'], default='any', space='sell', load=True, optimize=True)
|
|
csell_pullback = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=True)
|
|
csell_pullback_amount = DecimalParameter(0.005, 0.03, default=0.01, space='sell', load=True, optimize=True)
|
|
csell_pullback_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True, optimize=True)
|
|
csell_endtrend_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True, optimize=True)
|
|
|
|
# Custom Stoploss
|
|
cstop_loss_threshold = DecimalParameter(-0.05, -0.01, default=-0.03, space='sell', load=True, optimize=True)
|
|
cstop_bail_how = CategoricalParameter(['roc', 'time', 'any', 'none'], default='none', space='sell', load=True, optimize=True)
|
|
cstop_bail_roc = DecimalParameter(-5.0, -1.0, default=-3.0, space='sell', load=True, optimize=True)
|
|
cstop_bail_time = IntParameter(60, 1440, default=720, space='sell', load=True, optimize=True)
|
|
cstop_bail_time_trend = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=True)
|
|
|
|
timeframe = '5m'
|
|
inf_timeframe = '1h'
|
|
|
|
buy_params = {}
|
|
|
|
sell_params = {}
|
|
|
|
minimal_roi = {
|
|
"0": 100
|
|
}
|
|
|
|
stoploss = -0.99
|
|
use_custom_stoploss = True
|
|
|
|
# Recommended
|
|
use_sell_signal = True
|
|
sell_profit_only = True
|
|
ignore_roi_if_buy_signal = True
|
|
|
|
# Required
|
|
startup_candle_count: int = 233
|
|
process_only_new_candles = False
|
|
|
|
# Strategy Specific Variable Storage
|
|
custom_trade_info = {}
|
|
custom_fiat = "USD" # Only relevant if stake is BTC or ETH
|
|
custom_btc_inf = False # Don't change this.
|
|
|
|
"""
|
|
Informative Pair Definitions
|
|
"""
|
|
def informative_pairs(self):
|
|
# add all whitelisted pairs on informative timeframe
|
|
pairs = self.dp.current_whitelist()
|
|
informative_pairs = [(pair, self.inf_timeframe) for pair in pairs]
|
|
|
|
# add extra informative pairs if the stake is BTC or ETH
|
|
if self.config['stake_currency'] in ('BTC', 'ETH'):
|
|
for pair in pairs:
|
|
coin, stake = pair.split('/')
|
|
coin_fiat = f"{coin}/{self.custom_fiat}"
|
|
informative_pairs += [(coin_fiat, self.timeframe)]
|
|
|
|
stake_fiat = f"{self.config['stake_currency']}/{self.custom_fiat}"
|
|
informative_pairs += [(stake_fiat, self.timeframe)]
|
|
# if BTC/STAKE is not in whitelist, add it as an informative pair on both timeframes
|
|
else:
|
|
btc_stake = f"BTC/{self.config['stake_currency']}"
|
|
if not btc_stake in pairs:
|
|
informative_pairs += [(btc_stake, self.timeframe)]
|
|
|
|
return informative_pairs
|
|
|
|
"""
|
|
Indicator Definitions
|
|
"""
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
if not metadata['pair'] in self.custom_trade_info:
|
|
self.custom_trade_info[metadata['pair']] = {}
|
|
if not 'had-trend' in self.custom_trade_info[metadata["pair"]]:
|
|
self.custom_trade_info[metadata['pair']]['had-trend'] = False
|
|
|
|
## Base Timeframe / Pair
|
|
|
|
# Kaufmann Adaptive Moving Average
|
|
dataframe['kama'] = ta.KAMA(dataframe, length=233)
|
|
|
|
# RMI: https://www.tradingview.com/script/kwIt9OgQ-Relative-Momentum-Index/
|
|
dataframe['rmi'] = RMI(dataframe, length=24, mom=5)
|
|
|
|
# Momentum Pinball: https://www.tradingview.com/script/fBpVB1ez-Momentum-Pinball-Indicator/
|
|
dataframe['roc-mp'] = ta.ROC(dataframe, timeperiod=1)
|
|
dataframe['mp'] = ta.RSI(dataframe['roc-mp'], timeperiod=3)
|
|
|
|
# MA Streak: https://www.tradingview.com/script/Yq1z7cIv-MA-Streak-Can-Show-When-a-Run-Is-Getting-Long-in-the-Tooth/
|
|
dataframe['mastreak'] = mastreak(dataframe, period=4)
|
|
|
|
# Percent Change Channel: https://www.tradingview.com/script/6wwAWXA1-MA-Streak-Change-Channel/
|
|
upper, mid, lower = pcc(dataframe, period=40, mult=3)
|
|
dataframe['pcc-lowerband'] = lower
|
|
dataframe['pcc-upperband'] = upper
|
|
|
|
lookup_idxs = dataframe.index.values - (abs(dataframe['mastreak'].values) + 1)
|
|
valid_lookups = lookup_idxs >= 0
|
|
dataframe['sbc'] = np.nan
|
|
dataframe.loc[valid_lookups, 'sbc'] = dataframe['close'].to_numpy()[lookup_idxs[valid_lookups].astype(int)]
|
|
|
|
dataframe['streak-roc'] = 100 * (dataframe['close'] - dataframe['sbc']) / dataframe['sbc']
|
|
|
|
# Trends, Peaks and Crosses
|
|
dataframe['candle-up'] = np.where(dataframe['close'] >= dataframe['open'],1,0)
|
|
dataframe['candle-up-trend'] = np.where(dataframe['candle-up'].rolling(5).sum() >= 3,1,0)
|
|
|
|
dataframe['rmi-up'] = np.where(dataframe['rmi'] >= dataframe['rmi'].shift(),1,0)
|
|
dataframe['rmi-up-trend'] = np.where(dataframe['rmi-up'].rolling(5).sum() >= 3,1,0)
|
|
|
|
dataframe['rmi-dn'] = np.where(dataframe['rmi'] <= dataframe['rmi'].shift(),1,0)
|
|
dataframe['rmi-dn-count'] = dataframe['rmi-dn'].rolling(8).sum()
|
|
|
|
dataframe['streak-bo'] = np.where(dataframe['streak-roc'] < dataframe['pcc-lowerband'],1,0)
|
|
dataframe['streak-bo-count'] = dataframe['streak-bo'].rolling(8).sum()
|
|
|
|
# Indicators used only for ROI and Custom Stoploss
|
|
ssldown, sslup = SSLChannels_ATR(dataframe, length=21)
|
|
dataframe['sroc'] = SROC(dataframe, roclen=21, emalen=13, smooth=21)
|
|
dataframe['ssl-dir'] = np.where(sslup > ssldown,'up','down')
|
|
|
|
# Base pair informative timeframe indicators
|
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.inf_timeframe)
|
|
|
|
# Get the "average day range" between the 1d high and 1d low to set up guards
|
|
informative['1d-high'] = informative['close'].rolling(24).max()
|
|
informative['1d-low'] = informative['close'].rolling(24).min()
|
|
informative['adr'] = informative['1d-high'] - informative['1d-low']
|
|
|
|
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.inf_timeframe, ffill=True)
|
|
|
|
# Other stake specific informative indicators
|
|
# e.g if stake is BTC and current coin is XLM (pair: XLM/BTC)
|
|
if self.config['stake_currency'] in ('BTC', 'ETH'):
|
|
coin, stake = metadata['pair'].split('/')
|
|
fiat = self.custom_fiat
|
|
coin_fiat = f"{coin}/{fiat}"
|
|
stake_fiat = f"{stake}/{fiat}"
|
|
|
|
# Informative COIN/FIAT e.g. XLM/USD - Base Timeframe
|
|
coin_fiat_tf = self.dp.get_pair_dataframe(pair=coin_fiat, timeframe=self.timeframe)
|
|
dataframe[f"{fiat}_rmi"] = RMI(coin_fiat_tf, length=55, mom=5)
|
|
|
|
# Informative STAKE/FIAT e.g. BTC/USD - Base Timeframe
|
|
stake_fiat_tf = self.dp.get_pair_dataframe(pair=stake_fiat, timeframe=self.timeframe)
|
|
dataframe[f"{stake}_rmi"] = RMI(stake_fiat_tf, length=55, mom=5)
|
|
|
|
# Informatives for BTC/STAKE if not in whitelist
|
|
else:
|
|
pairs = self.dp.current_whitelist()
|
|
btc_stake = f"BTC/{self.config['stake_currency']}"
|
|
if not btc_stake in pairs:
|
|
self.custom_btc_inf = True
|
|
# BTC/STAKE - Base Timeframe
|
|
btc_stake_tf = self.dp.get_pair_dataframe(pair=btc_stake, timeframe=self.timeframe)
|
|
dataframe['BTC_rmi'] = RMI(btc_stake_tf, length=55, mom=5)
|
|
dataframe['BTC_close'] = btc_stake_tf['close']
|
|
dataframe['BTC_kama'] = ta.KAMA(btc_stake_tf, length=144)
|
|
|
|
return dataframe
|
|
|
|
"""
|
|
Buy Signal
|
|
"""
|
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
conditions = []
|
|
|
|
# Informative Timeframe Guards
|
|
conditions.append(
|
|
(dataframe['close'] <= dataframe[f"1d-low_{self.inf_timeframe}"] +
|
|
(self.inf_pct_adr.value * dataframe[f"adr_{self.inf_timeframe}"]))
|
|
)
|
|
|
|
# Base Timeframe Guards
|
|
conditions.append(
|
|
(dataframe['rmi-dn-count'] >= self.base_rmi_streak.value) &
|
|
(dataframe['streak-bo-count'] >= self.base_ma_streak.value) &
|
|
(dataframe['rmi'] <= self.base_rmi_max.value) &
|
|
(dataframe['rmi'] >= self.base_rmi_min.value) &
|
|
(dataframe['mp'] <= self.base_mp.value)
|
|
)
|
|
|
|
# Base Timeframe Trigger
|
|
if self.base_trigger.value == 'pcc':
|
|
conditions.append(qtpylib.crossed_above(dataframe['streak-roc'], dataframe['pcc-lowerband']))
|
|
|
|
if self.base_trigger.value == 'rmi':
|
|
conditions.append(dataframe['rmi-up-trend'] == 1)
|
|
|
|
# Extra conditions for */BTC and */ETH stakes on additional informative pairs
|
|
if self.config['stake_currency'] in ('BTC', 'ETH'):
|
|
conditions.append(
|
|
(dataframe[f"{self.custom_fiat}_rmi"] > self.xtra_base_fiat_rmi.value) |
|
|
(dataframe[f"{self.config['stake_currency']}_rmi"] < self.xtra_base_stake_rmi.value)
|
|
)
|
|
# Extra conditions for BTC/STAKE if not in whitelist
|
|
else:
|
|
if self.custom_btc_inf:
|
|
if self.xbtc_guard.value == 'strict':
|
|
conditions.append(
|
|
(
|
|
(dataframe['BTC_rmi'] > self.xbtc_base_rmi.value) &
|
|
(dataframe['BTC_close'] > dataframe['BTC_kama'])
|
|
)
|
|
)
|
|
if self.xbtc_guard.value == 'lazy':
|
|
conditions.append(
|
|
(dataframe['close'] > dataframe['kama']) |
|
|
(
|
|
(dataframe['BTC_rmi'] > self.xbtc_base_rmi.value) &
|
|
(dataframe['BTC_close'] > dataframe['BTC_kama'])
|
|
)
|
|
)
|
|
|
|
conditions.append(dataframe['volume'].gt(0))
|
|
|
|
if conditions:
|
|
dataframe.loc[
|
|
reduce(lambda x, y: x & y, conditions),
|
|
'buy'] = 1
|
|
|
|
return dataframe
|
|
|
|
"""
|
|
Sell Signal
|
|
"""
|
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
|
|
dataframe['sell'] = 0
|
|
|
|
return dataframe
|
|
|
|
"""
|
|
Custom Stoploss
|
|
"""
|
|
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
|
|
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)
|
|
in_trend = self.custom_trade_info[trade.pair]['had-trend']
|
|
|
|
# Determine how we sell when we are in a loss
|
|
if current_profit < self.cstop_loss_threshold.value:
|
|
if self.cstop_bail_how.value == 'roc' or self.cstop_bail_how.value == 'any':
|
|
# Dynamic bailout based on rate of change
|
|
if last_candle['sroc'] <= self.cstop_bail_roc.value:
|
|
return 0.01
|
|
if self.cstop_bail_how.value == 'time' or self.cstop_bail_how.value == 'any':
|
|
# Dynamic bailout based on time, unless time_trend is true and there is a potential reversal
|
|
if trade_dur > self.cstop_bail_time.value:
|
|
if self.cstop_bail_time_trend.value == True and in_trend == True:
|
|
return 1
|
|
else:
|
|
return 0.01
|
|
return 1
|
|
|
|
"""
|
|
Custom Sell
|
|
"""
|
|
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=pair, timeframe=self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
|
|
trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)
|
|
max_profit = max(0, trade.calc_profit_ratio(trade.max_rate))
|
|
pullback_value = max(0, (max_profit - self.csell_pullback_amount.value))
|
|
in_trend = False
|
|
|
|
# Determine our current ROI point based on the defined type
|
|
if self.csell_roi_type.value == 'static':
|
|
min_roi = self.csell_roi_start.value
|
|
elif self.csell_roi_type.value == 'decay':
|
|
min_roi = linear_decay(self.csell_roi_start.value, self.csell_roi_end.value, 0, self.csell_roi_time.value, trade_dur)
|
|
elif self.csell_roi_type.value == 'step':
|
|
if trade_dur < self.csell_roi_time.value:
|
|
min_roi = self.csell_roi_start.value
|
|
else:
|
|
min_roi = self.csell_roi_end.value
|
|
|
|
# Determine if there is a trend
|
|
if self.csell_trend_type.value == 'rmi' or self.csell_trend_type.value == 'any':
|
|
if last_candle['rmi-up-trend'] == 1:
|
|
in_trend = True
|
|
if self.csell_trend_type.value == 'ssl' or self.csell_trend_type.value == 'any':
|
|
if last_candle['ssl-dir'] == 'up':
|
|
in_trend = True
|
|
if self.csell_trend_type.value == 'candle' or self.csell_trend_type.value == 'any':
|
|
if last_candle['candle-up-trend'] == 1:
|
|
in_trend = True
|
|
|
|
# Don't sell if we are in a trend unless the pullback threshold is met
|
|
if in_trend == True and current_profit > 0:
|
|
# Record that we were in a trend for this trade/pair for a more useful sell message later
|
|
self.custom_trade_info[trade.pair]['had-trend'] = True
|
|
# If pullback is enabled and profit has pulled back allow a sell, maybe
|
|
if self.csell_pullback.value == True and (current_profit <= pullback_value):
|
|
if self.csell_pullback_respect_roi.value == True and current_profit > min_roi:
|
|
return 'intrend_pullback_roi'
|
|
elif self.csell_pullback_respect_roi.value == False:
|
|
if current_profit > min_roi:
|
|
return 'intrend_pullback_roi'
|
|
else:
|
|
return 'intrend_pullback_noroi'
|
|
# We are in a trend and pullback is disabled or has not happened or various criteria were not met, hold
|
|
return None
|
|
# If we are not in a trend, just use the roi value
|
|
elif in_trend == False:
|
|
if self.custom_trade_info[trade.pair]['had-trend']:
|
|
if current_profit > min_roi:
|
|
self.custom_trade_info[trade.pair]['had-trend'] = False
|
|
return 'trend_roi'
|
|
elif self.csell_endtrend_respect_roi.value == False:
|
|
self.custom_trade_info[trade.pair]['had-trend'] = False
|
|
return 'trend_noroi'
|
|
elif current_profit > min_roi:
|
|
return 'notrend_roi'
|
|
else:
|
|
return None
|
|
|