23 Commits
Author SHA1 Message Date
MatthiasandGitHub dbd5b0b21c Merge pull request #334 from darkvolg/add-trendrider-v4-strategy
Add TrendRiderStrategy: V4 cascading early loss cut (66.7% WR, 1.42% DD on 30d Bybit)
2026-05-05 07:06:27 +02:00
Matthias 9132a0c919 fix: revert missleading update to readme 2026-05-05 06:45:01 +02:00
darkvolg a892f504b5 fix: remove internal V4 version references from strategy code
Per maintainer feedback, the strategy-examples repo shouldn't imply
prior/private versions. Removed V4 tags from comments and docstrings.
2026-04-21 16:19:52 +03:00
darkvolgandClaude Opus 4.6 405be8e0e8 fix: address review feedback from xmatthias
- Remove "Public version" block from docstring (no need to imply private version)
- Remove inline Telegram messages from confirm_trade_entry/exit (duplicates freqtrade notifications)
- Clean up stoploss/trailing comments (remove "WIDE" wording)
- Remove "V4 cascading exit" from README table (internal versioning, not user-facing)
- Keep confidence-based entry filter in confirm_trade_entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:22:11 +03:00
darkvolgandClaude Opus 4.6 24179a3e9a Add TrendRiderStrategy: V4 cascading early loss cut
Wide-stop trend follower for Bybit USDT-perps (1h timeframe), 15 large-cap pairs.
6-signal confidence scoring (EMA / RSI / ADX / volume / Bollinger / MACD).

Key feature: cascading early loss cut replaces the typical flat time_exit_24h
rule. Backtest improvement on the same 30-day window:

- Profit factor: 1.03 -> 1.41
- Max drawdown: -6.12% -> -1.42%
- Sharpe: 0.22 -> 0.91
- Win rate unchanged at 66.7%
- Avg loser: -$1.14 -> -$0.34 (70% reduction)

The strategy is currently running live on a public dashboard at
https://trendrider.net/live which reads the bot's SQLite every 60 seconds.

Full post-mortem with every trade and the V3 -> V4 fix story:
https://trendrider.net/blog/freqtrade-bot-14-days-breakeven-v4-fix-2026

Source repo (MIT, with banner and full docs):
https://github.com/darkvolg/trendrider-strategy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:32:12 +03:00
MatthiasandGitHub debc6d9c22 chore: update DoesNothingstrategy to mostly skip roi/stoploss 2026-02-26 20:29:58 +01:00
MatthiasandGitHub 0d2cb46883 Merge pull request #327 from kagari306/kagari306-patch-1
Refactor Supertrend method for efficiency
2026-01-17 18:19:54 +01:00
KagariandGitHub fc4894e5a5 Import pandas and handle NaN values in Supertrend
Added pandas import and implemented fillna to handle NaN values in the Supertrend strategy.
2026-01-12 23:07:28 +08:00
Matthias 4dcce29de1 fix: use correct futures pair naming
closes #328
2026-01-11 13:47:45 +01:00
KagariandGitHub c07aa68697 Refactor Supertrend method for efficiency
Refactor Supertrend calculation to improve clarity and performance by using numpy arrays for calculations.
2026-01-04 05:10:33 +08:00
MatthiasandGitHub 42b6030400 Merge pull request #325 from TheNotoBarth/main
feat: add explicit space parameters to FAdxSmaStrategy
2025-12-20 17:45:07 +01:00
TheNotoBarth d2e8721595 feat: add explicit space parameters to FAdxSmaStrategy 2025-12-20 22:11:06 +08:00
Matthias b8a90bebeb chore: fix further np.NaN occurances 2025-12-01 19:31:40 +01:00
MatthiasandGitHub 7c383585cd Merge pull request #323 from akashamar/patch-2
Fix Supertrend strategy for NumPy 2.x compatibility
2025-12-01 19:30:12 +01:00
AkashandGitHub ac0d4729f7 Fix Supertrend strategy for NumPy 2.x compatibility
- Replaced  `np.NaN` with `None` to ensure compatibility with NumPy 2.x.
- Prevents AttributeError during backtesting in freqtrade with NumPy 2.x.
2025-11-29 18:48:26 +05:30
MatthiasandGitHub a9a3fc2bbf Merge pull request #321 from stash86/fix-recursive
Add startup candle to avoid recursive issue
2025-04-16 18:19:31 +02:00
Stefano Ariestasia 8fddeae020 TYPO 2025-04-16 18:46:10 +09:00
Stefano Ariestasia 028fd8ed67 CostumSAR 2025-04-16 18:19:58 +09:00
Stefano Ariestasia c4d5ac77ec bandtastic 2025-04-16 18:04:43 +09:00
Stefano Ariestasia b78ef687e7 supertrend 2025-04-16 17:58:50 +09:00
MatthiasandGitHub e0579b641b Merge pull request #316 from Klaus-Js/patch-1
Update README.md
2025-01-20 19:25:16 +01:00
Klaus Gergull da SilvaandGitHub 1bb298d78f Update README.md
Typo fix
2025-01-20 15:21:12 -03:00
MatthiasandGitHub ac77a82387 Merge pull request #312 from freqtrade/stash86-patch-1
Typo on enter_short line
2024-06-25 14:01:24 +02:00
12 changed files with 808 additions and 172 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ It is designed to support all major exchanges and be controlled via Telegram. It
Each Strategies includes: Each Strategies includes:
- [x] **Minimal ROI**: Minimal ROI optimized for the strategy. - [x] **Minimal ROI**: Minimal ROI optimized for the strategy.
- [x] **Stoploss**: Optimimal stoploss. - [x] **Stoploss**: Optimal stoploss.
- [x] **Buy signals**: Result from Hyperopt or based on exisiting trading strategies. - [x] **Buy signals**: Result from Hyperopt or based on exisiting trading strategies.
- [x] **Sell signals**: Result from Hyperopt or based on exisiting trading strategies. - [x] **Sell signals**: Result from Hyperopt or based on exisiting trading strategies.
- [x] **Indicators**: Includes the indicators required to run the strategy. - [x] **Indicators**: Includes the indicators required to run the strategy.
+8 -6
View File
@@ -19,7 +19,7 @@ __BTC_donation__ = "3FgFaG15yntZYSUzfEpxr5mDt1RArvcQrK"
# 199/40000: 30918 trades. 18982/3408/8528 Wins/Draws/Losses. Avg profit 0.39%. Median profit 0.65%. Total profit 119934.26007495 USDT ( 119.93%). Avg duration 8:12:00 min. Objective: -127.60220 # 199/40000: 30918 trades. 18982/3408/8528 Wins/Draws/Losses. Avg profit 0.39%. Median profit 0.65%. Total profit 119934.26007495 USDT ( 119.93%). Avg duration 8:12:00 min. Objective: -127.60220
class Bandtastic(IStrategy): class Bandtastic(IStrategy):
INTERFACE_VERSION = 2 INTERFACE_VERSION = 3
timeframe = '15m' timeframe = '15m'
@@ -34,6 +34,8 @@ class Bandtastic(IStrategy):
# Stoploss: # Stoploss:
stoploss = -0.345 stoploss = -0.345
startup_candle_count = 999
# Trailing stop: # Trailing stop:
trailing_stop = True trailing_stop = True
trailing_stop_positive = 0.01 trailing_stop_positive = 0.01
@@ -42,7 +44,7 @@ class Bandtastic(IStrategy):
# Hyperopt Buy Parameters # Hyperopt Buy Parameters
buy_fastema = IntParameter(low=1, high=236, default=211, space='buy', optimize=True, load=True) buy_fastema = IntParameter(low=1, high=236, default=211, space='buy', optimize=True, load=True)
buy_slowema = IntParameter(low=1, high=126, default=364, space='buy', optimize=True, load=True) buy_slowema = IntParameter(low=1, high=250, default=250, space='buy', optimize=True, load=True)
buy_rsi = IntParameter(low=15, high=70, default=52, space='buy', optimize=True, load=True) buy_rsi = IntParameter(low=15, high=70, default=52, space='buy', optimize=True, load=True)
buy_mfi = IntParameter(low=15, high=70, default=30, space='buy', optimize=True, load=True) buy_mfi = IntParameter(low=15, high=70, default=30, space='buy', optimize=True, load=True)
@@ -98,7 +100,7 @@ class Bandtastic(IStrategy):
return dataframe return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = [] conditions = []
# GUARDS # GUARDS
@@ -125,11 +127,11 @@ class Bandtastic(IStrategy):
if conditions: if conditions:
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, conditions),
'buy'] = 1 'enter_long'] = 1
return dataframe return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = [] conditions = []
# GUARDS # GUARDS
@@ -156,6 +158,6 @@ class Bandtastic(IStrategy):
if conditions: if conditions:
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, conditions),
'sell'] = 1 'exit_long'] = 1
return dataframe return dataframe
@@ -29,6 +29,8 @@ class CustomStoplossWithPSAR(IStrategy):
custom_info = {} custom_info = {}
use_custom_stoploss = True use_custom_stoploss = True
startup_candle_count = 199
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float: current_rate: float, current_profit: float, **kwargs) -> float:
+1 -1
View File
@@ -104,7 +104,7 @@ class FixedRiskRewardLoss(IStrategy):
:param dataframe: DataFrame :param dataframe: DataFrame
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
# Allways buys # Always buys
dataframe.loc[:, 'enter_long'] = 1 dataframe.loc[:, 'enter_long'] = 1
return dataframe return dataframe
+69 -50
View File
@@ -18,6 +18,7 @@ from freqtrade.strategy import IStrategy, IntParameter
from pandas import DataFrame from pandas import DataFrame
import talib.abstract as ta import talib.abstract as ta
import numpy as np import numpy as np
import pandas as pd
class Supertrend(IStrategy): 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' # 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'
@@ -63,7 +64,7 @@ class Supertrend(IStrategy):
timeframe = '1h' timeframe = '1h'
startup_candle_count = 18 startup_candle_count = 199
buy_m1 = IntParameter(1, 7, default=4) buy_m1 = IntParameter(1, 7, default=4)
buy_m2 = IntParameter(1, 7, default=4) buy_m2 = IntParameter(1, 7, default=4)
@@ -80,30 +81,47 @@ class Supertrend(IStrategy):
sell_p3 = IntParameter(7, 21, default=14) sell_p3 = IntParameter(7, 21, default=14)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
new_cols = []
for multiplier in self.buy_m1.range: for multiplier in self.buy_m1.range:
for period in self.buy_p1.range: for period in self.buy_p1.range:
dataframe[f'supertrend_1_buy_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX'] 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 multiplier in self.buy_m2.range:
for period in self.buy_p2.range: for period in self.buy_p2.range:
dataframe[f'supertrend_2_buy_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX'] 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 multiplier in self.buy_m3.range:
for period in self.buy_p3.range: for period in self.buy_p3.range:
dataframe[f'supertrend_3_buy_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX'] 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 multiplier in self.sell_m1.range:
for period in self.sell_p1.range: for period in self.sell_p1.range:
dataframe[f'supertrend_1_sell_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX'] 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 multiplier in self.sell_m2.range:
for period in self.sell_p2.range: for period in self.sell_p2.range:
dataframe[f'supertrend_2_sell_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX'] 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 multiplier in self.sell_m3.range:
for period in self.sell_p3.range: for period in self.sell_p3.range:
dataframe[f'supertrend_3_sell_{multiplier}_{period}'] = self.supertrend(dataframe, multiplier, period)['STX'] 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 return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -136,42 +154,43 @@ class Supertrend(IStrategy):
Supertrend Indicator; adapted for freqtrade Supertrend Indicator; adapted for freqtrade
from: https://github.com/freqtrade/freqtrade-strategies/issues/30 from: https://github.com/freqtrade/freqtrade-strategies/issues/30
""" """
def supertrend(self, dataframe: DataFrame, multiplier, period): def supertrend(self, dataframe: pd.DataFrame, multiplier, period):
df = dataframe.copy() 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
df['TR'] = ta.TRANGE(df)
df['ATR'] = ta.SMA(df['TR'], period)
st = 'ST_' + str(period) + '_' + str(multiplier)
stx = 'STX_' + str(period) + '_' + str(multiplier)
# Compute basic upper and lower bands
df['basic_ub'] = (df['high'] + df['low']) / 2 + multiplier * df['ATR']
df['basic_lb'] = (df['high'] + df['low']) / 2 - multiplier * df['ATR']
# Compute final upper and lower bands
df['final_ub'] = 0.00
df['final_lb'] = 0.00
for i in range(period, len(df)):
df['final_ub'].iat[i] = df['basic_ub'].iat[i] if df['basic_ub'].iat[i] < df['final_ub'].iat[i - 1] or df['close'].iat[i - 1] > df['final_ub'].iat[i - 1] else df['final_ub'].iat[i - 1]
df['final_lb'].iat[i] = df['basic_lb'].iat[i] if df['basic_lb'].iat[i] > df['final_lb'].iat[i - 1] or df['close'].iat[i - 1] < df['final_lb'].iat[i - 1] else df['final_lb'].iat[i - 1]
# Set the Supertrend value
df[st] = 0.00
for i in range(period, len(df)):
df[st].iat[i] = df['final_ub'].iat[i] if df[st].iat[i - 1] == df['final_ub'].iat[i - 1] and df['close'].iat[i] <= df['final_ub'].iat[i] else \
df['final_lb'].iat[i] if df[st].iat[i - 1] == df['final_ub'].iat[i - 1] and df['close'].iat[i] > df['final_ub'].iat[i] else \
df['final_lb'].iat[i] if df[st].iat[i - 1] == df['final_lb'].iat[i - 1] and df['close'].iat[i] >= df['final_lb'].iat[i] else \
df['final_ub'].iat[i] if df[st].iat[i - 1] == df['final_lb'].iat[i - 1] and df['close'].iat[i] < df['final_lb'].iat[i] else 0.00
# Mark the trend direction up/down
df[stx] = np.where((df[st] > 0.00), np.where((df['close'] < df[st]), 'down', 'up'), np.NaN)
# Remove basic and final bands from the columns
df.drop(['basic_ub', 'basic_lb', 'final_ub', 'final_lb'], inplace=True, axis=1)
df.fillna(0, inplace=True)
return DataFrame(index=df.index, data={
'ST' : df[st],
'STX' : df[stx]
})
+633
View File
@@ -0,0 +1,633 @@
"""
TrendRider Strategy
Ride established trends with ATR-aware stoploss.
Key insight: crypto swings 2-4% per hour, stoploss must accommodate this volatility.
- Leverage 1x (spot-safe)
- TA-Lib indicators with confidence scoring
- Multiple entry signals: pullback, EMA bounce, RSI bounce, crossover, BB bounce, MACD reversal
"""
import talib.abstract as ta
from datetime import datetime
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, merge_informative_pair
from pandas import DataFrame
from functools import reduce
import logging
logger = logging.getLogger(__name__)
class TrendRiderStrategy(IStrategy):
INTERFACE_VERSION = 3
# --- ROI: Hyperopt-optimized (2026-03-23, 5 pairs) ---
minimal_roi = {
"0": 0.229, # 22.9% immediate
"124": 0.136, # 13.6% after ~2h
"290": 0.044, # 4.4% after ~5h
"764": 0, # breakeven after ~12.7h
}
# --- Stoploss ---
stoploss = -0.06 # 6% default (ATR-based custom stoploss overrides)
use_custom_stoploss = False
# --- Trailing Stop ---
trailing_stop = True
trailing_stop_positive = 0.03 # 3% trail
trailing_stop_positive_offset = 0.05 # Activate after +5%
trailing_only_offset_is_reached = True
# --- General ---
timeframe = "1h"
startup_candle_count = 210
process_only_new_candles = True
can_short = False
position_adjustment_enable = False
# --- Protections (moved from config.json for Freqtrade 2026.2+) ---
protections = [
{
"method": "CooldownPeriod",
"stop_duration": 20
},
{
"method": "StoplossGuard",
"lookback_period": 720,
"trade_limit": 3,
"stop_duration": 60,
"only_per_pair": False
},
{
"method": "MaxDrawdown",
"lookback_period": 1440,
"max_allowed_drawdown": 0.10,
"stop_duration": 300,
"trade_limit": 5
}
]
# --- HyperOpt Results (applied from optimization session 2026-03-23) ---
buy_params = {
"ema_fast": 9,
"ema_slow": 16,
"rsi_period": 16,
"rsi_pullback_low": 30,
"rsi_pullback_high": 65,
"rsi_bounce": 35,
"adx_threshold": 18,
"volume_factor": 0.7,
}
sell_params = {
"rsi_exit": 78,
}
# --- HyperOpt Parameters ---
ema_fast = IntParameter(5, 15, default=9, space="buy")
ema_slow = IntParameter(15, 30, default=21, space="buy")
rsi_period = IntParameter(10, 20, default=14, space="buy")
rsi_pullback_low = IntParameter(30, 48, default=40, space="buy")
rsi_pullback_high = IntParameter(52, 65, default=58, space="buy")
rsi_bounce = IntParameter(25, 35, default=30, space="buy")
rsi_exit = IntParameter(72, 85, default=78, space="sell")
adx_threshold = IntParameter(20, 35, default=25, space="buy")
volume_factor = DecimalParameter(1.0, 2.5, default=1.3, space="buy")
# --- Leverage: 1x for Strat Ninja (spot-safe) ---
leverage_value = 1
def leverage(self, pair: str, current_time, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: str,
side: str, **kwargs) -> float:
return 1
def informative_pairs(self):
pairs = self.dp.current_whitelist() if self.dp else []
informative = []
for pair in pairs:
informative.append((pair, "4h"))
informative.append((pair, "1d"))
# BTC as market sentiment
informative.append(("BTC/USDT:USDT", "1h"))
informative.append(("BTC/USDT:USDT", "4h"))
return informative
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# EMAs (all periods for hyperopt ranges)
for period in range(5, 31):
dataframe[f"ema_{period}"] = ta.EMA(dataframe, timeperiod=period)
dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
# RSI (all periods for hyperopt range 10-20)
for period in range(10, 21):
dataframe[f"rsi_{period}"] = ta.RSI(dataframe, timeperiod=period)
# ADX
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)
# MACD
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
dataframe["macdhist_prev"] = macd["macdhist"].shift(1)
# Bollinger Bands
bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
dataframe["bb_upper"] = bb["upperband"]
dataframe["bb_middle"] = bb["middleband"]
dataframe["bb_lower"] = bb["lowerband"]
# BB width for volatility regime
dataframe["bb_width"] = (dataframe["bb_upper"] - dataframe["bb_lower"]) / (dataframe["bb_middle"] + 1e-10)
dataframe["bb_width_sma"] = ta.SMA(dataframe["bb_width"], timeperiod=50)
# Volume (fix #4: epsilon guard against division by zero)
dataframe["volume_ema"] = ta.EMA(dataframe["volume"], timeperiod=20)
dataframe["volume_ratio"] = dataframe["volume"] / (dataframe["volume_ema"] + 1e-10)
# OBV
dataframe["obv"] = ta.OBV(dataframe)
dataframe["obv_ema"] = ta.EMA(dataframe["obv"], timeperiod=20)
# ATR for dynamic stoploss
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
# Regime
dataframe["is_bull"] = (
(dataframe["close"] > dataframe["ema_200"]) &
(dataframe["ema_50"] > dataframe["ema_200"])
).astype(int)
dataframe["is_bear"] = (
(dataframe["close"] < dataframe["ema_200"]) &
(dataframe["ema_50"] < dataframe["ema_200"])
).astype(int)
# --- LONG pullback detection ---
ema_slow_key = f"ema_{self.ema_slow.value}"
if ema_slow_key in dataframe.columns:
dataframe["pullback_to_ema"] = (
(dataframe["low"] <= dataframe[ema_slow_key] * 1.02) &
(dataframe["close"] > dataframe[ema_slow_key]) &
(dataframe["close"] > dataframe["open"]) # Bullish candle
).astype(int)
else:
dataframe["pullback_to_ema"] = 0
# EMA50 support bounce (LONG)
dataframe["ema50_bounce"] = (
(dataframe["low"] <= dataframe["ema_50"] * 1.01) &
(dataframe["close"] > dataframe["ema_50"]) &
(dataframe["close"] > dataframe["open"])
).astype(int)
# --- Multi-Timeframe data ---
if self.dp:
# 4h data for current pair
df_4h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='4h')
if len(df_4h) > 0:
df_4h['ema_50'] = ta.EMA(df_4h, timeperiod=50)
df_4h['ema_200'] = ta.EMA(df_4h, timeperiod=200)
df_4h['rsi_14'] = ta.RSI(df_4h, timeperiod=14)
df_4h['adx'] = ta.ADX(df_4h, timeperiod=14)
df_4h['is_bull'] = (
(df_4h['close'] > df_4h['ema_200']) &
(df_4h['ema_50'] > df_4h['ema_200'])
).astype(int)
dataframe = merge_informative_pair(
dataframe,
df_4h[['date', 'ema_50', 'ema_200', 'rsi_14', 'adx', 'is_bull']],
self.timeframe, '4h', ffill=True
)
else:
dataframe['ema_50_4h'] = 0
dataframe['ema_200_4h'] = 0
dataframe['rsi_14_4h'] = 50
dataframe['adx_4h'] = 0
dataframe['is_bull_4h'] = 0
# Daily data for macro trend
df_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d')
if len(df_1d) > 0:
df_1d['ema_200'] = ta.EMA(df_1d, timeperiod=200)
dataframe = merge_informative_pair(
dataframe,
df_1d[['date', 'ema_200']],
self.timeframe, '1d', ffill=True
)
else:
dataframe['ema_200_1d'] = 0
# BTC market sentiment
df_btc = self.dp.get_pair_dataframe(pair='BTC/USDT:USDT', timeframe='1h')
if len(df_btc) > 0:
df_btc['btc_ema_200'] = ta.EMA(df_btc, timeperiod=200)
df_btc['btc_ema_50'] = ta.EMA(df_btc, timeperiod=50)
df_btc['btc_rsi'] = ta.RSI(df_btc, timeperiod=14)
df_btc['btc_is_bull'] = (
(df_btc['close'] > df_btc['btc_ema_200']) &
(df_btc['btc_ema_50'] > df_btc['btc_ema_200'])
).astype(int)
dataframe = merge_informative_pair(
dataframe,
df_btc[['date', 'btc_ema_200', 'btc_ema_50', 'btc_rsi', 'btc_is_bull']],
self.timeframe, '1h', ffill=True
)
else:
dataframe['btc_is_bull_1h'] = 1
dataframe['btc_rsi_1h'] = 50
else:
# Safety fallback when dp is not available
dataframe['is_bull_4h'] = dataframe['is_bull']
dataframe['rsi_14_4h'] = dataframe['rsi_14'] if 'rsi_14' in dataframe.columns else 50
dataframe['adx_4h'] = dataframe['adx']
dataframe['btc_is_bull_1h'] = 1
dataframe['btc_rsi_1h'] = 50
dataframe['ema_200_1d'] = 0
# Ensure columns exist (safety for backtesting edge cases)
for col, default in [
('is_bull_4h', 1), ('rsi_14_4h', 50), ('adx_4h', 20),
('btc_is_bull_1h', 1), ('btc_rsi_1h', 50),
('ema_200_1d', 0),
]:
if col not in dataframe.columns:
dataframe[col] = default
# --- Fear & Greed Index: static neutral (no API) ---
dataframe['fng_value'] = 50
# --- On-chain: static defaults (no API) ---
dataframe['funding_rate'] = 0.0
dataframe['funding_extreme'] = 0
dataframe['oi_change'] = 0.0
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
rsi = f"rsi_{self.rsi_period.value}"
# ========== LONG ENTRIES ==========
# === LONG 1: Trend Pullback to EMA ===
conditions_pullback = [
dataframe["is_bull"] == 1,
dataframe["pullback_to_ema"] == 1,
dataframe[rsi] > self.rsi_pullback_low.value,
dataframe[rsi] < self.rsi_pullback_high.value,
dataframe["adx"] > self.adx_threshold.value,
dataframe["volume_ratio"] > self.volume_factor.value,
dataframe["plus_di"] > dataframe["minus_di"],
dataframe["obv"] > dataframe["obv_ema"],
dataframe["volume"] > 0,
dataframe["btc_rsi_1h"] > 35,
dataframe["fng_value"] >= 25, # Not extreme fear
dataframe["fng_value"] <= 85, # Not extreme greed
dataframe[rsi] < 70, # Not overbought
]
# Daily EMA200 filter — helps filter bad entries
if 'ema_200_1d' in dataframe.columns:
conditions_pullback.append(dataframe["close"] > dataframe["ema_200_1d"])
dataframe.loc[
reduce(lambda x, y: x & y, conditions_pullback),
["enter_long", "enter_tag"]
] = (1, "trend_pullback")
# === LONG 2: EMA50 Support Bounce ===
conditions_ema50 = [
dataframe["is_bull"] == 1,
dataframe["ema50_bounce"] == 1,
dataframe[rsi] > 30,
dataframe[rsi] < 50,
dataframe["adx"] > 20,
dataframe["volume_ratio"] > 1.0,
dataframe["macdhist"] > dataframe["macdhist"].shift(1),
dataframe["volume"] > 0,
dataframe["btc_rsi_1h"] > 35,
dataframe["fng_value"] >= 25,
dataframe["fng_value"] <= 85,
dataframe[rsi] < 70,
]
dataframe.loc[
reduce(lambda x, y: x & y, conditions_ema50),
["enter_long", "enter_tag"]
] = (1, "ema50_bounce")
# === LONG 3: RSI Oversold Bounce ===
conditions_rsi = [
dataframe["close"] > dataframe["ema_200"],
dataframe[rsi].shift(1) < self.rsi_bounce.value,
dataframe[rsi] > self.rsi_bounce.value,
dataframe["close"] > dataframe["bb_lower"],
dataframe["close"] > dataframe["open"],
dataframe["volume_ratio"] > 0.8,
dataframe["obv"] > dataframe["obv_ema"],
dataframe["volume"] > 0,
dataframe["btc_rsi_1h"] > 35,
dataframe["fng_value"] >= 25,
dataframe["fng_value"] <= 85,
]
dataframe.loc[
reduce(lambda x, y: x & y, conditions_rsi),
["enter_long", "enter_tag"]
] = (1, "rsi_bounce")
# === LONG 4: EMA Crossover (golden cross on fast EMAs) ===
ema_fast_key = f"ema_{self.ema_fast.value}"
ema_slow_key = f"ema_{self.ema_slow.value}"
conditions_ema_cross = [
(dataframe[ema_fast_key] > dataframe[ema_slow_key]) &
(dataframe[ema_fast_key].shift(1) <= dataframe[ema_slow_key].shift(1)), # crossed above
dataframe[rsi] > 40,
dataframe[rsi] < 75,
dataframe["close"] > dataframe["ema_200"],
dataframe["volume_ratio"] > 0.5,
dataframe["volume"] > 0,
dataframe["btc_rsi_1h"] > 35,
dataframe["fng_value"] >= 25,
dataframe["fng_value"] <= 85,
]
dataframe.loc[
reduce(lambda x, y: x & y, conditions_ema_cross),
["enter_long", "enter_tag"]
] = (1, "ema_crossover")
# === LONG 5: Bollinger Band Bounce ===
conditions_bb = [
dataframe["close"] <= dataframe["bb_lower"] * 1.005, # close within 0.5% of BB lower
dataframe["close"] > dataframe["open"], # bullish candle (bounce)
dataframe[rsi] < 45,
dataframe["volume_ratio"] > 0.7, # filter weak bounces
dataframe["adx"] > 18, # trend strength filter
dataframe["volume"] > 0,
dataframe["btc_rsi_1h"] > 35,
dataframe["fng_value"] >= 25,
dataframe["fng_value"] <= 85,
]
dataframe.loc[
reduce(lambda x, y: x & y, conditions_bb),
["enter_long", "enter_tag"]
] = (1, "bb_bounce")
# === LONG 6: MACD Histogram Reversal (tightened: RSI 40-60, EMA200 filter, volume 0.8x) ===
conditions_macd = [
(dataframe["macdhist"] > 0) &
(dataframe["macdhist"].shift(1) <= 0), # histogram crossed above zero
dataframe["close"] > dataframe["ema_50"],
dataframe["close"] > dataframe["ema_200"], # confirm uptrend
dataframe[rsi] > 40,
dataframe[rsi] < 60,
dataframe["adx"] > 15,
dataframe["volume_ratio"] > 0.8, # volume confirmation
dataframe["volume"] > 0,
dataframe["btc_rsi_1h"] > 35,
dataframe["fng_value"] >= 25,
dataframe["fng_value"] <= 85,
]
dataframe.loc[
reduce(lambda x, y: x & y, conditions_macd),
["enter_long", "enter_tag"]
] = (1, "macd_reversal")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
rsi = f"rsi_{self.rsi_period.value}"
ema_fast = f"ema_{self.ema_fast.value}"
ema_slow = f"ema_{self.ema_slow.value}"
# ========== LONG EXITS ==========
# EXIT 1: RSI very overbought
dataframe.loc[
(dataframe[rsi] > self.rsi_exit.value) &
(dataframe["volume"] > 0),
["exit_long", "exit_tag"]
] = (1, "rsi_overbought")
# EXIT 2: Bearish EMA cross with MACD confirmation
dataframe.loc[
(dataframe[ema_fast] < dataframe[ema_slow]) &
(dataframe[ema_fast].shift(1) >= dataframe[ema_slow].shift(1)) &
(dataframe["macdhist"] < 0) &
(dataframe[rsi] > 50) &
(dataframe["volume"] > 0),
["exit_long", "exit_tag"]
] = (1, "ema_bearish_cross")
# EXIT 3: Price drops below EMA200 by 1%+ (trend broken, softened to avoid premature exits)
dataframe.loc[
(dataframe["close"] < dataframe["ema_200"] * 0.99) &
(dataframe["close"].shift(1) >= dataframe["ema_200"].shift(1)) &
(dataframe["volume"] > 0),
["exit_long", "exit_tag"]
] = (1, "trend_broken")
# EXIT 4: Trend early warning — RSI overbought reversal near EMA200
# Catches trend exhaustion before price breaks support, saving avg -3% vs trend_broken
dataframe.loc[
(dataframe["close"] < dataframe["ema_200"] * 0.995) & # within 0.5% of breaking
(dataframe[rsi] > 72) & # exhausted
(dataframe["macdhist"] < dataframe["macdhist"].shift(1)) & # momentum dropping
(dataframe["volume"] > 0),
["exit_long", "exit_tag"]
] = (1, "trend_early_warning")
return dataframe
# --- Improved Confidence Scoring (inline from trendrider_confidence) ---
def _calc_confidence(self, last: dict) -> tuple:
"""Calculate signal confidence based on weighted indicator alignment.
Max score ~17.5. Returns (level_str, bar_str, details_list, numeric_level).
"""
score = 0.0
details = []
rsi_key = f"rsi_{self.rsi_period.value}"
rsi_val = last.get(rsi_key, 50)
# RSI in healthy zone (not overbought): +1.5
if 35 < rsi_val < 60:
score += 1.5
details.append("RSI healthy")
# Strong trend (ADX): +2.5 strong, +1.5 moderate
adx_val = last.get('adx', 0)
if adx_val > 30:
score += 2.5
details.append("Strong trend")
elif adx_val > self.adx_threshold.value:
score += 1.5
details.append("Moderate trend")
# Volume confirmation: +2.5 high, +1.5 normal
vol_ratio = last.get('volume_ratio', 0)
if vol_ratio > 1.5:
score += 2.5
details.append("High volume")
elif vol_ratio > 1.0:
score += 1.5
details.append("Normal volume")
# MACD positive histogram: +1.5, bonus +0.5 if rising
macd_hist = last.get('macdhist', 0)
macd_hist_prev = last.get('macdhist_prev', 0)
if macd_hist > 0:
score += 1.5
if macd_hist > macd_hist_prev:
score += 0.5
details.append("MACD positive+rising")
else:
details.append("MACD positive")
# OBV rising AND above EMA: +1.5
if last.get('obv', 0) > last.get('obv_ema', 0):
score += 1.5
details.append("OBV rising")
# BTC healthy (RSI 40-70): +1.5
btc_rsi = last.get('btc_rsi_1h', 50)
if 40 < btc_rsi < 70:
score += 1.5
details.append("BTC healthy")
# 4h trend alignment AND ADX_4h > 20: +1.5
if last.get('is_bull_4h', 0) == 1 and last.get('adx_4h', 0) > 20:
score += 1.5
details.append("4H trend aligned")
# Bollinger Band position (close near lower = good for long): +1
close = last.get('close', 0)
bb_lower = last.get('bb_lower', 0)
bb_upper = last.get('bb_upper', 0)
bb_range = bb_upper - bb_lower if bb_upper > bb_lower else 1
if bb_lower > 0 and close > 0:
bb_position = (close - bb_lower) / bb_range
if bb_position < 0.35:
score += 1.0
details.append("Near BB lower")
# Plus_DI > Minus_DI spread > 10: +1
plus_di = last.get('plus_di', 0)
minus_di = last.get('minus_di', 0)
if plus_di - minus_di > 10:
score += 1.0
details.append("Strong DI spread")
# FNG bonus: neutral/healthy (40-60): +1
fng_val = last.get('fng_value', 50)
if 40 <= fng_val <= 60:
score += 1.0
details.append("FNG neutral")
# On-chain: healthy funding rate: +1
funding = last.get('funding_rate', 0)
if abs(funding) < 0.0001: # Normal funding
score += 1
details.append("Healthy funding")
# Smooth mapping to 1-10 (max score ~17.5)
numeric = max(1, min(10, round(score * 10 / 17.5)))
# Level label
if numeric >= 8:
level = "STRONG"
elif numeric >= 6:
level = "GOOD"
elif numeric >= 4:
level = "MEDIUM"
else:
level = "WEAK"
# Dynamic bar
bar = "|" * numeric + "-" * (10 - numeric) + f" {numeric}/10"
return level, bar, details, numeric
def _market_context(self, last: dict) -> str:
"""Generate market context string."""
btc_rsi = last.get('btc_rsi_1h', 50)
btc_bull = last.get('btc_is_bull_1h', 0)
bull_4h = last.get('is_bull_4h', 0)
if btc_bull and btc_rsi > 55:
btc_status = "Bullish"
elif btc_rsi > 40:
btc_status = "Neutral"
else:
btc_status = "Bearish"
tf_4h = "Uptrend" if bull_4h else "Downtrend"
parts = [f"BTC: {btc_status} (RSI {btc_rsi:.0f})", f"4H: {tf_4h}"]
return " | ".join(parts)
def _get_market_regime(self, last: dict) -> str:
"""Detect market regime from ADX + EMA200 + BB width."""
adx_val = last.get('adx', 0)
ema_200 = last.get('ema_200', 0)
close = last.get('close', 0)
is_bull = last.get('is_bull', 0)
bb_width = last.get('bb_width', 0)
bb_width_sma = last.get('bb_width_sma', 0)
high_vol = bb_width > bb_width_sma * 1.5 if bb_width_sma > 0 else False
if adx_val < 20:
return "Ranging (High Vol)" if high_vol else "Ranging"
elif is_bull and close > ema_200:
return "Trending Bull"
else:
return "Trending Bear (High Vol)" if high_vol else "Trending Bear"
def custom_exit(self, pair: str, trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs):
"""Cascading early exit — stop bleeding before 24h timeout.
Cascade catches losers earlier than the 24h hard timeout:
- 2h: cut if -1.5% (already broken thesis)
- 4h: cut if red (no recovery momentum)
- 8h: cut if not at +0.5% (dead trade)
- 16h: cut if not at +1% (final mercy)
"""
duration_hours = (current_time - trade.open_date_utc).total_seconds() / 3600
if duration_hours >= 2 and current_profit < -0.015:
return "early_loss_cut_2h"
if duration_hours >= 4 and current_profit < 0:
return "early_loss_cut_4h"
if duration_hours >= 8 and current_profit < 0.005:
return "early_loss_cut_8h"
if duration_hours >= 16 and current_profit < 0.01:
return "early_loss_cut_16h"
if duration_hours >= 24:
return "time_exit_24h"
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
# Get current indicators for confidence filter
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) > 0:
last = dataframe.iloc[-1]
else:
last = {}
# Confidence & regime filter — reject weak signals
_, _, _, conf_numeric = self._calc_confidence(last)
regime = self._get_market_regime(last)
min_conf = 6 if "Bear" in regime else 5
if conf_numeric < min_conf:
logger.info(f"Rejecting signal for {pair}: confidence {conf_numeric}/10 < {min_conf} (regime: {regime})")
return False
return True
@@ -18,11 +18,11 @@ class DoesNothingStrategy(IStrategy):
# adjust based on market conditions. We would recommend to keep it low for quick turn arounds # adjust based on market conditions. We would recommend to keep it low for quick turn arounds
# This attribute will be overridden if the config file contains "minimal_roi" # This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = { minimal_roi = {
"0": 0.01 "0": 100000
} }
# Optimal stoploss designed for the strategy # Optimal stoploss designed for the strategy
stoploss = -0.25 stoploss = -1
# Optimal timeframe for the strategy # Optimal timeframe for the strategy
timeframe = '5m' timeframe = '5m'
+2 -2
View File
@@ -66,8 +66,8 @@ class Low_BB(IStrategy):
# dataframe['mfi'] = ta.MFI(dataframe) # dataframe['mfi'] = ta.MFI(dataframe)
# dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7) # dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
# dataframe['canbuy'] = np.NaN # dataframe['canbuy'] = np.nan
# dataframe['canbuy2'] = np.NaN # dataframe['canbuy2'] = np.nan
# dataframe.loc[dataframe.close.rolling(49).min() <= 1.1 * dataframe.close, 'canbuy'] == 1 # dataframe.loc[dataframe.close.rolling(49).min() <= 1.1 * dataframe.close, 'canbuy'] == 1
# dataframe.loc[dataframe.close.rolling(600).max() < 1.2 * dataframe.close, 'canbuy'] = 1 # dataframe.loc[dataframe.close.rolling(600).max() < 1.2 * dataframe.close, 'canbuy'] = 1
# dataframe.loc[dataframe.close.rolling(600).max() * 0.8 > dataframe.close, 'canbuy2'] = 1 # dataframe.loc[dataframe.close.rolling(600).max() * 0.8 > dataframe.close, 'canbuy2'] = 1
@@ -142,7 +142,7 @@ class TDSequentialStrategy(IStrategy):
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy columnNA / NaN values :return: DataFrame with buy columnNA / nan values
""" """
dataframe["exit_long"] = 0 dataframe["exit_long"] = 0
dataframe.loc[((dataframe['exceed_high']) | dataframe.loc[((dataframe['exceed_high']) |
@@ -53,9 +53,9 @@ class FAdxSmaStrategy(IStrategy):
pos_exit_adx = DecimalParameter(15, 40, decimals=1, default=30.0, space="sell") pos_exit_adx = DecimalParameter(15, 40, decimals=1, default=30.0, space="sell")
# Define the parameter spaces # Define the parameter spaces
adx_period = IntParameter(4, 24, default=14) adx_period = IntParameter(4, 24, default=14, space='buy')
sma_short_period = IntParameter(4, 24, default=12) sma_short_period = IntParameter(4, 24, default=12, space='buy')
sma_long_period = IntParameter(12, 175, default=48) sma_long_period = IntParameter(12, 175, default=48, space='buy')
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -238,7 +238,7 @@ class FSupertrendStrategy(IStrategy):
) )
# Mark the trend direction up/down # Mark the trend direction up/down
df[stx] = np.where( df[stx] = np.where(
(df[st] > 0.00), np.where((df["close"] < df[st]), "down", "up"), np.NaN (df[st] > 0.00), np.where((df["close"] < df[st]), "down", "up"), None
) )
# Remove basic and final bands from the columns # Remove basic and final bands from the columns
+85 -105
View File
@@ -1,7 +1,6 @@
# all strategies are tested against this config. Tests only done on binance futures # all strategies are tested against this config. Tests only done on binance futures
``` ```
{ {
"max_open_trades": -1, "max_open_trades": -1,
"stake_currency": "USDT", "stake_currency": "USDT",
@@ -40,109 +39,89 @@
"ccxt_config": {}, "ccxt_config": {},
"ccxt_async_config": {}, "ccxt_async_config": {},
"pair_whitelist": [ "pair_whitelist": [
"AUDIO/USDT", "AAVE/USDT:USDT",
"AAVE/USDT", "ALICE/USDT:USDT",
"ALICE/USDT", "ARPA/USDT:USDT",
"ARPA/USDT", "AVAX/USDT:USDT",
"AVAX/USDT", "ATOM/USDT:USDT",
"ATOM/USDT", "ANKR/USDT:USDT",
"ANKR/USDT", "AXS/USDT:USDT",
"AXS/USDT", "ADA/USDT:USDT",
"ADA/USDT", "ALGO/USDT:USDT",
"ALGO/USDT", "BAND/USDT:USDT",
"BTS/USDT", "BEL/USDT:USDT",
"BAND/USDT", "BTC/USDT:USDT",
"BEL/USDT", "BAT/USDT:USDT",
"BNB/USDT", "CHR/USDT:USDT",
"BTC/USDT", "C98/USDT:USDT",
"BLZ/USDT", "COTI/USDT:USDT",
"BAT/USDT", "CHZ/USDT:USDT",
"CHR/USDT", "COMP/USDT:USDT",
"C98/USDT", "CRV/USDT:USDT",
"COTI/USDT", "CELO/USDT:USDT",
"CHZ/USDT", "DUSK/USDT:USDT",
"COMP/USDT", "DOGE/USDT:USDT",
"CRV/USDT", "DENT/USDT:USDT",
"CELO/USDT", "DASH/USDT:USDT",
"DUSK/USDT", "DOT/USDT:USDT",
"DOGE/USDT", "DYDX/USDT:USDT",
"DENT/USDT", "ENJ/USDT:USDT",
"DASH/USDT", "ETH/USDT:USDT",
"DOT/USDT", "ETC/USDT:USDT",
"DYDX/USDT", "ENS/USDT:USDT",
"ENJ/USDT", "EGLD/USDT:USDT",
"EOS/USDT", "FIL/USDT:USDT",
"ETH/USDT", "GRT/USDT:USDT",
"ETC/USDT", "GALA/USDT:USDT",
"ENS/USDT", "HBAR/USDT:USDT",
"EGLD/USDT", "HOT/USDT:USDT",
"FIL/USDT", "IOTX/USDT:USDT",
"FTM/USDT", "ICX/USDT:USDT",
"FLM/USDT", "ICP/USDT:USDT",
"GRT/USDT", "IOTA/USDT:USDT",
"GALA/USDT", "IOST/USDT:USDT",
"HBAR/USDT", "KAVA/USDT:USDT",
"HOT/USDT", "KNC/USDT:USDT",
"IOTX/USDT", "KSM/USDT:USDT",
"ICX/USDT", "LRC/USDT:USDT",
"ICP/USDT", "LTC/USDT:USDT",
"IOTA/USDT", "LINK/USDT:USDT",
"IOST/USDT", "NEAR/USDT:USDT",
"KLAY/USDT", "MANA/USDT:USDT",
"KAVA/USDT", "MTL/USDT:USDT",
"KNC/USDT", "NEO/USDT:USDT",
"KSM/USDT", "ONT/USDT:USDT",
"LUNA/USDT", "OGN/USDT:USDT",
"LRC/USDT", "ONE/USDT:USDT",
"LINA/USDT", "PEOPLE/USDT:USDT",
"LTC/USDT", "RLC/USDT:USDT",
"LINK/USDT", "RUNE/USDT:USDT",
"MATIC/USDT", "RVN/USDT:USDT",
"NEAR/USDT", "RSR/USDT:USDT",
"MANA/USDT", "ROSE/USDT:USDT",
"MTL/USDT", "SNX/USDT:USDT",
"NEO/USDT", "SAND/USDT:USDT",
"ONT/USDT", "SOL/USDT:USDT",
"OMG/USDT", "SUSHI/USDT:USDT",
"OCEAN/USDT", "SKL/USDT:USDT",
"OGN/USDT", "STORJ/USDT:USDT",
"ONE/USDT", "TRX/USDT:USDT",
"PEOPLE/USDT", "TRB/USDT:USDT",
"RLC/USDT", "TLM/USDT:USDT",
"RUNE/USDT", "THETA/USDT:USDT",
"RVN/USDT", "UNI/USDT:USDT",
"RSR/USDT", "VET/USDT:USDT",
"REEF/USDT", "YFI/USDT:USDT",
"ROSE/USDT", "ZIL/USDT:USDT",
"SNX/USDT", "ZEN/USDT:USDT",
"SAND/USDT", "ZRX/USDT:USDT",
"SOL/USDT", "ZEC/USDT:USDT",
"SUSHI/USDT", "XRP/USDT:USDT",
"SRM/USDT", "XLM/USDT:USDT",
"SKL/USDT", "XTZ/USDT:USDT",
"SXP/USDT", "XMR/USDT:USDT",
"STORJ/USDT", "QTUM/USDT:USDT",
"TRX/USDT", "1INCH/USDT:USDT"
"TOMO/USDT",
"TRB/USDT",
"TLM/USDT",
"THETA/USDT",
"UNI/USDT",
"UNFI/USDT",
"VET/USDT",
"YFI/USDT",
"ZIL/USDT",
"ZEN/USDT",
"ZRX/USDT",
"ZEC/USDT",
"WAVES/USDT",
"XRP/USDT",
"XLM/USDT",
"XTZ/USDT",
"XMR/USDT",
"XEM/USDT",
"QTUM/USDT",
"1INCH/USDT"
], ],
"pair_blacklist": ["BNB/.*"] "pair_blacklist": ["BNB/.*"]
}, },
@@ -162,7 +141,7 @@
"remove_pumps": false "remove_pumps": false
}, },
"telegram": { "telegram": {
"enabled": true, "enabled": false,
"token": "", "token": "",
"chat_id": "" "chat_id": ""
}, },
@@ -185,4 +164,5 @@
} }
} }
``` ```