Compare commits
37 Commits
frglstn-patch-1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dbd5b0b21c | |||
| 9132a0c919 | |||
| a892f504b5 | |||
| 405be8e0e8 | |||
| 24179a3e9a | |||
| debc6d9c22 | |||
| 0d2cb46883 | |||
| fc4894e5a5 | |||
| 4dcce29de1 | |||
| c07aa68697 | |||
| 42b6030400 | |||
| d2e8721595 | |||
| b8a90bebeb | |||
| 7c383585cd | |||
| ac0d4729f7 | |||
| a9a3fc2bbf | |||
| 8fddeae020 | |||
| 028fd8ed67 | |||
| c4d5ac77ec | |||
| b78ef687e7 | |||
| e0579b641b | |||
| 1bb298d78f | |||
| ac77a82387 | |||
| 0727df98ac | |||
| d207422bdf | |||
| 6fce5f382b | |||
| 89d3915a55 | |||
| 2ca5431b5d | |||
| b8820cbe4a | |||
| 190c419c2c | |||
| fd2edba838 | |||
| ca78f5670e | |||
| 43a21eefdb | |||
| 9c18ccd089 | |||
| ffae38235f | |||
| d0629c849a | |||
| 3d6219186e |
@@ -70,7 +70,7 @@ It is designed to support all major exchanges and be controlled via Telegram. It
|
||||
Each Strategies includes:
|
||||
|
||||
- [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] **Sell signals**: Result from Hyperopt or based on exisiting trading strategies.
|
||||
- [x] **Indicators**: Includes the indicators required to run the strategy.
|
||||
|
||||
@@ -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
|
||||
|
||||
class Bandtastic(IStrategy):
|
||||
INTERFACE_VERSION = 2
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
timeframe = '15m'
|
||||
|
||||
@@ -34,6 +34,8 @@ class Bandtastic(IStrategy):
|
||||
# Stoploss:
|
||||
stoploss = -0.345
|
||||
|
||||
startup_candle_count = 999
|
||||
|
||||
# Trailing stop:
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.01
|
||||
@@ -42,7 +44,7 @@ class Bandtastic(IStrategy):
|
||||
|
||||
# Hyperopt Buy Parameters
|
||||
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_mfi = IntParameter(low=15, high=70, default=30, space='buy', optimize=True, load=True)
|
||||
|
||||
@@ -98,7 +100,7 @@ class Bandtastic(IStrategy):
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
conditions = []
|
||||
|
||||
# GUARDS
|
||||
@@ -125,11 +127,11 @@ class Bandtastic(IStrategy):
|
||||
if conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
'buy'] = 1
|
||||
'enter_long'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
conditions = []
|
||||
|
||||
# GUARDS
|
||||
@@ -156,6 +158,6 @@ class Bandtastic(IStrategy):
|
||||
if conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
'sell'] = 1
|
||||
'exit_long'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
@@ -29,6 +29,8 @@ class CustomStoplossWithPSAR(IStrategy):
|
||||
custom_info = {}
|
||||
use_custom_stoploss = True
|
||||
|
||||
startup_candle_count = 199
|
||||
|
||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class FixedRiskRewardLoss(IStrategy):
|
||||
:param dataframe: DataFrame
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
# Allways buys
|
||||
# Always buys
|
||||
dataframe.loc[:, 'enter_long'] = 1
|
||||
return dataframe
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
# flake8: noqa: F401
|
||||
# isort: skip_file
|
||||
# --- Do not remove these libs ---
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
|
||||
IntParameter, IStrategy, merge_informative_pair)
|
||||
|
||||
# --------------------------------
|
||||
# Add your lib to import here
|
||||
import talib.abstract as ta
|
||||
import pandas_ta as pta
|
||||
from technical import qtpylib
|
||||
|
||||
|
||||
class PowerTower(IStrategy):
|
||||
# By: Masoud Azizi (@mablue)
|
||||
# Power Tower is a complitly New Strategy(or Candlistic Pattern or Indicator) to finding strongly rising coins.
|
||||
# much effective than "Three black Crows" but based on Idea of this candlestick pattern, but with different rules!
|
||||
|
||||
# Strategy interface version - allow new iterations of the strategy interface.
|
||||
# Check the documentation or the Sample strategy to get the latest version.
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# $ freqtrade hyperopt -s PowerTower --hyperopt-loss SharpeHyperOptLossDaily
|
||||
|
||||
# "max_open_trades": 1,
|
||||
# "stake_currency": "USDT",
|
||||
# "stake_amount": 990,
|
||||
# "dry_run_wallet": 1000,
|
||||
# "trading_mode": "spot",
|
||||
# "XMR/USDT","ATOM/USDT","FTM/USDT","CHR/USDT","BNB/USDT","ALGO/USDT","XEM/USDT","XTZ/USDT","ZEC/USDT","ADA/USDT",
|
||||
# "CHZ/USDT","BTT/USDT","LUNA/USDT","VRA/USDT","KSM/USDT","DASH/USDT","COMP/USDT","CRO/USDT","WAVES/USDT","MKR/USDT",
|
||||
# "DIA/USDT","LINK/USDT","DOT/USDT","YFI/USDT","UNI/USDT","FIL/USDT","AAVE/USDT","KCS/USDT","LTC/USDT","BSV/USDT",
|
||||
# "XLM/USDT","ETC/USDT","ETH/USDT","BTC/USDT","XRP/USDT","TRX/USDT","VET/USDT","NEO/USDT","EOS/USDT","BCH/USDT",
|
||||
# "CRV/USDT","SUSHI/USDT","KLV/USDT","DOGE/USDT","CAKE/USDT","AVAX/USDT","MANA/USDT","SAND/USDT","SHIB/USDT",
|
||||
# "KDA/USDT","ICP/USDT","MATIC/USDT","ELON/USDT","NFT/USDT","ARRR/USDT","NEAR/USDT","CLV/USDT","SOL/USDT","SLP/USDT",
|
||||
# "XPR/USDT","DYDX/USDT","FTT/USDT","KAVA/USDT","XEC/USDT"
|
||||
# "method": "StaticPairList"
|
||||
|
||||
# 38/100: 67 trades. 32/34/1 Wins/Draws/Losses.
|
||||
# Avg profit 1.23%. Median profit 0.00%.
|
||||
# Total profit 815.05358020 USDT ( 81.51%).
|
||||
# Avg duration 10:58:00 min. Objective: -9.86920
|
||||
|
||||
# ROI table:
|
||||
minimal_roi = {
|
||||
"0": 0.213,
|
||||
"39": 0.048,
|
||||
"56": 0.029,
|
||||
"159": 0
|
||||
}
|
||||
|
||||
# Stoploss:
|
||||
stoploss = -0.288
|
||||
|
||||
# Trailing stop:
|
||||
trailing_stop = False # value loaded from strategy
|
||||
trailing_stop_positive = None # value loaded from strategy
|
||||
trailing_stop_positive_offset = 0.0 # value loaded from strategy
|
||||
trailing_only_offset_is_reached = False # value loaded from strategy
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 30
|
||||
|
||||
# Strategy parameters
|
||||
buy_pow = DecimalParameter(0, 4, decimals=3, default=3.849, space="buy")
|
||||
sell_pow = DecimalParameter(0, 4, decimals=3, default=3.798, space="sell")
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['close'].shift(0) > dataframe['close'].shift(2) ** self.buy_pow.value) &
|
||||
(dataframe['close'].shift(1) > dataframe['close'].shift(3) ** self.buy_pow.value) &
|
||||
(dataframe['close'].shift(2) > dataframe['close'].shift(4) ** self.buy_pow.value)
|
||||
|
||||
),
|
||||
'enter_long'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[(
|
||||
(dataframe['close'].shift(0) < dataframe['close'].shift(2) ** self.sell_pow.value) |
|
||||
(dataframe['close'].shift(1) < dataframe['close'].shift(3) ** self.sell_pow.value) |
|
||||
(dataframe['close'].shift(2) < dataframe['close'].shift(4) ** self.sell_pow.value)
|
||||
),
|
||||
'exit_long'] = 1
|
||||
|
||||
return dataframe
|
||||
@@ -43,7 +43,7 @@ class Strategy001(IStrategy):
|
||||
trailing_stop_positive_offset = 0.02
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Experimental settings (configuration will overide these if set)
|
||||
use_exit_signal = True
|
||||
|
||||
@@ -44,7 +44,7 @@ class Strategy001_custom_exit(IStrategy):
|
||||
trailing_stop_positive_offset = 0.02
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Experimental settings (configuration will overide these if set)
|
||||
use_exit_signal = True
|
||||
|
||||
@@ -44,7 +44,7 @@ class Strategy002(IStrategy):
|
||||
trailing_stop_positive_offset = 0.02
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Experimental settings (configuration will overide these if set)
|
||||
use_exit_signal = True
|
||||
|
||||
@@ -44,7 +44,7 @@ class Strategy003(IStrategy):
|
||||
trailing_stop_positive_offset = 0.02
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Experimental settings (configuration will overide these if set)
|
||||
use_exit_signal = True
|
||||
|
||||
@@ -43,7 +43,7 @@ class Strategy004(IStrategy):
|
||||
trailing_stop_positive_offset = 0.02
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Experimental settings (configuration will overide these if set)
|
||||
use_exit_signal = True
|
||||
|
||||
@@ -45,7 +45,7 @@ class Strategy005(IStrategy):
|
||||
trailing_stop_positive_offset = 0.02
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Experimental settings (configuration will overide these if set)
|
||||
use_exit_signal = True
|
||||
|
||||
@@ -18,6 +18,7 @@ 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'
|
||||
@@ -63,7 +64,7 @@ class Supertrend(IStrategy):
|
||||
|
||||
timeframe = '1h'
|
||||
|
||||
startup_candle_count = 18
|
||||
startup_candle_count = 199
|
||||
|
||||
buy_m1 = 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)
|
||||
|
||||
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:
|
||||
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 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 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 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 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 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
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
@@ -136,42 +154,43 @@ class Supertrend(IStrategy):
|
||||
Supertrend Indicator; adapted for freqtrade
|
||||
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()
|
||||
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]
|
||||
})
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,113 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
# flake8: noqa: F401
|
||||
# isort: skip_file
|
||||
# --- Do not remove these libs ---
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
|
||||
IntParameter, IStrategy, merge_informative_pair)
|
||||
|
||||
# --------------------------------
|
||||
# Add your lib to import here
|
||||
import talib.abstract as ta
|
||||
import pandas_ta as pta
|
||||
from technical import qtpylib
|
||||
|
||||
|
||||
class UniversalMACD(IStrategy):
|
||||
# By: Masoud Azizi (@mablue)
|
||||
# Tradingview Page: https://www.tradingview.com/script/xNEWcB8s-Universal-Moving-Average-Convergence-Divergence/
|
||||
|
||||
# Strategy interface version - allow new iterations of the strategy interface.
|
||||
# Check the documentation or the Sample strategy to get the latest version.
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# $ freqtrade hyperopt -s UniversalMACD --hyperopt-loss SharpeHyperOptLossDaily
|
||||
|
||||
# "max_open_trades": 1,
|
||||
# "stake_currency": "USDT",
|
||||
# "stake_amount": 990,
|
||||
# "dry_run_wallet": 1000,
|
||||
# "trading_mode": "spot",
|
||||
# "XMR/USDT","ATOM/USDT","FTM/USDT","CHR/USDT","BNB/USDT","ALGO/USDT","XEM/USDT","XTZ/USDT","ZEC/USDT","ADA/USDT",
|
||||
# "CHZ/USDT","BTT/USDT","LUNA/USDT","VRA/USDT","KSM/USDT","DASH/USDT","COMP/USDT","CRO/USDT","WAVES/USDT","MKR/USDT",
|
||||
# "DIA/USDT","LINK/USDT","DOT/USDT","YFI/USDT","UNI/USDT","FIL/USDT","AAVE/USDT","KCS/USDT","LTC/USDT","BSV/USDT",
|
||||
# "XLM/USDT","ETC/USDT","ETH/USDT","BTC/USDT","XRP/USDT","TRX/USDT","VET/USDT","NEO/USDT","EOS/USDT","BCH/USDT",
|
||||
# "CRV/USDT","SUSHI/USDT","KLV/USDT","DOGE/USDT","CAKE/USDT","AVAX/USDT","MANA/USDT","SAND/USDT","SHIB/USDT",
|
||||
# "KDA/USDT","ICP/USDT","MATIC/USDT","ELON/USDT","NFT/USDT","ARRR/USDT","NEAR/USDT","CLV/USDT","SOL/USDT","SLP/USDT",
|
||||
# "XPR/USDT","DYDX/USDT","FTT/USDT","KAVA/USDT","XEC/USDT"
|
||||
# "method": "StaticPairList"
|
||||
|
||||
# *16 / 100: 40 trades.
|
||||
# 31 / 9 / 0 Wins / Draws / Losses.
|
||||
# Avg profit 2.34 %.
|
||||
# Median profit 3.00 %.
|
||||
# Total profit 928.95036811 USDT(92.90 %).
|
||||
# Avg duration 3: 13:00 min.\
|
||||
# Objective: -11.63412
|
||||
|
||||
# ROI table:
|
||||
minimal_roi = {
|
||||
"0": 0.213,
|
||||
"27": 0.099,
|
||||
"60": 0.03,
|
||||
"164": 0
|
||||
}
|
||||
|
||||
# Stoploss:
|
||||
stoploss = -0.318
|
||||
|
||||
# Trailing stop:
|
||||
trailing_stop = False # value loaded from strategy
|
||||
trailing_stop_positive = None # value loaded from strategy
|
||||
trailing_stop_positive_offset = 0.0 # value loaded from strategy
|
||||
trailing_only_offset_is_reached = False # value loaded from strategy
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 30
|
||||
|
||||
# Strategy parameters
|
||||
buy_umacd_max = DecimalParameter(-0.05, 0.05, decimals=5, default=-0.01176, space="buy")
|
||||
buy_umacd_min = DecimalParameter(-0.05, 0.05, decimals=5, default=-0.01416, space="buy")
|
||||
sell_umacd_max = DecimalParameter(-0.05, 0.05, decimals=5, default=-0.02323, space="sell")
|
||||
sell_umacd_min = DecimalParameter(-0.05, 0.05, decimals=5, default=-0.00707, space="sell")
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe['ma12'] = ta.EMA(dataframe, timeperiod=12)
|
||||
dataframe['ma26'] = ta.EMA(dataframe, timeperiod=26)
|
||||
dataframe['umacd'] = (dataframe['ma12'] / dataframe['ma26']) - 1
|
||||
|
||||
# Just for show user the min and max of indicator in different coins to set inside hyperoptable variables.cuz
|
||||
# in different timeframes should change the min and max in hyperoptable variables.
|
||||
# print(dataframe['umacd'].min(), dataframe['umacd'].max())
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['umacd'].between(self.buy_umacd_min.value, self.buy_umacd_max.value))
|
||||
|
||||
),
|
||||
'enter_long'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['umacd'].between(self.sell_umacd_min.value, self.sell_umacd_max.value))
|
||||
),
|
||||
'exit_long'] = 1
|
||||
|
||||
return dataframe
|
||||
@@ -18,11 +18,11 @@ class DoesNothingStrategy(IStrategy):
|
||||
# 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"
|
||||
minimal_roi = {
|
||||
"0": 0.01
|
||||
"0": 100000
|
||||
}
|
||||
|
||||
# Optimal stoploss designed for the strategy
|
||||
stoploss = -0.25
|
||||
stoploss = -1
|
||||
|
||||
# Optimal timeframe for the strategy
|
||||
timeframe = '5m'
|
||||
|
||||
@@ -66,8 +66,8 @@ class Low_BB(IStrategy):
|
||||
# dataframe['mfi'] = ta.MFI(dataframe)
|
||||
# dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
|
||||
|
||||
# dataframe['canbuy'] = np.NaN
|
||||
# dataframe['canbuy2'] = np.NaN
|
||||
# dataframe['canbuy'] = np.nan
|
||||
# dataframe['canbuy2'] = np.nan
|
||||
# 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() * 0.8 > dataframe.close, 'canbuy2'] = 1
|
||||
|
||||
@@ -41,7 +41,7 @@ class ReinforcedAverageStrategy(IStrategy):
|
||||
trailing_only_offset_is_reached = False
|
||||
|
||||
# run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Experimental settings (configuration will overide these if set)
|
||||
use_exit_signal = True
|
||||
|
||||
@@ -142,7 +142,7 @@ class TDSequentialStrategy(IStrategy):
|
||||
Based on TA indicators, populates the sell signal for the given dataframe
|
||||
:param dataframe: DataFrame
|
||||
: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.loc[((dataframe['exceed_high']) |
|
||||
|
||||
@@ -41,7 +41,7 @@ class FAdxSmaStrategy(IStrategy):
|
||||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||||
|
||||
# Run "populate_indicators()" only for new candle.
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 14
|
||||
@@ -53,9 +53,9 @@ class FAdxSmaStrategy(IStrategy):
|
||||
pos_exit_adx = DecimalParameter(15, 40, decimals=1, default=30.0, space="sell")
|
||||
|
||||
# Define the parameter spaces
|
||||
adx_period = IntParameter(4, 24, default=14)
|
||||
sma_short_period = IntParameter(4, 24, default=12)
|
||||
sma_long_period = IntParameter(12, 175, default=48)
|
||||
adx_period = IntParameter(4, 24, default=14, space='buy')
|
||||
sma_short_period = IntParameter(4, 24, default=12, space='buy')
|
||||
sma_long_period = IntParameter(12, 175, default=48, space='buy')
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class FReinforcedStrategy(IStrategy):
|
||||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||||
|
||||
# Run "populate_indicators()" only for new candle.
|
||||
process_only_new_candles = False
|
||||
process_only_new_candles = True
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 14
|
||||
|
||||
@@ -238,7 +238,7 @@ class FSupertrendStrategy(IStrategy):
|
||||
)
|
||||
# Mark the trend direction up/down
|
||||
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
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# all strategies are tested against this config. Tests only done on binance futures
|
||||
|
||||
```
|
||||
|
||||
{
|
||||
"max_open_trades": -1,
|
||||
"stake_currency": "USDT",
|
||||
@@ -40,109 +39,89 @@
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"AUDIO/USDT",
|
||||
"AAVE/USDT",
|
||||
"ALICE/USDT",
|
||||
"ARPA/USDT",
|
||||
"AVAX/USDT",
|
||||
"ATOM/USDT",
|
||||
"ANKR/USDT",
|
||||
"AXS/USDT",
|
||||
"ADA/USDT",
|
||||
"ALGO/USDT",
|
||||
"BTS/USDT",
|
||||
"BAND/USDT",
|
||||
"BEL/USDT",
|
||||
"BNB/USDT",
|
||||
"BTC/USDT",
|
||||
"BLZ/USDT",
|
||||
"BAT/USDT",
|
||||
"CHR/USDT",
|
||||
"C98/USDT",
|
||||
"COTI/USDT",
|
||||
"CHZ/USDT",
|
||||
"COMP/USDT",
|
||||
"CRV/USDT",
|
||||
"CELO/USDT",
|
||||
"DUSK/USDT",
|
||||
"DOGE/USDT",
|
||||
"DENT/USDT",
|
||||
"DASH/USDT",
|
||||
"DOT/USDT",
|
||||
"DYDX/USDT",
|
||||
"ENJ/USDT",
|
||||
"EOS/USDT",
|
||||
"ETH/USDT",
|
||||
"ETC/USDT",
|
||||
"ENS/USDT",
|
||||
"EGLD/USDT",
|
||||
"FIL/USDT",
|
||||
"FTM/USDT",
|
||||
"FLM/USDT",
|
||||
"GRT/USDT",
|
||||
"GALA/USDT",
|
||||
"HBAR/USDT",
|
||||
"HOT/USDT",
|
||||
"IOTX/USDT",
|
||||
"ICX/USDT",
|
||||
"ICP/USDT",
|
||||
"IOTA/USDT",
|
||||
"IOST/USDT",
|
||||
"KLAY/USDT",
|
||||
"KAVA/USDT",
|
||||
"KNC/USDT",
|
||||
"KSM/USDT",
|
||||
"LUNA/USDT",
|
||||
"LRC/USDT",
|
||||
"LINA/USDT",
|
||||
"LTC/USDT",
|
||||
"LINK/USDT",
|
||||
"MATIC/USDT",
|
||||
"NEAR/USDT",
|
||||
"MANA/USDT",
|
||||
"MTL/USDT",
|
||||
"NEO/USDT",
|
||||
"ONT/USDT",
|
||||
"OMG/USDT",
|
||||
"OCEAN/USDT",
|
||||
"OGN/USDT",
|
||||
"ONE/USDT",
|
||||
"PEOPLE/USDT",
|
||||
"RLC/USDT",
|
||||
"RUNE/USDT",
|
||||
"RVN/USDT",
|
||||
"RSR/USDT",
|
||||
"REEF/USDT",
|
||||
"ROSE/USDT",
|
||||
"SNX/USDT",
|
||||
"SAND/USDT",
|
||||
"SOL/USDT",
|
||||
"SUSHI/USDT",
|
||||
"SRM/USDT",
|
||||
"SKL/USDT",
|
||||
"SXP/USDT",
|
||||
"STORJ/USDT",
|
||||
"TRX/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"
|
||||
"AAVE/USDT:USDT",
|
||||
"ALICE/USDT:USDT",
|
||||
"ARPA/USDT:USDT",
|
||||
"AVAX/USDT:USDT",
|
||||
"ATOM/USDT:USDT",
|
||||
"ANKR/USDT:USDT",
|
||||
"AXS/USDT:USDT",
|
||||
"ADA/USDT:USDT",
|
||||
"ALGO/USDT:USDT",
|
||||
"BAND/USDT:USDT",
|
||||
"BEL/USDT:USDT",
|
||||
"BTC/USDT:USDT",
|
||||
"BAT/USDT:USDT",
|
||||
"CHR/USDT:USDT",
|
||||
"C98/USDT:USDT",
|
||||
"COTI/USDT:USDT",
|
||||
"CHZ/USDT:USDT",
|
||||
"COMP/USDT:USDT",
|
||||
"CRV/USDT:USDT",
|
||||
"CELO/USDT:USDT",
|
||||
"DUSK/USDT:USDT",
|
||||
"DOGE/USDT:USDT",
|
||||
"DENT/USDT:USDT",
|
||||
"DASH/USDT:USDT",
|
||||
"DOT/USDT:USDT",
|
||||
"DYDX/USDT:USDT",
|
||||
"ENJ/USDT:USDT",
|
||||
"ETH/USDT:USDT",
|
||||
"ETC/USDT:USDT",
|
||||
"ENS/USDT:USDT",
|
||||
"EGLD/USDT:USDT",
|
||||
"FIL/USDT:USDT",
|
||||
"GRT/USDT:USDT",
|
||||
"GALA/USDT:USDT",
|
||||
"HBAR/USDT:USDT",
|
||||
"HOT/USDT:USDT",
|
||||
"IOTX/USDT:USDT",
|
||||
"ICX/USDT:USDT",
|
||||
"ICP/USDT:USDT",
|
||||
"IOTA/USDT:USDT",
|
||||
"IOST/USDT:USDT",
|
||||
"KAVA/USDT:USDT",
|
||||
"KNC/USDT:USDT",
|
||||
"KSM/USDT:USDT",
|
||||
"LRC/USDT:USDT",
|
||||
"LTC/USDT:USDT",
|
||||
"LINK/USDT:USDT",
|
||||
"NEAR/USDT:USDT",
|
||||
"MANA/USDT:USDT",
|
||||
"MTL/USDT:USDT",
|
||||
"NEO/USDT:USDT",
|
||||
"ONT/USDT:USDT",
|
||||
"OGN/USDT:USDT",
|
||||
"ONE/USDT:USDT",
|
||||
"PEOPLE/USDT:USDT",
|
||||
"RLC/USDT:USDT",
|
||||
"RUNE/USDT:USDT",
|
||||
"RVN/USDT:USDT",
|
||||
"RSR/USDT:USDT",
|
||||
"ROSE/USDT:USDT",
|
||||
"SNX/USDT:USDT",
|
||||
"SAND/USDT:USDT",
|
||||
"SOL/USDT:USDT",
|
||||
"SUSHI/USDT:USDT",
|
||||
"SKL/USDT:USDT",
|
||||
"STORJ/USDT:USDT",
|
||||
"TRX/USDT:USDT",
|
||||
"TRB/USDT:USDT",
|
||||
"TLM/USDT:USDT",
|
||||
"THETA/USDT:USDT",
|
||||
"UNI/USDT:USDT",
|
||||
"VET/USDT:USDT",
|
||||
"YFI/USDT:USDT",
|
||||
"ZIL/USDT:USDT",
|
||||
"ZEN/USDT:USDT",
|
||||
"ZRX/USDT:USDT",
|
||||
"ZEC/USDT:USDT",
|
||||
"XRP/USDT:USDT",
|
||||
"XLM/USDT:USDT",
|
||||
"XTZ/USDT:USDT",
|
||||
"XMR/USDT:USDT",
|
||||
"QTUM/USDT:USDT",
|
||||
"1INCH/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": ["BNB/.*"]
|
||||
},
|
||||
@@ -162,7 +141,7 @@
|
||||
"remove_pumps": false
|
||||
},
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
@@ -185,4 +164,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -46,7 +46,7 @@ class TrendFollowingStrategy(IStrategy):
|
||||
(dataframe['close'] < dataframe['trend']) &
|
||||
(dataframe['close'].shift(1) >= dataframe['trend'].shift(1)) &
|
||||
(dataframe['obv'] < dataframe['obv'].shift(1)),
|
||||
'enter_short'] = -1
|
||||
'enter_short'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
|
||||
Reference in New Issue
Block a user